PHP WebShell

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

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

import { AlgoLib, Talgo } from '../../src';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { BitGoAPI } from '@bitgo/sdk-api';
import * as AlgoResources from '../fixtures/algo';
import { randomBytes } from 'crypto';
import { coins } from '@bitgo/statics';
import Sinon, { SinonStub } from 'sinon';
import assert from 'assert';
import { Algo } from '../../src/algo';
import BigNumber from 'bignumber.js';
import { TransactionBuilderFactory } from '../../src/lib';
import { KeyPair } from '@bitgo/sdk-core';

describe('ALGO:', function () {
  let bitgo: TestBitGoAPI;
  let basecoin;
  const receiver = AlgoResources.accounts.account2;

  before(function () {
    bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' });
    bitgo.safeRegister('talgo', Talgo.createInstance);
    bitgo.initializeTestVars();
    basecoin = bitgo.coin('talgo');
  });

  describe('Should Fail: ', () => {
    it('Does not have a txHex', async () => {
      await basecoin
        .explainTransaction({
          params: {},
        })
        .should.be.rejectedWith('missing explain tx parameters');
    });

    it('Does not have a fee', async () => {
      await basecoin
        .explainTransaction({
          params: {
            txHex: 'Some Valid Hex',
          },
        })
        .should.be.rejectedWith('missing explain tx parameters');
    });
  });

  describe('Transfer Builder: ', () => {
    const buildBaseTransferTransaction = ({ destination, amount = 10000, sender, memo = '' }) => {
      const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
      const txBuilder = factory.getTransferBuilder();
      const lease = new Uint8Array(randomBytes(32));
      const note = new Uint8Array(Buffer.from(memo, 'utf-8'));
      txBuilder
        .sender({ address: sender })
        .to({ address: destination })
        .amount(amount)
        .isFlatFee(true)
        .fee({
          fee: '1000',
        })
        .firstRound(1)
        .lastRound(100)
        .lease(lease)
        .note(note)
        .testnet();
      return txBuilder;
    };

    /**
     * Build an unsigned account-lib single-signature send transaction
     * @param sender The senders address
     * @param destination The destination address of the transaction
     * @param amount The amount to send to the recipient
     * @param memo Optional note with the transaction
     */
    const buildUnsignedTransaction = async function ({ sender, destination, amount = 10000, memo = '' }) {
      const txBuilder = buildBaseTransferTransaction({ sender, destination, amount, memo });
      return await txBuilder.build();
    };

    /**
     * Build a signed account-lib single-signature send transaction
     * @param sender The senders address
     * @param destination The destination address of the transaction
     * @param amount The amount to send to the recipient
     * @param memo Optional note with the transaction
     */
    const buildSignedTransaction = async function ({ sender, destination, amount = 10000, memo = '' }) {
      const txBuilder = buildBaseTransferTransaction({ sender, destination, amount, memo });
      txBuilder.numberOfSigners(1);
      txBuilder.sign({ key: AlgoResources.accounts.account1.prvKey });
      return await txBuilder.build();
    };

    /**
     * Build a multi-signed account-lib single-signature send transaction
     * @param senders The list of senders
     * @param destination The destination address of the transaction
     * @param amount The amount to send to the recipient
     * @param memo Optional note with the transaction
     */
    const buildMultiSignedTransaction = async function ({ senders, destination, amount = 10000, memo = '' }) {
      const txBuilder = buildBaseTransferTransaction({ sender: senders[0], destination, amount, memo });
      txBuilder.numberOfSigners(2);
      txBuilder.setSigners(senders);
      txBuilder.sign({ key: AlgoResources.accounts.account1.prvKey });
      txBuilder.sign({ key: AlgoResources.accounts.account3.prvKey });
      return await txBuilder.build();
    };

    it('should explain an unsigned transfer transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.transfer.unsigned,
        feeInfo: { fee: '1000' },
      });
      explain.outputAmount.should.equal('10000');
      explain.outputs[0].amount.should.equal('10000');
      explain.outputs[0].address.should.equal(receiver.address);
      Buffer.from(explain.outputs[0].memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
    });

    it('should explain a signed transfer transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.transfer.signed,
        feeInfo: { fee: '1000' },
      });
      explain.outputAmount.should.equal('10000');
      explain.outputs[0].amount.should.equal('10000');
      explain.outputs[0].address.should.equal(receiver.address);
      Buffer.from(explain.outputs[0].memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
    });

    it('should explain a multiSig transfer transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.transfer.multiSigned,
        feeInfo: { fee: '1000' },
      });
      explain.outputAmount.should.equal('10000');
      explain.outputs[0].amount.should.equal('10000');
      explain.outputs[0].address.should.equal(receiver.address);
      Buffer.from(explain.outputs[0].memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
    });

    it('should explain a half signed transfer transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        halfSigned: {
          txHex: AlgoResources.explainRawTx.transfer.halfSigned,
        },
        feeInfo: { fee: '1000' },
      });
      explain.outputAmount.should.equal('10000');
      explain.outputs[0].amount.should.equal('10000');
      explain.outputs[0].address.should.equal(receiver.address);
      Buffer.from(explain.outputs[0].memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
    });

    it('should explain an unsigned transaction', async function () {
      const sender = AlgoResources.accounts.account1.address;
      const destination = AlgoResources.accounts.account2.address;
      const amount = 10000;
      const memo = AlgoResources.explainRawTx.transfer.note;
      const unsignedTransaction = await buildUnsignedTransaction({
        sender,
        destination,
        amount,
        memo,
      });
      const unsignedHex = Buffer.from(unsignedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: unsignedHex,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.outputs[0].memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.outputs[0].amount.should.equal(amount.toString());
      explain.outputs[0].address.should.equal(destination);
    });

    it('should explain a signed transaction', async function () {
      const sender = AlgoResources.accounts.account1.address;
      const destination = AlgoResources.accounts.account2.address;
      const amount = 10000;
      const memo = AlgoResources.explainRawTx.transfer.note;
      const signedTransaction = await buildSignedTransaction({
        sender,
        destination,
        amount,
        memo,
      });
      const signedHex = Buffer.from(signedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: signedHex,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.outputs[0].memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.outputs[0].amount.should.equal(amount.toString());
      explain.outputs[0].address.should.equal(destination);
    });

    it('should explain a multiSigned transaction', async function () {
      const senders = [AlgoResources.accounts.account1.address, AlgoResources.accounts.account3.address];
      const destination = AlgoResources.accounts.account2.address;
      const amount = 10000;
      const memo = AlgoResources.explainRawTx.transfer.note;
      const signedTransaction = await buildMultiSignedTransaction({
        senders,
        destination,
        amount,
        memo,
      });
      const signedHex = Buffer.from(signedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: signedHex,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.outputs[0].memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.outputs[0].amount.should.equal(amount.toString());
      explain.outputs[0].address.should.equal(destination);
    });
  });

  describe('Asset Transfer Builder: ', () => {
    const buildBaseAssetTransferTransaction = ({ destination, amount = 1000, tokenId, sender }) => {
      const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
      const txBuilder = factory.getAssetTransferBuilder();
      const lease = new Uint8Array(randomBytes(32));
      txBuilder
        .sender({ address: sender })
        .isFlatFee(true)
        .fee({
          fee: '1000',
        })
        .tokenId(tokenId)
        .firstRound(1)
        .lastRound(100)
        .lease(lease)
        .to({ address: destination })
        .amount(amount)
        .testnet();
      return txBuilder;
    };

    /**
     * Build an unsigned account-lib single-signature asset transfer transaction
     * @param sender The senders address
     * @param destination The destination address of the transaction
     * @param amount The amount to send to the recipient
     * @param tokenId The assetIndex for the token
     */
    const buildUnsignedTransaction = async function ({ sender, destination, amount = 10000, tokenId }) {
      const txBuilder = buildBaseAssetTransferTransaction({ sender, destination, amount, tokenId });
      return await txBuilder.build();
    };

    /**
     * Build a signed account-lib single-signature send transaction
     * @param sender The senders address
     * @param destination The destination address of the transaction
     * @param amount The amount to send to the recipient
     * @param tokenId The assetIndex for the token
     */
    const buildSignedTransaction = async function ({ sender, destination, amount = 10000, tokenId }) {
      const txBuilder = buildBaseAssetTransferTransaction({ sender, destination, amount, tokenId });
      txBuilder.numberOfSigners(1);
      txBuilder.sign({ key: AlgoResources.accounts.account1.prvKey });
      return await txBuilder.build();
    };

    it('should explain an unsigned asset transfer transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.assetTransfer.unsigned,
        feeInfo: { fee: '1000' },
      });
      explain.outputAmount.should.equal('1000');
      explain.outputs[0].amount.should.equal('1000');
      explain.outputs[0].address.should.equal(receiver.address);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
      explain.tokenId.should.equal(1);
    });

    it('should explain a signed asset transfer transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.assetTransfer.signed,
        feeInfo: { fee: '1000' },
      });
      explain.outputAmount.should.equal('10000000000000000000');
      explain.outputs[0].amount.should.equal('10000000000000000000');
      explain.outputs[0].address.should.equal(receiver.address);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
      explain.tokenId.should.equal(1);
    });

    it('should explain an unsigned transaction', async function () {
      const sender = AlgoResources.accounts.account1.address;
      const destination = AlgoResources.accounts.account2.address;
      const amount = 10000;
      const tokenId = 1;
      const unsignedTransaction = await buildUnsignedTransaction({
        sender,
        destination,
        amount,
        tokenId,
      });
      const unsignedHex = Buffer.from(unsignedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: unsignedHex,
        feeInfo: { fee: '1000' },
      });
      explain.outputs[0].amount.should.equal(amount.toString());
      explain.outputs[0].address.should.equal(destination);
      explain.tokenId.should.equal(1);
    });

    it('should explain a signed transaction', async function () {
      const sender = AlgoResources.accounts.account1.address;
      const destination = AlgoResources.accounts.account2.address;
      const amount = 10000;
      const tokenId = 1;
      const signedTransaction = await buildSignedTransaction({
        sender,
        destination,
        amount,
        tokenId,
      });
      const signedHex = Buffer.from(signedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: signedHex,
        feeInfo: { fee: '1000' },
      });
      explain.outputs[0].amount.should.equal(amount.toString());
      explain.outputs[0].address.should.equal(destination);
      explain.tokenId.should.equal(1);
    });
  });

  describe('Wallet Init Builder: ', () => {
    const buildBaseKeyRegTransaction = ({ sender, memo = '' }) => {
      const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
      const txBuilder = factory.getWalletInitializationBuilder();
      const lease = new Uint8Array(randomBytes(32));
      const note = new Uint8Array(Buffer.from(memo, 'utf-8'));
      txBuilder
        .sender({ address: sender.address })
        .isFlatFee(true)
        .fee({
          fee: '1000',
        })
        .firstRound(1)
        .lastRound(100)
        .lease(lease)
        .note(note)
        .voteKey(sender.voteKey)
        .selectionKey(sender.selectionKey)
        .voteFirst(1)
        .voteLast(100)
        .voteKeyDilution(9)
        .testnet();
      return txBuilder;
    };

    /**
     * Build an unsigned account-lib single-signature send transaction
     * @param sender The senders address
     * @param memo Optional note with the transaction
     */
    const buildUnsignedTransaction = async function ({ sender, memo = '' }) {
      const txBuilder = buildBaseKeyRegTransaction({ sender, memo });
      return await txBuilder.build();
    };

    /**
     * Build a signed account-lib single-signature send transaction
     * @param sender The senders address
     * @param memo Optional note with the transaction
     */
    const buildSignedTransaction = async function ({ sender, memo = '' }) {
      const txBuilder = buildBaseKeyRegTransaction({ sender, memo });
      txBuilder.numberOfSigners(1);
      txBuilder.sign({ key: AlgoResources.accounts.account1.prvKey });
      return await txBuilder.build();
    };

    /**
     * Build a multi-signed account-lib single-signature send transaction
     * @param senders The list of senders
     * @param memo Optional note with the transaction
     */
    const buildMultiSignedTransaction = async function ({ senders, memo = '' }) {
      const txBuilder = buildBaseKeyRegTransaction({ sender: senders[0], memo });
      txBuilder.numberOfSigners(2);
      txBuilder.setSigners(senders.map(({ address }) => address));
      txBuilder.sign({ key: AlgoResources.accounts.account1.prvKey });
      txBuilder.sign({ key: AlgoResources.accounts.account3.prvKey });
      return await txBuilder.build();
    };

    it('should explain an unsigned KeyReg transfer transaction hex', async function () {
      const sender = AlgoResources.accounts.account1;
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.keyreg.unsigned,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.memo).toString().should.equal(AlgoResources.explainRawTx.keyreg.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
      explain.voteKey.should.equal(sender.voteKey);
      explain.selectionKey.should.equal(sender.selectionKey);
      explain.voteFirst.should.equal(1);
      explain.voteLast.should.equal(100);
      explain.voteKeyDilution.should.equal(9);
    });

    it('should explain a signed transfer transaction hex', async function () {
      const sender = AlgoResources.accounts.account1;
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.keyreg.signed,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.memo).toString().should.equal(AlgoResources.explainRawTx.keyreg.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
      explain.voteKey.should.equal(sender.voteKey);
      explain.selectionKey.should.equal(sender.selectionKey);
      explain.voteFirst.should.equal(1);
      explain.voteLast.should.equal(100);
      explain.voteKeyDilution.should.equal(9);
    });

    it('should explain a multiSig transfer transaction hex', async function () {
      const sender = AlgoResources.accounts.account1;
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.keyreg.multiSigned,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.memo).toString().should.equal(AlgoResources.explainRawTx.keyreg.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
      explain.voteKey.should.equal(sender.voteKey);
      explain.selectionKey.should.equal(sender.selectionKey);
      explain.voteFirst.should.equal(1);
      explain.voteLast.should.equal(100);
      explain.voteKeyDilution.should.equal(9);
    });

    it('should explain a half signed transfer transaction hex', async function () {
      const sender = AlgoResources.accounts.account1;
      const explain = await basecoin.explainTransaction({
        halfSigned: {
          txHex: AlgoResources.explainRawTx.keyreg.halfSigned,
        },
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.memo).toString().should.equal(AlgoResources.explainRawTx.keyreg.note);
      explain.fee.should.equal(1000);
      explain.changeAmount.should.equal('0');
      explain.voteKey.should.equal(sender.voteKey);
      explain.selectionKey.should.equal(sender.selectionKey);
      explain.voteFirst.should.equal(1);
      explain.voteLast.should.equal(100);
      explain.voteKeyDilution.should.equal(9);
    });

    it('should explain an unsigned transaction', async function () {
      const sender = AlgoResources.accounts.account1;
      const memo = AlgoResources.explainRawTx.transfer.note;
      const unsignedTransaction = await buildUnsignedTransaction({
        sender,
        memo,
      });

      const unsignedHex = Buffer.from(unsignedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: unsignedHex,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.voteKey.should.equal(sender.voteKey);
      explain.selectionKey.should.equal(sender.selectionKey);
      explain.voteFirst.should.equal(1);
      explain.voteLast.should.equal(100);
      explain.voteKeyDilution.should.equal(9);
    });

    it('should explain a signed transaction', async function () {
      const sender = AlgoResources.accounts.account1;
      const memo = AlgoResources.explainRawTx.transfer.note;
      const signedTransaction = await buildSignedTransaction({
        sender,
        memo,
      });
      const signedHex = Buffer.from(signedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: signedHex,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.voteKey.should.equal(sender.voteKey);
      explain.selectionKey.should.equal(sender.selectionKey);
      explain.voteFirst.should.equal(1);
      explain.voteLast.should.equal(100);
      explain.voteKeyDilution.should.equal(9);
    });

    it('should explain a multiSigned transaction', async function () {
      const senders = [AlgoResources.accounts.account1, AlgoResources.accounts.account3];
      const memo = AlgoResources.explainRawTx.transfer.note;
      const signedTransaction = await buildMultiSignedTransaction({
        senders,
        memo,
      });
      const signedHex = Buffer.from(signedTransaction.toBroadcastFormat()).toString('hex');
      const explain = await basecoin.explainTransaction({
        txHex: signedHex,
        feeInfo: { fee: '1000' },
      });
      Buffer.from(explain.memo).toString().should.equal(AlgoResources.explainRawTx.transfer.note);
      explain.voteKey.should.equal(AlgoResources.accounts.account1.voteKey);
      explain.selectionKey.should.equal(AlgoResources.accounts.account1.selectionKey);
      explain.voteFirst.should.equal(1);
      explain.voteLast.should.equal(100);
      explain.voteKeyDilution.should.equal(9);
    });
  });
  describe('Sign transaction', () => {
    it('should sign transaction', async function () {
      const signed = await basecoin.signTransaction({
        txPrebuild: {
          txHex: AlgoResources.rawTx.transfer.unsigned,
          keys: [AlgoResources.accounts.account1.pubKey.toString('hex')],
          addressVersion: 1,
        },
        prv: AlgoResources.accounts.account1.prvKey,
      });
      signed.txHex.should.equal(AlgoResources.rawTx.transfer.signed);
    });

    it('should sign transaction with root key', async function () {
      const keypair = basecoin.generateRootKeyPair(AlgoResources.accounts.account1.secretKey);

      const signed = await basecoin.signTransaction({
        txPrebuild: {
          txHex: AlgoResources.rawTx.transfer.unsigned,
          keys: [keypair.pub],
          addressVersion: 1,
        },
        prv: keypair.prv,
      });
      signed.txHex.should.equal(AlgoResources.rawTx.transfer.signed);
    });

    it('should sign half signed transaction', async function () {
      const signed = await basecoin.signTransaction({
        txPrebuild: {
          halfSigned: {
            txHex: AlgoResources.rawTx.transfer.halfSigned,
          },
          keys: [
            AlgoResources.accounts.account1.pubKey.toString('hex'),
            AlgoResources.accounts.account3.pubKey.toString('hex'),
          ],
          addressVersion: 1,
        },
        prv: AlgoResources.accounts.account3.prvKey,
      });
      signed.txHex.should.equal(AlgoResources.rawTx.transfer.multisig);
    });

    it('should sign half signed transaction with root key', async function () {
      const signed = await basecoin.signTransaction({
        txPrebuild: {
          halfSigned: {
            txHex: AlgoResources.rootKeyData.unsignedTx,
          },
          keys: [
            AlgoResources.rootKeyData.userKeyPair.pub,
            AlgoResources.rootKeyData.backupPub,
            AlgoResources.rootKeyData.bitgoPub,
          ],
          addressVersion: 1,
        },
        prv: AlgoResources.rootKeyData.userKeyPair.prv,
      });

      signed.txHex.should.deepEqual(AlgoResources.rootKeyData.halfSignedTx);
      const factory = new TransactionBuilderFactory(coins.get('algo'));
      const tx = await factory.from(signed.txHex).build();
      const txJson = tx.toJson();
      txJson.from.should.equal(AlgoResources.rootKeyData.senderAddress);
    });

    it('should verify sign params if the key array contains addresses', function () {
      const keys = [
        AlgoResources.accounts.account1.address,
        AlgoResources.accounts.account2.address,
        AlgoResources.accounts.account3.address,
      ];

      const verifiedParams = basecoin.verifySignTransactionParams({
        txPrebuild: {
          txHex: AlgoResources.rawTx.transfer.unsigned,
          keys,
          addressVersion: 1,
        },
        prv: AlgoResources.accounts.account2.secretKey.toString('hex'),
      });
      verifiedParams.should.have.properties([
        'txHex',
        'addressVersion',
        'signers',
        'prv',
        'isHalfSigned',
        'numberSigners',
      ]);
      const { txHex, signers, isHalfSigned } = verifiedParams;
      txHex.should.be.equal(AlgoResources.rawTx.transfer.unsigned);
      signers.should.be.deepEqual(keys);
      isHalfSigned.should.be.equal(false);
    });

    it('should sign half signed transaction if the key array contains addresses', async function () {
      const signed = await basecoin.signTransaction({
        txPrebuild: {
          halfSigned: {
            txHex: AlgoResources.rawTx.transfer.halfSigned,
          },
          keys: [AlgoResources.accounts.account1.address, AlgoResources.accounts.account3.address],
          addressVersion: 1,
        },
        prv: AlgoResources.accounts.account3.prvKey,
      });
      signed.txHex.should.equal(AlgoResources.rawTx.transfer.multisig);
    });
  });

  describe('Sign message', () => {
    it('should sign message', async function () {
      const signed = await basecoin.signMessage(
        { prv: AlgoResources.accounts.account1.prvKey },
        AlgoResources.message.unsigned
      );
      signed.toString('hex').should.equal(AlgoResources.message.signed);
    });
  });

  describe('Generate wallet key pair: ', () => {
    it('should generate key pair', () => {
      const kp = basecoin.generateKeyPair();
      basecoin.isValidPub(kp.pub).should.equal(true);
      basecoin.isValidPrv(kp.prv).should.equal(true);
    });

    it('should generate key pair from seed', () => {
      const seed = Buffer.from('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', 'hex');
      const kp = basecoin.generateKeyPair(seed);
      basecoin.isValidPub(kp.pub).should.equal(true);
      basecoin.isValidPrv(kp.prv).should.equal(true);
    });
  });

  describe('Generate wallet Root key pair: ', () => {
    it('should generate key pair', () => {
      const kp = basecoin.generateRootKeyPair();
      basecoin.isValidPub(kp.pub).should.equal(true);
      basecoin.isValidPrv(kp.prv).should.equal(true);
    });

    it('should generate key pair from seed', () => {
      const seed = Buffer.from('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', 'hex');
      const kp = basecoin.generateRootKeyPair(seed);
      basecoin.isValidPub(kp.pub).should.equal(true);
      kp.pub.should.equal('d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a');
      basecoin.isValidPrv(kp.prv).should.equal(true);
      kp.prv.should.equal(
        '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
      );
    });
  });

  describe('Enable, disable and transfer Token ', () => {
    it('should explain an enable token transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.enableToken,
        feeInfo: { fee: '1000' },
      });
      explain.operations.length.should.equals(1);
      explain.operations[0].type.should.equals('enableToken');
      explain.operations[0].coin.should.equals('talgo:USON-16026728');
    });

    it('should explain an disable token transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.disableToken,
        feeInfo: { fee: '1000' },
      });
      explain.operations.length.should.equals(1);
      explain.operations[0].type.should.equals('disableToken');
      explain.operations[0].coin.should.equals('talgo:USON-16026728');
    });
    it('should explain an transfer token transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.assetTransfer.signed,
        feeInfo: { fee: '1000' },
      });
      explain.operations.length.should.equals(1);
      explain.operations[0].type.should.equals('transferToken');
      explain.operations[0].coin.should.equals('AlgoToken unknown');
    });
    it('should explain an enable USDT token transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.enableTokenUSDT,
        feeInfo: { fee: '1000' },
      });
      explain.operations.length.should.equals(1);
      explain.operations[0].type.should.equals('enableToken');
      explain.operations[0].coin.should.equals('talgo:USDt-180447');
    });
    it('should explain an enable USDC token transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.enableTokenUSDC,
        feeInfo: { fee: '1000' },
      });
      explain.operations.length.should.equals(1);
      explain.operations[0].type.should.equals('enableToken');
      explain.operations[0].coin.should.equals('talgo:USDC-10458941');
    });
    it('should explain an disable USDC token transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.disableTokenUSDC,
        feeInfo: { fee: '1000' },
      });
      explain.operations.length.should.equals(1);
      explain.operations[0].type.should.equals('disableToken');
      explain.operations[0].coin.should.equals('talgo:USDC-10458941');
    });
    it('should explain an transfer USDC token transaction hex', async function () {
      const explain = await basecoin.explainTransaction({
        txHex: AlgoResources.explainRawTx.USDCAssetTransfer.signed,
        feeInfo: { fee: '1000' },
      });
      explain.operations.length.should.equals(1);
      explain.operations[0].type.should.equals('transferToken');
      explain.operations[0].coin.should.equals('talgo:USDC-10458941');
    });
  });

  describe('deriveKeyWithSeed', function () {
    it('should derive key with seed', function () {
      (() => {
        basecoin.deriveKeyWithSeed('test');
      }).should.throw('method deriveKeyWithSeed not supported for eddsa curve');
    });
  });

  describe('Recovery', function () {
    const fee = 1000;
    const userKey =
      '{"iv":"ZJg0a0+zT+684MUl44Lm4A==","v":1,"iter":10000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"abQy0OL2468=","ct":"LNlSlTJED8jSwHCmUflzqFtRPL+PojzOgfd5mD2nmLVdAoyKCWHvAieKt7lJ7zg417CUi6Qj77/s3lbqmxVsfEsk"}';
    const userPub = 'S4D7DDRAHWZIB2RCZICSRODFCNQXGANHGA7VCWBK5I37SQT6KVHXQNKMTE';
    const backupKey =
      '{"iv":"mZY8XTvHxX8BPc1rdGQQww==","v":1,"iter":10000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"abQy0OL2468=","ct":"UQXo0EaPXb6TIZDYYhKYS9d/fRMNT6ptpl9BgJw3AVooSbO4nppWnTRYlQO7hpON4XY85hYDu/7hy91IX1z1bDDq"}';
    const backupPub = '6FVGZUZOHZSXTTBRLWZDXGYSWVVYNN4ZESIEMZEMIBJCUBHC5C77OIE5RQ';
    const rootAddress = 'FWLNDL7UXCSOPOQXA5VU2DMANZAYCCMBY377HKTGGMZ4GEPEJBFARDOGBA';
    const walletPassphrase = 'Testing@43210!';
    const recoveryDestination = 'GB3YETD5TSTBAIYGYHVWU3O3I7XGOB44HOZA5MOEF5M23CLLZKRQLEVAOA';
    const bitgoPub = 'FJSWLLPRBXEGMWZY5BXA6673YKIK7JOURVCQEOWXC5TQPCXCOK3VHOO2VQ';
    const nativeBalance = 10000000; // 10 ALGO
    const MIN_ACCOUNT_BALANCE = 100000; // 1 AGLO

    const nodeParams = {
      token: '2810c2d168e8417c5f111d38d68327b8cfe2d0ddc02986490c22f8ddf4128bcd',
      baseServer: 'http://localhost/',
      port: 8443,
    };

    describe('Non-BitGo', async function () {
      const sandBox = Sinon.createSandbox();
      const expectedAmount = new BigNumber(nativeBalance).minus(fee).minus(MIN_ACCOUNT_BALANCE).toString();

      afterEach(function () {
        sandBox.verifyAndRestore();
      });

      it('should build and sign the recovery tx', async function () {
        const getBalanceStub = sandBox.stub(Algo.prototype, 'getAccountBalance').resolves(nativeBalance);

        const recovery = await basecoin.recover({
          userKey,
          backupKey,
          rootAddress,
          walletPassphrase,
          fee,
          bitgoKey: bitgoPub,
          recoveryDestination: recoveryDestination,
          firstRound: 5002596,
          nodeParams,
        });

        recovery.should.not.be.undefined();
        recovery.should.have.property('id');
        recovery.should.have.property('tx');
        recovery.should.have.property('fee');
        recovery.should.have.property('coin', 'talgo');
        recovery.should.have.property('firstRound');
        recovery.should.have.property('lastRound');
        getBalanceStub.callCount.should.equal(1);
        const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
        const txBuilder = factory.from(recovery.tx);
        const tx = await txBuilder.build();
        const txBroadcastFormat = Buffer.from(tx.toBroadcastFormat()).toString('base64');
        txBroadcastFormat.should.deepEqual(recovery.tx);
        const txJson = tx.toJson();
        txJson.amount.should.equal(expectedAmount);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(fee);
      });

      it('should throw for invalid rootAddress', async function () {
        const invalidRootAddress = 'randomstring';
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress: invalidRootAddress,
              walletPassphrase,
              fee,
              recoveryDestination: recoveryDestination,
              firstRound: 5002596,
              nodeParams,
            });
          },
          { message: 'invalid rootAddress, got: ' + invalidRootAddress }
        );
      });

      it('should throw for invalid recoveryDestination', async function () {
        const invalidRecoveryDestination = 'randomstring';
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              walletPassphrase,
              fee,
              recoveryDestination: invalidRecoveryDestination,
              firstRound: 5002596,
              nodeParams,
            });
          },
          { message: 'invalid recoveryDestination, got: ' + invalidRecoveryDestination }
        );
      });

      it('should throw if there is no enough balance to recover', async function () {
        const getBalanceStub = sandBox.stub(Algo.prototype, 'getAccountBalance').resolves(100500);
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              fee,
              walletPassphrase,
              bitgoKey: bitgoPub,
              recoveryDestination,
              firstRound: 5003596,
              nodeParams,
            });
          },
          { message: 'Insufficient balance to recover, got balance: 100500 fee: 1000 min account balance: 100000' }
        );

        getBalanceStub.callCount.should.equal(1);
      });

      it('should throw if the walletPassphrase is undefined', async function () {
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              fee,
              recoveryDestination,
              firstRound: 5003596,
              nodeParams,
            });
          },
          { message: 'walletPassphrase is required for non-bitgo recovery' }
        );
      });

      it('should throw if the walletPassphrase is wrong', async function () {
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              bitgoKey: bitgoPub,
              walletPassphrase: 'wrongpassword',
              fee,
              recoveryDestination,
              firstRound: 5003596,
              nodeParams,
            });
          },
          {
            message:
              "unable to decrypt userKey or backupKey with the walletPassphrase provided, got error: password error - ccm: tag doesn't match",
          }
        );
      });

      it('should throw if bitgo key is not provided', async function () {
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              walletPassphrase,
              fee,
              recoveryDestination,
              firstRound: 5003596,
              nodeParams,
            });
          },
          {
            message: 'bitgo public key from the keyCard is required for non-bitgo recovery',
          }
        );
      });

      it('should be able to pass a utf-8 encoded note', async function () {
        const note = 'Non-BitGo Recovery Sweep Tx';
        sandBox.stub(Algo.prototype, 'getAccountBalance').resolves(nativeBalance);
        const recovery = await basecoin.recover({
          userKey,
          backupKey,
          rootAddress,
          walletPassphrase,
          fee,
          bitgoKey: bitgoPub,
          recoveryDestination: recoveryDestination,
          firstRound: 5002596,
          nodeParams,
          note,
        });

        recovery.should.not.be.undefined();
        recovery.note.should.be.equal(note);
      });
    });

    describe('Unsigned Sweep', function () {
      const sandBox = Sinon.createSandbox();
      const expectedAmount = new BigNumber(nativeBalance).minus(fee).minus(MIN_ACCOUNT_BALANCE).toString();
      let getBalanceStub: SinonStub;

      beforeEach(function () {
        getBalanceStub = sandBox.stub(Algo.prototype, 'getAccountBalance').resolves(nativeBalance);
      });

      afterEach(function () {
        sandBox.verifyAndRestore();
      });

      it('should build unsigned sweep tx', async function () {
        const recovery = await basecoin.recover({
          userKey: userPub,
          backupKey: backupPub,
          bitgoKey: bitgoPub,
          rootAddress,
          walletPassphrase,
          fee,
          recoveryDestination,
          firstRound: 5003596,
          nodeParams,
        });

        getBalanceStub.callCount.should.equal(1);

        recovery.should.not.be.undefined();
        recovery.should.have.property('txHex');
        recovery.should.have.property('type');
        recovery.should.have.property('amount');
        recovery.should.have.property('feeInfo');
        recovery.should.have.property('coin', 'talgo');
        recovery.firstRound.should.not.be.undefined();
        recovery.lastRound.should.not.be.undefined();
        recovery.should.have.property('keys');
        recovery.keys.should.deepEqual([userPub, backupPub, bitgoPub]);
        recovery.addressVersion.should.equal(1);

        getBalanceStub.callCount.should.equal(1);
        const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
        const txBuilder = factory.from(recovery.txHex);
        const tx = await txBuilder.build();
        Buffer.from(tx.toBroadcastFormat()).toString('hex').should.deepEqual(recovery.txHex);
        const txJson = tx.toJson();
        txJson.amount.should.equal(expectedAmount);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(fee);
      });
    });

    describe('Recovery with root keys', function () {
      const sandBox = Sinon.createSandbox();
      let userKp: KeyPair;
      let backupKp: KeyPair;
      let rootAddress: string;
      let encryptedUserPrv: string;
      let encryptedBackupPrv: string;

      const expectedAmount = new BigNumber(nativeBalance).minus(fee).minus(MIN_ACCOUNT_BALANCE).toString();
      let getBalanceStub: SinonStub;

      beforeEach(function () {
        const userSeed = Buffer.from('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', 'hex');
        userKp = basecoin.generateRootKeyPair(userSeed);
        encryptedUserPrv = bitgo.encrypt({
          input: userKp.prv,
          password: walletPassphrase,
        });
        assert(userKp.pub);

        const backupSeed = Buffer.from('6d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', 'hex');
        backupKp = basecoin.generateRootKeyPair(backupSeed);
        encryptedBackupPrv = bitgo.encrypt({
          input: backupKp.prv,
          password: walletPassphrase,
        });
        const bitgoPub = 'FJSWLLPRBXEGMWZY5BXA6673YKIK7JOURVCQEOWXC5TQPCXCOK3VHOO2VQ';
        const userAddress = AlgoLib.algoUtils.privateKeyToAlgoAddress(userKp.prv);
        const backupAddress = AlgoLib.algoUtils.privateKeyToAlgoAddress(backupKp.prv);

        rootAddress = AlgoLib.algoUtils.multisigAddress(1, 2, [userAddress, backupAddress, bitgoPub]);
      });

      afterEach(function () {
        sandBox.verifyAndRestore();
      });

      it('should build and sign non-bitgo recovery tx with root keys', async function () {
        getBalanceStub = sandBox.stub(Algo.prototype, 'getAccountBalance').resolves(nativeBalance);
        const recovery = await basecoin.recover({
          userKey: encryptedUserPrv,
          backupKey: encryptedBackupPrv,
          rootAddress,
          walletPassphrase,
          fee,
          bitgoKey: bitgoPub,
          recoveryDestination: recoveryDestination,
          firstRound: 5002596,
          nodeParams,
        });

        recovery.should.not.be.undefined();
        recovery.should.have.property('id');
        recovery.should.have.property('tx');
        recovery.should.have.property('fee');
        recovery.should.have.property('coin', 'talgo');
        recovery.should.have.property('firstRound');
        recovery.should.have.property('lastRound');
        getBalanceStub.callCount.should.equal(1);
        const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
        const txBuilder = factory.from(recovery.tx);
        const tx = await txBuilder.build();
        const txBroadcastFormat = Buffer.from(tx.toBroadcastFormat()).toString('base64');
        txBroadcastFormat.should.deepEqual(recovery.tx);
        const txJson = tx.toJson();
        txJson.amount.should.equal(expectedAmount);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(fee);
      });

      it('should build unsigned sweep tx', async function () {
        getBalanceStub = sandBox.stub(Algo.prototype, 'getAccountBalance').resolves(nativeBalance);
        const recovery = await basecoin.recover({
          userKey: userKp.pub!,
          backupKey: backupKp.pub!,
          bitgoKey: bitgoPub,
          rootAddress,
          walletPassphrase,
          fee,
          recoveryDestination,
          firstRound: 5003596,
          nodeParams,
        });

        getBalanceStub.callCount.should.equal(1);

        recovery.should.not.be.undefined();
        recovery.should.have.property('txHex');
        recovery.should.have.property('type');
        recovery.should.have.property('amount');
        recovery.should.have.property('feeInfo');
        recovery.should.have.property('coin', 'talgo');
        recovery.firstRound.should.not.be.undefined();
        recovery.lastRound.should.not.be.undefined();
        recovery.should.have.property('keys');
        recovery.keys.should.deepEqual([userKp.pub, backupKp.pub, bitgoPub]);
        recovery.addressVersion.should.equal(1);

        getBalanceStub.callCount.should.equal(1);
        const factory = new AlgoLib.TransactionBuilderFactory(coins.get('algo'));
        const txBuilder = factory.from(recovery.txHex);
        const tx = await txBuilder.build();
        Buffer.from(tx.toBroadcastFormat()).toString('hex').should.deepEqual(recovery.txHex);
        const txJson = tx.toJson();
        txJson.amount.should.equal(expectedAmount);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(fee);
      });
    });
  });
});

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


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