PHP WebShell

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

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

import { getBuilderFactory } from '../getBuilderFactory';
import * as testData from '../../resources/sui';
import should from 'should';
import { TransactionType } from '@bitgo/sdk-core';
import { SuiTransactionType } from '../../../src/lib/iface';
import { CUSTOM_TX_STAKING_POOL_SPLIT, recipients, STAKING_AMOUNT, UNSUPPORTED_TX } from '../../resources/sui';
import { KeyPair } from '../../../src/lib/keyPair';
import { GasData } from '../../../src/lib/mystenlab/types';
import { StakingTransaction, TransferTransaction } from '../../../src';
import { UnstakingTransaction } from '../../../src/lib/unstakingTransaction';
import { AMOUNT_UNKNOWN_TEXT } from '../../../src/lib/constants';
import { CustomTransaction } from '../../../src/lib/customTransaction';

describe('Sui Transaction Builder', async () => {
  let builders;
  const factory = getBuilderFactory('tsui');

  describe('Transfer TX', async () => {
    beforeEach(function (done) {
      builders = [factory.getTransferBuilder()];
      done();
    });

    it('should build a transfer transaction and serialize it and deserialize it', async function () {
      const txBuilder = factory.getTransferBuilder();
      txBuilder.type(SuiTransactionType.Transfer);
      txBuilder.sender(testData.sender.address);
      txBuilder.send(recipients);
      txBuilder.gasData(testData.gasData);
      const tx = await txBuilder.build();
      should.equal(tx.type, TransactionType.Send);
      const rawTx = tx.toBroadcastFormat();
      should.equal(rawTx, testData.TRANSFER);
      const reserialized = await factory.from(rawTx).build();
      reserialized.should.be.deepEqual(tx);
      reserialized.toBroadcastFormat().should.equal(rawTx);
    });

    it('should build and sign a transfer tx with gasPayment', async function () {
      const txBuilder = factory.getTransferBuilder();
      txBuilder.type(SuiTransactionType.Transfer);
      txBuilder.sender(testData.sender.address);
      txBuilder.send(recipients);
      txBuilder.gasData(testData.gasData);
      const tx = await txBuilder.build();
      should.equal(tx.id, 'UNAVAILABLE');
      const rawTx = tx.toBroadcastFormat();
      should.equal(rawTx, testData.TRANSFER);

      const txBuilder2 = await factory.from(rawTx);
      await txBuilder2.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
      const signedTx = await txBuilder2.build();
      should.equal(signedTx.type, TransactionType.Send);
      should.equal(signedTx.id, 'BxoeGXbBCuw6VFEcgwHHUAKrCoAsGanPB39kdVVKZZcR');

      const rawSignedTx = signedTx.toBroadcastFormat();
      should.equal(rawSignedTx, testData.TRANSFER);
      const reserializedTxBuilder = factory.from(rawSignedTx);
      reserializedTxBuilder.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
      const reserialized = await reserializedTxBuilder.build();

      reserialized.should.be.deepEqual(signedTx);
      reserialized.toBroadcastFormat().should.equal(rawSignedTx);
    });

    it('should submit a transfer transaction with private keys', async () => {
      const keyPairSender = new KeyPair({ prv: testData.privateKeys.prvKey1 });
      const keyPairRecipient = new KeyPair({ prv: testData.privateKeys.prvKey2 });
      const senderAddress = keyPairSender.getAddress();
      const receiveAddress = keyPairRecipient.getAddress();
      const expectedTransferTxSig =
        'AI8Q9KINqSCOeHvhv6MJ6Vf3TWbTcxhpclg0PD9gBgand2kLQZxEJS/HJASganLKfZ277Il4mAifKHzvtb2fCA+lzaq1j4wMuCiXuFW4ojFfuoBhEiBy/K4eB5BkHZ+eZw==';
      const expectedTransferTxHex =
        'AAACAAgA4fUFAAAAAAAgtev+nncDfTtowJZEPHUMsJXia4wKmT/Xpgtrzsy6O38CAgABAQAAAQECAAABAQCQB0K7kj6pBqVvMloXWXdS7NOsMCa7qW4O3/Rd6NszqwJ3AbiIwWM5ms1bgEYzwdDlMrwxQ8/vNMo2C+YHxo3N72YEAAAAAAAAIDrMcEOTidjOdp7a1J/jjJ9tOjb6P2WTyBSwQqAHiA1/yfVla+cYIwE9k34GVOs+3LJhla/SMAm+mrlufz8twgNmBAAAAAAAACAhXnkXobS2E/RZ/cLDQ/n3BH/TxAjKv5VxsbLEZCUxu5AHQruSPqkGpW8yWhdZd1Ls06wwJrupbg7f9F3o2zOr6AMAAAAAAAAALTEBAAAAAAA=';

      const coins = [
        {
          digest: '4xXTGhhtNUjBU8nMivsLWTMRWvZRc5RWDMgyTMnnRDYS',
          objectId: '0x7701b888c163399acd5b804633c1d0e532bc3143cfef34ca360be607c68dcdef',
          version: 1126,
        },
        {
          digest: '3FG1Nfk5HrQ1tWYKS8BX9ynZTSprL5hLn3pgw7kdj1RU',
          objectId: '0xc9f5656be71823013d937e0654eb3edcb26195afd23009be9ab96e7f3f2dc203',
          version: 1126,
        },
      ];
      const txBuilder_1 = factory.getTransferBuilder();
      txBuilder_1.type(SuiTransactionType.Transfer);
      txBuilder_1.sender(senderAddress);
      txBuilder_1.send([{ address: receiveAddress, amount: '100000000' }]);
      const gasData: GasData = {
        payment: coins,
        owner: senderAddress,
        budget: testData.GAS_BUDGET,
        price: 1000,
      };
      txBuilder_1.gasData(gasData);

      const tx_1 = await txBuilder_1.build();
      const signable_1 = tx_1.signablePayload;
      const signature_1 = keyPairSender.signMessageinUint8Array(signable_1);
      txBuilder_1.addSignature({ pub: keyPairSender.getKeys().pub }, Buffer.from(signature_1));
      const signedTx_1 = (await txBuilder_1.build()) as TransferTransaction;
      const txHex_1 = signedTx_1.toBroadcastFormat();
      const sig_1 = Buffer.from(signedTx_1.serializedSig).toString('base64');

      const txBuilderFromRaw_2 = factory.getTransferBuilder();
      txBuilderFromRaw_2.from(txHex_1);
      const signable_2 = tx_1.signablePayload;
      txBuilderFromRaw_2.sign({ key: testData.privateKeys.prvKey1 });
      const signedTx_2 = (await txBuilderFromRaw_2.build()) as TransferTransaction;
      const txHex_2 = signedTx_2.toBroadcastFormat();
      const sig_2 = Buffer.from(signedTx_2.serializedSig).toString('base64');

      should.equal(Buffer.from(signable_1).toString('hex'), Buffer.from(signable_2).toString('hex'));
      should.equal(txHex_1, txHex_2);
      should.equal(txHex_1, expectedTransferTxHex);
      should.equal(txHex_2, expectedTransferTxHex);
      should.equal(sig_1, sig_2);
      should.equal(sig_1, expectedTransferTxSig);
      should.equal(sig_2, expectedTransferTxSig);
    });

    it('should submit a slit coin transaction with private keys', async () => {
      const keyPairSender = new KeyPair({ prv: testData.privateKeys.prvKey1 });
      const senderAddress = keyPairSender.getAddress();
      const expectedTransferTxSig =
        'AGGWHZuyUjsxMhMoPoqdsZRTC50a8mByDhupwE0WrDlvvqzwaRO7R5sY20/IYgabUA9fep3o35ssnRhUZ0jDqwmlzaq1j4wMuCiXuFW4ojFfuoBhEiBy/K4eB5BkHZ+eZw==';
      const expectedTransferTxHex =
        'AAAVAAgA4fUFAAAAAAAgkAdCu5I+qQalbzJaF1l3UuzTrDAmu6luDt/0XejbM6sACADh9QUAAAAAAAgA4fUFAAAAAAAIAOH1BQAAAAAACADh9QUAAAAAAAgA4fUFAAAAAAAIAOH1BQAAAAAACADh9QUAAAAAAAgA4fUFAAAAAAAIAOH1BQAAAAAACADh9QUAAAAAAAgA4fUFAAAAAAAIAOH1BQAAAAAACADh9QUAAAAAAAgA4fUFAAAAAAAIAOH1BQAAAAAACADh9QUAAAAAAAgA4fUFAAAAAAAIAOH1BQAAAAAACADh9QUAAAAAKAIAAQEAAAEBAgAAAQEAAgABAQIAAQECAgABAQACAAEBAwABAQIEAAEBAAIAAQEEAAEBAgYAAQEAAgABAQUAAQECCAABAQACAAEBBgABAQIKAAEBAAIAAQEHAAEBAgwAAQEAAgABAQgAAQECDgABAQACAAEBCQABAQIQAAEBAAIAAQEKAAEBAhIAAQEAAgABAQsAAQECFAABAQACAAEBDAABAQIWAAEBAAIAAQENAAEBAhgAAQEAAgABAQ4AAQECGgABAQACAAEBDwABAQIcAAEBAAIAAQEQAAEBAh4AAQEAAgABAREAAQECIAABAQACAAEBEgABAQIiAAEBAAIAAQETAAEBAiQAAQEAAgABARQAAQECJgABAQCQB0K7kj6pBqVvMloXWXdS7NOsMCa7qW4O3/Rd6NszqwJ3AbiIwWM5ms1bgEYzwdDlMrwxQ8/vNMo2C+YHxo3N72YEAAAAAAAAIDrMcEOTidjOdp7a1J/jjJ9tOjb6P2WTyBSwQqAHiA1/yfVla+cYIwE9k34GVOs+3LJhla/SMAm+mrlufz8twgNmBAAAAAAAACAhXnkXobS2E/RZ/cLDQ/n3BH/TxAjKv5VxsbLEZCUxu5AHQruSPqkGpW8yWhdZd1Ls06wwJrupbg7f9F3o2zOr6AMAAAAAAAAALTEBAAAAAAA=';

      const coins = [
        {
          digest: '4xXTGhhtNUjBU8nMivsLWTMRWvZRc5RWDMgyTMnnRDYS',
          objectId: '0x7701b888c163399acd5b804633c1d0e532bc3143cfef34ca360be607c68dcdef',
          version: 1126,
        },
        {
          digest: '3FG1Nfk5HrQ1tWYKS8BX9ynZTSprL5hLn3pgw7kdj1RU',
          objectId: '0xc9f5656be71823013d937e0654eb3edcb26195afd23009be9ab96e7f3f2dc203',
          version: 1126,
        },
      ];
      const txBuilder_1 = factory.getTransferBuilder();
      txBuilder_1.type(SuiTransactionType.Transfer);
      txBuilder_1.sender(senderAddress);
      txBuilder_1.send(Array(20).fill({ address: senderAddress, amount: '100000000' }));
      const gasData: GasData = {
        payment: coins,
        owner: senderAddress,
        budget: testData.GAS_BUDGET,
        price: 1000,
      };
      txBuilder_1.gasData(gasData);

      const tx_1 = await txBuilder_1.build();
      const signable_1 = tx_1.signablePayload;
      const signature_1 = keyPairSender.signMessageinUint8Array(signable_1);
      txBuilder_1.addSignature({ pub: keyPairSender.getKeys().pub }, Buffer.from(signature_1));
      const signedTx_1 = (await txBuilder_1.build()) as TransferTransaction;
      const txHex_1 = signedTx_1.toBroadcastFormat();
      const sig_1 = Buffer.from(signedTx_1.serializedSig).toString('base64');

      const txBuilderFromRaw_2 = factory.getTransferBuilder();
      txBuilderFromRaw_2.from(txHex_1);
      const signable_2 = tx_1.signablePayload;
      const signature_2 = keyPairSender.signMessageinUint8Array(signable_2);
      txBuilderFromRaw_2.addSignature({ pub: keyPairSender.getKeys().pub }, Buffer.from(signature_2));
      const signedTx_2 = (await txBuilderFromRaw_2.build()) as TransferTransaction;
      const txHex_2 = signedTx_2.toBroadcastFormat();
      const sig_2 = Buffer.from(signedTx_2.serializedSig).toString('base64');

      should.equal(Buffer.from(signable_1).toString('hex'), Buffer.from(signable_2).toString('hex'));
      should.equal(txHex_1, txHex_2);
      should.equal(txHex_1, expectedTransferTxHex);
      should.equal(txHex_2, expectedTransferTxHex);
      should.equal(sig_1, sig_2);
      should.equal(sig_1, expectedTransferTxSig);
      should.equal(sig_2, expectedTransferTxSig);
    });

    it('should fail to build if missing type', async function () {
      for (const txBuilder of builders) {
        txBuilder.sender(testData.sender.address);
        txBuilder.send(recipients);
        txBuilder.gasData(testData.gasData);
        await txBuilder.build().should.rejectedWith('type is required before building');
      }
    });

    it('should fail to build if missing sender', async function () {
      for (const txBuilder of builders) {
        txBuilder.type(SuiTransactionType.Transfer);
        txBuilder.send(recipients);
        txBuilder.gasData(testData.gasData);
        await txBuilder.build().should.rejectedWith('sender is required before building');
      }
    });

    it('should fail to build if missing recipients', async function () {
      for (const txBuilder of builders) {
        txBuilder.type(SuiTransactionType.Transfer);
        txBuilder.sender(testData.sender.address);
        txBuilder.gasData(testData.gasData);
        await txBuilder.build().should.rejectedWith('at least one recipient is required before building');
      }
    });

    it('should fail to build if missing gasData', async function () {
      for (const txBuilder of builders) {
        txBuilder.type(SuiTransactionType.Transfer);
        txBuilder.sender(testData.sender.address);
        txBuilder.send(recipients);
        await txBuilder.build().should.rejectedWith('gasData is required before building');
      }
    });

    it('should fail to build if missing payment coins in gasData', async function () {
      for (const txBuilder of builders) {
        txBuilder.type(SuiTransactionType.Transfer);
        txBuilder.sender(testData.sender.address);
        txBuilder.send(recipients);
        should(() => txBuilder.gasData(testData.gasDataWithoutGasPayment)).throwError(
          `gas payment is required before building`
        );
        await txBuilder.build().should.rejectedWith('gasData is required before building');
      }
    });

    it('should build a send from rawTx', async function () {
      const txBuilder = factory.from(testData.TRANSFER);
      const builtTx = await txBuilder.build();
      should.equal(builtTx.type, TransactionType.Send);
      should.equal(builtTx.id, 'BxoeGXbBCuw6VFEcgwHHUAKrCoAsGanPB39kdVVKZZcR');
      builtTx.inputs.length.should.equal(1);
      builtTx.inputs[0].should.deepEqual({
        address: testData.sender.address,
        value: (testData.AMOUNT * 2).toString(),
        coin: 'tsui',
      });
      builtTx.outputs.length.should.equal(2);
      builtTx.outputs[0].should.deepEqual({
        address: testData.recipients[0].address,
        value: testData.recipients[0].amount,
        coin: 'tsui',
      });
      builtTx.outputs[1].should.deepEqual({
        address: testData.recipients[1].address,
        value: testData.recipients[1].amount,
        coin: 'tsui',
      });
      const jsonTx = builtTx.toJson();
      jsonTx.gasData.should.deepEqual(testData.gasData);
      jsonTx.kind.ProgrammableTransaction.should.deepEqual({
        inputs: testData.txInputs,
        transactions: testData.txTransactions,
      });
      jsonTx.sender.should.equal(testData.sender.address);
      jsonTx.gasData.should.deepEqual(testData.gasData);
      builtTx.toBroadcastFormat().should.equal(testData.TRANSFER);
    });
  });
  describe('Staking TX', async () => {
    beforeEach(function (done) {
      builders = [factory.getStakingBuilder()];
      done();
    });

    it('should build an add staking transaction and serialize it and deserialize it', async function () {
      const txBuilder = factory.getStakingBuilder();
      txBuilder.type(SuiTransactionType.AddStake);
      txBuilder.sender(testData.sender.address);
      txBuilder.stake([testData.requestAddStake]);
      txBuilder.gasData(testData.gasData);
      const tx = await txBuilder.build();
      should.equal(tx.type, TransactionType.StakingAdd);
      const rawTx = tx.toBroadcastFormat();
      should.equal(rawTx, testData.ADD_STAKE);
      const reserialized = await factory.from(rawTx).build();
      reserialized.toBroadcastFormat().should.equal(rawTx);
    });

    it('should build an stakeMany transaction and serialize it and deserialize it', async function () {
      const txBuilder = factory.getStakingBuilder();
      txBuilder.type(SuiTransactionType.AddStake);
      txBuilder.sender(testData.sender.address);
      txBuilder.stake(testData.requestAddStakeMany);
      txBuilder.gasData(testData.gasData);
      const tx = await txBuilder.build();
      should.equal(tx.type, TransactionType.StakingAdd);
      const rawTx = tx.toBroadcastFormat();
      should.equal(rawTx, testData.STAKE_MANY);
      const reserialized = await factory.from(rawTx).build();
      // reserialized.should.be.deepEqual(tx);
      reserialized.toBroadcastFormat().should.equal(rawTx);
    });

    it('should build and sign a staking tx with gasPayment', async function () {
      const txBuilder = factory.getStakingBuilder();
      txBuilder.type(SuiTransactionType.AddStake);
      txBuilder.sender(testData.sender.address);
      txBuilder.stake([testData.requestAddStake]);
      txBuilder.gasData(testData.gasData);
      const tx = await txBuilder.build();
      should.equal(tx.id, 'bP78boZ48sDdJsg2V1tJahpGyBwaC9GSTL2rvyADnsh');
      const rawTx = tx.toBroadcastFormat();
      should.equal(rawTx, testData.ADD_STAKE);

      const txBuilder2 = await factory.from(rawTx);
      await txBuilder2.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
      const signedTx = await txBuilder2.build();
      should.equal(signedTx.type, TransactionType.StakingAdd);
      should.equal(signedTx.id, 'bP78boZ48sDdJsg2V1tJahpGyBwaC9GSTL2rvyADnsh');

      const rawSignedTx = signedTx.toBroadcastFormat();
      should.equal(rawSignedTx, testData.ADD_STAKE);
      const reserializedTxBuilder = factory.from(rawSignedTx);
      reserializedTxBuilder.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
      const reserialized = await reserializedTxBuilder.build();

      reserialized.should.be.deepEqual(signedTx);
      reserialized.toBroadcastFormat().should.equal(rawSignedTx);
    });

    it('should build and sign a staking transaction with private keys', async () => {
      const keyPairSender = new KeyPair({ prv: testData.privateKeys.prvKey1 });
      const senderAddress = keyPairSender.getAddress();
      const expectedStakingTxSig =
        'AD8755e+kA3/Iy+3oRxBbQiK0Iz4qmD4sZcpoQN0UMPxIXv7Qx4twvuAiZf9H2nHYa/Ae0asM4Rkz1SCP0dhXgqlzaq1j4wMuCiXuFW4ojFfuoBhEiBy/K4eB5BkHZ+eZw==';
      const expectedStakingTxHex =
        'AAAJAAgALTEBAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUBAAAAAAAAAAEAIESxsxniNJWZX8g32v0o/Gr4tkXt3f8PwUZ/GtYxNiwjAAgArCP8BgAAAAAgRLGzGeI0lZlfyDfa/Sj8avi2Re3d/w/BRn8a1jE2LCYACAAtMQEAAAAAACBEsbMZ4jSVmV/IN9r9KPxq+LZF7d3/D8FGfxrWMTYsJgAIAKwj/AYAAAAAIESxsxniNJWZX8g32v0o/Gr4tkXt3f8PwUZ/GtYxNiwjCAIAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwpzdWlfc3lzdGVtEXJlcXVlc3RfYWRkX3N0YWtlAAMBAQACAAABAgACAAEBAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKc3VpX3N5c3RlbRFyZXF1ZXN0X2FkZF9zdGFrZQADAQEAAgIAAQQAAgABAQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCnN1aV9zeXN0ZW0RcmVxdWVzdF9hZGRfc3Rha2UAAwEBAAIEAAEGAAIAAQEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwpzdWlfc3lzdGVtEXJlcXVlc3RfYWRkX3N0YWtlAAMBAQACBgABCACQB0K7kj6pBqVvMloXWXdS7NOsMCa7qW4O3/Rd6NszqwJ3AbiIwWM5ms1bgEYzwdDlMrwxQ8/vNMo2C+YHxo3N72YEAAAAAAAAIDrMcEOTidjOdp7a1J/jjJ9tOjb6P2WTyBSwQqAHiA1/yfVla+cYIwE9k34GVOs+3LJhla/SMAm+mrlufz8twgNmBAAAAAAAACAhXnkXobS2E/RZ/cLDQ/n3BH/TxAjKv5VxsbLEZCUxu5AHQruSPqkGpW8yWhdZd1Ls06wwJrupbg7f9F3o2zOr6AMAAAAAAAAALTEBAAAAAAA=';

      const coins = [
        {
          digest: '4xXTGhhtNUjBU8nMivsLWTMRWvZRc5RWDMgyTMnnRDYS',
          objectId: '0x7701b888c163399acd5b804633c1d0e532bc3143cfef34ca360be607c68dcdef',
          version: 1126,
        },
        {
          digest: '3FG1Nfk5HrQ1tWYKS8BX9ynZTSprL5hLn3pgw7kdj1RU',
          objectId: '0xc9f5656be71823013d937e0654eb3edcb26195afd23009be9ab96e7f3f2dc203',
          version: 1126,
        },
      ];
      const txBuilder_1 = factory.getStakingBuilder();
      txBuilder_1.type(SuiTransactionType.AddStake);
      txBuilder_1.sender(senderAddress);
      txBuilder_1.stake(testData.requestAddStakeMany);
      const gasData: GasData = {
        payment: coins,
        owner: senderAddress,
        budget: testData.GAS_BUDGET,
        price: 1000,
      };
      txBuilder_1.gasData(gasData);

      const tx_1 = await txBuilder_1.build();
      const signable_1 = tx_1.signablePayload;
      const signature_1 = keyPairSender.signMessageinUint8Array(signable_1);
      txBuilder_1.addSignature({ pub: keyPairSender.getKeys().pub }, Buffer.from(signature_1));
      const signedTx_1 = (await txBuilder_1.build()) as StakingTransaction;
      const txHex_1 = signedTx_1.toBroadcastFormat();
      const sig_1 = Buffer.from(signedTx_1.serializedSig).toString('base64');

      const txBuilderFromRaw_2 = factory.getStakingBuilder();
      txBuilderFromRaw_2.from(txHex_1);
      const signable_2 = tx_1.signablePayload;
      txBuilderFromRaw_2.sign({ key: testData.privateKeys.prvKey1 });
      const signedTx_2 = (await txBuilderFromRaw_2.build()) as StakingTransaction;
      const txHex_2 = signedTx_2.toBroadcastFormat();
      const sig_2 = Buffer.from(signedTx_2.serializedSig).toString('base64');

      should.equal(Buffer.from(signable_1).toString('hex'), Buffer.from(signable_2).toString('hex'));
      should.equal(txHex_1, txHex_2);
      should.equal(txHex_1, expectedStakingTxHex);
      should.equal(txHex_2, expectedStakingTxHex);
      should.equal(sig_1, sig_2);
      should.equal(sig_1, expectedStakingTxSig);
      should.equal(sig_2, expectedStakingTxSig);
    });

    it('should fail to build if missing type', async function () {
      for (const txBuilder of builders) {
        txBuilder.sender(testData.sender.address);
        txBuilder.stake([testData.requestAddStake]);
        txBuilder.gasData(testData.gasData);
        await txBuilder.build().should.rejectedWith('type is required before building');
      }
    });

    it('should fail to build if missing sender', async function () {
      for (const txBuilder of builders) {
        txBuilder.type(SuiTransactionType.AddStake);
        txBuilder.stake([testData.requestAddStake]);
        txBuilder.gasData(testData.gasData);
        await txBuilder.build().should.rejectedWith('sender is required before building');
      }
    });

    it('should fail to build if missing gasData', async function () {
      for (const txBuilder of builders) {
        txBuilder.sender(testData.sender.address);
        txBuilder.type(SuiTransactionType.AddStake);
        txBuilder.stake([testData.requestAddStake]);
        await txBuilder.build().should.rejectedWith('gasData is required before building');
      }
    });

    it('should fail to build if missing payment coins in gasData', async function () {
      for (const txBuilder of builders) {
        txBuilder.sender(testData.sender.address);
        txBuilder.type(SuiTransactionType.AddStake);
        txBuilder.stake([testData.requestAddStake]);
        should(() => txBuilder.gasData(testData.gasDataWithoutGasPayment)).throwError(
          `gas payment is required before building`
        );
        await txBuilder.build().should.rejectedWith('gasData is required before building');
      }
    });

    it('should build a send from rawTx', async function () {
      const txBuilder = factory.from(testData.ADD_STAKE);
      const builtTx = await txBuilder.build();
      should.equal(builtTx.type, TransactionType.StakingAdd);
      should.equal(builtTx.id, 'bP78boZ48sDdJsg2V1tJahpGyBwaC9GSTL2rvyADnsh');
      builtTx.inputs.length.should.equal(1);
      builtTx.inputs[0].should.deepEqual({
        address: testData.sender.address,
        value: STAKING_AMOUNT.toString(),
        coin: 'tsui',
      });
      builtTx.outputs.length.should.equal(1);
      builtTx.outputs[0].should.deepEqual({
        address: testData.requestAddStake.validatorAddress,
        value: STAKING_AMOUNT.toString(),
        coin: 'tsui',
      });
      const jsonTx = builtTx.toJson();
      jsonTx.gasData.should.deepEqual(testData.gasData);
      jsonTx.kind.ProgrammableTransaction.should.deepEqual({
        inputs: testData.txInputsAddStake,
        transactions: testData.txTransactionsAddStake,
      });
      jsonTx.sender.should.equal(testData.sender.address);
      jsonTx.gasData.should.deepEqual(testData.gasData);
      builtTx.toBroadcastFormat().should.equal(testData.ADD_STAKE);
    });
  });
  describe('Unstaking TX', async () => {
    beforeEach(function (done) {
      builders = [factory.getUnstakingBuilder()];
      done();
    });

    it('should build an unstaking transaction and serialize it and deserialize it', async function () {
      const txBuilder = factory.getUnstakingBuilder();
      txBuilder.type(SuiTransactionType.WithdrawStake);
      txBuilder.sender(testData.sender.address);
      txBuilder.unstake(testData.requestWithdrawStakedSui);
      txBuilder.gasData(testData.gasData);
      const tx = await txBuilder.build();
      should.equal(tx.type, TransactionType.StakingClaim);
      const rawTx = tx.toBroadcastFormat();
      should.equal(rawTx, testData.WITHDRAW_STAKED_SUI);
      const reserialized = await factory.from(rawTx).build();
      reserialized.toBroadcastFormat().should.equal(rawTx);
    });

    it('should build and sign a staking tx with gasPayment', async function () {
      const txBuilder = factory.getUnstakingBuilder();
      txBuilder.type(SuiTransactionType.WithdrawStake);
      txBuilder.sender(testData.sender.address);
      txBuilder.unstake(testData.requestWithdrawStakedSui);
      txBuilder.gasData(testData.gasData);
      const tx = await txBuilder.build();
      should.equal(tx.id, 'Rixz9C2yQ6jDFFsovUs84Y6yw2dco8HH4QhK7RCQYNo');
      const rawTx = tx.toBroadcastFormat();
      should.equal(rawTx, testData.WITHDRAW_STAKED_SUI);

      const txBuilder2 = await factory.from(rawTx);
      await txBuilder2.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
      const signedTx = await txBuilder2.build();
      should.equal(signedTx.type, TransactionType.StakingClaim);
      should.equal(signedTx.id, 'Rixz9C2yQ6jDFFsovUs84Y6yw2dco8HH4QhK7RCQYNo');

      const rawSignedTx = signedTx.toBroadcastFormat();
      should.equal(rawSignedTx, testData.WITHDRAW_STAKED_SUI);
      const reserializedTxBuilder = factory.from(rawSignedTx);
      reserializedTxBuilder.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
      const reserialized = await reserializedTxBuilder.build();

      reserialized.should.be.deepEqual(signedTx);
      reserialized.toBroadcastFormat().should.equal(rawSignedTx);
    });

    it('should build and sign a staking transaction with private keys', async () => {
      const keyPairSender = new KeyPair({ prv: testData.privateKeys.prvKey1 });
      const senderAddress = keyPairSender.getAddress();
      const expectedStakingTxSig =
        'ABqwaOIsxZjf/LIE985SJK2lH56oNxIuHVpZhN+6L9uZFYFuipa6zsg5fnprxuBPIUVTorw3yEJcoKNGZwOpQgKlzaq1j4wMuCiXuFW4ojFfuoBhEiBy/K4eB5BkHZ+eZw==';
      const expectedStakingTxHex =
        'AAACAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQEAAAAAAAAAAQEA7m38PaMuIVQaKurfzSUPigoju3q9qciYhAf8MgaMN0ZhBAAAAAAAACDJYCWUFis6HawzxGyErvRT03pYayRliLki0kYsV0XCBAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKc3VpX3N5c3RlbRZyZXF1ZXN0X3dpdGhkcmF3X3N0YWtlAAIBAAABAQCQB0K7kj6pBqVvMloXWXdS7NOsMCa7qW4O3/Rd6NszqwJ3AbiIwWM5ms1bgEYzwdDlMrwxQ8/vNMo2C+YHxo3N72YEAAAAAAAAIDrMcEOTidjOdp7a1J/jjJ9tOjb6P2WTyBSwQqAHiA1/yfVla+cYIwE9k34GVOs+3LJhla/SMAm+mrlufz8twgNmBAAAAAAAACAhXnkXobS2E/RZ/cLDQ/n3BH/TxAjKv5VxsbLEZCUxu5AHQruSPqkGpW8yWhdZd1Ls06wwJrupbg7f9F3o2zOr6AMAAAAAAAAALTEBAAAAAAA=';

      const coins = [
        {
          digest: '4xXTGhhtNUjBU8nMivsLWTMRWvZRc5RWDMgyTMnnRDYS',
          objectId: '0x7701b888c163399acd5b804633c1d0e532bc3143cfef34ca360be607c68dcdef',
          version: 1126,
        },
        {
          digest: '3FG1Nfk5HrQ1tWYKS8BX9ynZTSprL5hLn3pgw7kdj1RU',
          objectId: '0xc9f5656be71823013d937e0654eb3edcb26195afd23009be9ab96e7f3f2dc203',
          version: 1126,
        },
      ];
      const txBuilder_1 = factory.getUnstakingBuilder();
      txBuilder_1.type(SuiTransactionType.WithdrawStake);
      txBuilder_1.sender(senderAddress);
      txBuilder_1.unstake(testData.requestWithdrawStakedSui);
      const gasData: GasData = {
        payment: coins,
        owner: senderAddress,
        budget: testData.GAS_BUDGET,
        price: 1000,
      };
      txBuilder_1.gasData(gasData);

      const tx_1 = await txBuilder_1.build();
      const signable_1 = tx_1.signablePayload;
      const signature_1 = keyPairSender.signMessageinUint8Array(signable_1);
      txBuilder_1.addSignature({ pub: keyPairSender.getKeys().pub }, Buffer.from(signature_1));
      const signedTx_1 = (await txBuilder_1.build()) as UnstakingTransaction;
      const txHex_1 = signedTx_1.toBroadcastFormat();
      const sig_1 = Buffer.from(signedTx_1.serializedSig).toString('base64');

      const txBuilderFromRaw_2 = factory.getUnstakingBuilder();
      txBuilderFromRaw_2.from(txHex_1);
      const signable_2 = tx_1.signablePayload;
      txBuilderFromRaw_2.sign({ key: testData.privateKeys.prvKey1 });
      const signedTx_2 = (await txBuilderFromRaw_2.build()) as UnstakingTransaction;
      const txHex_2 = signedTx_2.toBroadcastFormat();
      const sig_2 = Buffer.from(signedTx_2.serializedSig).toString('base64');

      should.equal(Buffer.from(signable_1).toString('hex'), Buffer.from(signable_2).toString('hex'));
      should.equal(txHex_1, txHex_2);
      should.equal(txHex_1, expectedStakingTxHex);
      should.equal(txHex_2, expectedStakingTxHex);
      should.equal(sig_1, sig_2);
      should.equal(sig_1, expectedStakingTxSig);
      should.equal(sig_2, expectedStakingTxSig);
    });

    it('should fail to build if missing type', async function () {
      for (const txBuilder of builders) {
        txBuilder.sender(testData.sender.address);
        txBuilder.unstake(testData.requestWithdrawStakedSui);
        txBuilder.gasData(testData.gasData);
        await txBuilder.build().should.rejectedWith('type is required before building');
      }
    });

    it('should fail to build if missing sender', async function () {
      for (const txBuilder of builders) {
        txBuilder.type(SuiTransactionType.WithdrawStake);
        txBuilder.unstake(testData.requestWithdrawStakedSui);
        txBuilder.gasData(testData.gasData);
        await txBuilder.build().should.rejectedWith('sender is required before building');
      }
    });

    it('should fail to build if missing gasData', async function () {
      for (const txBuilder of builders) {
        txBuilder.sender(testData.sender.address);
        txBuilder.type(SuiTransactionType.WithdrawStake);
        txBuilder.unstake(testData.requestWithdrawStakedSui);
        await txBuilder.build().should.rejectedWith('gasData is required before building');
      }
    });

    it('should fail to build if missing payment coins in gasData', async function () {
      for (const txBuilder of builders) {
        txBuilder.sender(testData.sender.address);
        txBuilder.type(SuiTransactionType.WithdrawStake);
        txBuilder.unstake(testData.requestWithdrawStakedSui);
        should(() => txBuilder.gasData(testData.gasDataWithoutGasPayment)).throwError(
          `gas payment is required before building`
        );
        await txBuilder.build().should.rejectedWith('gasData is required before building');
      }
    });

    it('should build a send from rawTx', async function () {
      const txBuilder = factory.from(testData.WITHDRAW_STAKED_SUI);
      const builtTx = await txBuilder.build();
      should.equal(builtTx.type, TransactionType.StakingClaim);
      should.equal(builtTx.id, 'Rixz9C2yQ6jDFFsovUs84Y6yw2dco8HH4QhK7RCQYNo');
      builtTx.inputs.length.should.equal(1);
      builtTx.inputs[0].should.deepEqual({
        address: testData.requestWithdrawStakedSui.stakedSui.objectId,
        value: AMOUNT_UNKNOWN_TEXT,
        coin: 'tsui',
      });
      builtTx.outputs.length.should.equal(1);
      builtTx.outputs[0].should.deepEqual({
        address: testData.sender.address,
        value: AMOUNT_UNKNOWN_TEXT,
        coin: 'tsui',
      });
      const jsonTx = builtTx.toJson();
      jsonTx.gasData.should.deepEqual(testData.gasData);
      jsonTx.kind.ProgrammableTransaction.should.deepEqual({
        inputs: testData.txInputWithdrawStaked,
        transactions: testData.txTransactionsWithdrawStaked,
      });
      jsonTx.sender.should.equal(testData.sender.address);
      jsonTx.gasData.should.deepEqual(testData.gasData);
      builtTx.toBroadcastFormat().should.equal(testData.WITHDRAW_STAKED_SUI);
    });

    it('should determine correct type for withdrawal with amount', async function () {
      const txBuilder = factory.from(testData.WITHDRAW_STAKED_SUI_WITH_AMOUNT);
      const tx = await txBuilder.build();
      should.equal(tx.type, TransactionType.StakingClaim);
    });
  });
  describe('Custom TX', async () => {
    it('should sign a custom tx', async function () {
      const keyPairSender = new KeyPair({ prv: testData.privateKeys.prvKey1 });

      const txBuilder = factory.from(CUSTOM_TX_STAKING_POOL_SPLIT);
      const tx = await txBuilder.build();
      const unsignedTxHex = tx.toBroadcastFormat();
      const signable = tx.signablePayload;
      const signatureBytes = keyPairSender.signMessageinUint8Array(signable);
      txBuilder.addSignature({ pub: keyPairSender.getKeys().pub }, Buffer.from(signatureBytes));
      const signedTx = (await txBuilder.build()) as CustomTransaction;
      const signedTxHex = signedTx.toBroadcastFormat();
      should.equal(signedTxHex, unsignedTxHex);
      should.equal(signedTx.signature.length, 1);
      should.equal(signedTx.signature[0], Buffer.from(signatureBytes).toString('hex'));

      const serializedSig = Buffer.from(signedTx.serializedSig).toString('base64');
      should.equal(
        serializedSig,
        'APGQHoYbVSyL6M7lOQL5w2YYzeeVcTMEbe0Y4jVphQA+0QHq3VEDoXVwIukkL44z+vqsekviS4gQ0ZwUPTWHFQilzaq1j4wMuCiXuFW4ojFfuoBhEiBy/K4eB5BkHZ+eZw=='
      );
    });

    it('should fail to build if unsupported txn type', async function () {
      should(() => factory.from(UNSUPPORTED_TX)).throwError(
        'unsupported target method 0000000000000000000000000000000000000000000000000000000000000003::staking_pool::split_staked_sui'
      );
    });
  });
});

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


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