UNPKG

@avalanche-sdk/client

Version:

A TypeScript SDK for interacting with the Avalanche network through JSON-RPC APIs. This SDK provides a comprehensive set of tools to interact with all Avalanche chains (P-Chain, X-Chain, C-Chain) and various APIs, including wallet functionality for transa

374 lines (334 loc) 11.9 kB
import { avaxSerial, avmSerial, pvmSerial, UnsignedTx, utils, } from "@avalabs/avalanchejs"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { avalancheFuji } from "../../../chains"; import { createAvalancheWalletClient } from "../../../clients/createAvalancheWalletClient"; import { getTxFromBytes } from "../../../utils"; import { testContext } from "../fixtures/testContext"; import { account1, account2, account3, account4, } from "../fixtures/transactions/common"; import { getValidUTXO, getXChainMockServer, } from "../fixtures/transactions/xChain"; import { checkOutputs } from "../fixtures/utils"; import { Output } from "../types/common"; import { toTransferableOutput } from "../utils"; import { PrepareBaseTxnParameters } from "./types/prepareBaseTxn"; const testInputAmount = 1; const xChainWorker = getXChainMockServer({}); describe("newBaseTx", () => { const walletClient = createAvalancheWalletClient({ account: account1, chain: avalancheFuji, transport: { type: "http", url: "https://api.avax-test.network", }, }); beforeAll(() => { xChainWorker.listen(); }); afterAll(() => { xChainWorker.close(); }); it("should create correct outputs and fees", async () => { const receiverAddresses = [ account1.getXPAddress("X", "fuji"), account3.getXPAddress("X", "fuji"), ]; const changeAddresses = [account2.getXPAddress("X", "fuji")]; const testOutputAmount = 0.1234; const testOutputs: Output[] = [ { amount: testOutputAmount, addresses: receiverAddresses, }, ]; const mockTxParams = { changeAddresses: changeAddresses, outputs: testOutputs, context: testContext, }; const txnRequest = await walletClient.xChain.prepareBaseTxn(mockTxParams); const outputs = ( txnRequest.tx.getTx() as avmSerial.BaseTx ).baseTx.outputs.map(toTransferableOutput); const fee = testContext.baseTxFee; const expectedFeesInAvax = Number(fee) / 1e9; const expectedChangeAmount = testInputAmount - testOutputAmount - expectedFeesInAvax; // expected change output testOutputs.push({ amount: expectedChangeAmount, addresses: changeAddresses, }); // theres must be 2 outputs expect(outputs.length, "expected and actual outputs length mismatch").toBe( 2 ); // check outputs checkOutputs(testOutputs, outputs); // actual burned amount is same as fees const allInputAmounts = (txnRequest.tx.getTx() as avaxSerial.AvaxTx) .getInputs() .reduce((acc, i) => acc + i.amount(), 0n); const allOutputAmounts = ( txnRequest.tx.getTx() as pvmSerial.BaseTx ).baseTx.outputs.reduce((acc, i) => acc + i.amount(), 0n); expect( allInputAmounts - allOutputAmounts, "expected and actual burned amount mismatch" ).toBe(BigInt(expectedFeesInAvax * 1e9)); }); it("should only use utxos passed in params", async () => { const receiverAddresses = [ account1.getXPAddress("X", "fuji"), account3.getXPAddress("X", "fuji"), ]; const changeAddresses = [account2.getXPAddress("X", "fuji")]; const testOutputAmount = 0.1234; const testInputAmount = 0.5; const testOutputs = [ { amount: testOutputAmount, addresses: receiverAddresses, }, ]; const mockTxParams = { changeAddresses: changeAddresses, // utxos passed here override the wallet's or fromAddresses' utxos utxos: [ getValidUTXO( testInputAmount, testContext.avaxAssetID, [account1.getXPAddress("X", "fuji")], 0, 1 ), ], outputs: testOutputs, context: testContext, }; const txnRequest = await walletClient.xChain.prepareBaseTxn(mockTxParams); const outputs = ( txnRequest.tx.getTx() as avmSerial.BaseTx ).baseTx.outputs.map(toTransferableOutput); // calculate fees as per AvalancheJS method const fee = testContext.baseTxFee; const expectedFeesInAvax = Number(fee) / 1e9; // if utxos passed in params are only used, then testInputAmount will be 0.5 instead of default 1 const expectedChangeAmount = testInputAmount - testOutputAmount - expectedFeesInAvax; // expected change output testOutputs.push({ amount: expectedChangeAmount, addresses: changeAddresses, }); // theres must be 2 outputs expect(outputs.length, "expected and actual outputs length mismatch").toBe( 2 ); // match testOutputs with outputs checkOutputs(testOutputs, outputs); // actual burned amount is same as fees const allInputAmounts = (txnRequest.tx.getTx() as avaxSerial.AvaxTx) .getInputs() .reduce((acc, i) => acc + i.amount(), 0n); const allOutputAmounts = ( txnRequest.tx.getTx() as avmSerial.BaseTx ).baseTx.outputs.reduce((acc, i) => acc + i.amount(), 0n); expect(allInputAmounts - allOutputAmounts).toBe( BigInt(expectedFeesInAvax * 1e9) ); }); it("should use `fromAddresses` for fetching utxos and change addresses", async () => { const testSpentAmount = 1; const spenderAddresses = [account1.getXPAddress("X", "fuji")]; const receiverAddresses = [ account4.getXPAddress("X", "fuji"), account3.getXPAddress("X", "fuji"), ]; const testOutputAmount = 0.1234; const testOutputs: Output[] = [ { amount: testOutputAmount, addresses: receiverAddresses, }, ]; const mockTxParams = { fromAddresses: spenderAddresses, outputs: testOutputs, context: testContext, }; const txnRequest = await walletClient.xChain.prepareBaseTxn(mockTxParams); const outputs = ( txnRequest.tx.getTx() as avmSerial.BaseTx ).baseTx.outputs.map(toTransferableOutput); const fee = testContext.baseTxFee; const expectedFeesInAvax = Number(fee) / 1e9; const expectedChangeAmount = testSpentAmount - testOutputAmount - expectedFeesInAvax; // expected change output testOutputs.push({ amount: expectedChangeAmount, // this will test if change address is not passed, // then fromAddresses will be used. addresses: spenderAddresses, }); // theres must be 2 outputs expect(outputs.length, "expected and actual outputs length mismatch").toBe( 2 ); // check outputs checkOutputs(testOutputs, outputs); // actual burned amount is same as fees const allInputAmounts = (txnRequest.tx.getTx() as avaxSerial.AvaxTx) .getInputs() .reduce((acc, i) => acc + i.amount(), 0n); const allOutputAmounts = ( txnRequest.tx.getTx() as avmSerial.BaseTx ).baseTx.outputs.reduce((acc, i) => acc + i.amount(), 0n); expect( allInputAmounts - allOutputAmounts, "expected and actual burned amount mismatch" ).toBe(BigInt(expectedFeesInAvax * 1e9)); }); it("should sign the tx properly", async () => { const receiverAddresses = [ account1.getXPAddress("X", "fuji"), account3.getXPAddress("X", "fuji"), ]; const changeAddresses = [account2.getXPAddress("X", "fuji")]; const testOutputAmount = 0.1234; const testOutputs: Output[] = [ { amount: testOutputAmount, addresses: receiverAddresses, }, ]; const mockTxParams = { changeAddresses: changeAddresses, outputs: testOutputs, context: testContext, }; const txnRequest = await walletClient.xChain.prepareBaseTxn(mockTxParams); const signedTx = await walletClient.signXPTransaction(txnRequest); const [txn, credentials] = await getTxFromBytes(signedTx.signedTxHex, "X"); const unsignedTxInstance = new UnsignedTx( txn, [], new utils.AddressMaps(), credentials ); expect( unsignedTxInstance.hasAllSignatures(), "tx did not have all signatures" ).toBe(true); }); it("should sign the multi sig tx properly", async () => { const receiverAddresses = [account3.getXPAddress("X", "fuji")]; const changeAddresses = [account4.getXPAddress("X", "fuji")]; const testOutputAmount = 1.5; const testOutputs: Output[] = [ { amount: testOutputAmount, addresses: receiverAddresses, }, ]; const mockTxParams = { fromAddresses: [ account1.getXPAddress("X", "fuji"), account2.getXPAddress("X", "fuji"), ], changeAddresses: changeAddresses, outputs: testOutputs, context: testContext, }; const txnRequest = await walletClient.xChain.prepareBaseTxn(mockTxParams); const partialSignedTx = await walletClient.signXPTransaction(txnRequest); const [txn1, credentials1] = await getTxFromBytes( partialSignedTx.signedTxHex, "X" ); const unsignedTxInstance1 = new UnsignedTx( txn1, [], new utils.AddressMaps(), credentials1 ); expect( unsignedTxInstance1.hasAllSignatures(), "tx have all signatures for the multi-sig without signing" ).toBe(false); const signedTx = await walletClient.signXPTransaction({ ...partialSignedTx, account: account2, }); const [txn2, credentials2] = await getTxFromBytes( signedTx.signedTxHex, "X" ); const unsignedTxInstance2 = new UnsignedTx( txn2, [], new utils.AddressMaps(), credentials2 ); expect( unsignedTxInstance2.hasAllSignatures(), "tx did not have all signatures" ).toBe(true); }); it("should give correct tx hash", async () => { const receiverAddresses = [ account1.getXPAddress("X", "fuji"), account3.getXPAddress("X", "fuji"), ]; const changeAddresses = [account2.getXPAddress("X", "fuji")]; const testOutputAmount = 0.1234; const testOutputs: Output[] = [ { amount: testOutputAmount, addresses: receiverAddresses, }, ]; const mockTxParams: PrepareBaseTxnParameters = { changeAddresses: changeAddresses, outputs: testOutputs, context: testContext, }; const result = await walletClient.xChain.prepareBaseTxn(mockTxParams); const signedTx = await walletClient.signXPTransaction(result); expect(signedTx.signedTxHex, "expected and actual tx hash mismatch").toBe( "0x00000000000000000005ed5f38341e436e5d46e2bb00b45d62ae97d1b050c64bc634ae10626739e35c4b0000000221e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff0000000700000000075aef40000000000000000000000001000000022a705f0a71d8b6e19d5e955b19d683ca6d682370931887940fd0ef612f2aa42fcdc8556405b7e76721e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff000000070000000034309880000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001ba5eeb9cf2e099134ffba3d2ce1310fa6f07413e4512044cdd1caba9e03fa8c90000000021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff00000005000000003b9aca00000000010000000000000000000000010000000900000001bfabf38fde0cf2ef08f3b24ac3e3d7cac3050cd29b2b0dce1e2695a384121e121747a248f331a6179db94c177c3ef81e812fd59d645360efa28eca37f28d5bf0002f4dbec4" ); }); it("should handle errors appropriately", async () => { const mockTxParams: PrepareBaseTxnParameters = { fromAddresses: [account1.getXPAddress("X", "fuji")], outputs: [ { amount: 40, addresses: [account1.getXPAddress("X", "fuji")], }, ], context: testContext, }; // Verify that the error is propagated await expect( walletClient.xChain.prepareBaseTxn(mockTxParams) ).rejects.toThrow( "insufficient funds (Burn Amount): need 39001000000 more units of FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z to burn" ); }); });