PHP WebShell

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

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

import assert from 'assert';

import * as _ from 'lodash';
import Sinon, { SinonStub } from 'sinon';
import { randomBytes } from 'crypto';
import { BigNumber } from 'bignumber.js';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { BitGoAPI } from '@bitgo/sdk-api';
import { TxData, Transfer } from '../../src/lib/iface';
import { Wallet } from '@bitgo/sdk-core';

import * as TestData from '../fixtures/hbar';
import { Hbar, Thbar, KeyPair, HbarToken } from '../../src';
import { getBuilderFactory } from './getBuilderFactory';

describe('Hedera Hashgraph:', function () {
  let bitgo: TestBitGoAPI;
  let basecoin;
  let token;

  before(function () {
    bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' });
    bitgo.safeRegister('thbar', Thbar.createInstance);
    bitgo.safeRegister('hbar', Hbar.createInstance);
    HbarToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
      bitgo.safeRegister(name, coinConstructor);
    });
    bitgo.initializeTestVars();
    basecoin = bitgo.coin('thbar');
    token = bitgo.coin('thbar:usdc');
  });

  it('should instantiate the coin', function () {
    const basecoin = bitgo.coin('hbar');
    basecoin.should.be.an.instanceof(Hbar);
  });

  it('should check valid addresses', async function () {
    const badAddresses = [
      '',
      '0.0',
      'YZ09fd-',
      '0.0.0.a',
      'sadasdfggg',
      '0.2.a.b',
      '0.0.100?=sksjd',
      '0.0.41098?memoId=',
    ];
    const goodAddresses = [
      '0',
      '0.0.0',
      '0.0.41098',
      '0.0.0?memoId=84',
      '0.0.41098',
      '0.0.41098?memoId=2aaaaa',
      '0.0.41098?memoId=1',
    ];

    badAddresses.map((addr) => {
      basecoin.isValidAddress(addr).should.equal(false);
    });
    goodAddresses.map((addr) => {
      basecoin.isValidAddress(addr).should.equal(true);
    });

    const hexAddress = '0x23C3E227BE97281A70A549c7dDB8d5Caad3E7C84';
    basecoin.isValidAddress(hexAddress).should.equal(false);
  });

  it('should explain a transaction', async function () {
    const tx = JSON.parse(TestData.rawTransactionForExplain);
    const explain = await basecoin.explainTransaction(tx);

    explain.id.should.equal('0.0.43285@1600529800.643093586');
    explain.outputAmount.should.equal('2200000000');
    explain.timestamp.should.equal('1600529800.643093586');
    explain.expiration.should.equal('180');
    explain.outputs[0].amount.should.equal('2200000000');
    explain.outputs[0].address.should.equal('0.0.43283');
    explain.outputs[0].memo.should.equal('1');
    explain.fee.should.equal(1160407);
    explain.changeAmount.should.equal('0');
  });

  it('should explain a token transfer transaction', async function () {
    const tokenTransferParam = {
      txHex: TestData.UNSIGNED_TOKEN_TRANSFER,
      feeInfo: {
        size: 1000,
        fee: 1160407,
        feeRate: 1160407,
      },
    };
    const explain = await basecoin.explainTransaction(tokenTransferParam);

    explain.id.should.equal('0.0.81320@1596110493.372646570');
    explain.outputAmount.should.equal('0');
    explain.timestamp.should.equal('1596110493.372646570');
    explain.expiration.should.equal('180');
    explain.outputs[0].amount.should.equal('10');
    explain.outputs[0].address.should.equal('0.0.75861');
    explain.outputs[0].memo.should.equal('');
    explain.outputs[0].tokenName.should.equal('thbar:usdc');
    explain.fee.should.equal(1160407);
    explain.changeAmount.should.equal('0');
  });

  it('should explain a multirecipients transfer transaction', async function () {
    const multiTransferParam = {
      txHex: TestData.UNSIGNED_MULTI_TRANSFER,
      feeInfo: {
        size: 1000,
        fee: 1160407,
        feeRate: 1160407,
      },
    };
    const explain = await basecoin.explainTransaction(multiTransferParam);

    explain.id.should.equal('0.0.81320@1596110493.372646570');
    explain.outputAmount.should.equal('25');
    explain.expiration.should.equal('180');
    explain.outputs[0].amount.should.equal('10');
    explain.outputs[0].address.should.equal('0.0.75861');
    explain.outputs[0].memo.should.equal('');
    explain.outputs[1].amount.should.equal('15');
    explain.outputs[1].address.should.equal('0.0.78963');
    explain.fee.should.equal(1160407);
    explain.changeAmount.should.equal('0');
  });

  it('should explain a multirecipients token transfer transaction', async function () {
    const tokenMultiTransferParam = {
      txHex: TestData.UNSIGNED_TOKEN_MULTI_TRANSFER,
      feeInfo: {
        size: 1000,
        fee: 1160407,
        feeRate: 1160407,
      },
    };
    const explain = await basecoin.explainTransaction(tokenMultiTransferParam);

    explain.id.should.equal('0.0.81320@1596110493.372646570');
    explain.outputAmount.should.equal('0');
    explain.timestamp.should.equal('1596110493.372646570');
    explain.expiration.should.equal('180');
    explain.outputs[0].amount.should.equal('10');
    explain.outputs[0].address.should.equal('0.0.75861');
    explain.outputs[0].memo.should.equal('');
    explain.outputs[0].tokenName.should.equal('thbar:usdc');
    explain.outputs[1].amount.should.equal('15');
    explain.outputs[1].address.should.equal('0.0.78963');
    explain.outputs[1].tokenName.should.equal('thbar:usdc');
    explain.fee.should.equal(1160407);
    explain.changeAmount.should.equal('0');
  });

  it('should explain a token associate transaction', async function () {
    const tokenAssociateParam = {
      txHex: TestData.UNSIGNED_TOKEN_ASSOCIATE,
      feeInfo: {
        size: 1000,
        fee: 1160407,
        feeRate: 1160407,
      },
    };
    const explain = await basecoin.explainTransaction(tokenAssociateParam);

    explain.id.should.equal('0.0.81320@1596110493.372646570');
    explain.outputAmount.should.equal('0');
    explain.timestamp.should.equal('1596110493.372646570');
    explain.expiration.should.equal('180');
    explain.outputs[0].amount.should.equal('0');
    explain.outputs[0].address.should.equal('0.0.81320');
    explain.outputs[0].memo.should.equal('');
    explain.outputs[0].tokenName.should.equal('thbar:usdc');
    explain.fee.should.equal(1160407);
    explain.changeAmount.should.equal('0');
  });

  it('should verify isWalletAddress', async function () {
    const baseAddress = '0.0.41098';
    const validAddress1 = '0.0.41098?memoId=1';
    const validAddress2 = '0.0.41098?memoId=2';
    const unrelatedValidAddress = '0.1.41098?memoId=1';
    const invalidAddress = '0.0.0.a';
    (await basecoin.isWalletAddress({ address: validAddress1, baseAddress })).should.true();
    (await basecoin.isWalletAddress({ address: validAddress2, baseAddress })).should.true();
    (await basecoin.isWalletAddress({ address: validAddress2, baseAddress: validAddress1 })).should.true();
    (await basecoin.isWalletAddress({ address: unrelatedValidAddress, baseAddress })).should.false();

    assert.rejects(
      async () => basecoin.isWalletAddress({ address: invalidAddress, baseAddress }),
      `invalid address ${invalidAddress}`
    );
  });

  describe('Keypairs:', () => {
    it('should generate a keypair from random seed', function () {
      const keyPair = basecoin.generateKeyPair();
      keyPair.should.have.property('pub');
      keyPair.should.have.property('prv');

      basecoin.isValidPub(keyPair.pub).should.equal(true);
    });

    it('should generate a keypair from a seed', function () {
      const seedText = '80350b4208d381fbfe2276a326603049fe500731c46d3c9936b5ce036b51377f';
      const seed = Buffer.from(seedText, 'hex');
      const keyPair = basecoin.generateKeyPair(seed);

      keyPair.prv.should.equal(
        '302e020100300506032b65700422042080350b4208d381fbfe2276a326603049fe500731c46d3c9936b5ce036b51377f'
      );
      keyPair.pub.should.equal(
        '302a300506032b65700321009cc402b5c75214269c2826e3c6119377cab6c367601338661c87a4e07c6e0333'
      );
    });

    it('should validate a stellar seed', function () {
      basecoin.isStellarSeed('SBMWLNV75BPI2VB4G27RWOMABVRTSSF7352CCYGVELZDSHCXWCYFKXIX').should.ok();
    });

    it('should convert a stellar seed to an hbar prv', function () {
      const seed = basecoin.convertFromStellarSeed('SBMWLNV75BPI2VB4G27RWOMABVRTSSF7352CCYGVELZDSHCXWCYFKXIX');
      seed.should.equal(
        '302e020100300506032b6570042204205965b6bfe85e8d543c36bf1b39800d633948bfdf742160d522f2391c57b0b055'
      );
    });
  });

  describe('Verify Transaction:', () => {
    let newTxPrebuild;
    let newTxParams;
    let newTxParamsWithError;
    let newTxParamsWithExtraData;
    const txPrebuild = {
      recipients: [
        {
          address: 'lionteste212',
          amount: '1000',
        },
      ],
      txHex: TestData.UNSIGNED_MULTI_TRANSFER,
      txid: '586c5b59b10b134d04c16ac1b273fe3c5529f34aef75db4456cd469c5cdac7e2',
      isVotingTransaction: false,
      coin: 'thbar',
      feeInfo: {
        size: 1000,
        fee: 1160407,
        feeRate: 1160407,
      },
    };
    const txParams = {
      txPrebuild,
      recipients: [
        {
          address: '0.0.75861',
          amount: '10',
        },
        {
          address: '0.0.78963',
          amount: '15',
        },
      ],
    };
    const memo = { value: '' };
    const txParamsWithError = {
      txPrebuild,
      recipients: [
        {
          address: '0.0.75861',
          amount: '1000',
        },
      ],
    };
    const txParamsWithExtraData = {
      txPrebuild,
      recipients: [
        {
          address: '0.0.75861',
          amount: '10',
          data: undefined,
        },
        {
          address: '0.0.78963',
          amount: '15',
          data: undefined,
        },
      ],
    };
    const walletData = {
      id: '5b34252f1bf349930e34020a00000000',
      coin: 'thbar',
      keys: [
        '5b3424f91bf349930e34017500000000',
        '5b3424f91bf349930e34017600000000',
        '5b3424f91bf349930e34017700000000',
      ],
      coinSpecific: {
        baseAddress: '0.0.2935',
      },
      multisigType: 'onchain',
    };
    const walletObj = new Wallet(bitgo, basecoin, walletData);

    before(function () {
      newTxPrebuild = () => {
        return _.cloneDeep(txPrebuild);
      };
      newTxParams = () => {
        return _.cloneDeep(txParams);
      };
      newTxParamsWithError = () => {
        return _.cloneDeep(txParamsWithError);
      };
      newTxParamsWithExtraData = () => {
        return _.cloneDeep(txParamsWithExtraData);
      };
    });

    it('should verify native transfer transactions', async function () {
      const txParams = newTxParams();
      const txPrebuild = newTxPrebuild();
      const validTransaction = await basecoin.verifyTransaction({
        txParams,
        txPrebuild,
        memo,
        wallet: walletObj,
      } as any);
      validTransaction.should.equal(true);
    });

    it('should fail verify when input `recipients` is absent', async function () {
      const txParams = newTxParams();
      txParams.recipients = undefined;
      const txPrebuild = newTxPrebuild();
      await basecoin
        .verifyTransaction({ txParams, txPrebuild, memo: memo, wallet: walletObj } as any)
        .should.be.rejectedWith('missing required tx params property recipients');
    });

    it('should fail verify transactions when have different recipients', async function () {
      const txParams = newTxParamsWithError();
      const txPrebuild = newTxPrebuild();
      await basecoin
        .verifyTransaction({ txParams, txPrebuild, memo, wallet: walletObj } as any)
        .should.be.rejectedWith('Tx outputs does not match with expected txParams recipients');
    });

    it('should succeed to verify transactions when recipients has extra data', async function () {
      const txParams = newTxParamsWithExtraData();
      const txPrebuild = newTxPrebuild();
      const validTransaction = await basecoin.verifyTransaction({
        txParams,
        txPrebuild,
        memo,
        wallet: walletObj,
      } as any);
      validTransaction.should.equal(true);
    });

    it('should verify create associated token account transaction', async function () {
      const txParams = newTxParams();
      const txPrebuild = newTxPrebuild();
      txPrebuild.txHex = TestData.UNSIGNED_TOKEN_ASSOCIATE;
      txParams.recipients = [
        {
          address: '0.0.81320',
          amount: '0',
          tokenName: 'thbar:usdc',
        },
      ];
      const validTransaction = await basecoin.verifyTransaction({
        txParams,
        txPrebuild,
        memo,
        wallet: walletObj,
      } as any);
      validTransaction.should.equal(true);
    });

    it('should fail verify create associated token account transaction with mismatch recipients', async function () {
      const txParams = newTxParams();
      const txPrebuild = newTxPrebuild();
      txPrebuild.txHex = TestData.UNSIGNED_TOKEN_ASSOCIATE;
      txParams.recipients = [
        {
          address: '0.0.81321',
          amount: '0',
          tokenName: 'thbar:usdc',
        },
      ];
      await basecoin
        .verifyTransaction({ txParams, txPrebuild, memo, wallet: walletObj } as any)
        .should.be.rejectedWith('Tx outputs does not match with expected txParams recipients');
    });

    it('should verify token transfer transaction', async function () {
      const txParams = newTxParams();
      const txPrebuild = newTxPrebuild();
      txPrebuild.txHex = TestData.UNSIGNED_TOKEN_MULTI_TRANSFER;
      txParams.recipients = [
        {
          address: '0.0.75861',
          amount: '10',
          tokenName: 'thbar:usdc',
        },
        {
          address: '0.0.78963',
          amount: '15',
          tokenName: 'thbar:usdc',
        },
      ];
      const validTransaction = await token.verifyTransaction({
        txParams,
        txPrebuild,
        memo,
        wallet: walletObj,
      } as any);
      validTransaction.should.equal(true);
    });

    it('should verify token transfer transaction with any token name on token base coin', async function () {
      const txParams = newTxParams();
      const txPrebuild = newTxPrebuild();
      txPrebuild.txHex = TestData.UNSIGNED_TOKEN_MULTI_TRANSFER;
      txParams.recipients = [
        {
          address: '0.0.75861',
          amount: '10',
          tokenName: 'thbar:usdc',
        },
        {
          address: '0.0.78963',
          amount: '15',
        },
      ];
      (await token.verifyTransaction({ txParams, txPrebuild, memo, wallet: walletObj } as any)).should.equal(true);
    });

    it('should fail to verify token transfer with mismatched recipients', async function () {
      const txParams = newTxParams();
      const txPrebuild = newTxPrebuild();
      txPrebuild.txHex = TestData.UNSIGNED_TOKEN_MULTI_TRANSFER;
      txParams.recipients = [
        {
          address: '0.0.75861',
          amount: '11',
          tokenName: 'thbar:usdc',
        },
        {
          address: '0.0.78963',
          amount: '15',
        },
      ];
      await token
        .verifyTransaction({ txParams, txPrebuild, memo, wallet: walletObj } as any)
        .should.be.rejectedWith('Tx outputs does not match with expected txParams recipients');
    });

    it('should fail to verify token transfer with incorrect token name', async function () {
      const txParams = newTxParams();
      const txPrebuild = newTxPrebuild();
      txPrebuild.txHex = TestData.UNSIGNED_TOKEN_MULTI_TRANSFER;
      txParams.recipients = [
        {
          address: '0.0.75861',
          amount: '11',
          tokenName: 'thbar:usdc',
        },
        {
          address: '0.0.78963',
          amount: '15',
          tokenName: 'invalidtoken',
        },
      ];
      await token
        .verifyTransaction({ txParams, txPrebuild, memo, wallet: walletObj } as any)
        .should.be.rejectedWith('Incorrect token name specified in recipients');
    });
  });

  describe('Sign Message', () => {
    it('should be performed', async () => {
      const keyPair = new KeyPair();
      const messageToSign = Buffer.from(randomBytes(32)).toString('hex');
      const signature = await basecoin.signMessage(keyPair.getKeys(), messageToSign);
      keyPair.verifySignature(messageToSign, Uint8Array.from(Buffer.from(signature, 'hex'))).should.equals(true);
    });

    it('should fail with missing private key', async () => {
      const keyPair = new KeyPair({
        pub: '302a300506032b6570032100d8fd745361df270776a3ab1b55d5590ec00a26ab45eea37197dc9894a81fcb82',
      }).getKeys();
      const messageToSign = Buffer.from(randomBytes(32)).toString('hex');
      await basecoin.signMessage(keyPair, messageToSign).should.be.rejectedWith('Invalid key pair options');
    });
  });

  describe('Sign transaction:', () => {
    const destination = '0.0.129369';
    const source = '0.0.1234';
    const amount = '100000';
    /**
     * Build an unsigned account-lib multi-signature send transaction
     * @param destination The destination address of the transaction
     * @param source The account sending thist ransaction
     * @param amount The amount to send to the recipient
     */
    const buildUnsignedTransaction = async function ({ destination, source, amount = '100000' }) {
      const factory = getBuilderFactory('thbar');
      const txBuilder = factory.getTransferBuilder();
      txBuilder.fee({
        fee: '100000',
      });
      txBuilder.source({ address: source });
      txBuilder.send({ address: destination, amount });

      return await txBuilder.build();
    };

    it('should sign transaction', async function () {
      const key = new KeyPair();
      const unsignedTransaction = await buildUnsignedTransaction({
        destination,
        source,
        amount,
      });

      const tx = await basecoin.signTransaction({
        prv: key.getKeys().prv!.toString(),
        txPrebuild: {
          txHex: unsignedTransaction.toBroadcastFormat(),
        },
      });

      const factory = getBuilderFactory('thbar');
      const txBuilder = factory.from(tx.halfSigned.txHex);
      const signedTx = await txBuilder.build();
      const txJson = signedTx.toJson() as TxData;
      txJson.should.have.properties('to', 'amount');
      txJson.to?.should.equal(destination);
      txJson.from.should.equal(source);
      txJson.amount?.should.equal(amount);
      (txJson.instructionsData as Transfer).params.recipients[0].should.deepEqual({
        address: destination,
        amount,
      });
      signedTx.signature.length.should.equal(1);
    });

    it('should fully sign transaction with root key', async function () {
      const key1 = basecoin.generateRootKeyPair();
      const key2 = basecoin.generateRootKeyPair();

      const unsignedTransaction = await buildUnsignedTransaction({
        destination,
        source,
        amount,
      });

      const txHalfSigned = await basecoin.signTransaction({
        prv: key1.prv,
        txPrebuild: {
          txHex: unsignedTransaction.toBroadcastFormat(),
        },
      });

      const factory = getBuilderFactory('thbar');
      const txBuilderHalfSigned = factory.from(txHalfSigned.halfSigned.txHex);
      const halfSignedTx = await txBuilderHalfSigned.build();
      const halfSignedTxJson = halfSignedTx.toJson() as TxData;
      halfSignedTxJson.should.have.properties('to', 'amount');
      halfSignedTxJson.to?.should.equal(destination);
      halfSignedTxJson.from.should.equal(source);
      halfSignedTxJson.amount?.should.equal(amount);
      (halfSignedTxJson.instructionsData as Transfer).params.recipients[0].should.deepEqual({
        address: destination,
        amount,
      });
      halfSignedTx.signature.length.should.equal(1);

      const txSigned = await basecoin.signTransaction({
        prv: key2.prv,
        txPrebuild: {
          txHex: halfSignedTx.toBroadcastFormat(),
        },
      });

      const txBuilderSigned = factory.from(txSigned.txHex);
      const signedTx = await txBuilderSigned.build();
      const signedTxJson = signedTx.toJson() as TxData;
      signedTxJson.should.have.properties('to', 'amount');
      signedTxJson.to?.should.equal(destination);
      signedTxJson.from.should.equal(source);
      signedTxJson.amount?.should.equal(amount);
      (signedTxJson.instructionsData as Transfer).params.recipients[0].should.deepEqual({
        address: destination,
        amount,
      });
      signedTx.signature.length.should.equal(2);
    });
  });

  describe('Recovery', function () {
    const defaultValidDuration = '180';
    const defaultFee = 10000000;
    const defaultNodeId = '0.0.3';
    const userKey =
      '{"iv":"WlPuJOejRWgj/NTd3UMgrw==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"6yAVFvreHSQ=","ct":"8j/lBVkFByKlVhaS9JWmmLja5yTokjaIiLDxMIDjMojVEim9T36WAm5qW6v1V0A7QcEuGiVl3PKMDa+Gr6tI/HT58DW5RE+pHzya9MUQpAgNrJr8VEWjrXWqZECVtra1/bKCyB+mozc="}';
    const userPub = '302a300506032b6570032100ddd53a1591d72b181109bd3e57b18603740490b9ab4d37bc7fa27480e6b8c911';
    const backupKey =
      '{"iv":"D5DVDozQx9B02JeFV0/OVA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"7FUNF8M35bo=","ct":"ZiPsu5Qe/AIS4JQXt+rrusHnYCy4CqurM16R5wJrd4CEx7u85y3yy5ErnsdyYYcc3txyNmUIQ2/CBq/LKoKO/VIeU++CnKxzGuHGcNI47BPk3RQK42a66uIQn/yTR++XgdK1KhvUL3U="}';
    const backupPub = '302a300506032b65700321006293e4ec9bf1b2d8fae631119107248a65e2207a05d32a11f42cc3d9a3005d4a';
    const rootAddress = '0.0.7671186';
    const walletPassphrase = 'TestPasswordPleaseIgnore';
    const recoveryDestination = '0.0.7651908';
    const bitgoKey = '5a93b01ea87e963f61c974a89d62e3841392f1ba020fbbcc65a8089ca025abbb';
    const memo = '4';
    const balance = '1000000000';
    const formatBalanceResponse = (balance: string) =>
      new BigNumber(balance).dividedBy(basecoin.getBaseFactor()).toFixed(9) + ' ℏ';
    const tokenId = '0.0.13078';

    describe('Non-BitGo', async function () {
      const sandBox = Sinon.createSandbox();

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

      it('should build and sign the recovery tx', async function () {
        const expectedAmount = new BigNumber(balance).minus(defaultFee).toString();
        const getBalanceStub = sandBox
          .stub(Hbar.prototype, 'getAccountBalance')
          .resolves({ hbars: formatBalanceResponse(balance), tokens: [] });

        const recovery = await basecoin.recover({
          userKey,
          backupKey,
          rootAddress,
          walletPassphrase,
          recoveryDestination: recoveryDestination + '?memoId=' + memo,
        });

        recovery.should.not.be.undefined();
        recovery.should.have.property('id');
        recovery.should.have.property('tx');
        recovery.should.have.property('coin', 'thbar');
        recovery.should.have.property('nodeId', defaultNodeId);
        getBalanceStub.callCount.should.equal(1);
        const txBuilder = basecoin.getBuilderFactory().from(recovery.tx);
        const tx = await txBuilder.build();
        tx.toBroadcastFormat().should.equal(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(defaultFee);
        txJson.node.should.equal(defaultNodeId);
        txJson.memo.should.equal(memo);
        txJson.validDuration.should.equal(defaultValidDuration);
        txJson.should.have.property('startTime');
        recovery.should.have.property('startTime', txJson.startTime);
        recovery.should.have.property('id', rootAddress + '@' + txJson.startTime);
      });

      it('should throw for invalid rootAddress', async function () {
        const invalidRootAddress = 'randomstring';
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress: 'randomstring',
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
            });
          },
          { 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,
              recoveryDestination: 'randomstring',
            });
          },
          { message: 'invalid recoveryDestination, got: ' + invalidRecoveryDestination }
        );
      });

      it('should throw for invalid nodeId', async function () {
        const invalidNodeId = 'a.2.3';
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              nodeId: invalidNodeId,
            });
          },
          { message: 'invalid nodeId, got: ' + invalidNodeId }
        );
      });

      it('should throw for invalid maxFee', async function () {
        const invalidMaxFee = '-32';
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              maxFee: invalidMaxFee,
            });
          },
          { message: 'invalid maxFee, got: ' + invalidMaxFee }
        );
      });

      it('should throw if there is no enough balance to recover', async function () {
        const getBalanceStub = sandBox
          .stub(Hbar.prototype, 'getAccountBalance')
          .resolves({ hbars: formatBalanceResponse('100'), tokens: [] });
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress,
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
            });
          },
          { message: 'Insufficient balance to recover, got balance: 100 fee: 10000000' }
        );

        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,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
            });
          },
          { 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,
              walletPassphrase: 'randompassword',
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
            });
          },
          {
            message:
              "unable to decrypt userKey or backupKey with the walletPassphrase provided, got error: password error - ccm: tag doesn't match",
          }
        );
      });

      it('should build and sign the recovery tx for tokens', async function () {
        const balance = '100';
        const data = {
          hbars: '1',
          tokens: [{ tokenId: tokenId, balance: balance, decimals: 6 }],
        };
        const getBalanceStub = sandBox.stub(Hbar.prototype, 'getAccountBalance').resolves(data);

        const recovery = await basecoin.recover({
          userKey,
          backupKey,
          rootAddress,
          walletPassphrase,
          recoveryDestination: recoveryDestination + '?memoId=' + memo,
          tokenId: tokenId,
        });

        recovery.should.not.be.undefined();
        recovery.should.have.property('id');
        recovery.should.have.property('tx');
        recovery.should.have.property('coin', 'thbar');
        recovery.should.have.property('nodeId', defaultNodeId);
        getBalanceStub.callCount.should.equal(1);
        const txBuilder = basecoin.getBuilderFactory().from(recovery.tx);
        const tx = await txBuilder.build();
        tx.toBroadcastFormat().should.equal(recovery.tx);
        const txJson = tx.toJson();
        txJson.amount.should.equal(balance);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(defaultFee);
        txJson.node.should.equal(defaultNodeId);
        txJson.memo.should.equal(memo);
        txJson.validDuration.should.equal(defaultValidDuration);
        txJson.should.have.property('startTime');
        recovery.should.have.property('startTime', txJson.startTime);
        recovery.should.have.property('id', rootAddress + '@' + txJson.startTime);
      });

      it('should throw error for non supported invalid tokenId', async function () {
        const invalidTokenId = 'randomstring';
        const data = {
          hbars: '1',
          tokens: [{ tokenId: tokenId, balance: '100', decimals: 6 }],
        };
        sandBox.stub(Hbar.prototype, 'getAccountBalance').resolves(data);
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress: rootAddress,
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              tokenId: invalidTokenId,
            });
          },
          { message: 'Unsupported token: ' + invalidTokenId }
        );
      });

      it('should throw error for insufficient balance for tokenId if token balance not exist', async function () {
        const data = {
          hbars: '100',
          tokens: [{ tokenId: 'randomString', balance: '100', decimals: 6 }],
        };
        sandBox.stub(Hbar.prototype, 'getAccountBalance').resolves(data);
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress: rootAddress,
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              tokenId: tokenId,
            });
          },
          { message: 'Insufficient balance to recover token: ' + tokenId + ' for account: ' + rootAddress }
        );
      });

      it('should throw error for insufficient balance for tokenId if token balance exist with 0 amount', async function () {
        const data = {
          hbars: '100',
          tokens: [{ tokenId: 'randomString', balance: '0', decimals: 6 }],
        };
        sandBox.stub(Hbar.prototype, 'getAccountBalance').resolves(data);
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress: rootAddress,
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              tokenId: tokenId,
            });
          },
          { message: 'Insufficient balance to recover token: ' + tokenId + ' for account: ' + rootAddress }
        );
      });

      it('should throw error for insufficient native balance for token transfer', async function () {
        const data = {
          hbars: '0.01',
          tokens: [{ tokenId: tokenId, balance: '10', decimals: 6 }],
        };
        sandBox.stub(Hbar.prototype, 'getAccountBalance').resolves(data);
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey,
              backupKey,
              rootAddress: rootAddress,
              walletPassphrase,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              tokenId: tokenId,
            });
          },
          { message: 'Insufficient native balance to recover tokens, got native balance: 1000000 fee: ' + defaultFee }
        );
      });
    });

    describe('Unsigned Sweep', function () {
      const sandBox = Sinon.createSandbox();
      let getBalanceStub: SinonStub;

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

      it('should build unsigned sweep tx', async function () {
        getBalanceStub = sandBox
          .stub(Hbar.prototype, 'getAccountBalance')
          .resolves({ hbars: formatBalanceResponse(balance), tokens: [] });
        const startTime = (Date.now() / 1000 + 10).toFixed(); // timestamp in seconds, 10 seconds from now
        const expectedAmount = new BigNumber(balance).minus(defaultFee).toString();

        const recovery = await basecoin.recover({
          userKey: userPub,
          backupKey: backupPub,
          rootAddress,
          bitgoKey,
          recoveryDestination: recoveryDestination + '?memoId=' + memo,
          startTime,
        });

        getBalanceStub.callCount.should.equal(1);

        recovery.should.not.be.undefined();
        recovery.should.have.property('txHex');
        recovery.should.have.property('id', rootAddress + '@' + startTime + '.0');
        recovery.should.have.property('userKey', userPub);
        recovery.should.have.property('backupKey', backupPub);
        recovery.should.have.property('bitgoKey', bitgoKey);
        recovery.should.have.property('address', rootAddress);
        recovery.should.have.property('coin', 'thbar');
        recovery.should.have.property('maxFee', defaultFee.toString());
        recovery.should.have.property('recipients', [{ address: recoveryDestination, amount: expectedAmount }]);
        recovery.should.have.property('amount', expectedAmount);
        recovery.should.have.property('validDuration', defaultValidDuration);
        recovery.should.have.property('nodeId', defaultNodeId);
        recovery.should.have.property('memo', memo);
        recovery.should.have.property('startTime', startTime + '.0');
        const txBuilder = basecoin.getBuilderFactory().from(recovery.txHex);
        const tx = await txBuilder.build();
        const txJson = tx.toJson();
        txJson.id.should.equal(rootAddress + '@' + startTime + '.0');
        txJson.amount.should.equal(expectedAmount);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(defaultFee);
        txJson.node.should.equal(defaultNodeId);
        txJson.memo.should.equal(memo);
        txJson.validDuration.should.equal(defaultValidDuration);
        txJson.startTime.should.equal(startTime + '.0');
        txJson.validDuration.should.equal(defaultValidDuration);
      });

      it('should build unsigned sweep tx for tokens', async function () {
        const balance = '100';
        const data = {
          hbars: '1',
          tokens: [{ tokenId: tokenId, balance: balance, decimals: 6 }],
        };
        getBalanceStub = sandBox.stub(Hbar.prototype, 'getAccountBalance').resolves(data);
        const startTime = (Date.now() / 1000 + 10).toFixed(); // timestamp in seconds, 10 seconds from now
        const recovery = await basecoin.recover({
          userKey: userPub,
          backupKey: backupPub,
          rootAddress,
          bitgoKey,
          recoveryDestination: recoveryDestination + '?memoId=' + memo,
          startTime,
          tokenId: tokenId,
        });

        getBalanceStub.callCount.should.equal(1);

        recovery.should.not.be.undefined();
        recovery.should.have.property('txHex');
        recovery.should.have.property('id', rootAddress + '@' + startTime + '.0');
        recovery.should.have.property('userKey', userPub);
        recovery.should.have.property('backupKey', backupPub);
        recovery.should.have.property('bitgoKey', bitgoKey);
        recovery.should.have.property('address', rootAddress);
        recovery.should.have.property('coin', 'thbar');
        recovery.should.have.property('maxFee', defaultFee.toString());
        recovery.should.have.property('recipients', [
          { address: recoveryDestination, amount: balance, tokenName: 'thbar:usdc' },
        ]);
        recovery.should.have.property('amount', balance);
        recovery.should.have.property('validDuration', defaultValidDuration);
        recovery.should.have.property('nodeId', defaultNodeId);
        recovery.should.have.property('memo', memo);
        recovery.should.have.property('startTime', startTime + '.0');
        const txBuilder = basecoin.getBuilderFactory().from(recovery.txHex);
        const tx = await txBuilder.build();
        const txJson = tx.toJson();
        txJson.id.should.equal(rootAddress + '@' + startTime + '.0');
        txJson.amount.should.equal(balance);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(defaultFee);
        txJson.node.should.equal(defaultNodeId);
        txJson.memo.should.equal(memo);
        txJson.validDuration.should.equal(defaultValidDuration);
        txJson.startTime.should.equal(startTime + '.0');
        txJson.validDuration.should.equal(defaultValidDuration);
      });

      it('should throw if startTime is undefined', async function () {
        const startTime = undefined;

        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey: userPub,
              backupKey: backupPub,
              rootAddress,
              bitgoKey,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              startTime,
            });
          },
          { message: 'start time is required for unsigned sweep' }
        );
      });

      it('should throw for invalid userKey', async function () {
        const startTime = (Date.now() / 1000 + 10).toFixed();
        const invalidUserPub = '302a300506032b6570032100randomstring';
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey: invalidUserPub,
              backupKey: backupPub,
              bitgoKey,
              rootAddress,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              startTime,
            });
          },
          { message: 'invalid userKey, got: ' + invalidUserPub }
        );
      });

      it('should throw for invalid backupKey', async function () {
        const invalidBackupPub = '302a300506032b6570032100randomstring';
        const startTime = (Date.now() / 1000 + 10).toFixed();
        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey: userPub,
              backupKey: invalidBackupPub,
              bitgoKey,
              rootAddress,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              startTime,
            });
          },
          { message: 'invalid backupKey, got: ' + invalidBackupPub }
        );
      });

      it('should throw if startTime is a valid timestamp', async function () {
        const startTime = 'asd';

        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey: userPub,
              backupKey: backupPub,
              rootAddress,
              bitgoKey,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              startTime,
            });
          },
          { message: 'invalid startTime, got: ' + startTime }
        );
      });

      it('should throw if startTime is not a future date', async function () {
        const startTime = (Date.now() / 1000 - 1).toString(); // timestamp in seconds, 1 second ago

        await assert.rejects(
          async () => {
            await basecoin.recover({
              userKey: userPub,
              backupKey: backupPub,
              rootAddress,
              bitgoKey,
              recoveryDestination: recoveryDestination + '?memoId=' + memo,
              startTime,
            });
          },
          { message: 'startTime must be a future timestamp, got: ' + startTime }
        );
      });
    });

    describe('Recovery with root keys', function () {
      const sandBox = Sinon.createSandbox();
      let getBalanceStub: SinonStub;
      const walletPassphrase = 'testbtcpassword1999';
      const userEddsaRootXPrv =
        '{"iv":"lHOTkiuucR2JWFD1x1gqpQ==","v":1,"iter":10000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"HkFrDVH++d8=","ct":"NBtbYdFEK84oH9uxwl/UrhRsW5nGJPnMSpRAo8Blrc7WTSPxGXmVS/EpUYEV03HG06/EnyBR0/Y6bjLQz4gkL6cGJD9hgyKqDvc9RtKHagEbo75oxPr0zP+r1HMUGBW38Ttgor674gBeb1Myew69xcS9KgguNxwz77X6fdeBhrfogLY22vcuLA=="}';
      const userEddsaRootPub = 'd9cb9c9c617cfa0b715849516bb054a2b5d78c0e3eeef011176fb8bc0108c531';
      const backupEddsaRootXprv =
        '{"iv":"sBoEFBBNoi2YVICPf16/BQ==","v":1,"iter":10000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"HkFrDVH++d8=","ct":"GscOqJC+Iq+Lr39plQp5ZCamVlpJHOltGTZ7/UnUunIhFmZWMBLxjEVnMOtPreb0NZ4/SFqO/N3mZvq6JbB7vWRxJuqkBIiVcIRwkSWdW55cboKx2ec3ajg8+uO2pbvNDs26Q+9NtZ4jZnKNqSUXiCmtJXLRHQ32oyD+olKRpIR2NQo2+7kIEw=="}';
      const backupEddsaRootPub = 'f163b1b8ee4c3343a97ac1d2470b967e967ac7b4e3731cacf02f28a1434a2f99';
      const rootAddress = '0.0.3644667';
      const memoId = '0';

      beforeEach(function () {
        getBalanceStub = sandBox
          .stub(Hbar.prototype, 'getAccountBalance')
          .resolves({ hbars: formatBalanceResponse(balance), tokens: [] });
      });

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

      it('should build and sign non-bitgo recovery tx with root keys', async function () {
        const expectedAmount = new BigNumber(balance).minus(defaultFee).toString();

        const recovery = await basecoin.recover({
          userKey: userEddsaRootXPrv,
          backupKey: backupEddsaRootXprv,
          rootAddress,
          walletPassphrase,
          recoveryDestination: recoveryDestination + '?memoId=' + memoId,
        });

        getBalanceStub.callCount.should.equal(1);

        recovery.should.not.be.undefined();
        recovery.should.have.property('id');
        recovery.should.have.property('tx');
        recovery.should.have.property('coin', 'thbar');
        recovery.should.have.property('nodeId', defaultNodeId);
        getBalanceStub.callCount.should.equal(1);
        const txBuilder = basecoin.getBuilderFactory().from(recovery.tx);
        const tx = await txBuilder.build();
        tx.toBroadcastFormat().should.equal(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(defaultFee);
        txJson.node.should.equal(defaultNodeId);
        txJson.memo.should.equal(memoId);
        txJson.validDuration.should.equal(defaultValidDuration);
        txJson.should.have.property('startTime');
        recovery.should.have.property('startTime', txJson.startTime);
        recovery.should.have.property('id', rootAddress + '@' + txJson.startTime);
      });

      it('should build unsigned sweep tx', async function () {
        const startTime = (Date.now() / 1000 + 10).toFixed(); // timestamp in seconds, 10 seconds from now
        const expectedAmount = new BigNumber(balance).minus(defaultFee).toString();

        const recovery = await basecoin.recover({
          userKey: userEddsaRootPub,
          backupKey: backupEddsaRootPub,
          rootAddress,
          bitgoKey,
          recoveryDestination: recoveryDestination + '?memoId=' + memoId,
          startTime,
        });

        getBalanceStub.callCount.should.equal(1);

        recovery.should.not.be.undefined();
        recovery.should.have.property('txHex');
        recovery.should.have.property('id', rootAddress + '@' + startTime + '.0');
        recovery.should.have.property('userKey', userEddsaRootPub);
        recovery.should.have.property('backupKey', backupEddsaRootPub);
        recovery.should.have.property('bitgoKey', bitgoKey);
        recovery.should.have.property('address', rootAddress);
        recovery.should.have.property('coin', 'thbar');
        recovery.should.have.property('maxFee', defaultFee.toString());
        recovery.should.have.property('recipients', [{ address: recoveryDestination, amount: expectedAmount }]);
        recovery.should.have.property('amount', expectedAmount);
        recovery.should.have.property('validDuration', defaultValidDuration);
        recovery.should.have.property('nodeId', defaultNodeId);
        recovery.should.have.property('memo', memoId);
        recovery.should.have.property('startTime', startTime + '.0');
        const txBuilder = basecoin.getBuilderFactory().from(recovery.txHex);
        const tx = await txBuilder.build();
        const txJson = tx.toJson();
        txJson.id.should.equal(rootAddress + '@' + startTime + '.0');
        txJson.amount.should.equal(expectedAmount);
        txJson.to.should.equal(recoveryDestination);
        txJson.from.should.equal(rootAddress);
        txJson.fee.should.equal(defaultFee);
        txJson.node.should.equal(defaultNodeId);
        txJson.memo.should.equal(memoId);
        txJson.validDuration.should.equal(defaultValidDuration);
        txJson.startTime.should.equal(startTime + '.0');
        txJson.validDuration.should.equal(defaultValidDuration);
      });
    });
  });

  describe('broadcastTransaction', function () {
    const sandBox = Sinon.createSandbox();

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

    it('should succeed if the startTime and serializedSignedTransaction are valid', async function () {
      const startTime = (Date.now() / 1000 - 3).toFixed(); // timestamp in seconds, -3 seconds from now so it's valid after 2 seconds
      const expectedResponse = { txId: '0.0.7668465@' + startTime + '.0', status: 'SUCCESS' };
      const broadcastStub = sandBox.stub(Hbar.prototype, 'clientBroadcastTransaction').resolves(expectedResponse);
      const serializedSignedTransaction =
        '1acc010a640a20592a4fbb7263c59d450e651df96620dc9208ee7c7d9d6f2fdcb91c53f88312611a40105b7d250c81f3705bc0b85168ce3fd00330131bb7701378681c8c2e6a09a91828715e7334f4ef28d20ff09887c6e87c0a5c693e23824c26f3ba161fce0448050a640a20a6905095616c3cfaa1bf61b53de30e938ce4112c3cc4d25393ec6b9bf4dea0631a40bf98c5b89b7a08544edaa1f4c08a0dfa6ec3f78b7e2fd27049283984050f38ccf0303ee57a377cc0a725ffd99d69e9fd914770ab0949ba556d84b3b00cb07e0d22500a130a0608cea8c1ad0612090800100018f185d40312060800100018031880c2d72f220308b40132013472240a220a0f0a090800100018f185d40310d3b0510a0f0a090800100018c484d30310d4b051';
      const result = await basecoin.broadcastTransaction({ serializedSignedTransaction, startTime });
      broadcastStub.callCount.should.equal(1);
      result.should.deepEqual(expectedResponse);
    });

    it('should throw if the startTime is expired', async function () {
      const startTime = (Date.now() / 1000 - 2000).toFixed(); // timestamp in seconds, 2000 seconds from now
      const serializedSignedTransaction =
        '1acc010a640a20592a4fbb7263c59d450e651df96620dc9208ee7c7d9d6f2fdcb91c53f88312611a40105b7d250c81f3705bc0b85168ce3fd00330131bb7701378681c8c2e6a09a91828715e7334f4ef28d20ff09887c6e87c0a5c693e23824c26f3ba161fce0448050a640a20a6905095616c3cfaa1bf61b53de30e938ce4112c3cc4d25393ec6b9bf4dea0631a40bf98c5b89b7a08544edaa1f4c08a0dfa6ec3f78b7e2fd27049283984050f38ccf0303ee57a377cc0a725ffd99d69e9fd914770ab0949ba556d84b3b00cb07e0d22500a130a0608cea8c1ad0612090800100018f185d40312060800100018031880c2d72f220308b40132013472240a220a0f0a090800100018f185d40310d3b0510a0f0a090800100018c484d30310d4b051';
      await assert.rejects(
        async () => {
          await basecoin.broadcastTransaction({ serializedSignedTransaction, startTime });
        },
        (error: any) => {
          assert.ok(error.message.includes('Failed to broadcast transaction, error: startTime window expired'));
          return true;
        }
      );
    });

    it('should throw if the serializedSignedTransaction is invalid', async function () {
      const startTime = (Date.now() / 1000 - 10).toFixed(); // timestamp in seconds, 10 seconds from now
      const serializedSignedTransaction = 'randomstring';
      await assert.rejects(async () => {
        await basecoin.broadcastTransaction({ serializedSignedTransaction, startTime });
      });
    });

    it('should throw if the startTime in the tx is invalid', async function () {
      const expectedResponse =
        'transaction 0.0.7668465@1706056301.000000000 failed precheck with status INVALID_TRANSACTION_START';
      sandBox.stub(Hbar.prototype, 'clientBroadcastTransaction').rejects(new Error(expectedResponse));
      const serializedSignedTransaction =
        '1acc010a640a20592a4fbb7263c59d450e651df96620dc9208ee7c7d9d6f2fdcb91c53f88312611a40105b7d250c81f3705bc0b85168ce3fd00330131bb7701378681c8c2e6a09a91828715e7334f4ef28d20ff09887c6e87c0a5c693e23824c26f3ba161fce0448050a640a20a6905095616c3cfaa1bf61b53de30e938ce4112c3cc4d25393ec6b9bf4dea0631a40bf98c5b89b7a08544edaa1f4c08a0dfa6ec3f78b7e2fd27049283984050f38ccf0303ee57a377cc0a725ffd99d69e9fd914770ab0949ba556d84b3b00cb07e0d22500a130a0608cea8c1ad0612090800100018f185d40312060800100018031880c2d72f220308b40132013472240a220a0f0a090800100018f185d40310d3b0510a0f0a090800100018c484d30310d4b051';

      await assert.rejects(
        async () => {
          await basecoin.broadcastTransaction({ serializedSignedTransaction });
        },
        { message: expectedResponse }
      );
    });
  });

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

  describe('Generate wallet Root key pair: ', () => {
    it('should generate key pair', () => {
      const kp = basecoin.generateRootKeyPair();
      basecoin.isValidPub(kp.pub).should.equal(true);
      const keypair = new KeyPair({ prv: kp.prv }).getKeys(true);
      keypair.should.have.property('prv');
      keypair.prv?.should.equal(kp.prv.slice(0, 64));
      keypair.pub.should.equal(kp.pub);
    });

    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');
      kp.prv.should.equal(
        '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
      );

      const keypair = new KeyPair({ prv: kp.prv }).getKeys(true);
      keypair.should.have.property('prv');
      keypair.prv?.should.equal(kp.prv.slice(0, 64));
      keypair.pub.should.equal(kp.pub);
    });
  });
});

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


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