PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-sol/test/unit/transactionBuilder

Просмотр файла: ataInitBuilder.ts

import { KeyPair, Utils, AtaInitializationBuilder } from '../../../src';
import should from 'should';
import * as testData from '../../resources/sol';
import { BaseTransaction } from '@bitgo/sdk-core';
import { getBuilderFactory } from '../getBuilderFactory';

describe('Sol Associated Token Account Builder', () => {
  function verifyInputOutputAndRawTransaction(
    tx: BaseTransaction,
    rawTx: string,
    owner: { pubkey: string; ataPubkey: string } = sender
  ) {
    tx.inputs.length.should.equal(0);
    tx.outputs.length.should.equal(0);
    const instructions = tx.toJson().instructionsData;
    let ataInitInstruction;
    for (const instruction of instructions) {
      if (instruction.type === 'CreateAssociatedTokenAccount') {
        ataInitInstruction = instruction;
        break;
      }
    }
    should.exist(ataInitInstruction);
    ataInitInstruction.params.tokenName.should.equal(mint);

    should.equal(Utils.isValidRawTransaction(rawTx), true);
  }

  const factory = getBuilderFactory('sol');
  const ataInitBuilder = () => {
    const txBuilder = factory.getAtaInitializationBuilder();
    txBuilder.nonce(recentBlockHash);
    txBuilder.sender(account.pub);
    txBuilder.mint(mint);
    txBuilder.rentExemptAmount(rentAmount);

    return txBuilder;
  };

  const account = new KeyPair(testData.associatedTokenAccounts.accounts[0]).getKeys();
  const nonceAccount = new KeyPair(testData.nonceAccount).getKeys();
  const sender = {
    pubkey: account.pub,
    ataPubkey: testData.associatedTokenAccounts.accounts[0].ata,
  };
  const wrongAccount = new KeyPair({ prv: testData.prvKeys.prvKey1.base58 }).getKeys();
  const mint = testData.associatedTokenAccounts.mint;
  const recentBlockHash = 'GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi';
  const rentAmount = '30000';

  // for diff owner case
  const accountOwner = new KeyPair(testData.associatedTokenAccounts.accounts[1]).getKeys();
  const ownerPubkeys = {
    pubkey: accountOwner.pub,
    ataPubkey: testData.associatedTokenAccounts.accounts[1].ata,
  };

  describe('Build and sign', () => {
    describe('Succeed', () => {
      it('build an associated token account init tx unsigned', async () => {
        const txBuilder = ataInitBuilder();
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);
        should.equal(rawTx, testData.ATA_INIT_UNSIGNED_TX);
      });

      it('build an associated token account init tx unsigned with memo', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.memo('test memo please ignore');
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);
        should.equal(rawTx, testData.ATA_INIT_UNSIGNED_TX_WITH_MEMO);
      });

      it('build an associated token account init tx signed', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.sender(account.pub);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_TX);
      });

      it('build an associated token account init tx with memo signed', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.memo('test memo please ignore');
        txBuilder.sender(account.pub);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_TX_WITH_MEMO);
      });

      it('build an associated token account init tx with durable nonce unsigned', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.memo('test memo please ignore');
        txBuilder.sender(account.pub);
        txBuilder.nonce(recentBlockHash, { walletNonceAddress: nonceAccount.pub, authWalletAddress: account.pub });

        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);

        should.equal(rawTx, testData.ATA_INIT_UNSIGNED_TX_DURABLE_NONCE);
      });

      it('build an associated token account init tx with durable nonce signed', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.memo('test memo please ignore');
        txBuilder.sender(account.pub);
        txBuilder.nonce(recentBlockHash, { walletNonceAddress: nonceAccount.pub, authWalletAddress: account.pub });
        txBuilder.sign({ key: account.prv });

        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);

        should.equal(rawTx, testData.ATA_INIT_SIGNED_TX_DURABLE_NONCE);
      });
    });

    describe('ATA creation with different owner', () => {
      it('build an associated token account init for diff owner tx unsigned', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.owner(accountOwner.pub);
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx, ownerPubkeys);
        should.equal(rawTx, testData.ATA_INIT_UNSIGNED_DIFF_OWNER_TX);
      });

      it('build an associated token account init for diff owner tx unsigned with memo', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.owner(accountOwner.pub);
        txBuilder.memo('test memo please ignore');
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx, ownerPubkeys);
        should.equal(rawTx, testData.ATA_INIT_UNSIGNED_DIFF_OWNER_TX_WITH_MEMO);
      });

      it('build an associated token account init for diff owner tx signed', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.owner(accountOwner.pub);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx, ownerPubkeys);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_DIFF_OWNER_TX);
      });

      it('build an associated token account init for diff owner tx with memo signed', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.owner(accountOwner.pub);
        txBuilder.memo('test memo please ignore');
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx, ownerPubkeys);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_DIFF_OWNER_TX_WITH_MEMO);
      });
    });

    describe('Fail', () => {
      it('build an associated token account init tx when mint is invalid', () => {
        const txBuilder = ataInitBuilder();
        should(() => txBuilder.mint('invalidToken')).throwError(
          'Invalid transaction: invalid token name, got: invalidToken'
        );
      });

      it('build a wallet init tx and sign with an incorrect account', async () => {
        const txBuilder = ataInitBuilder();
        txBuilder.sender(account.pub);
        txBuilder.sign({ key: wrongAccount.prv });
        await txBuilder.build().should.rejectedWith('unknown signer: CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S');
      });

      it('build when nonce is not provided', async () => {
        const txBuilder = factory.getAtaInitializationBuilder();
        txBuilder.sender(account.pub);
        txBuilder.mint(mint);
        txBuilder.sign({ key: account.prv });
        await txBuilder.build().should.rejectedWith('Invalid transaction: missing nonce blockhash');
      });

      it('build when sender is not provided', async () => {
        const txBuilder = factory.getAtaInitializationBuilder();
        txBuilder.nonce(recentBlockHash);
        txBuilder.mint(mint);
        txBuilder.sign({ key: account.prv });
        await txBuilder.build().should.rejectedWith('Invalid transaction: missing sender');
      });

      it('build when mint is not provided', async () => {
        const txBuilder = factory.getAtaInitializationBuilder();
        txBuilder.sender(account.pub);
        txBuilder.nonce(recentBlockHash);
        txBuilder.sign({ key: account.prv });
        await txBuilder.build().should.rejectedWith('Mint must be set before building the transaction');
      });

      it('build when mint is invalid', async () => {
        const txBuilder = factory.getAtaInitializationBuilder();
        should(() => txBuilder.mint('sol:invalid mint')).throwError(
          'Invalid transaction: invalid token name, got: sol:invalid mint'
        );
      });

      it('build when rentExemptAmount is invalid', async () => {
        const txBuilder = ataInitBuilder();
        should(() => txBuilder.rentExemptAmount('invalid amount')).throwError(
          'Invalid tokenAccountRentExemptAmount, got: invalid amount'
        );
        should(() => txBuilder.associatedTokenAccountRent('invalid amount')).throwError(
          'Invalid tokenAccountRentExemptAmount, got: invalid amount'
        );
      });

      it('build when owner is invalid', async () => {
        const txBuilder = ataInitBuilder();
        should(() => txBuilder.owner('invalid owner')).throwError(
          'Invalid or missing ownerAddress, got: invalid owner'
        );
      });

      it('to sign twice with the same key', () => {
        const txBuilder = factory.from(testData.ATA_INIT_UNSIGNED_TX);
        txBuilder.sign({ key: account.prv });
        should(() => txBuilder.sign({ key: account.prv })).throwError('Duplicated signer: ' + account.prv?.toString());
      });
    });
  });

  describe('Build and sign with enableToken', () => {
    const recipients = [
      {
        ownerAddress: sender.pubkey,
        tokenName: mint,
        ataAddress: sender.ataPubkey,
      },
      {
        ownerAddress: ownerPubkeys.pubkey,
        tokenName: 'sol:ray',
        ataAddress: 'ACEuzYtR4gBFt6HLQTYisg2T7k8Vh4ss1SpnqmbVQSNy',
      },
    ];
    const multiAtaInitBuilder = (recipients) => {
      const txBuilder = factory.getAtaInitializationBuilder();
      txBuilder.nonce(recentBlockHash);
      recipients.forEach((recipient) => {
        txBuilder.enableToken(recipient);
      });
      txBuilder.sender(sender.pubkey);
      txBuilder.rentExemptAmount(rentAmount);

      return txBuilder;
    };

    describe('ATA creation for multiple recipients', () => {
      it('build an associated token account init for multiple recipients', async () => {
        const txBuilder = multiAtaInitBuilder(recipients);
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();
        const ownerRayATA = 'ACEuzYtR4gBFt6HLQTYisg2T7k8Vh4ss1SpnqmbVQSNy';

        tx.inputs.length.should.equal(0);
        tx.outputs.length.should.equal(0);
        const instructions = tx.toJson().instructionsData;

        instructions.length.should.equal(2);
        instructions[0].params.tokenName.should.equal(mint);
        instructions[0].params.ownerAddress.should.equal(sender.pubkey);
        instructions[0].params.ataAddress.should.equal(sender.ataPubkey);
        instructions[1].params.tokenName.should.equal('sol:ray');
        instructions[1].params.ownerAddress.should.equal(ownerPubkeys.pubkey);
        instructions[1].params.ataAddress.should.equal(ownerRayATA);

        should.equal(rawTx, testData.MULTI_ATA_INIT_UNSIGNED_TX);
      });

      it('build an associated token account init for multiple recipients with memo', async () => {
        const txBuilder = multiAtaInitBuilder(recipients);
        txBuilder.memo('test memo please ignore');
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        should.equal(rawTx, testData.MULTI_ATA_INIT_UNSIGNED_TX_WITH_MEMO);
      });

      it('build an associated token account init tx for multiple recipients signed', async () => {
        const txBuilder = multiAtaInitBuilder(recipients);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        should.equal(rawTx, testData.MULTI_ATA_INIT_SIGNED_TX);
      });

      it('build an associated token account init for multiple recipients tx with memo signed', async () => {
        const txBuilder = multiAtaInitBuilder(recipients);
        txBuilder.memo('test memo please ignore');
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        should.equal(rawTx, testData.MULTI_ATA_INIT_SIGNED_TX_WITH_MEMO);
      });
    });

    describe('Fail', () => {
      it('should fail to build an associated token account init with duplicate recipients', async () => {
        const duplicateRecipient = {
          ownerAddress: sender.pubkey,
          tokenName: mint,
          ataAddress: sender.ataPubkey,
        };
        const txBuilder = multiAtaInitBuilder(recipients);
        should(() => txBuilder.enableToken(duplicateRecipient)).throwError(
          'Invalid transaction: invalid duplicate recipients, got: owner 12f6D3WubGVeQoH2m8kTvvcrasWdXWwtVzUCyRNDZxA2 and tokenName sol:usdc twice'
        );
      });

      it('build an associated token account init tx when mint is invalid', () => {
        const errorMintRecipient = {
          ownerAddress: ownerPubkeys.pubkey,
          tokenName: 'invalidToken',
          ataAddress: ownerPubkeys.ataPubkey,
        };
        const txBuilder = multiAtaInitBuilder(recipients);
        should(() => txBuilder.enableToken(errorMintRecipient)).throwError(
          'Invalid transaction: invalid token name, got: invalidToken'
        );
      });

      it('build a wallet init tx and sign with an incorrect account', async () => {
        const txBuilder = multiAtaInitBuilder(recipients);
        txBuilder.sender(account.pub);
        txBuilder.sign({ key: wrongAccount.prv });
        await txBuilder.build().should.rejectedWith('unknown signer: CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S');
      });

      it('build when nonce is not provided', async () => {
        const txBuilder = factory.getAtaInitializationBuilder();
        txBuilder.sender(account.pub);
        txBuilder.enableToken({
          ownerAddress: account.pub,
          tokenName: mint,
          ataAddress: sender.ataPubkey,
        });
        txBuilder.sign({ key: account.prv });
        await txBuilder.build().should.rejectedWith('Invalid transaction: missing nonce blockhash');
      });

      it('build when sender is not provided', async () => {
        const txBuilder = factory.getAtaInitializationBuilder();
        txBuilder.nonce(recentBlockHash);
        txBuilder.enableToken({
          ownerAddress: account.pub,
          tokenName: mint,
          ataAddress: sender.ataPubkey,
        });
        txBuilder.sign({ key: account.prv });
        await txBuilder.build().should.rejectedWith('Invalid transaction: missing sender');
      });

      it('build when recipient is not provided', async () => {
        const txBuilder = factory.getAtaInitializationBuilder();
        txBuilder.sender(account.pub);
        txBuilder.nonce(recentBlockHash);
        txBuilder.sign({ key: account.prv });
        await txBuilder.build().should.rejectedWith('Mint must be set before building the transaction');
      });

      it('build when rentExemptAmount is invalid', async () => {
        const txBuilder = multiAtaInitBuilder(recipients);
        should(() => txBuilder.rentExemptAmount('invalid amount')).throwError(
          'Invalid tokenAccountRentExemptAmount, got: invalid amount'
        );
        should(() => txBuilder.associatedTokenAccountRent('invalid amount')).throwError(
          'Invalid tokenAccountRentExemptAmount, got: invalid amount'
        );
      });

      it('build when token owner is invalid', async () => {
        const invalidOwner = {
          ownerAddress: 'invalid owner',
          tokenName: mint,
          ataAddress: sender.ataPubkey,
        };
        const txBuilder = multiAtaInitBuilder(recipients);
        should(() => txBuilder.enableToken(invalidOwner)).throwError(
          'Invalid or missing ownerAddress, got: invalid owner'
        );
      });

      it('to sign twice with the same key', () => {
        const txBuilder = factory.from(testData.MULTI_ATA_INIT_UNSIGNED_TX);
        txBuilder.sign({ key: account.prv });
        should(() => txBuilder.sign({ key: account.prv })).throwError('Duplicated signer: ' + account.prv?.toString());
      });
    });
  });

  describe('From and sign', () => {
    describe('Succeed', () => {
      it('build from a unsigned ATA init and sign it', async () => {
        const txBuilder = factory.from(testData.ATA_INIT_UNSIGNED_TX);
        (txBuilder as AtaInitializationBuilder).rentExemptAmount(rentAmount);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_TX);
      });

      it('build from a unsigned ATA init with memo and sign it', async () => {
        const txBuilder = factory.from(testData.ATA_INIT_UNSIGNED_TX_WITH_MEMO);
        (txBuilder as AtaInitializationBuilder).rentExemptAmount(rentAmount);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_TX_WITH_MEMO);
      });

      it('build from a unsigned ATA init with diff owner and sign it', async () => {
        const txBuilder = factory.from(testData.ATA_INIT_UNSIGNED_DIFF_OWNER_TX);
        (txBuilder as AtaInitializationBuilder).rentExemptAmount(rentAmount);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx, ownerPubkeys);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_DIFF_OWNER_TX);
      });

      it('build from a unsigned ATA init with diff owner with memo and sign it', async () => {
        const txBuilder = factory.from(testData.ATA_INIT_UNSIGNED_DIFF_OWNER_TX_WITH_MEMO);
        (txBuilder as AtaInitializationBuilder).rentExemptAmount(rentAmount);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        verifyInputOutputAndRawTransaction(tx, rawTx, ownerPubkeys);
        should.equal(rawTx, testData.ATA_INIT_SIGNED_DIFF_OWNER_TX_WITH_MEMO);
      });

      it('build from a unsigned ATA init for multi recipients and sign it', async () => {
        const txBuilder = factory.from(testData.MULTI_ATA_INIT_UNSIGNED_TX);
        (txBuilder as AtaInitializationBuilder).rentExemptAmount(rentAmount);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        should.equal(rawTx, testData.MULTI_ATA_INIT_SIGNED_TX);
      });

      it('build from a unsigned ATA init for multi recipients with memo and sign it', async () => {
        const txBuilder = factory.from(testData.MULTI_ATA_INIT_UNSIGNED_TX_WITH_MEMO);
        (txBuilder as AtaInitializationBuilder).rentExemptAmount(rentAmount);
        txBuilder.sign({ key: account.prv });
        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        should.equal(rawTx, testData.MULTI_ATA_INIT_SIGNED_TX_WITH_MEMO);
      });

      it('build from an unsigned ATA init with durable nonce and sign it', async () => {
        const txBuilder = factory.from(testData.ATA_INIT_UNSIGNED_TX_DURABLE_NONCE);
        txBuilder.sign({ key: account.prv });

        const tx = await txBuilder.build();
        const rawTx = tx.toBroadcastFormat();

        should.equal(rawTx, testData.ATA_INIT_SIGNED_TX_DURABLE_NONCE);
      });
    });

    describe('Fail', () => {
      it('build from a unsigned ATA init and fail to sign it', async () => {
        const txBuilder = factory.from(testData.ATA_INIT_UNSIGNED_TX);
        txBuilder.sign({ key: wrongAccount.prv });
        await txBuilder.build().should.rejectedWith('unknown signer: CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S');
      });
      it('build from a signed ATA init and fail to sign it', async () => {
        const txBuilder = factory.from(testData.ATA_INIT_SIGNED_TX);
        txBuilder.sign({ key: wrongAccount.prv });
        await txBuilder.build().should.rejectedWith('unknown signer: CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S');
      });
    });
  });
});

Выполнить команду


Для локальной разработки. Не используйте в интернете!