From aecb558241c9a1d37e07f41a5f41923b4759322d Mon Sep 17 00:00:00 2001 From: peak3d Date: Mon, 9 Jan 2023 10:20:24 +0100 Subject: [PATCH 1/2] [TX] Add remaining platform tx to caminoExecutor (spend / lock) --- e2e_tests/cchain_nomock.test.ts | 6 +- e2e_tests/e2etestlib.ts | 7 +- e2e_tests/health_nomock.test.ts | 2 +- e2e_tests/info_nomock.test.ts | 2 +- e2e_tests/keystore_nomock.test.ts | 26 +- e2e_tests/pchain_nomock.test.ts | 11 +- e2e_tests/xchain_nomock.test.ts | 19 +- examples/avm/buildExportTx-cchain-ant.ts | 6 +- .../platformvm/baseTx-avax-create-multisig.ts | 2 - examples/platformvm/buildAddDelegatorTx.ts | 10 +- .../platformvm/buildAddSubnetValidatorTx.ts | 14 +- examples/platformvm/buildAddValidatorTx.ts | 12 +- examples/platformvm/buildCreateChainTx.ts | 13 +- examples/platformvm/buildCreateSubnetTx.ts | 13 +- examples/platformvm/buildExportTx-CChain.ts | 8 +- examples/platformvm/buildExportTx-XChain.ts | 11 +- examples/platformvm/buildImportTx-CChain.ts | 15 +- examples/platformvm/buildImportTx-XChain.ts | 15 +- examples/platformvm/buildRegisterNodeTx.ts | 1 + package.json | 2 +- src/apis/avm/api.ts | 67 +- src/apis/avm/utxos.ts | 113 +- src/apis/evm/api.ts | 15 +- src/apis/evm/utxos.ts | 17 +- src/apis/platformvm/api.ts | 250 ++-- src/apis/platformvm/builder.ts | 1128 +++++++++++++++++ src/apis/platformvm/camino_executor.ts | 236 ---- src/apis/platformvm/interfaces.ts | 15 +- src/apis/platformvm/spender.ts | 66 + src/apis/platformvm/utxos.ts | 863 +------------ src/common/assetamount.ts | 16 +- tests/apis/platformvm/api.test.ts | 109 +- tests/apis/platformvm/tx.test.ts | 32 +- 33 files changed, 1676 insertions(+), 1446 deletions(-) create mode 100644 src/apis/platformvm/builder.ts delete mode 100644 src/apis/platformvm/camino_executor.ts create mode 100644 src/apis/platformvm/spender.ts diff --git a/e2e_tests/cchain_nomock.test.ts b/e2e_tests/cchain_nomock.test.ts index fd4b88965..f78b5c0f2 100644 --- a/e2e_tests/cchain_nomock.test.ts +++ b/e2e_tests/cchain_nomock.test.ts @@ -27,7 +27,9 @@ describe("CChain", (): void => { () => keystore.createUser(user, passwd), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "importKey", @@ -70,4 +72,4 @@ describe("CChain", (): void => { ] createTests(tests_spec) -}) \ No newline at end of file +}) diff --git a/e2e_tests/e2etestlib.ts b/e2e_tests/e2etestlib.ts index d8529219c..23c5a0597 100644 --- a/e2e_tests/e2etestlib.ts +++ b/e2e_tests/e2etestlib.ts @@ -10,7 +10,7 @@ export const getAvalanche = (): Avalanche => { const avalanche: Avalanche = new Avalanche( process.env.CAMINOGO_IP, parseInt(process.env.CAMINOGO_PORT), - 'http', + "http", 12345 ) return avalanche @@ -35,7 +35,9 @@ export const createTests = (tests_spec: any[]): void => { expect(preprocess(await promise())).toEqual(expected()) } if (matcher == Matcher.toContain) { - expect(preprocess(await promise())).toEqual(expect.arrayContaining(expected())) + expect(preprocess(await promise())).toEqual( + expect.arrayContaining(expected()) + ) } if (matcher == Matcher.toMatch) { expect(preprocess(await promise())).toMatch(expected()) @@ -50,4 +52,3 @@ export const createTests = (tests_spec: any[]): void => { }) } } - diff --git a/e2e_tests/health_nomock.test.ts b/e2e_tests/health_nomock.test.ts index a5ceac9f6..f6790fbc6 100644 --- a/e2e_tests/health_nomock.test.ts +++ b/e2e_tests/health_nomock.test.ts @@ -20,4 +20,4 @@ describe("Info", (): void => { ] createTests(tests_spec) -}) \ No newline at end of file +}) diff --git a/e2e_tests/info_nomock.test.ts b/e2e_tests/info_nomock.test.ts index 410c4516c..7c8446274 100644 --- a/e2e_tests/info_nomock.test.ts +++ b/e2e_tests/info_nomock.test.ts @@ -69,4 +69,4 @@ describe("Info", (): void => { ] createTests(tests_spec) -}) \ No newline at end of file +}) diff --git a/e2e_tests/keystore_nomock.test.ts b/e2e_tests/keystore_nomock.test.ts index 95fd21b3c..c8ebd96d6 100644 --- a/e2e_tests/keystore_nomock.test.ts +++ b/e2e_tests/keystore_nomock.test.ts @@ -27,7 +27,9 @@ describe("Keystore", (): void => { () => keystore.createUser(username1, password), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "createRepeatedUser", @@ -62,7 +64,9 @@ describe("Keystore", (): void => { () => keystore.importUser(username2, exportedUser.value, password), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "exportImportUser", @@ -73,7 +77,9 @@ describe("Keystore", (): void => { })(), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "listUsers2", @@ -87,23 +93,29 @@ describe("Keystore", (): void => { () => keystore.deleteUser(username1, password), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "deleteUser2", () => keystore.deleteUser(username2, password), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "deleteUser3", () => keystore.deleteUser(username3, password), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ] ] createTests(tests_spec) -}) \ No newline at end of file +}) diff --git a/e2e_tests/pchain_nomock.test.ts b/e2e_tests/pchain_nomock.test.ts index 39e02cfac..95476431a 100644 --- a/e2e_tests/pchain_nomock.test.ts +++ b/e2e_tests/pchain_nomock.test.ts @@ -28,8 +28,7 @@ describe("PChain", (): void => { const nodeID: string = "NodeID-AK7sPBsZM9rQwse23aLhEEBPHZD5gkLrL" const subnetID: string = "2bGsYJorY6X7RhjPBFs3kYjiNEHo4zGrD2eeyZbb43T2KKi7fM" const xChainAddr: string = "X-custom18jma8ppw3nhx5r4ap8clazz0dps7rv5u9xde7p" - const avalancheBlockChainID: string = - "11111111111111111111111111111111LpoYY" + const avalancheBlockChainID: string = "11111111111111111111111111111111LpoYY" const rewardUTXOTxID: string = "2nmH8LithVbdjaXsxVQCQfXtzN9hBbmebrsaEYnLM9T32Uy2Y4" @@ -40,7 +39,9 @@ describe("PChain", (): void => { () => keystore.createUser(user, passwd), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "createaddrB", @@ -81,7 +82,7 @@ describe("PChain", (): void => { ], [ "getBalanceOfMultipleAddresses", - () => pchain.getBalance({ addresses: [ whaleAddr ] }), + () => pchain.getBalance({ addresses: [whaleAddr] }), (x) => x.balance, Matcher.toBe, () => "30000000000000000" @@ -273,4 +274,4 @@ describe("PChain", (): void => { ] createTests(tests_spec) -}) \ No newline at end of file +}) diff --git a/e2e_tests/xchain_nomock.test.ts b/e2e_tests/xchain_nomock.test.ts index c3879ebe0..89f5212e6 100644 --- a/e2e_tests/xchain_nomock.test.ts +++ b/e2e_tests/xchain_nomock.test.ts @@ -28,7 +28,9 @@ describe("XChain", (): void => { () => keystore.createUser(user, passwd), (x) => x, Matcher.toEqual, - () => { return {} } + () => { + return {} + } ], [ "createaddrB", @@ -59,7 +61,8 @@ describe("XChain", (): void => { ), (x) => x, Matcher.toThrow, - () => `problem retrieving user "${badUser}": incorrect password for user "${badUser}"` + () => + `problem retrieving user "${badUser}": incorrect password for user "${badUser}"` ], [ "incorrectPass", @@ -76,7 +79,8 @@ describe("XChain", (): void => { ), (x) => x, Matcher.toThrow, - () => `problem retrieving user "${user}": incorrect password for user "${user}"` + () => + `problem retrieving user "${user}": incorrect password for user "${user}"` ], [ "getBalance", @@ -159,8 +163,8 @@ describe("XChain", (): void => { "AVAX" ), (x) => x, - Matcher.toThrow, - () => "couldn't unmarshal an argument" + Matcher.Get, + () => tx ], [ "import", @@ -195,7 +199,8 @@ describe("XChain", (): void => { xchain.mint(user, passwd, 1500, asset.value, addrB.value, [whaleAddr]), (x) => x, Matcher.toThrow, - () => "couldn't unmarshal an argument" + () => + "provided addresses don't have the authority to mint the provided asset" ], [ "getTx", @@ -221,4 +226,4 @@ describe("XChain", (): void => { ] createTests(tests_spec) -}) \ No newline at end of file +}) diff --git a/examples/avm/buildExportTx-cchain-ant.ts b/examples/avm/buildExportTx-cchain-ant.ts index 13bddd23d..36674028b 100644 --- a/examples/avm/buildExportTx-cchain-ant.ts +++ b/examples/avm/buildExportTx-cchain-ant.ts @@ -73,7 +73,8 @@ const main = async (): Promise => { ) const utxoSet: UTXOSet = avmUTXOResponse.utxos const amount: BN = new BN(350) - const threshold: number = 1 + const toThreshold: number = 1 + const changeThreshold: number = 1 const assetID: string = "Ycg5QzddNwe3ebfFXhoGUDnWgC6GE88QRakRnn9dp3nGwqCwD" const unsignedTx: UnsignedTx = await xchain.buildExportTx( @@ -86,7 +87,8 @@ const main = async (): Promise => { memo, asOf, locktime, - threshold, + toThreshold, + changeThreshold, assetID ) diff --git a/examples/platformvm/baseTx-avax-create-multisig.ts b/examples/platformvm/baseTx-avax-create-multisig.ts index b8671ad19..5ae795175 100644 --- a/examples/platformvm/baseTx-avax-create-multisig.ts +++ b/examples/platformvm/baseTx-avax-create-multisig.ts @@ -48,7 +48,6 @@ let pAddresses: Buffer[] let pAddressStrings: string[] let avaxAssetID: string let fee: BN -let pChainBlockchainID: string let avaxAssetIDBuf: Buffer let xBlockchainID: string let xBlockchainIDBuf: Buffer @@ -71,7 +70,6 @@ const InitAvalanche = async () => { pAddressStrings = pchain.keyChain().getAddressStrings() avaxAssetID = avalanche.getNetwork().X.avaxAssetID fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID avaxAssetIDBuf = bintools.cb58Decode(avaxAssetID) xBlockchainID = avalanche.getNetwork().X.blockchainID xBlockchainIDBuf = bintools.cb58Decode(xBlockchainID) diff --git a/examples/platformvm/buildAddDelegatorTx.ts b/examples/platformvm/buildAddDelegatorTx.ts index b1a81ed0c..1d959f1d3 100644 --- a/examples/platformvm/buildAddDelegatorTx.ts +++ b/examples/platformvm/buildAddDelegatorTx.ts @@ -27,29 +27,21 @@ const locktime: BN = new BN(0) const memo: Buffer = Buffer.from( "PlatformVM utility method buildAddDelegatorTx to add a delegator to the primary subnet" ) -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) const nodeID: string = "NodeID-DueWyGi3B9jtKfa9mPoecd4YSDJ1ftF69" const startTime: BN = UnixNow().add(new BN(60 * 1)) const endTime: BN = startTime.add(new BN(2630000)) let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string -let fee: BN -let pChainBlockchainID: string const InitAvalanche = async () => { await avalanche.fetchNetworkSettings() pchain = avalanche.PChain() pKeychain = pchain.keyChain() pKeychain.importKey(privKey) - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID - fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID } const main = async (): Promise => { diff --git a/examples/platformvm/buildAddSubnetValidatorTx.ts b/examples/platformvm/buildAddSubnetValidatorTx.ts index 248451797..27f0ac9a1 100644 --- a/examples/platformvm/buildAddSubnetValidatorTx.ts +++ b/examples/platformvm/buildAddSubnetValidatorTx.ts @@ -9,8 +9,7 @@ import { import { GetUTXOsResponse } from "@c4tplatform/caminojs/dist/apis/platformvm/interfaces" import { PrivateKeyPrefix, - DefaultLocalGenesisPrivateKey, - UnixNow + DefaultLocalGenesisPrivateKey } from "@c4tplatform/caminojs/dist/utils" import { ExamplesConfig } from "../common/examplesConfig" @@ -26,16 +25,11 @@ let privKey: string = `${PrivateKeyPrefix}${DefaultLocalGenesisPrivateKey}` const nodeID: string = "NodeID-NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN" const startTime: BN = new BN(1652217329) const endTime: BN = new BN(1653511017) -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string -let fee: BN -let pChainBlockchainID: string -let avaxAssetIDBuf: Buffer const InitAvalanche = async () => { await avalanche.fetchNetworkSettings() @@ -54,11 +48,7 @@ const InitAvalanche = async () => { pKeychain.importKey( "PrivateKey-2uWuEQbY5t7NPzgqzDrXSgGPhi3uyKj2FeAvPUHYo6CmENHJfn" ) - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID - fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID } const main = async (): Promise => { diff --git a/examples/platformvm/buildAddValidatorTx.ts b/examples/platformvm/buildAddValidatorTx.ts index 1f0244171..63e218fe1 100644 --- a/examples/platformvm/buildAddValidatorTx.ts +++ b/examples/platformvm/buildAddValidatorTx.ts @@ -27,7 +27,6 @@ const locktime: BN = new BN(0) const memo: Buffer = Buffer.from( "PlatformVM utility method buildAddValidatorTx to add a validator to the primary subnet" ) -const asOf: BN = UnixNow() const nodeID: string = "NodeID-D1LbWvUf9iaeEyUbTYYtYq4b7GaYR5tnJ" const startTime: BN = UnixNow().add(new BN(60 * 1)) const endTime: BN = startTime.add(new BN(26300000)) @@ -35,22 +34,14 @@ const delegationFee: number = 10 let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string -let fee: BN -let pChainBlockchainID: string const InitAvalanche = async () => { await avalanche.fetchNetworkSettings() pchain = avalanche.PChain() pKeychain = pchain.keyChain() pKeychain.importKey(privKey) - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID - fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID } const main = async (): Promise => { @@ -73,8 +64,7 @@ const main = async (): Promise => { delegationFee, locktime, threshold, - memo, - asOf + memo ) const tx: Tx = unsignedTx.sign(pKeychain) diff --git a/examples/platformvm/buildCreateChainTx.ts b/examples/platformvm/buildCreateChainTx.ts index 5a636dd5d..318c799f9 100644 --- a/examples/platformvm/buildCreateChainTx.ts +++ b/examples/platformvm/buildCreateChainTx.ts @@ -14,8 +14,7 @@ import { } from "@c4tplatform/caminojs/dist/apis/platformvm" import { PrivateKeyPrefix, - DefaultLocalGenesisPrivateKey, - UnixNow + DefaultLocalGenesisPrivateKey } from "@c4tplatform/caminojs/dist/utils" import { ExamplesConfig } from "../common/examplesConfig" @@ -32,16 +31,12 @@ const avalanche: Avalanche = new Avalanche( */ const bintools: BinTools = BinTools.getInstance() let privKey: string = `${PrivateKeyPrefix}${DefaultLocalGenesisPrivateKey}` -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) let pchain: PlatformVMAPI let pKeychain: KeyChain let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string -let fee: BN -let pChainBlockchainID: string -let avaxAssetIDBuf: Buffer const InitAvalanche = async () => { await avalanche.fetchNetworkSettings() @@ -60,10 +55,6 @@ const InitAvalanche = async () => { // P-local1t3qjau2pf3ys83yallqt4y5xc3l6ya5f7wr6aq pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID - fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID - avaxAssetIDBuf = bintools.cb58Decode(avaxAssetID) } const main = async (): Promise => { diff --git a/examples/platformvm/buildCreateSubnetTx.ts b/examples/platformvm/buildCreateSubnetTx.ts index 71a690b64..d94d971b9 100644 --- a/examples/platformvm/buildCreateSubnetTx.ts +++ b/examples/platformvm/buildCreateSubnetTx.ts @@ -9,8 +9,7 @@ import { import { GetUTXOsResponse } from "@c4tplatform/caminojs/dist/apis/platformvm/interfaces" import { PrivateKeyPrefix, - DefaultLocalGenesisPrivateKey, - UnixNow + DefaultLocalGenesisPrivateKey } from "@c4tplatform/caminojs/dist/utils" import { ExamplesConfig } from "../common/examplesConfig" @@ -27,15 +26,11 @@ const threshold: number = 2 const memo: Buffer = Buffer.from( "PlatformVM utility method buildCreateSubnetTx to create a CreateSubnetTx which creates a 1-of-2 AVAX utxo and a 2-of-3 SubnetAuth" ) -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string -let fee: BN -let pChainBlockchainID: string let subnetAuthKeychain: string[] const InitAvalanche = async () => { @@ -53,11 +48,7 @@ const InitAvalanche = async () => { "PrivateKey-24gdABgapjnsJfnYkfev6YPyQhTaCU72T9bavtDNTYivBLp2eW" ) // P-local1t3qjau2pf3ys83yallqt4y5xc3l6ya5f7wr6aq - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID - fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID subnetAuthKeychain = [ pAddressStrings[1], pAddressStrings[2], diff --git a/examples/platformvm/buildExportTx-CChain.ts b/examples/platformvm/buildExportTx-CChain.ts index 7ae7bea5a..eb039bc70 100644 --- a/examples/platformvm/buildExportTx-CChain.ts +++ b/examples/platformvm/buildExportTx-CChain.ts @@ -32,15 +32,12 @@ const locktime: BN = new BN(0) const memo: Buffer = Buffer.from( "PlatformVM utility method buildExportTx to export AVAX from the P-Chain to the C-Chain" ) -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string let fee: BN -let pChainBlockchainID: string let xchain: AVMAPI let xKeychain: AVMKeyChain @@ -51,11 +48,8 @@ const InitAvalanche = async () => { pchain = avalanche.PChain() pKeychain = pchain.keyChain() pKeychain.importKey(privKey) - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID xchain = avalanche.XChain() xKeychain = xchain.keyChain() diff --git a/examples/platformvm/buildExportTx-XChain.ts b/examples/platformvm/buildExportTx-XChain.ts index 1714c75f7..4148391f0 100644 --- a/examples/platformvm/buildExportTx-XChain.ts +++ b/examples/platformvm/buildExportTx-XChain.ts @@ -12,8 +12,7 @@ import { } from "@c4tplatform/caminojs/dist/apis/platformvm" import { PrivateKeyPrefix, - DefaultLocalGenesisPrivateKey, - UnixNow + DefaultLocalGenesisPrivateKey } from "@c4tplatform/caminojs/dist/utils" import { ExamplesConfig } from "../common/examplesConfig" @@ -31,15 +30,12 @@ const locktime: BN = new BN(0) const memo: Buffer = Buffer.from( "PlatformVM utility method buildExportTx to export AVAX from the P-Chain to the X-Chain" ) -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string let fee: BN -let pChainBlockchainID: string let xchain: AVMAPI let xKeychain: AVMKeyChain @@ -51,11 +47,8 @@ const InitAvalanche = async () => { pchain = avalanche.PChain() pKeychain = pchain.keyChain() pKeychain.importKey(privKey) - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID xchain = avalanche.XChain() xChainBlockchainID = avalanche.getNetwork().X.blockchainID diff --git a/examples/platformvm/buildImportTx-CChain.ts b/examples/platformvm/buildImportTx-CChain.ts index 2f2d9baf6..b8c9175f0 100644 --- a/examples/platformvm/buildImportTx-CChain.ts +++ b/examples/platformvm/buildImportTx-CChain.ts @@ -8,8 +8,7 @@ import { } from "@c4tplatform/caminojs/dist/apis/platformvm" import { PrivateKeyPrefix, - DefaultLocalGenesisPrivateKey, - UnixNow + DefaultLocalGenesisPrivateKey } from "@c4tplatform/caminojs/dist//utils" import { ExamplesConfig } from "../common/examplesConfig" @@ -27,16 +26,11 @@ const locktime: BN = new BN(0) const memo: Buffer = Buffer.from( "PlatformVM utility method buildImportTx to import AVAX to the P-Chain from the X-Chain" ) -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string -let fee: BN -let pChainBlockchainID: string - let cChainBlockchainID: string const InitAvalanche = async () => { @@ -44,12 +38,7 @@ const InitAvalanche = async () => { pchain = avalanche.PChain() pKeychain = pchain.keyChain() pKeychain.importKey(privKey) - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID - fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID - cChainBlockchainID = avalanche.getNetwork().C.blockchainID } diff --git a/examples/platformvm/buildImportTx-XChain.ts b/examples/platformvm/buildImportTx-XChain.ts index 6394b7949..b09ab1dbf 100644 --- a/examples/platformvm/buildImportTx-XChain.ts +++ b/examples/platformvm/buildImportTx-XChain.ts @@ -8,8 +8,7 @@ import { } from "@c4tplatform/caminojs/dist/apis/platformvm" import { PrivateKeyPrefix, - DefaultLocalGenesisPrivateKey, - UnixNow + DefaultLocalGenesisPrivateKey } from "@c4tplatform/caminojs/dist/utils" import { ExamplesConfig } from "../common/examplesConfig" @@ -27,17 +26,12 @@ const locktime: BN = new BN(0) const memo: Buffer = Buffer.from( "PlatformVM utility method buildImportTx to import AVAX to the P-Chain from the X-Chain" ) -const asOf: BN = UnixNow() +const asOf: BN = new BN(0) let pchain: PlatformVMAPI let pKeychain: KeyChain -let pAddresses: Buffer[] let pAddressStrings: string[] -let avaxAssetID: string -let fee: BN let pChainBlockchainID: string -let avaxAssetIDBuf: Buffer - let xChainBlockchainID: string const InitAvalanche = async () => { @@ -45,12 +39,7 @@ const InitAvalanche = async () => { pchain = avalanche.PChain() pKeychain = pchain.keyChain() pKeychain.importKey(privKey) - pAddresses = pchain.keyChain().getAddresses() pAddressStrings = pchain.keyChain().getAddressStrings() - avaxAssetID = avalanche.getNetwork().X.avaxAssetID - fee = pchain.getDefaultTxFee() - pChainBlockchainID = avalanche.getNetwork().P.blockchainID - xChainBlockchainID = avalanche.getNetwork().X.blockchainID } diff --git a/examples/platformvm/buildRegisterNodeTx.ts b/examples/platformvm/buildRegisterNodeTx.ts index 648047abd..e3164a8a4 100644 --- a/examples/platformvm/buildRegisterNodeTx.ts +++ b/examples/platformvm/buildRegisterNodeTx.ts @@ -58,6 +58,7 @@ const main = async (): Promise => { ) const unsignedTx: UnsignedTx = await pchain.buildRegisterNodeTx( + undefined, pAddressStrings, pAddressStrings, oldNodeID, diff --git a/package.json b/package.json index 75cfe9384..339890ed1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "docs": "yarn docshtml && yarn docsmd", "prettier-src": "prettier --write ./src", "prettier-examples": "prettier --write ./examples", - "prettier-tests": "prettier --write ./tests", + "prettier-tests": "prettier --write ./tests ./e2e_tests", "prettier-web": "prettier --write ./web", "prettier-mocks": "prettier --write ./__mocks__", "prettier-workflows": "prettier --write .github/**/*.yml", diff --git a/src/apis/avm/api.ts b/src/apis/avm/api.ts index 6ba2e27bf..81d3ecd7c 100644 --- a/src/apis/avm/api.ts +++ b/src/apis/avm/api.ts @@ -1017,7 +1017,8 @@ export class AVMAPI extends JRPCAPI { * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[BaseTx]]. * @@ -1034,7 +1035,8 @@ export class AVMAPI extends JRPCAPI { memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): Promise => { const caller: string = "buildBaseTx" const to: Buffer[] = this._cleanAddressArray(toAddresses, caller).map( @@ -1073,7 +1075,8 @@ export class AVMAPI extends JRPCAPI { memo, asOf, locktime, - threshold + toThreshold, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1098,7 +1101,8 @@ export class AVMAPI extends JRPCAPI { * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[NFTTransferTx]]. * @@ -1114,7 +1118,8 @@ export class AVMAPI extends JRPCAPI { memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): Promise => { const caller: string = "buildNFTTransferTx" const to: Buffer[] = this._cleanAddressArray(toAddresses, caller).map( @@ -1152,7 +1157,8 @@ export class AVMAPI extends JRPCAPI { memo, asOf, locktime, - threshold + toThreshold, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1178,7 +1184,8 @@ export class AVMAPI extends JRPCAPI { * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[ImportTx]]. * @@ -1195,7 +1202,8 @@ export class AVMAPI extends JRPCAPI { memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): Promise => { const caller: string = "buildImportTx" const to: Buffer[] = this._cleanAddressArray(toAddresses, caller).map( @@ -1257,7 +1265,8 @@ export class AVMAPI extends JRPCAPI { memo, asOf, locktime, - threshold + toThreshold, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1283,7 +1292,8 @@ export class AVMAPI extends JRPCAPI { * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * @param assetID Optional. The assetID of the asset to send. Defaults to AVAX assetID. * Regardless of the asset which you"re exporting, all fees are paid in AVAX. * @@ -1299,7 +1309,8 @@ export class AVMAPI extends JRPCAPI { memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1, + toThreshold: number = 1, + changeThreshold: number = 1, assetID: string = undefined ): Promise => { const prefixes: object = {} @@ -1372,7 +1383,8 @@ export class AVMAPI extends JRPCAPI { memo, asOf, locktime, - threshold + toThreshold, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1399,6 +1411,7 @@ export class AVMAPI extends JRPCAPI { * @param mintOutputs Optional. Array of [[SECPMintOutput]]s to be included in the transaction. These outputs can be spent to mint more tokens. * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[CreateAssetTx]]. * @@ -1413,7 +1426,8 @@ export class AVMAPI extends JRPCAPI { denomination: number, mintOutputs: SECPMintOutput[] = undefined, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = UnixNow(), + changeThreshold: number = 1 ): Promise => { const caller: string = "buildCreateAssetTx" const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( @@ -1458,7 +1472,8 @@ export class AVMAPI extends JRPCAPI { fee, avaxAssetID, memo, - asOf + asOf, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx, fee))) { @@ -1471,6 +1486,22 @@ export class AVMAPI extends JRPCAPI { return builtUnsignedTx } + /** + * Creates an unsigned Secp mint transaction. For more granular control, you may create your own + * [[OperationTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). + * + * @param utxoset A set of UTXOs that the transaction is built on + * @param mintOwner A [[SECPMintOutput]] which specifies the new set of minters + * @param transferOwner A [[SECPTransferOutput]] which specifies where the minted tokens will go + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs + * @param mintUTXOID The UTXOID for the [[SCPMintOutput]] being spent to produce more tokens + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[SECPMintTx]]. + */ buildSECPMintTx = async ( utxoset: UTXOSet, mintOwner: SECPMintOutput, @@ -1479,7 +1510,8 @@ export class AVMAPI extends JRPCAPI { changeAddresses: string[], mintUTXOID: string, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = UnixNow(), + changeThreshold: number = 1 ): Promise => { const caller: string = "buildSECPMintTx" const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( @@ -1509,7 +1541,8 @@ export class AVMAPI extends JRPCAPI { fee, avaxAssetID, memo, - asOf + asOf, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ @@ -1533,6 +1566,7 @@ export class AVMAPI extends JRPCAPI { * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting mint output + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * ```js * Example minterSets: @@ -1635,6 +1669,7 @@ export class AVMAPI extends JRPCAPI { * @param payload Optional. Data for NFT Payload as either a [[PayloadBase]] or a {@link https://github.com/feross/buffer|Buffer} * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains an [[OperationTx]]. * diff --git a/src/apis/avm/utxos.ts b/src/apis/avm/utxos.ts index 57463e7f0..55f79c7e0 100644 --- a/src/apis/avm/utxos.ts +++ b/src/apis/avm/utxos.ts @@ -343,7 +343,8 @@ export class UTXOSet extends StandardUTXOSet { * @param memo Optional. Contains arbitrary data, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. * @@ -361,9 +362,10 @@ export class UTXOSet extends StandardUTXOSet { memo: Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): UnsignedTx => { - if (threshold > toAddresses.length) { + if (toThreshold > toAddresses.length) { /* istanbul ignore next */ throw new ThresholdError( "Error - UTXOSet.buildBaseTx: threshold is greater than number of addresses" @@ -386,8 +388,10 @@ export class UTXOSet extends StandardUTXOSet { const aad: AssetAmountDestination = new AssetAmountDestination( toAddresses, + toThreshold, fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) if (assetID.toString("hex") === feeAssetID.toString("hex")) { aad.addAssetAmount(assetID, amount, fee) @@ -401,12 +405,7 @@ export class UTXOSet extends StandardUTXOSet { let ins: TransferableInput[] = [] let outs: TransferableOutput[] = [] - const success: Error = this.getMinimumSpendable( - aad, - asOf, - locktime, - threshold - ) + const success: Error = this.getMinimumSpendable(aad, asOf, locktime) if (typeof success === "undefined") { ins = aad.getInputs() outs = aad.getAllOutputs() @@ -435,6 +434,7 @@ export class UTXOSet extends StandardUTXOSet { * @param feeAssetID Optional. The assetID of the fees being burned. * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. * @@ -452,7 +452,8 @@ export class UTXOSet extends StandardUTXOSet { fee: BN = undefined, feeAssetID: Buffer = undefined, memo: Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = UnixNow(), + changeThreshold: number = 1 ): UnsignedTx => { const zero: BN = new BN(0) let ins: TransferableInput[] = [] @@ -460,9 +461,11 @@ export class UTXOSet extends StandardUTXOSet { if (this._feeCheck(fee, feeAssetID)) { const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, fromAddresses, - fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) aad.addAssetAmount(feeAssetID, zero, fee) const success: Error = this.getMinimumSpendable(aad, asOf) @@ -514,6 +517,9 @@ export class UTXOSet extends StandardUTXOSet { * @param feeAssetID Optional. The assetID of the fees being burned. * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. */ buildSECPMintTx = ( networkID: number, @@ -526,7 +532,8 @@ export class UTXOSet extends StandardUTXOSet { fee: BN = undefined, feeAssetID: Buffer = undefined, memo: Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = UnixNow(), + changeThreshold: number = 1 ): UnsignedTx => { const zero: BN = new BN(0) let ins: TransferableInput[] = [] @@ -534,9 +541,11 @@ export class UTXOSet extends StandardUTXOSet { if (this._feeCheck(fee, feeAssetID)) { const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, fromAddresses, - fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) aad.addAssetAmount(feeAssetID, zero, fee) const success: Error = this.getMinimumSpendable(aad, asOf) @@ -608,6 +617,7 @@ export class UTXOSet extends StandardUTXOSet { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting mint output + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. * @@ -624,7 +634,8 @@ export class UTXOSet extends StandardUTXOSet { feeAssetID: Buffer = undefined, memo: Buffer = undefined, asOf: BN = UnixNow(), - locktime: BN = undefined + locktime: BN = undefined, + changeThreshold: number = 1 ): UnsignedTx => { const zero: BN = new BN(0) let ins: TransferableInput[] = [] @@ -632,9 +643,11 @@ export class UTXOSet extends StandardUTXOSet { if (this._feeCheck(fee, feeAssetID)) { const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, fromAddresses, - fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) aad.addAssetAmount(feeAssetID, zero, fee) const success: Error = this.getMinimumSpendable(aad, asOf) @@ -686,6 +699,7 @@ export class UTXOSet extends StandardUTXOSet { * @param feeAssetID Optional. The assetID of the fees being burned. * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. * @@ -702,7 +716,8 @@ export class UTXOSet extends StandardUTXOSet { fee: BN = undefined, feeAssetID: Buffer = undefined, memo: Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = UnixNow(), + changeThreshold: number = 1 ): UnsignedTx => { const zero: BN = new BN(0) let ins: TransferableInput[] = [] @@ -710,9 +725,11 @@ export class UTXOSet extends StandardUTXOSet { if (this._feeCheck(fee, feeAssetID)) { const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, fromAddresses, - fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) aad.addAssetAmount(feeAssetID, zero, fee) const success: Error = this.getMinimumSpendable(aad, asOf) @@ -779,7 +796,8 @@ export class UTXOSet extends StandardUTXOSet { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. * @@ -796,7 +814,8 @@ export class UTXOSet extends StandardUTXOSet { memo: Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): UnsignedTx => { const zero: BN = new BN(0) let ins: TransferableInput[] = [] @@ -804,9 +823,11 @@ export class UTXOSet extends StandardUTXOSet { if (this._feeCheck(fee, feeAssetID)) { const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, fromAddresses, - fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) aad.addAssetAmount(feeAssetID, zero, fee) const success: Error = this.getMinimumSpendable(aad, asOf) @@ -829,7 +850,7 @@ export class UTXOSet extends StandardUTXOSet { out.getPayload(), toAddresses, locktime, - threshold + toThreshold ) const op: NFTTransferOperation = new NFTTransferOperation(outbound) @@ -878,7 +899,9 @@ export class UTXOSet extends StandardUTXOSet { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * * @returns An unsigned transaction created from the passed in parameters. * */ @@ -895,7 +918,8 @@ export class UTXOSet extends StandardUTXOSet { memo: Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): UnsignedTx => { const zero: BN = new BN(0) let ins: TransferableInput[] = [] @@ -961,7 +985,7 @@ export class UTXOSet extends StandardUTXOSet { infeeamount, toAddresses, locktime, - threshold + toThreshold ) as AmountOutput const xferout: TransferableOutput = new TransferableOutput( assetID, @@ -976,16 +1000,13 @@ export class UTXOSet extends StandardUTXOSet { if (feeRemaining.gt(zero) && this._feeCheck(feeRemaining, feeAssetID)) { const aad: AssetAmountDestination = new AssetAmountDestination( toAddresses, + toThreshold, fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) aad.addAssetAmount(feeAssetID, zero, feeRemaining) - const success: Error = this.getMinimumSpendable( - aad, - asOf, - locktime, - threshold - ) + const success: Error = this.getMinimumSpendable(aad, asOf, locktime) if (typeof success === "undefined") { ins = aad.getInputs() outs = aad.getAllOutputs() @@ -1022,7 +1043,9 @@ export class UTXOSet extends StandardUTXOSet { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * * @returns An unsigned transaction created from the passed in parameters. * */ @@ -1040,7 +1063,8 @@ export class UTXOSet extends StandardUTXOSet { memo: Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): UnsignedTx => { let ins: TransferableInput[] = [] let outs: TransferableOutput[] = [] @@ -1066,8 +1090,10 @@ export class UTXOSet extends StandardUTXOSet { const aad: AssetAmountDestination = new AssetAmountDestination( toAddresses, + toThreshold, fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) if (assetID.toString("hex") === feeAssetID.toString("hex")) { aad.addAssetAmount(assetID, amount, fee) @@ -1077,12 +1103,7 @@ export class UTXOSet extends StandardUTXOSet { aad.addAssetAmount(feeAssetID, zero, fee) } } - const success: Error = this.getMinimumSpendable( - aad, - asOf, - locktime, - threshold - ) + const success: Error = this.getMinimumSpendable(aad, asOf, locktime) if (typeof success === "undefined") { ins = aad.getInputs() outs = aad.getChangeOutputs() diff --git a/src/apis/evm/api.ts b/src/apis/evm/api.ts index 5e8b9db41..0f7b34589 100644 --- a/src/apis/evm/api.ts +++ b/src/apis/evm/api.ts @@ -638,12 +638,13 @@ export class EVMAPI extends JRPCAPI { * @param amount The amount being exported as a {@link https://github.com/indutny/bn.js/|BN} * @param assetID The asset id which is being sent * @param destinationChain The chainid for where the assets will be sent. - * @param toAddresses The addresses to send the funds - * @param fromAddresses The addresses being used to send the funds from the UTXOs provided - * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param fromAddressesHex The addresses to send the funds (hex) + * @param fromAddressesBech The addresses being used to send the funds from the UTXOs provided + * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who recieves the AVAX + * @param nonce Optional. The nonce to be used * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param fee Optional. The the fee for this transaction * * @returns An unsigned transaction ([[UnsignedTx]]) which contains an [[ExportTx]]. */ @@ -656,7 +657,7 @@ export class EVMAPI extends JRPCAPI { toAddresses: string[], nonce: number = 0, locktime: BN = new BN(0), - threshold: number = 1, + toThreshold: number = 1, fee: BN = new BN(0) ): Promise => { const prefixes: object = {} @@ -731,7 +732,7 @@ export class EVMAPI extends JRPCAPI { amount, to, locktime, - threshold + toThreshold ) const transferableOutput: TransferableOutput = new TransferableOutput( bintools.cb58Decode(assetID), diff --git a/src/apis/evm/utxos.ts b/src/apis/evm/utxos.ts index 1662b7a52..6c90c72fc 100644 --- a/src/apis/evm/utxos.ts +++ b/src/apis/evm/utxos.ts @@ -436,7 +436,8 @@ export class UTXOSet extends StandardUTXOSet { * @param feeAssetID Optional. The assetID of the fees being burned. * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changethreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * @returns An unsigned transaction created from the passed in parameters. * */ @@ -453,7 +454,8 @@ export class UTXOSet extends StandardUTXOSet { feeAssetID: Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), - threshold: number = 1 + toThreshold: number = 1, + changeThreshold: number = 1 ): UnsignedTx => { let ins: EVMInput[] = [] let exportouts: TransferableOutput[] = [] @@ -483,8 +485,10 @@ export class UTXOSet extends StandardUTXOSet { const aad: AssetAmountDestination = new AssetAmountDestination( toAddresses, + toThreshold, fromAddresses, - changeAddresses + changeAddresses, + changeThreshold ) if (avaxAssetID.toString("hex") === feeAssetID.toString("hex")) { aad.addAssetAmount(avaxAssetID, amount, fee) @@ -494,12 +498,7 @@ export class UTXOSet extends StandardUTXOSet { aad.addAssetAmount(feeAssetID, zero, fee) } } - const success: Error = this.getMinimumSpendable( - aad, - asOf, - locktime, - threshold - ) + const success: Error = this.getMinimumSpendable(aad, asOf, locktime) if (typeof success === "undefined") { exportouts = aad.getOutputs() } else { diff --git a/src/apis/platformvm/api.ts b/src/apis/platformvm/api.ts index 4daadde2a..f1b1700a3 100644 --- a/src/apis/platformvm/api.ts +++ b/src/apis/platformvm/api.ts @@ -7,7 +7,11 @@ import BN from "bn.js" import AvalancheCore from "../../camino" import { JRPCAPI } from "../../common/jrpcapi" import { RequestResponseData } from "../../common/apibase" -import { ErrorResponseObject } from "../../utils/errors" +import { + ErrorResponseObject, + ProtocolError, + UTXOError +} from "../../utils/errors" import BinTools from "../../utils/bintools" import { KeyChain } from "./keychain" import { ONEAVAX } from "../../utils/constants" @@ -63,7 +67,9 @@ import { TransferableInput } from "./inputs" import { TransferableOutput } from "./outputs" import { Serialization, SerializedType } from "../../utils" import { GenesisData } from "../avm" -import { CaminoExecutor } from "./camino_executor" +import { LockMode, Builder } from "./builder" +import { Network } from "../../utils/networks" +import { Spender } from "./spender" /** * @ignore @@ -71,6 +77,7 @@ import { CaminoExecutor } from "./camino_executor" const bintools: BinTools = BinTools.getInstance() const serialization: Serialization = Serialization.getInstance() +const ZeroBN: BN = new BN(0) const NanoBN = new BN(1000000000) const rewardPercentDenom = 1000000 @@ -110,6 +117,15 @@ export class PlatformVMAPI extends JRPCAPI { return this.core.getNetwork().P.alias } + /** + * Gets the current network, fetched via avalanche.fetchNetworkSettings. + * + * @returns The current Network + */ + getNetwork = (): Network => { + return this.core.getNetwork() + } + /** * Gets the blockchainID and returns it. * @@ -287,10 +303,10 @@ export class PlatformVMAPI extends JRPCAPI { */ checkGooseEgg = async ( utx: UnsignedTx, - outTotal: BN = new BN(0) + outTotal: BN = ZeroBN ): Promise => { const avaxAssetID: Buffer = await this.getAVAXAssetID() - let outputTotal: BN = outTotal.gt(new BN(0)) + let outputTotal: BN = outTotal.gt(ZeroBN) ? outTotal : utx.getOutputTotal(avaxAssetID) const fee: BN = utx.getBurn(avaxAssetID) @@ -1259,7 +1275,8 @@ export class PlatformVMAPI extends JRPCAPI { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[ImportTx]]. * @@ -1274,9 +1291,10 @@ export class PlatformVMAPI extends JRPCAPI { fromAddresses: string[], changeAddresses: string[] = undefined, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow(), - locktime: BN = new BN(0), - threshold: number = 1 + asOf: BN = ZeroBN, + locktime: BN = ZeroBN, + toThreshold: number = 1, + changeThreshold: number = 1 ): Promise => { const to: Buffer[] = this._cleanAddressArray( toAddresses, @@ -1317,7 +1335,9 @@ export class PlatformVMAPI extends JRPCAPI { const atomics: UTXO[] = atomicUTXOs.getAllUTXOs() - const builtUnsignedTx: UnsignedTx = utxoset.buildImportTx( + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildImportTx( this.core.getNetworkID(), bintools.cb58Decode(this.blockchainID), to, @@ -1330,7 +1350,8 @@ export class PlatformVMAPI extends JRPCAPI { memo, asOf, locktime, - threshold + toThreshold, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1354,7 +1375,8 @@ export class PlatformVMAPI extends JRPCAPI { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains an [[ExportTx]]. */ @@ -1366,9 +1388,10 @@ export class PlatformVMAPI extends JRPCAPI { fromAddresses: string[], changeAddresses: string[] = undefined, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow(), - locktime: BN = new BN(0), - threshold: number = 1 + asOf: BN = ZeroBN, + locktime: BN = ZeroBN, + toThreshold: number = 1, + changeThreshold: number = 1 ): Promise => { let prefixes: object = {} toAddresses.map((a: string): void => { @@ -1417,21 +1440,24 @@ export class PlatformVMAPI extends JRPCAPI { const avaxAssetID: Buffer = await this.getAVAXAssetID() - const builtUnsignedTx: UnsignedTx = utxoset.buildExportTx( + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildExportTx( this.core.getNetworkID(), bintools.cb58Decode(this.blockchainID), amount, avaxAssetID, to, from, - change, destinationChain, + change, this.getTxFee(), avaxAssetID, memo, asOf, locktime, - threshold + toThreshold, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1456,6 +1482,7 @@ export class PlatformVMAPI extends JRPCAPI { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param subnetAuthCredentials Optional. An array of index and address to sign for each SubnetAuth. + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. */ @@ -1470,8 +1497,9 @@ export class PlatformVMAPI extends JRPCAPI { weight: BN, subnetID: string, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow(), - subnetAuthCredentials: [number, Buffer][] = [] + asOf: BN = ZeroBN, + subnetAuthCredentials: [number, Buffer][] = [], + changeThreshold: number = 1 ): Promise => { const from: Buffer[] = this._cleanAddressArray( fromAddresses, @@ -1495,7 +1523,9 @@ export class PlatformVMAPI extends JRPCAPI { ) } - const builtUnsignedTx: UnsignedTx = utxoset.buildAddSubnetValidatorTx( + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildAddSubnetValidatorTx( this.core.getNetworkID(), bintools.cb58Decode(this.blockchainID), from, @@ -1509,7 +1539,8 @@ export class PlatformVMAPI extends JRPCAPI { avaxAssetID, memo, asOf, - subnetAuthCredentials + subnetAuthCredentials, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1537,6 +1568,8 @@ export class PlatformVMAPI extends JRPCAPI { * @param rewardThreshold Opional. The number of signatures required to spend the funds in the resultant reward UTXO. Default 1. * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. */ @@ -1550,10 +1583,12 @@ export class PlatformVMAPI extends JRPCAPI { endTime: BN, stakeAmount: BN, rewardAddresses: string[], - rewardLocktime: BN = new BN(0), + rewardLocktime: BN = ZeroBN, rewardThreshold: number = 1, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = ZeroBN, + toThreshold: number = 1, + changeThreshold: number = 1 ): Promise => { const to: Buffer[] = this._cleanAddressArray( toAddresses, @@ -1593,7 +1628,15 @@ export class PlatformVMAPI extends JRPCAPI { ) } - const builtUnsignedTx: UnsignedTx = utxoset.buildAddDelegatorTx( + if (this.core.getNetwork().P.lockModeBondDeposit) { + throw new UTXOError( + "PlatformVMAPI.buildAddDelegatorTx -- not supported in lockmodeBondDeposit" + ) + } + + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildAddDelegatorTx( this.core.getNetworkID(), bintools.cb58Decode(this.blockchainID), avaxAssetID, @@ -1607,10 +1650,12 @@ export class PlatformVMAPI extends JRPCAPI { rewardLocktime, rewardThreshold, rewards, - new BN(0), + ZeroBN, avaxAssetID, memo, - asOf + asOf, + toThreshold, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { @@ -1639,6 +1684,8 @@ export class PlatformVMAPI extends JRPCAPI { * @param rewardThreshold Opional. The number of signatures required to spend the funds in the resultant reward UTXO. Default 1. * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. */ @@ -1653,10 +1700,12 @@ export class PlatformVMAPI extends JRPCAPI { stakeAmount: BN, rewardAddresses: string[], delegationFee: number, - rewardLocktime: BN = new BN(0), + rewardLocktime: BN = ZeroBN, rewardThreshold: number = 1, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = ZeroBN, + toThreshold: number = 1, + changeThreshold: number = 1 ): Promise => { const to: Buffer[] = this._cleanAddressArray( toAddresses, @@ -1705,49 +1754,31 @@ export class PlatformVMAPI extends JRPCAPI { "PlatformVMAPI.buildAddValidatorTx -- startTime must be in the future and endTime must come after startTime" ) } - var builtUnsignedTx: UnsignedTx - if (this.core.getNetwork().P.lockModeBondDeposit) { - const executor = new CaminoExecutor(this) - - builtUnsignedTx = await executor.buildCaminoAddValidatorTx( - this.core.getNetworkID(), - bintools.cb58Decode(this.blockchainID), - to, - from, - change, - NodeIDStringToBuffer(nodeID), - startTime, - endTime, - stakeAmount, - rewards, - rewardLocktime, - rewardThreshold, - memo, - avaxAssetID - ) - } else { - builtUnsignedTx = utxoset.buildAddValidatorTx( - this.core.getNetworkID(), - bintools.cb58Decode(this.blockchainID), - avaxAssetID, - to, - from, - change, - NodeIDStringToBuffer(nodeID), - startTime, - endTime, - stakeAmount, - rewardLocktime, - rewardThreshold, - rewards, - delegationFee, - new BN(0), - avaxAssetID, - memo, - asOf - ) - } + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildAddValidatorTx( + this.core.getNetworkID(), + bintools.cb58Decode(this.blockchainID), + to, + from, + change, + NodeIDStringToBuffer(nodeID), + startTime, + endTime, + stakeAmount, + avaxAssetID, + rewardLocktime, + rewardThreshold, + rewards, + delegationFee, + ZeroBN, + avaxAssetID, + memo, + asOf, + toThreshold, + changeThreshold + ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ @@ -1767,6 +1798,7 @@ export class PlatformVMAPI extends JRPCAPI { * @param subnetOwnerThreshold A number indicating the amount of signatures required to add validators to a subnet * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. */ @@ -1777,7 +1809,8 @@ export class PlatformVMAPI extends JRPCAPI { subnetOwnerAddresses: string[], subnetOwnerThreshold: number, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow() + asOf: BN = ZeroBN, + changeThreshold: number = 1 ): Promise => { const from: Buffer[] = this._cleanAddressArray( fromAddresses, @@ -1800,7 +1833,10 @@ export class PlatformVMAPI extends JRPCAPI { const networkID: number = this.core.getNetworkID() const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const fee: BN = this.getCreateSubnetTxFee() - const builtUnsignedTx: UnsignedTx = utxoset.buildCreateSubnetTx( + + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildCreateSubnetTx( networkID, blockchainID, from, @@ -1810,7 +1846,8 @@ export class PlatformVMAPI extends JRPCAPI { fee, avaxAssetID, memo, - asOf + asOf, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx, this.getCreationTxFee()))) { @@ -1835,6 +1872,7 @@ export class PlatformVMAPI extends JRPCAPI { * @param memo Optional contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param subnetAuthCredentials Optional. An array of index and address to sign for each SubnetAuth. + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. */ @@ -1848,8 +1886,9 @@ export class PlatformVMAPI extends JRPCAPI { fxIDs: string[] = undefined, genesisData: string | GenesisData = undefined, memo: PayloadBase | Buffer = undefined, - asOf: BN = UnixNow(), - subnetAuthCredentials: [number, Buffer][] = [] + asOf: BN = ZeroBN, + subnetAuthCredentials: [number, Buffer][] = [], + changeThreshold: number = 1 ): Promise => { const from: Buffer[] = this._cleanAddressArray( fromAddresses, @@ -1870,7 +1909,10 @@ export class PlatformVMAPI extends JRPCAPI { const networkID: number = this.core.getNetworkID() const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const fee: BN = this.getCreateChainTxFee() - const builtUnsignedTx: UnsignedTx = utxoset.buildCreateChainTx( + + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildCreateChainTx( networkID, blockchainID, from, @@ -1884,7 +1926,8 @@ export class PlatformVMAPI extends JRPCAPI { avaxAssetID, memo, asOf, - subnetAuthCredentials + subnetAuthCredentials, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx, this.getCreationTxFee()))) { @@ -1898,6 +1941,7 @@ export class PlatformVMAPI extends JRPCAPI { /** * Build an unsigned [[RegisterNodeTx]]. * + * @param utxoset A set of UTXOs that the transaction is built on * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param oldNodeID Optional. ID of the existing NodeID to replace or remove. @@ -1905,17 +1949,22 @@ export class PlatformVMAPI extends JRPCAPI { * @param address The consortiumMemberAddress, single or multi-sig. * @param consortiumMemberAuthCredentials An array of index and address to sign for each SubnetAuth. * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO * * @returns An unsigned transaction created from the passed in parameters. */ buildRegisterNodeTx = async ( + utxoset: UTXOSet, fromAddresses: string[], - changeAddresses: string[], + changeAddresses: string[] = undefined, oldNodeID: string | Buffer = undefined, newNodeID: string | Buffer = undefined, address: Buffer = undefined, consortiumMemberAuthCredentials: [number, Buffer][] = [], - memo: PayloadBase | Buffer = undefined + memo: PayloadBase | Buffer = undefined, + asOf: BN = ZeroBN, + changeThreshold: number = 1 ): Promise => { const from: Buffer[] = this._cleanAddressArray( fromAddresses, @@ -1935,9 +1984,9 @@ export class PlatformVMAPI extends JRPCAPI { const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const fee: BN = this.getTxFee() - const executor = new CaminoExecutor(this) - - const builtUnsignedTx: UnsignedTx = await executor.buildRegisterNodeTx( + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildRegisterNodeTx( networkID, blockchainID, from, @@ -1948,7 +1997,9 @@ export class PlatformVMAPI extends JRPCAPI { consortiumMemberAuthCredentials, fee, avaxAssetID, - memo + memo, + asOf, + changeThreshold ) if (!(await this.checkGooseEgg(builtUnsignedTx, this.getCreationTxFee()))) { @@ -2077,18 +2128,34 @@ export class PlatformVMAPI extends JRPCAPI { */ spend = async ( from: string[] | string, - changeAddr: string, - lockMode: "Unlocked" | "Deposit" | "Bond", + to: string[], + toThreshold: number, + toLockTime: BN, + change: string[], + changeThreshold: number, + lockMode: LockMode, amountToLock: BN, amountToBurn: BN, + asOf: BN, encoding?: string ): Promise => { + if (!["Unlocked", "Deposit", "Bond"].includes(lockMode)) { + throw new ProtocolError("Error -- PlatformAPI.spend: invalid lockMode") + } const params: SpendParams = { from, - changeAddr, + to: + to.length > 0 + ? { locktime: toLockTime, threshold: toThreshold, addresses: to } + : undefined, + change: + change.length > 0 + ? { locktime: ZeroBN, threshold: changeThreshold, addresses: change } + : undefined, lockMode: lockMode === "Unlocked" ? 0 : lockMode === "Deposit" ? 1 : 2, - amountToLock: amountToLock.toString(), - amountToBurn: amountToBurn.toString(), + amountToLock: amountToLock, + amountToBurn: amountToBurn, + asOf: asOf, encoding: encoding ?? "hex" } @@ -2111,4 +2178,11 @@ export class PlatformVMAPI extends JRPCAPI { out: TransferableOutput.fromArray(Buffer.from(r.outs.slice(2), "hex")) } } + + _getBuilder = (utxoSet: UTXOSet): Builder => { + if (this.core.getNetwork().P.lockModeBondDeposit) { + return new Builder(new Spender(this), true) + } + return new Builder(utxoSet, false) + } } diff --git a/src/apis/platformvm/builder.ts b/src/apis/platformvm/builder.ts new file mode 100644 index 000000000..4c8dd0f23 --- /dev/null +++ b/src/apis/platformvm/builder.ts @@ -0,0 +1,1128 @@ +/** + * @packageDocumentation + * @module API-PlatformVM-Builder + */ + +import BN from "bn.js" + +import { Buffer } from "buffer/" +import { DefaultNetworkID, UnixNow } from "../../utils" +import { + AddressError, + FeeAssetError, + ProtocolError, + ThresholdError, + TimeError +} from "../../utils/errors" +import { + AddDelegatorTx, + AddSubnetValidatorTx, + AddValidatorTx, + AmountOutput, + AssetAmountDestination, + BaseTx, + CaminoAddValidatorTx, + CreateChainTx, + CreateSubnetTx, + ExportTx, + ImportTx, + ParseableOutput, + RegisterNodeTx, + SECPOwnerOutput, + SECPTransferInput, + SECPTransferOutput, + SelectOutputClass, + TransferableInput, + TransferableOutput, + UnsignedTx, + UTXO +} from "." +import { GenesisData } from "../avm" + +export type LockMode = "Unlocked" | "Bond" | "Deposit" | "Stake" + +export interface MinimumSpendable { + getMinimumSpendable( + aad: AssetAmountDestination, + asOf: BN, + locktime: BN, + lockMode: LockMode + ): Promise +} + +const zero: BN = new BN(0) + +export class Builder { + spender: MinimumSpendable + caminoEnabled: boolean + + constructor(spender: MinimumSpendable, caminoEnabled: boolean) { + this.spender = spender + this.caminoEnabled = caminoEnabled + } + + /** + * Creates an [[UnsignedTx]] wrapping a [[BaseTx]]. For more granular control, you may create your own + * [[UnsignedTx]] wrapping a [[BaseTx]] manually (with their corresponding [[TransferableInput]]s and [[TransferableOutput]]s). + * + * @param networkID The number representing NetworkID of the node + * @param blockchainID The {@link https://github.com/feross/buffer|Buffer} representing the BlockchainID for the transaction + * @param amount The amount of the asset to be spent in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN}. + * @param assetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for the UTXO + * @param toAddresses The addresses to send the funds + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses Optional. The addresses that can spend the change remaining from the spent UTXOs. Default: toAddresses + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned. Default: assetID + * @param memo Optional. Contains arbitrary data, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param locktime Optional. The locktime field created in the resulting outputs + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + * + */ + buildBaseTx = async ( + networkID: number, + blockchainID: Buffer, + amount: BN, + amountAssetID: Buffer, + toAddresses: Buffer[], + fromAddresses: Buffer[], + changeAddresses: Buffer[] = undefined, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + lockTime: BN = zero, + toThreshold: number = 1, + changeThreshold: number = 1 + ): Promise => { + if (toThreshold > toAddresses.length) { + /* istanbul ignore next */ + throw new ThresholdError( + "Error - UTXOSet.buildBaseTx: threshold is greater than number of addresses" + ) + } + + if (typeof changeAddresses === "undefined") { + changeAddresses = [] + } + + if (typeof feeAssetID === "undefined") { + feeAssetID = amountAssetID + } + + if (amount.eq(zero)) { + return undefined + } + + const aad: AssetAmountDestination = new AssetAmountDestination( + toAddresses, + toThreshold, + fromAddresses, + changeAddresses, + changeThreshold + ) + if (amountAssetID.toString("hex") === feeAssetID.toString("hex")) { + aad.addAssetAmount(amountAssetID, amount, fee) + } else { + aad.addAssetAmount(amountAssetID, amount, zero) + if (this._feeCheck(fee, feeAssetID)) { + aad.addAssetAmount(feeAssetID, zero, fee) + } + } + + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + lockTime, + "Unlocked" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + + const baseTx: BaseTx = new BaseTx(networkID, blockchainID, outs, ins, memo) + return new UnsignedTx(baseTx) + } + + /** + * Creates an unsigned ImportTx transaction. + * + * @param networkID The number representing NetworkID of the node + * @param blockchainID The {@link https://github.com/feross/buffer|Buffer} representing the BlockchainID for the transaction + * @param toAddresses The addresses to send the funds + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses Optional. The addresses that can spend the change remaining from the spent UTXOs. Default: toAddresses + * @param importIns An array of [[TransferableInput]]s being imported + * @param sourceChain A {@link https://github.com/feross/buffer|Buffer} for the chainid where the imports are coming from. + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN}. Fee will come from the inputs first, if they can. + * @param feeAssetID Optional. The assetID of the fees being burned. + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param locktime Optional. The locktime field created in the resulting outputs + * @param toThreshold Optional. The number of signatures required to spend the funds in the received UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + * + */ + buildImportTx = async ( + networkID: number, + blockchainID: Buffer, + toAddresses: Buffer[], + fromAddresses: Buffer[], + changeAddresses: Buffer[], + atomics: UTXO[], + sourceChain: Buffer = undefined, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + locktime: BN = zero, + toThreshold: number = 1, + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + if (typeof fee === "undefined") { + fee = zero.clone() + } + + const importIns: TransferableInput[] = [] + let feepaid: BN = new BN(0) + let feeAssetStr: string = feeAssetID.toString("hex") + for (let i: number = 0; i < atomics.length; i++) { + const utxo: UTXO = atomics[`${i}`] + const assetID: Buffer = utxo.getAssetID() + const output: AmountOutput = utxo.getOutput() as AmountOutput + let amt: BN = output.getAmount().clone() + + let infeeamount = amt.clone() + let assetStr: string = assetID.toString("hex") + if ( + typeof feeAssetID !== "undefined" && + fee.gt(zero) && + feepaid.lt(fee) && + assetStr === feeAssetStr + ) { + feepaid = feepaid.add(infeeamount) + if (feepaid.gte(fee)) { + infeeamount = feepaid.sub(fee) + feepaid = fee.clone() + } else { + infeeamount = zero.clone() + } + } + + const txid: Buffer = utxo.getTxID() + const outputidx: Buffer = utxo.getOutputIdx() + const input: SECPTransferInput = new SECPTransferInput(amt) + const xferin: TransferableInput = new TransferableInput( + txid, + outputidx, + assetID, + input + ) + const from: Buffer[] = output.getAddresses() + const spenders: Buffer[] = output.getSpenders(from) + for (let j: number = 0; j < spenders.length; j++) { + const idx: number = output.getAddressIdx(spenders[`${j}`]) + if (idx === -1) { + /* istanbul ignore next */ + throw new AddressError( + "Error - UTXOSet.buildImportTx: no such " + + `address in output: ${spenders[`${j}`]}` + ) + } + xferin.getInput().addSignatureIdx(idx, spenders[`${j}`]) + } + importIns.push(xferin) + //add extra outputs for each amount (calculated from the imported inputs), minus fees + if (infeeamount.gt(zero)) { + const spendout: AmountOutput = SelectOutputClass( + output.getOutputID(), + infeeamount, + toAddresses, + locktime, + toThreshold + ) as AmountOutput + const xferout: TransferableOutput = new TransferableOutput( + assetID, + spendout + ) + outs.push(xferout) + } + } + + // get remaining fees from the provided addresses + let feeRemaining: BN = fee.sub(feepaid) + if (feeRemaining.gt(zero) && this._feeCheck(feeRemaining, feeAssetID)) { + const aad: AssetAmountDestination = new AssetAmountDestination( + toAddresses, + toThreshold, + fromAddresses, + changeAddresses, + changeThreshold + ) + + aad.addAssetAmount(feeAssetID, zero, feeRemaining) + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + locktime, + "Unlocked" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + } + + const importTx: ImportTx = new ImportTx( + networkID, + blockchainID, + outs, + ins, + memo, + sourceChain, + importIns + ) + return new UnsignedTx(importTx) + } + + /** + * Creates an unsigned ExportTx transaction. + * + * @param networkID The number representing NetworkID of the node + * @param blockchainID The {@link https://github.com/feross/buffer|Buffer} representing the BlockchainID for the transaction + * @param amount The amount being exported as a {@link https://github.com/indutny/bn.js/|BN} + * @param avaxAssetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for AVAX + * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who recieves the AVAX + * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who owns the AVAX + * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover of the AVAX + * @param destinationChain Optional. A {@link https://github.com/feross/buffer|Buffer} for the chainid where to send the asset. + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned. + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param locktime Optional. The locktime field created in the resulting outputs + * @param toThreshold Optional. The number of signatures required to spend the funds in the received UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + * + */ + buildExportTx = async ( + networkID: number, + blockchainID: Buffer, + amount: BN, + amountAssetID: Buffer, + toAddresses: Buffer[], + fromAddresses: Buffer[], + destinationChain: Buffer, + changeAddresses: Buffer[] = undefined, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + locktime: BN = zero, + toThreshold: number = 1, + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + if (typeof changeAddresses === "undefined") { + changeAddresses = toAddresses + } + + if (amount.eq(zero)) { + return undefined + } + + if (typeof feeAssetID === "undefined") { + feeAssetID = amountAssetID + } else if (feeAssetID.toString("hex") !== amountAssetID.toString("hex")) { + /* istanbul ignore next */ + throw new FeeAssetError( + "Error - CaminoExecutor.buildExportTx: " + + `feeAssetID must match avaxAssetID` + ) + } + + const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, + fromAddresses, + changeAddresses, + changeThreshold + ) + + if (amountAssetID.toString("hex") === feeAssetID.toString("hex")) { + aad.addAssetAmount(amountAssetID, zero, fee.add(amount)) + } else { + aad.addAssetAmount(amountAssetID, amount, zero) + if (this._feeCheck(fee, feeAssetID)) { + aad.addAssetAmount(feeAssetID, zero, fee) + } + } + + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + locktime, + "Unlocked" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getChangeOutputs() + exports = aad.getOutputs() + } else { + throw minSpendableErr + } + + const exportTx: ExportTx = new ExportTx( + networkID, + blockchainID, + outs, + ins, + memo, + destinationChain, + exports.length > 0 + ? exports + : [ + new SECPTransferOutput( + amount, + toAddresses, + locktime, + toThreshold + ).makeTransferable(amountAssetID) + ] + ) + + return new UnsignedTx(exportTx) + } + + /** + * Class representing an unsigned [[AddSubnetValidatorTx]] transaction. + * + * @param networkID Networkid, [[DefaultNetworkID]] + * @param blockchainID Blockchainid, default undefined + * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who pays the fees in AVAX + * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the fee payment + * @param nodeID The node ID of the validator being added. + * @param startTime The Unix time when the validator starts validating the Primary Network. + * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). + * @param weight The amount of weight for this subnet validator. + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned. + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param subnetAuthCredentials Optional. An array of index and address to sign for each SubnetAuth. + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + */ + buildAddSubnetValidatorTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + fromAddresses: Buffer[], + changeAddresses: Buffer[], + nodeID: Buffer, + startTime: BN, + endTime: BN, + weight: BN, + subnetID: string, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + subnetAuthCredentials: [number, Buffer][] = [], + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + const now: BN = UnixNow() + if (startTime.lt(now) || endTime.lte(startTime)) { + throw new Error( + "CaminoExecutor.buildAddSubnetValidatorTx -- startTime must be in the future and endTime must come after startTime" + ) + } + + if (this._feeCheck(fee, feeAssetID)) { + const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, + fromAddresses, + changeAddresses, + changeThreshold + ) + + aad.addAssetAmount(feeAssetID, zero, fee) + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Stake" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + } + + const addSubnetValidatorTx: AddSubnetValidatorTx = new AddSubnetValidatorTx( + networkID, + blockchainID, + outs, + ins, + memo, + nodeID, + startTime, + endTime, + weight, + subnetID + ) + subnetAuthCredentials.forEach( + (subnetAuthCredential: [number, Buffer]): void => { + addSubnetValidatorTx.addSignatureIdx( + subnetAuthCredential[0], + subnetAuthCredential[1] + ) + } + ) + return new UnsignedTx(addSubnetValidatorTx) + } + + /** + * Class representing an unsigned [[AddDelegatorTx]] transaction. + * + * @param networkID Networkid, [[DefaultNetworkID]] + * @param blockchainID Blockchainid, default undefined + * @param avaxAssetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for AVAX + * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} recieves the stake at the end of the staking period + * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who pays the fees and the stake + * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the staking payment + * @param nodeID The node ID of the validator being added. + * @param startTime The Unix time when the validator starts validating the Primary Network. + * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). + * @param stakeAmount A {@link https://github.com/indutny/bn.js/|BN} for the amount of stake to be delegated in nAVAX. + * @param rewardLocktime The locktime field created in the resulting reward outputs + * @param rewardThreshold The number of signatures required to spend the funds in the resultant reward UTXO + * @param rewardAddresses The addresses the validator reward goes. + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned. + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param toThreshold Optional. The number of signatures required to spend the funds in the stake UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + */ + buildAddDelegatorTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + avaxAssetID: Buffer, + toAddresses: Buffer[], + fromAddresses: Buffer[], + changeAddresses: Buffer[], + nodeID: Buffer, + startTime: BN, + endTime: BN, + stakeAmount: BN, + rewardLocktime: BN, + rewardThreshold: number, + rewardAddresses: Buffer[], + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + toThreshold: number = 1, + changeThreshold: number = 1 + ): Promise => { + if (this.caminoEnabled) { + throw new ProtocolError( + "buildAddDelegatorTx - Not supported in Camino mode" + ) + } + + if (rewardThreshold > rewardAddresses.length) { + /* istanbul ignore next */ + throw new ThresholdError( + "Error - UTXOSet.buildAddDelegatorTx: reward threshold is greater than number of addresses" + ) + } + + if (typeof changeAddresses === "undefined") { + changeAddresses = toAddresses + } + + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + let stakeOuts: TransferableOutput[] = [] + + const now: BN = UnixNow() + if (startTime.lt(now) || endTime.lte(startTime)) { + throw new TimeError( + "UTXOSet.buildAddDelegatorTx -- startTime must be in the future and endTime must come after startTime" + ) + } + + const aad: AssetAmountDestination = new AssetAmountDestination( + toAddresses, + toThreshold, + fromAddresses, + changeAddresses, + changeThreshold + ) + if (avaxAssetID.toString("hex") === feeAssetID.toString("hex")) { + aad.addAssetAmount(avaxAssetID, stakeAmount, fee) + } else { + aad.addAssetAmount(avaxAssetID, stakeAmount, zero) + if (this._feeCheck(fee, feeAssetID)) { + aad.addAssetAmount(feeAssetID, zero, fee) + } + } + + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Stake" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getChangeOutputs() + stakeOuts = aad.getOutputs() + } else { + throw minSpendableErr + } + + const rewardOutputOwners: SECPOwnerOutput = new SECPOwnerOutput( + rewardAddresses, + rewardLocktime, + rewardThreshold + ) + + const UTx: AddDelegatorTx = new AddDelegatorTx( + networkID, + blockchainID, + outs, + ins, + memo, + nodeID, + startTime, + endTime, + stakeAmount, + stakeOuts, + new ParseableOutput(rewardOutputOwners) + ) + return new UnsignedTx(UTx) + } + + /** + * Class representing an unsigned [[AddValidatorTx]] transaction. + * + * @param networkID NetworkID, [[DefaultNetworkID]] + * @param blockchainID BlockchainID, default undefined + * @param avaxAssetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for AVAX + * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} recieves the stake at the end of the staking period + * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who pays the fees and the stake + * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the staking payment + * @param nodeID The node ID of the validator being added. + * @param startTime The Unix time when the validator starts validating the Primary Network. + * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). + * @param stakeAmount A {@link https://github.com/indutny/bn.js/|BN} for the amount of stake to be delegated in nAVAX. + * @param rewardLocktime The locktime field created in the resulting reward outputs + * @param rewardThreshold The number of signatures required to spend the funds in the resultant reward UTXO + * @param rewardAddresses The addresses the validator reward goes. + * @param delegationFee A number for the percentage of reward to be given to the validator when someone delegates to them. Must be between 0 and 100. + * @param minStake A {@link https://github.com/indutny/bn.js/|BN} representing the minimum stake required to validate on this network. + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned. + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param toThreshold Optional. The number of signatures required to spend the funds in the stake UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the change UTXO + */ + buildAddValidatorTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + toAddresses: Buffer[], + fromAddresses: Buffer[], + changeAddresses: Buffer[], + nodeID: Buffer, + startTime: BN, + endTime: BN, + stakeAmount: BN, + stakeAssetID: Buffer, + rewardLocktime: BN, + rewardThreshold: number, + rewardAddresses: Buffer[], + delegationFee: number, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = UnixNow(), + toThreshold: number = 1, + changeThreshold: number = 1 + ): Promise => { + if (this.caminoEnabled) { + return this.buildCaminoAddValidatorTx( + networkID, + blockchainID, + toAddresses, + fromAddresses, + changeAddresses, + nodeID, + startTime, + endTime, + stakeAmount, + stakeAssetID, + rewardAddresses, + rewardLocktime, + rewardThreshold, + memo, + asOf, + toThreshold, + changeThreshold + ) + } + + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + let stakeOuts: TransferableOutput[] = [] + + const now: BN = UnixNow() + if (startTime.lt(now) || endTime.lte(startTime)) { + throw new TimeError( + "UTXOSet.buildAddValidatorTx -- startTime must be in the future and endTime must come after startTime" + ) + } + + if (delegationFee > 100 || delegationFee < 0) { + throw new TimeError( + "UTXOSet.buildAddValidatorTx -- startTime must be in the range of 0 to 100, inclusively" + ) + } + + const aad: AssetAmountDestination = new AssetAmountDestination( + toAddresses, + toThreshold, + fromAddresses, + changeAddresses, + changeThreshold + ) + if (stakeAssetID.toString("hex") === feeAssetID.toString("hex")) { + aad.addAssetAmount(stakeAssetID, stakeAmount, fee) + } else { + aad.addAssetAmount(stakeAssetID, stakeAmount, zero) + if (this._feeCheck(fee, feeAssetID)) { + aad.addAssetAmount(feeAssetID, zero, fee) + } + } + + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Stake" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getChangeOutputs() + stakeOuts = aad.getOutputs() + } else { + throw minSpendableErr + } + + const rewardOutputOwners: SECPOwnerOutput = new SECPOwnerOutput( + rewardAddresses, + rewardLocktime, + rewardThreshold + ) + + const UTx: AddValidatorTx = new AddValidatorTx( + networkID, + blockchainID, + outs, + ins, + memo, + nodeID, + startTime, + endTime, + stakeAmount, + stakeOuts, + new ParseableOutput(rewardOutputOwners), + delegationFee + ) + return new UnsignedTx(UTx) + } + + /** + * Class representing an unsigned [[CreateSubnetTx]] transaction. + * + * @param networkID Networkid, [[DefaultNetworkID]] + * @param blockchainID Blockchainid, default undefined + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. + * @param subnetOwnerAddresses An array of {@link https://github.com/feross/buffer|Buffer} for the addresses to add to a subnet + * @param subnetOwnerThreshold The number of owners's signatures required to add a validator to the network + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + */ + buildCreateSubnetTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + fromAddresses: Buffer[], + changeAddresses: Buffer[], + subnetOwnerAddresses: Buffer[], + subnetOwnerThreshold: number, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + if (this._feeCheck(fee, feeAssetID)) { + const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, + fromAddresses, + changeAddresses, + changeThreshold + ) + + aad.addAssetAmount(feeAssetID, zero, fee) + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Unlocked" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + } + + const locktime: BN = new BN(0) + const subnetOwners: SECPOwnerOutput = new SECPOwnerOutput( + subnetOwnerAddresses, + locktime, + subnetOwnerThreshold + ) + const createSubnetTx: CreateSubnetTx = new CreateSubnetTx( + networkID, + blockchainID, + outs, + ins, + memo, + subnetOwners + ) + + return new UnsignedTx(createSubnetTx) + } + + /** + * Build an unsigned [[CreateChainTx]]. + * + * @param networkID Networkid, [[DefaultNetworkID]] + * @param blockchainID Blockchainid, default undefined + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. + * @param subnetID Optional ID of the Subnet that validates this blockchain + * @param chainName Optional A human readable name for the chain; need not be unique + * @param vmID Optional ID of the VM running on the new chain + * @param fxIDs Optional IDs of the feature extensions running on the new chain + * @param genesisData Optional Byte representation of genesis state of the new chain + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param subnetAuthCredentials Optional. An array of index and address to sign for each SubnetAuth. + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned CreateChainTx created from the passed in parameters. + */ + buildCreateChainTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + fromAddresses: Buffer[], + changeAddresses: Buffer[], + subnetID: string | Buffer = undefined, + chainName: string = undefined, + vmID: string = undefined, + fxIDs: string[] = undefined, + genesisData: string | GenesisData = undefined, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + subnetAuthCredentials: [number, Buffer][] = [], + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + if (this._feeCheck(fee, feeAssetID)) { + const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, + fromAddresses, + changeAddresses, + changeThreshold + ) + + aad.addAssetAmount(feeAssetID, zero, fee) + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Unlocked" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + } + + const createChainTx: CreateChainTx = new CreateChainTx( + networkID, + blockchainID, + outs, + ins, + memo, + subnetID, + chainName, + vmID, + fxIDs, + genesisData + ) + subnetAuthCredentials.forEach( + (subnetAuthCredential: [number, Buffer]): void => { + createChainTx.addSignatureIdx( + subnetAuthCredential[0], + subnetAuthCredential[1] + ) + } + ) + + return new UnsignedTx(createChainTx) + } + + /** + * Helper function which creates an unsigned [[CaminoAddValidatorTx]]. For more granular control, you may create your own + * [[UnsignedTx]] manually and import the [[CaminoAddValidatorTx]] class directly. + * + * @param networkID Networkid, [[DefaultNetworkID]] + * @param blockchainID Blockchainid, default undefined + * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who received the staked tokens at the end of the staking period + * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who own the staking UTXOs the fees in AVAX + * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the fee payment + * @param nodeID The node ID of the validator being added. + * @param startTime The Unix time when the validator starts validating the Primary Network. + * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). + * @param stakeAmount The amount being delegated as a {@link https://github.com/indutny/bn.js/|BN} + * @param rewardAddresses The addresses which will recieve the rewards from the delegated stake. + * @param rewardLocktime Optional. The locktime field created in the resulting reward outputs + * @param rewardThreshold Opional. The number of signatures required to spend the funds in the resultant reward UTXO. Default 1. + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param toThreshold Optional. The number of signatures required to spend the funds in the received UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + */ + buildCaminoAddValidatorTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + to: Buffer[], + from: Buffer[], + change: Buffer[], + nodeID: Buffer, + startTime: BN, + endTime: BN, + stakeAmount: BN, + stakeAssetID: Buffer, + rewards: Buffer[], + rewardLocktime: BN = zero, + rewardThreshold: number = 1, + memo: Buffer = undefined, + asOf: BN = zero, + toThreshold: number = 1, + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + const now: BN = UnixNow() + if (startTime.lt(now) || endTime.lte(startTime)) { + throw new TimeError( + "buildCaminoAddValidatorTx -- startTime must be in the future and endTime must come after startTime" + ) + } + + const aad: AssetAmountDestination = new AssetAmountDestination( + to, + toThreshold, + from, + change, + changeThreshold + ) + + aad.addAssetAmount(stakeAssetID, stakeAmount, new BN(0)) + + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Bond" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + + const rewardOutputOwners: SECPOwnerOutput = new SECPOwnerOutput( + rewards, + rewardLocktime, + rewardThreshold + ) + + const tx: CaminoAddValidatorTx = new CaminoAddValidatorTx( + networkID, + blockchainID, + outs, + ins, + memo, + nodeID, + startTime, + endTime, + stakeAmount, + new ParseableOutput(rewardOutputOwners) + ) + + return new UnsignedTx(tx) + } + + /** + * Build an unsigned [[RegisterNodeTx]]. + * + * @param networkID Networkid, [[DefaultNetworkID]] + * @param blockchainID Blockchainid, default undefined + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. + * @param oldNodeID Optional. ID of the existing NodeID to replace or remove. + * @param newNodeID Optional. ID of the newNodID to register address. + * @param address The consortiumMemberAddress, single or multi-sig. + * @param consortiumMemberAuthCredentials An array of index and address to sign for each SubnetAuth. + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned RegisterNodeTx created from the passed in parameters. + */ + buildRegisterNodeTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + fromAddresses: Buffer[], + changeAddresses: Buffer[], + oldNodeID: string | Buffer = undefined, + newNodeID: string | Buffer = undefined, + address: Buffer = undefined, + consortiumMemberAuthCredentials: [number, Buffer][] = [], + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + if (this._feeCheck(fee, feeAssetID)) { + const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, + fromAddresses, + changeAddresses, + changeThreshold + ) + + aad.addAssetAmount(feeAssetID, zero, fee) + + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Unlocked" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + } + + const registerNodeTx: RegisterNodeTx = new RegisterNodeTx( + networkID, + blockchainID, + outs, + ins, + memo, + oldNodeID, + newNodeID, + address + ) + consortiumMemberAuthCredentials.forEach( + (subnetAuthCredential: [number, Buffer]): void => { + registerNodeTx.addSignatureIdx( + subnetAuthCredential[0], + subnetAuthCredential[1] + ) + } + ) + + return new UnsignedTx(registerNodeTx) + } + + _feeCheck(fee: BN, feeAssetID: Buffer): boolean { + return ( + typeof fee !== "undefined" && + typeof feeAssetID !== "undefined" && + fee.gt(new BN(0)) && + feeAssetID instanceof Buffer + ) + } +} diff --git a/src/apis/platformvm/camino_executor.ts b/src/apis/platformvm/camino_executor.ts deleted file mode 100644 index 7615ccda9..000000000 --- a/src/apis/platformvm/camino_executor.ts +++ /dev/null @@ -1,236 +0,0 @@ -/** - * @packageDocumentation - * @module API-PlatformVM-CaminoExecutor - */ - -import BN from "bn.js" - -import { Buffer } from "buffer/" -import { DefaultNetworkID, UnixNow } from "../../utils" -import { TimeError } from "../../utils/errors" -import { - AssetAmountDestination, - CaminoAddValidatorTx, - ParseableOutput, - PlatformVMAPI, - RegisterNodeTx, - SECPOwnerOutput, - TransferableInput, - TransferableOutput, - UnsignedTx -} from "." - -type CaminoLockMode = "Unlocked" | "Bond" | "Deposit" - -export class CaminoExecutor { - platformAPI: PlatformVMAPI - - constructor(platformAPI: PlatformVMAPI) { - this.platformAPI = platformAPI - } - - spend = async ( - aad: AssetAmountDestination, - lockMode: CaminoLockMode, - feeAssetID: Buffer = undefined - ): Promise => { - const addr = aad - .getSenders() - .map((a) => this.platformAPI.addressFromBuffer(a)) - const changeAddr = - aad.getChangeAddresses().length > 0 - ? this.platformAPI.addressFromBuffer(aad.getChangeAddresses()[0]) - : "" - - const aa = aad.getAssetAmount(feeAssetID.toString("hex")) - - const result = await this.platformAPI.spend( - addr, - changeAddr, - lockMode, - aa.getAmount(), - aa.getBurn() - ) - - result.ins.forEach((inp) => { - aad.addInput(inp) - }) - result.out.forEach((out) => { - aad.addOutput(out) - }) - - return - } - - /** - * Helper function which creates an unsigned [[CaminoAddValidatorTx]]. For more granular control, you may create your own - * [[UnsignedTx]] manually and import the [[CaminoAddValidatorTx]] class directly. - * - * @param networkID Networkid, [[DefaultNetworkID]] - * @param blockchainID Blockchainid, default undefined - * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who received the staked tokens at the end of the staking period - * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who own the staking UTXOs the fees in AVAX - * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the fee payment - * @param nodeID The node ID of the validator being added. - * @param startTime The Unix time when the validator starts validating the Primary Network. - * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). - * @param stakeAmount The amount being delegated as a {@link https://github.com/indutny/bn.js/|BN} - * @param rewardAddresses The addresses which will recieve the rewards from the delegated stake. - * @param rewardLocktime Optional. The locktime field created in the resulting reward outputs - * @param rewardThreshold Opional. The number of signatures required to spend the funds in the resultant reward UTXO. Default 1. - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * - * @returns An unsigned transaction created from the passed in parameters. - */ - buildCaminoAddValidatorTx = async ( - networkID: number = DefaultNetworkID, - blockchainID: Buffer, - to: Buffer[], - from: Buffer[], - change: Buffer[], - nodeID: Buffer, - startTime: BN, - endTime: BN, - stakeAmount: BN, - rewards: Buffer[], - rewardLocktime: BN = new BN(0), - rewardThreshold: number = 1, - memo: Buffer = undefined, - avaxAssetID: Buffer - ): Promise => { - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - - const zero: BN = new BN(0) - const now: BN = UnixNow() - if (startTime.lt(now) || endTime.lte(startTime)) { - throw new TimeError( - "buildCaminoAddValidatorTx -- startTime must be in the future and endTime must come after startTime" - ) - } - - const aad: AssetAmountDestination = new AssetAmountDestination( - to, - from, - change - ) - - aad.addAssetAmount(avaxAssetID, stakeAmount, new BN(0)) - - const minSpendableErr: Error = await this.spend(aad, "Bond", avaxAssetID) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getAllOutputs() - } else { - throw minSpendableErr - } - - const rewardOutputOwners: SECPOwnerOutput = new SECPOwnerOutput( - rewards, - rewardLocktime, - rewardThreshold - ) - - const tx: CaminoAddValidatorTx = new CaminoAddValidatorTx( - networkID, - blockchainID, - outs, - ins, - memo, - nodeID, - startTime, - endTime, - stakeAmount, - new ParseableOutput(rewardOutputOwners) - ) - - return new UnsignedTx(tx) - } - - /** - * Build an unsigned [[RegisterNodeTx]]. - * - * @param networkID Networkid, [[DefaultNetworkID]] - * @param blockchainID Blockchainid, default undefined - * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} - * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. - * @param oldNodeID Optional. ID of the existing NodeID to replace or remove. - * @param newNodeID Optional. ID of the newNodID to register address. - * @param address The consortiumMemberAddress, single or multi-sig. - * @param consortiumMemberAuthCredentials An array of index and address to sign for each SubnetAuth. - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * - * @returns An unsigned RegisterNodeTx created from the passed in parameters. - */ - buildRegisterNodeTx = async ( - networkID: number = DefaultNetworkID, - blockchainID: Buffer, - fromAddresses: Buffer[], - changeAddresses: Buffer[], - oldNodeID: string | Buffer = undefined, - newNodeID: string | Buffer = undefined, - address: Buffer = undefined, - consortiumMemberAuthCredentials: [number, Buffer][] = [], - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined - ): Promise => { - const zero: BN = new BN(0) - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - - if (this._feeCheck(fee, feeAssetID)) { - const aad: AssetAmountDestination = new AssetAmountDestination( - fromAddresses, - fromAddresses, - changeAddresses - ) - - aad.addAssetAmount(feeAssetID, zero, fee) - - const minSpendableErr: Error = await this.spend( - aad, - "Unlocked", - feeAssetID - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getAllOutputs() - } else { - throw minSpendableErr - } - } - - const registerNodeTx: RegisterNodeTx = new RegisterNodeTx( - networkID, - blockchainID, - outs, - ins, - memo, - oldNodeID, - newNodeID, - address - ) - consortiumMemberAuthCredentials.forEach( - (subnetAuthCredential: [number, Buffer]): void => { - registerNodeTx.addSignatureIdx( - subnetAuthCredential[0], - subnetAuthCredential[1] - ) - } - ) - - return new UnsignedTx(registerNodeTx) - } - - _feeCheck(fee: BN, feeAssetID: Buffer): boolean { - return ( - typeof fee !== "undefined" && - typeof feeAssetID !== "undefined" && - fee.gt(new BN(0)) && - feeAssetID instanceof Buffer - ) - } -} diff --git a/src/apis/platformvm/interfaces.ts b/src/apis/platformvm/interfaces.ts index 8fe42d9d3..616fbc913 100644 --- a/src/apis/platformvm/interfaces.ts +++ b/src/apis/platformvm/interfaces.ts @@ -222,12 +222,21 @@ export interface GetMaxStakeAmountParams { endTime: BN } +export interface Owner { + locktime: BN + threshold: number + addresses: string[] +} + export interface SpendParams { from: string[] | string - changeAddr: string + to?: Owner + change?: Owner + lockMode: 0 | 1 | 2 - amountToLock: string - amountToBurn: string + amountToLock: BN + amountToBurn: BN + asOf: BN encoding?: string } diff --git a/src/apis/platformvm/spender.ts b/src/apis/platformvm/spender.ts new file mode 100644 index 000000000..685dce7ea --- /dev/null +++ b/src/apis/platformvm/spender.ts @@ -0,0 +1,66 @@ +/** + * @packageDocumentation + * @module API-PlatformVM-Spender + */ + +import BN from "bn.js" + +import { AssetAmountDestination, PlatformVMAPI } from "." +import { FeeAssetError, TimeError } from "../../utils/errors" + +import { LockMode } from "./builder" + +export class Spender { + platformAPI: PlatformVMAPI + + constructor(platformAPI: PlatformVMAPI) { + this.platformAPI = platformAPI + } + + getMinimumSpendable = async ( + aad: AssetAmountDestination, + asOf: BN, + lockTime: BN, + lockMode: LockMode + ): Promise => { + if (aad.getAmounts().length !== 1) { + return new FeeAssetError("spender -- multiple assets not yet supported") + } + + const addr = aad + .getSenders() + .map((a) => this.platformAPI.addressFromBuffer(a)) + + const to = aad + .getDestinations() + .map((a) => this.platformAPI.addressFromBuffer(a)) + + const change = aad + .getChangeAddresses() + .map((a) => this.platformAPI.addressFromBuffer(a)) + + const aa = aad.getAmounts()[0] + + const result = await this.platformAPI.spend( + addr, + to, + aad.getDestinationsThreshold(), + lockTime, + change, + aad.getChangeAddressesThreshold(), + lockMode, + aa.getAmount(), + aa.getBurn(), + asOf + ) + + result.ins.forEach((inp) => { + aad.addInput(inp) + }) + result.out.forEach((out) => { + aad.addOutput(out) + }) + + return + } +} diff --git a/src/apis/platformvm/utxos.ts b/src/apis/platformvm/utxos.ts index 35af6ad03..be4f272e8 100644 --- a/src/apis/platformvm/utxos.ts +++ b/src/apis/platformvm/utxos.ts @@ -9,7 +9,6 @@ import { AmountOutput, SelectOutputClass, TransferableOutput, - SECPOwnerOutput, ParseableOutput, StakeableLockOut, SECPTransferOutput @@ -23,32 +22,19 @@ import { import { UnixNow } from "../../utils/helperfunctions" import { StandardUTXO, StandardUTXOSet } from "../../common/utxos" import { PlatformVMConstants } from "./constants" -import { UnsignedTx } from "./tx" -import { ExportTx } from "../platformvm/exporttx" -import { ImportTx } from "../platformvm/importtx" -import { BaseTx } from "../platformvm/basetx" import { StandardAssetAmountDestination, AssetAmount } from "../../common/assetamount" import { BaseInput } from "../../common/input" import { BaseOutput } from "../../common/output" -import { AddDelegatorTx, AddValidatorTx } from "./validationtx" -import { CreateSubnetTx } from "./createsubnettx" import { Serialization, SerializedEncoding } from "../../utils/serialization" import { UTXOError, AddressError, - InsufficientFundsError, - ThresholdError, - FeeAssetError, - TimeError + InsufficientFundsError } from "../../utils/errors" -import { DefaultNetworkID } from "../../utils/constants" -import Networks from "../../utils/networks" -import { CreateChainTx } from "." -import { GenesisData } from "../avm" -import { AddSubnetValidatorTx } from "../platformvm/addsubnetvalidatortx" +import { LockMode } from "./builder" /** * @ignore @@ -56,6 +42,8 @@ import { AddSubnetValidatorTx } from "../platformvm/addsubnetvalidatortx" const bintools: BinTools = BinTools.getInstance() const serialization: Serialization = Serialization.getInstance() +const zeroBN = new BN(0) + /** * Class for representing a single UTXO. */ @@ -252,16 +240,17 @@ export class UTXOSet extends StandardUTXOSet { }) } - getMinimumSpendable = ( + getMinimumSpendable = async ( aad: AssetAmountDestination, - asOf: BN = UnixNow(), - locktime: BN = new BN(0), - threshold: number = 1, - stakeable: boolean = false - ): Error => { - let utxoArray: UTXO[] = this.getConsumableUXTO(asOf, stakeable) + asOf: BN = zeroBN, + lockTime: BN = zeroBN, + lockMode: LockMode = "Unlocked" + ): Promise => { + if (asOf.isZero()) asOf = UnixNow() + + let utxoArray: UTXO[] = this.getConsumableUXTO(asOf, lockMode == "Stake") let tmpUTXOArray: UTXO[] = [] - if (stakeable) { + if (lockMode == "Stake") { // If this is a stakeable transaction then have StakeableLockOut come before SECPTransferOutput // so that users first stake locked tokens before staking unlocked tokens utxoArray.forEach((utxo: UTXO) => { @@ -482,7 +471,7 @@ export class UTXOSet extends StandardUTXOSet { unlockedChange, aad.getChangeAddresses(), zero.clone(), // make sure that we don't lock the change output. - threshold + aad.getChangeAddressesThreshold() ) as AmountOutput const transferOutput: TransferableOutput = new TransferableOutput( assetID, @@ -508,8 +497,8 @@ export class UTXOSet extends StandardUTXOSet { const newOutput: AmountOutput = new SECPTransferOutput( unlockedAmount, aad.getDestinations(), - locktime, - threshold + lockTime, + aad.getDestinationsThreshold() ) as AmountOutput const transferOutput: TransferableOutput = new TransferableOutput( assetID, @@ -520,824 +509,4 @@ export class UTXOSet extends StandardUTXOSet { }) return undefined } - - /** - * Creates an [[UnsignedTx]] wrapping a [[BaseTx]]. For more granular control, you may create your own - * [[UnsignedTx]] wrapping a [[BaseTx]] manually (with their corresponding [[TransferableInput]]s and [[TransferableOutput]]s). - * - * @param networkID The number representing NetworkID of the node - * @param blockchainID The {@link https://github.com/feross/buffer|Buffer} representing the BlockchainID for the transaction - * @param amount The amount of the asset to be spent in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN}. - * @param assetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for the UTXO - * @param toAddresses The addresses to send the funds - * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} - * @param changeAddresses Optional. The addresses that can spend the change remaining from the spent UTXOs. Default: toAddresses - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned. Default: assetID - * @param memo Optional. Contains arbitrary data, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO - * - * @returns An unsigned transaction created from the passed in parameters. - * - */ - buildBaseTx = ( - networkID: number, - blockchainID: Buffer, - amount: BN, - assetID: Buffer, - toAddresses: Buffer[], - fromAddresses: Buffer[], - changeAddresses: Buffer[] = undefined, - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow(), - locktime: BN = new BN(0), - threshold: number = 1 - ): UnsignedTx => { - if (threshold > toAddresses.length) { - /* istanbul ignore next */ - throw new ThresholdError( - "Error - UTXOSet.buildBaseTx: threshold is greater than number of addresses" - ) - } - - if (typeof changeAddresses === "undefined") { - changeAddresses = toAddresses - } - - if (typeof feeAssetID === "undefined") { - feeAssetID = assetID - } - - const zero: BN = new BN(0) - - if (amount.eq(zero)) { - return undefined - } - - const aad: AssetAmountDestination = new AssetAmountDestination( - toAddresses, - fromAddresses, - changeAddresses - ) - if (assetID.toString("hex") === feeAssetID.toString("hex")) { - aad.addAssetAmount(assetID, amount, fee) - } else { - aad.addAssetAmount(assetID, amount, zero) - if (this._feeCheck(fee, feeAssetID)) { - aad.addAssetAmount(feeAssetID, zero, fee) - } - } - - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - - const minSpendableErr: Error = this.getMinimumSpendable( - aad, - asOf, - locktime, - threshold - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getAllOutputs() - } else { - throw minSpendableErr - } - - const baseTx: BaseTx = new BaseTx(networkID, blockchainID, outs, ins, memo) - return new UnsignedTx(baseTx) - } - - /** - * Creates an unsigned ImportTx transaction. - * - * @param networkID The number representing NetworkID of the node - * @param blockchainID The {@link https://github.com/feross/buffer|Buffer} representing the BlockchainID for the transaction - * @param toAddresses The addresses to send the funds - * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} - * @param changeAddresses Optional. The addresses that can spend the change remaining from the spent UTXOs. Default: toAddresses - * @param importIns An array of [[TransferableInput]]s being imported - * @param sourceChain A {@link https://github.com/feross/buffer|Buffer} for the chainid where the imports are coming from. - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN}. Fee will come from the inputs first, if they can. - * @param feeAssetID Optional. The assetID of the fees being burned. - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO - * @returns An unsigned transaction created from the passed in parameters. - * - */ - buildImportTx = ( - networkID: number, - blockchainID: Buffer, - toAddresses: Buffer[], - fromAddresses: Buffer[], - changeAddresses: Buffer[], - atomics: UTXO[], - sourceChain: Buffer = undefined, - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow(), - locktime: BN = new BN(0), - threshold: number = 1 - ): UnsignedTx => { - const zero: BN = new BN(0) - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - if (typeof fee === "undefined") { - fee = zero.clone() - } - - const importIns: TransferableInput[] = [] - let feepaid: BN = new BN(0) - let feeAssetStr: string = feeAssetID.toString("hex") - for (let i: number = 0; i < atomics.length; i++) { - const utxo: UTXO = atomics[`${i}`] - const assetID: Buffer = utxo.getAssetID() - const output: AmountOutput = utxo.getOutput() as AmountOutput - let amt: BN = output.getAmount().clone() - - let infeeamount = amt.clone() - let assetStr: string = assetID.toString("hex") - if ( - typeof feeAssetID !== "undefined" && - fee.gt(zero) && - feepaid.lt(fee) && - assetStr === feeAssetStr - ) { - feepaid = feepaid.add(infeeamount) - if (feepaid.gte(fee)) { - infeeamount = feepaid.sub(fee) - feepaid = fee.clone() - } else { - infeeamount = zero.clone() - } - } - - const txid: Buffer = utxo.getTxID() - const outputidx: Buffer = utxo.getOutputIdx() - const input: SECPTransferInput = new SECPTransferInput(amt) - const xferin: TransferableInput = new TransferableInput( - txid, - outputidx, - assetID, - input - ) - const from: Buffer[] = output.getAddresses() - const spenders: Buffer[] = output.getSpenders(from, asOf) - for (let j: number = 0; j < spenders.length; j++) { - const idx: number = output.getAddressIdx(spenders[`${j}`]) - if (idx === -1) { - /* istanbul ignore next */ - throw new AddressError( - "Error - UTXOSet.buildImportTx: no such " + - `address in output: ${spenders[`${j}`]}` - ) - } - xferin.getInput().addSignatureIdx(idx, spenders[`${j}`]) - } - importIns.push(xferin) - //add extra outputs for each amount (calculated from the imported inputs), minus fees - if (infeeamount.gt(zero)) { - const spendout: AmountOutput = SelectOutputClass( - output.getOutputID(), - infeeamount, - toAddresses, - locktime, - threshold - ) as AmountOutput - const xferout: TransferableOutput = new TransferableOutput( - assetID, - spendout - ) - outs.push(xferout) - } - } - - // get remaining fees from the provided addresses - let feeRemaining: BN = fee.sub(feepaid) - if (feeRemaining.gt(zero) && this._feeCheck(feeRemaining, feeAssetID)) { - const aad: AssetAmountDestination = new AssetAmountDestination( - toAddresses, - fromAddresses, - changeAddresses - ) - aad.addAssetAmount(feeAssetID, zero, feeRemaining) - const minSpendableErr: Error = this.getMinimumSpendable( - aad, - asOf, - locktime, - threshold - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getAllOutputs() - } else { - throw minSpendableErr - } - } - - const importTx: ImportTx = new ImportTx( - networkID, - blockchainID, - outs, - ins, - memo, - sourceChain, - importIns - ) - return new UnsignedTx(importTx) - } - - /** - * Creates an unsigned ExportTx transaction. - * - * @param networkID The number representing NetworkID of the node - * @param blockchainID The {@link https://github.com/feross/buffer|Buffer} representing the BlockchainID for the transaction - * @param amount The amount being exported as a {@link https://github.com/indutny/bn.js/|BN} - * @param avaxAssetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for AVAX - * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who recieves the AVAX - * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who owns the AVAX - * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover of the AVAX - * @param destinationChain Optional. A {@link https://github.com/feross/buffer|Buffer} for the chainid where to send the asset. - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned. - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * @param locktime Optional. The locktime field created in the resulting outputs - * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO - * - * @returns An unsigned transaction created from the passed in parameters. - * - */ - buildExportTx = ( - networkID: number, - blockchainID: Buffer, - amount: BN, - avaxAssetID: Buffer, // TODO: rename this to amountAssetID - toAddresses: Buffer[], - fromAddresses: Buffer[], - changeAddresses: Buffer[] = undefined, - destinationChain: Buffer = undefined, - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow(), - locktime: BN = new BN(0), - threshold: number = 1 - ): UnsignedTx => { - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - let exportouts: TransferableOutput[] = [] - - if (typeof changeAddresses === "undefined") { - changeAddresses = toAddresses - } - - const zero: BN = new BN(0) - - if (amount.eq(zero)) { - return undefined - } - - if (typeof feeAssetID === "undefined") { - feeAssetID = avaxAssetID - } else if (feeAssetID.toString("hex") !== avaxAssetID.toString("hex")) { - /* istanbul ignore next */ - throw new FeeAssetError( - "Error - UTXOSet.buildExportTx: " + `feeAssetID must match avaxAssetID` - ) - } - - if (typeof destinationChain === "undefined") { - destinationChain = bintools.cb58Decode( - Networks.getNetwork(networkID).X.blockchainID - ) - } - - const aad: AssetAmountDestination = new AssetAmountDestination( - toAddresses, - fromAddresses, - changeAddresses - ) - if (avaxAssetID.toString("hex") === feeAssetID.toString("hex")) { - aad.addAssetAmount(avaxAssetID, amount, fee) - } else { - aad.addAssetAmount(avaxAssetID, amount, zero) - if (this._feeCheck(fee, feeAssetID)) { - aad.addAssetAmount(feeAssetID, zero, fee) - } - } - - const minSpendableErr: Error = this.getMinimumSpendable( - aad, - asOf, - locktime, - threshold - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getChangeOutputs() - exportouts = aad.getOutputs() - } else { - throw minSpendableErr - } - - const exportTx: ExportTx = new ExportTx( - networkID, - blockchainID, - outs, - ins, - memo, - destinationChain, - exportouts - ) - - return new UnsignedTx(exportTx) - } - - /** - * Class representing an unsigned [[AddSubnetValidatorTx]] transaction. - * - * @param networkID Networkid, [[DefaultNetworkID]] - * @param blockchainID Blockchainid, default undefined - * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who pays the fees in AVAX - * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the fee payment - * @param nodeID The node ID of the validator being added. - * @param startTime The Unix time when the validator starts validating the Primary Network. - * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). - * @param weight The amount of weight for this subnet validator. - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned. - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * @param subnetAuthCredentials Optional. An array of index and address to sign for each SubnetAuth. - * - * @returns An unsigned transaction created from the passed in parameters. - */ - buildAddSubnetValidatorTx = ( - networkID: number = DefaultNetworkID, - blockchainID: Buffer, - fromAddresses: Buffer[], - changeAddresses: Buffer[], - nodeID: Buffer, - startTime: BN, - endTime: BN, - weight: BN, - subnetID: string, - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow(), - subnetAuthCredentials: [number, Buffer][] = [] - ): UnsignedTx => { - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - - const zero: BN = new BN(0) - const now: BN = UnixNow() - if (startTime.lt(now) || endTime.lte(startTime)) { - throw new Error( - "UTXOSet.buildAddSubnetValidatorTx -- startTime must be in the future and endTime must come after startTime" - ) - } - - if (this._feeCheck(fee, feeAssetID)) { - const aad: AssetAmountDestination = new AssetAmountDestination( - fromAddresses, - fromAddresses, - changeAddresses - ) - aad.addAssetAmount(feeAssetID, zero, fee) - const success: Error = this.getMinimumSpendable( - aad, - asOf, - undefined, - undefined, - true - ) - if (typeof success === "undefined") { - ins = aad.getInputs() - outs = aad.getAllOutputs() - } else { - throw success - } - } - - const addSubnetValidatorTx: AddSubnetValidatorTx = new AddSubnetValidatorTx( - networkID, - blockchainID, - outs, - ins, - memo, - nodeID, - startTime, - endTime, - weight, - subnetID - ) - subnetAuthCredentials.forEach( - (subnetAuthCredential: [number, Buffer]): void => { - addSubnetValidatorTx.addSignatureIdx( - subnetAuthCredential[0], - subnetAuthCredential[1] - ) - } - ) - return new UnsignedTx(addSubnetValidatorTx) - } - - /** - * Class representing an unsigned [[AddDelegatorTx]] transaction. - * - * @param networkID Networkid, [[DefaultNetworkID]] - * @param blockchainID Blockchainid, default undefined - * @param avaxAssetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for AVAX - * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} recieves the stake at the end of the staking period - * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who pays the fees and the stake - * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the staking payment - * @param nodeID The node ID of the validator being added. - * @param startTime The Unix time when the validator starts validating the Primary Network. - * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). - * @param stakeAmount A {@link https://github.com/indutny/bn.js/|BN} for the amount of stake to be delegated in nAVAX. - * @param rewardLocktime The locktime field created in the resulting reward outputs - * @param rewardThreshold The number of signatures required to spend the funds in the resultant reward UTXO - * @param rewardAddresses The addresses the validator reward goes. - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned. - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * @param changeThreshold Optional. The number of signatures required to spend the funds in the change UTXO - * - * @returns An unsigned transaction created from the passed in parameters. - */ - buildAddDelegatorTx = ( - networkID: number = DefaultNetworkID, - blockchainID: Buffer, - avaxAssetID: Buffer, - toAddresses: Buffer[], - fromAddresses: Buffer[], - changeAddresses: Buffer[], - nodeID: Buffer, - startTime: BN, - endTime: BN, - stakeAmount: BN, - rewardLocktime: BN, - rewardThreshold: number, - rewardAddresses: Buffer[], - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow(), - changeThreshold: number = 1 - ): UnsignedTx => { - if (rewardThreshold > rewardAddresses.length) { - /* istanbul ignore next */ - throw new ThresholdError( - "Error - UTXOSet.buildAddDelegatorTx: reward threshold is greater than number of addresses" - ) - } - - if (typeof changeAddresses === "undefined") { - changeAddresses = toAddresses - } - - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - let stakeOuts: TransferableOutput[] = [] - - const zero: BN = new BN(0) - const now: BN = UnixNow() - if (startTime.lt(now) || endTime.lte(startTime)) { - throw new TimeError( - "UTXOSet.buildAddDelegatorTx -- startTime must be in the future and endTime must come after startTime" - ) - } - - const aad: AssetAmountDestination = new AssetAmountDestination( - toAddresses, - fromAddresses, - changeAddresses - ) - if (avaxAssetID.toString("hex") === feeAssetID.toString("hex")) { - aad.addAssetAmount(avaxAssetID, stakeAmount, fee) - } else { - aad.addAssetAmount(avaxAssetID, stakeAmount, zero) - if (this._feeCheck(fee, feeAssetID)) { - aad.addAssetAmount(feeAssetID, zero, fee) - } - } - - const minSpendableErr: Error = this.getMinimumSpendable( - aad, - asOf, - undefined, - changeThreshold, - true - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getChangeOutputs() - stakeOuts = aad.getOutputs() - } else { - throw minSpendableErr - } - - const rewardOutputOwners: SECPOwnerOutput = new SECPOwnerOutput( - rewardAddresses, - rewardLocktime, - rewardThreshold - ) - - const UTx: AddDelegatorTx = new AddDelegatorTx( - networkID, - blockchainID, - outs, - ins, - memo, - nodeID, - startTime, - endTime, - stakeAmount, - stakeOuts, - new ParseableOutput(rewardOutputOwners) - ) - return new UnsignedTx(UTx) - } - - /** - * Class representing an unsigned [[AddValidatorTx]] transaction. - * - * @param networkID NetworkID, [[DefaultNetworkID]] - * @param blockchainID BlockchainID, default undefined - * @param avaxAssetID {@link https://github.com/feross/buffer|Buffer} of the asset ID for AVAX - * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} recieves the stake at the end of the staking period - * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who pays the fees and the stake - * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the staking payment - * @param nodeID The node ID of the validator being added. - * @param startTime The Unix time when the validator starts validating the Primary Network. - * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). - * @param stakeAmount A {@link https://github.com/indutny/bn.js/|BN} for the amount of stake to be delegated in nAVAX. - * @param rewardLocktime The locktime field created in the resulting reward outputs - * @param rewardThreshold The number of signatures required to spend the funds in the resultant reward UTXO - * @param rewardAddresses The addresses the validator reward goes. - * @param delegationFee A number for the percentage of reward to be given to the validator when someone delegates to them. Must be between 0 and 100. - * @param minStake A {@link https://github.com/indutny/bn.js/|BN} representing the minimum stake required to validate on this network. - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned. - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * - * @returns An unsigned transaction created from the passed in parameters. - */ - buildAddValidatorTx = ( - networkID: number = DefaultNetworkID, - blockchainID: Buffer, - avaxAssetID: Buffer, - toAddresses: Buffer[], - fromAddresses: Buffer[], - changeAddresses: Buffer[], - nodeID: Buffer, - startTime: BN, - endTime: BN, - stakeAmount: BN, - rewardLocktime: BN, - rewardThreshold: number, - rewardAddresses: Buffer[], - delegationFee: number, - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow() - ): UnsignedTx => { - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - let stakeOuts: TransferableOutput[] = [] - - const zero: BN = new BN(0) - const now: BN = UnixNow() - if (startTime.lt(now) || endTime.lte(startTime)) { - throw new TimeError( - "UTXOSet.buildAddValidatorTx -- startTime must be in the future and endTime must come after startTime" - ) - } - - if (delegationFee > 100 || delegationFee < 0) { - throw new TimeError( - "UTXOSet.buildAddValidatorTx -- startTime must be in the range of 0 to 100, inclusively" - ) - } - - const aad: AssetAmountDestination = new AssetAmountDestination( - toAddresses, - fromAddresses, - changeAddresses - ) - if (avaxAssetID.toString("hex") === feeAssetID.toString("hex")) { - aad.addAssetAmount(avaxAssetID, stakeAmount, fee) - } else { - aad.addAssetAmount(avaxAssetID, stakeAmount, zero) - if (this._feeCheck(fee, feeAssetID)) { - aad.addAssetAmount(feeAssetID, zero, fee) - } - } - - const minSpendableErr: Error = this.getMinimumSpendable( - aad, - asOf, - undefined, - undefined, - true - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getChangeOutputs() - stakeOuts = aad.getOutputs() - } else { - throw minSpendableErr - } - - const rewardOutputOwners: SECPOwnerOutput = new SECPOwnerOutput( - rewardAddresses, - rewardLocktime, - rewardThreshold - ) - - const UTx: AddValidatorTx = new AddValidatorTx( - networkID, - blockchainID, - outs, - ins, - memo, - nodeID, - startTime, - endTime, - stakeAmount, - stakeOuts, - new ParseableOutput(rewardOutputOwners), - delegationFee - ) - return new UnsignedTx(UTx) - } - - /** - * Class representing an unsigned [[CreateSubnetTx]] transaction. - * - * @param networkID Networkid, [[DefaultNetworkID]] - * @param blockchainID Blockchainid, default undefined - * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} - * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. - * @param subnetOwnerAddresses An array of {@link https://github.com/feross/buffer|Buffer} for the addresses to add to a subnet - * @param subnetOwnerThreshold The number of owners's signatures required to add a validator to the network - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * - * @returns An unsigned transaction created from the passed in parameters. - */ - buildCreateSubnetTx = ( - networkID: number = DefaultNetworkID, - blockchainID: Buffer, - fromAddresses: Buffer[], - changeAddresses: Buffer[], - subnetOwnerAddresses: Buffer[], - subnetOwnerThreshold: number, - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow() - ): UnsignedTx => { - const zero: BN = new BN(0) - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - - if (this._feeCheck(fee, feeAssetID)) { - const aad: AssetAmountDestination = new AssetAmountDestination( - fromAddresses, - fromAddresses, - changeAddresses - ) - aad.addAssetAmount(feeAssetID, zero, fee) - const minSpendableErr: Error = this.getMinimumSpendable( - aad, - asOf, - undefined, - undefined - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getAllOutputs() - } else { - throw minSpendableErr - } - } - - const locktime: BN = new BN(0) - const subnetOwners: SECPOwnerOutput = new SECPOwnerOutput( - subnetOwnerAddresses, - locktime, - subnetOwnerThreshold - ) - const createSubnetTx: CreateSubnetTx = new CreateSubnetTx( - networkID, - blockchainID, - outs, - ins, - memo, - subnetOwners - ) - - return new UnsignedTx(createSubnetTx) - } - - /** - * Build an unsigned [[CreateChainTx]]. - * - * @param networkID Networkid, [[DefaultNetworkID]] - * @param blockchainID Blockchainid, default undefined - * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} - * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. - * @param subnetID Optional ID of the Subnet that validates this blockchain - * @param chainName Optional A human readable name for the chain; need not be unique - * @param vmID Optional ID of the VM running on the new chain - * @param fxIDs Optional IDs of the feature extensions running on the new chain - * @param genesisData Optional Byte representation of genesis state of the new chain - * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} - * @param feeAssetID Optional. The assetID of the fees being burned - * @param memo Optional contains arbitrary bytes, up to 256 bytes - * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} - * @param subnetAuthCredentials Optional. An array of index and address to sign for each SubnetAuth. - * - * @returns An unsigned CreateChainTx created from the passed in parameters. - */ - buildCreateChainTx = ( - networkID: number = DefaultNetworkID, - blockchainID: Buffer, - fromAddresses: Buffer[], - changeAddresses: Buffer[], - subnetID: string | Buffer = undefined, - chainName: string = undefined, - vmID: string = undefined, - fxIDs: string[] = undefined, - genesisData: string | GenesisData = undefined, - fee: BN = undefined, - feeAssetID: Buffer = undefined, - memo: Buffer = undefined, - asOf: BN = UnixNow(), - subnetAuthCredentials: [number, Buffer][] = [] - ): UnsignedTx => { - const zero: BN = new BN(0) - let ins: TransferableInput[] = [] - let outs: TransferableOutput[] = [] - - if (this._feeCheck(fee, feeAssetID)) { - const aad: AssetAmountDestination = new AssetAmountDestination( - fromAddresses, - fromAddresses, - changeAddresses - ) - aad.addAssetAmount(feeAssetID, zero, fee) - const minSpendableErr: Error = this.getMinimumSpendable( - aad, - asOf, - undefined, - undefined - ) - if (typeof minSpendableErr === "undefined") { - ins = aad.getInputs() - outs = aad.getAllOutputs() - } else { - throw minSpendableErr - } - } - - const createChainTx: CreateChainTx = new CreateChainTx( - networkID, - blockchainID, - outs, - ins, - memo, - subnetID, - chainName, - vmID, - fxIDs, - genesisData - ) - subnetAuthCredentials.forEach( - (subnetAuthCredential: [number, Buffer]): void => { - createChainTx.addSignatureIdx( - subnetAuthCredential[0], - subnetAuthCredential[1] - ) - } - ) - - return new UnsignedTx(createChainTx) - } } diff --git a/src/common/assetamount.ts b/src/common/assetamount.ts index e205fe0de..a22e849b8 100644 --- a/src/common/assetamount.ts +++ b/src/common/assetamount.ts @@ -113,8 +113,10 @@ export abstract class StandardAssetAmountDestination< > { protected amounts: AssetAmount[] = [] protected destinations: Buffer[] = [] + protected destinationsThreshold: number = 0 protected senders: Buffer[] = [] protected changeAddresses: Buffer[] = [] + protected changeAddressesThreshold: number = 0 protected amountkey: object = {} protected inputs: TI[] = [] protected outputs: TO[] = [] @@ -148,6 +150,10 @@ export abstract class StandardAssetAmountDestination< return this.destinations } + getDestinationsThreshold = (): number => { + return this.destinationsThreshold + } + getSenders = (): Buffer[] => { return this.senders } @@ -156,6 +162,10 @@ export abstract class StandardAssetAmountDestination< return this.changeAddresses } + getChangeAddressesThreshold = (): number => { + return this.changeAddressesThreshold + } + getAssetAmount = (assetHexStr: string): AssetAmount => { return this.amountkey[`${assetHexStr}`] } @@ -191,11 +201,15 @@ export abstract class StandardAssetAmountDestination< constructor( destinations: Buffer[], + destinationsThreshold: number, senders: Buffer[], - changeAddresses: Buffer[] + changeAddresses: Buffer[], + changeAddressesThreshold: number ) { this.destinations = destinations + this, (destinationsThreshold = destinationsThreshold) this.changeAddresses = changeAddresses + this.changeAddressesThreshold = changeAddressesThreshold this.senders = senders } } diff --git a/tests/apis/platformvm/api.test.ts b/tests/apis/platformvm/api.test.ts index b5ec04e8b..eeb16f389 100644 --- a/tests/apis/platformvm/api.test.ts +++ b/tests/apis/platformvm/api.test.ts @@ -53,6 +53,7 @@ import { GetBalanceResponse, GetUTXOsResponse } from "src/apis/platformvm/interfaces" +import { Builder } from "../../../src/apis/platformvm/builder" /** * @ignore @@ -1039,6 +1040,8 @@ describe("PlatformVMAPI", (): void => { describe("Transactions", (): void => { let set: UTXOSet let lset: UTXOSet + let builder: Builder + let lbuilder: Builder let keymgr2: KeyChain let keymgr3: KeyChain let addrs1: string[] @@ -1069,6 +1072,8 @@ describe("PlatformVMAPI", (): void => { platformvm.setAVAXAssetID(assetID) set = new UTXOSet() lset = new UTXOSet() + builder = new Builder(set, false) + lbuilder = new Builder(lset, false) platformvm.newKeyChain() keymgr2 = new KeyChain(avalanche.getHRP(), alias) keymgr3 = new KeyChain(avalanche.getHRP(), alias) @@ -1206,7 +1211,7 @@ describe("PlatformVMAPI", (): void => { test("signTx", async (): Promise => { const assetID: Buffer = await platformvm.getAVAXAssetID() - const txu2: UnsignedTx = set.buildBaseTx( + const txu2: UnsignedTx = await builder.buildBaseTx( networkID, bintools.cb58Decode(blockchainID), new BN(amnt), @@ -1259,7 +1264,7 @@ describe("PlatformVMAPI", (): void => { mockAxios.mockResponse(responseObj) const txu1: UnsignedTx = await result - const txu2: UnsignedTx = set.buildImportTx( + const txu2: UnsignedTx = await builder.buildImportTx( networkID, bintools.cb58Decode(blockchainID), addrbuff3, @@ -1324,15 +1329,15 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu2: UnsignedTx = set.buildExportTx( + const txu2: UnsignedTx = await builder.buildExportTx( networkID, bintools.cb58Decode(blockchainID), amount, assetID, addrbuff3, addrbuff1, - addrbuff2, bintools.cb58Decode(TestXBlockchainID), + addrbuff2, platformvm.getTxFee(), assetID, new UTF8Payload("hello world").getPayload(), @@ -1355,25 +1360,24 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu4: UnsignedTx = set.buildExportTx( + const txu4: UnsignedTx = await builder.buildExportTx( networkID, bintools.cb58Decode(blockchainID), amount, assetID, addrbuff3, addrbuff1, - addrbuff2, undefined, + addrbuff2, platformvm.getTxFee(), assetID, new UTF8Payload("hello world").getPayload(), UnixNow() ) - expect(txu4.toBuffer().toString("hex")).toBe( - txu3.toBuffer().toString("hex") - ) - expect(txu4.toString()).toBe(txu3.toString()) + expect((): void => { + txu4.toBuffer() + }).toThrow() expect(txu2.toBuffer().toString("hex")).toBe( txu1.toBuffer().toString("hex") @@ -1403,42 +1407,43 @@ describe("PlatformVMAPI", (): void => { serialzeit(tx1, "ExportTx") }) + /* - test('buildAddSubnetValidatorTx', async (): Promise => { - platformvm.setFee(new BN(fee)); - const addrbuff1 = addrs1.map((a) => platformvm.parseAddress(a)); - const addrbuff2 = addrs2.map((a) => platformvm.parseAddress(a)); - const addrbuff3 = addrs3.map((a) => platformvm.parseAddress(a)); - const amount:BN = new BN(90); - - const txu1:UnsignedTx = await platformvm.buildAddSubnetValidatorTx( - set, - addrs1, - addrs2, - nodeID, - startTime, - endTime, - PlatformVMConstants.MINSTAKE, - new UTF8Payload("hello world"), UnixNow() - ); - - const txu2:UnsignedTx = set.buildAddSubnetValidatorTx( - networkID, bintools.cb58Decode(blockchainID), - addrbuff1, - addrbuff2, - NodeIDStringToBuffer(nodeID), - startTime, - endTime, - PlatformVMConstants.MINSTAKE, - platformvm.getFee(), - assetID, - new UTF8Payload("hello world").getPayload(), UnixNow() - ); - expect(txu2.toBuffer().toString('hex')).toBe(txu1.toBuffer().toString('hex')); - expect(txu2.toString()).toBe(txu1.toString()); - - }); + test('buildAddSubnetValidatorTx', async (): Promise => { + platformvm.setFee(new BN(fee)); + const addrbuff1 = addrs1.map((a) => platformvm.parseAddress(a)); + const addrbuff2 = addrs2.map((a) => platformvm.parseAddress(a)); + const addrbuff3 = addrs3.map((a) => platformvm.parseAddress(a)); + const amount:BN = new BN(90); + + const txu1:UnsignedTx = await platformvm.buildAddSubnetValidatorTx( + set, + addrs1, + addrs2, + nodeID, + startTime, + endTime, + PlatformVMConstants.MINSTAKE, + new UTF8Payload("hello world"), UnixNow() + ); + + const txu2:UnsignedTx = set.buildAddSubnetValidatorTx( + networkID, bintools.cb58Decode(blockchainID), + addrbuff1, + addrbuff2, + NodeIDStringToBuffer(nodeID), + startTime, + endTime, + PlatformVMConstants.MINSTAKE, + platformvm.getFee(), + assetID, + new UTF8Payload("hello world").getPayload(), UnixNow() + ); + expect(txu2.toBuffer().toString('hex')).toBe(txu1.toBuffer().toString('hex')); + expect(txu2.toString()).toBe(txu1.toString()); + }); */ + test("buildAddDelegatorTx 1", async (): Promise => { const addrbuff1 = addrs1.map((a) => platformvm.parseAddress(a)) const addrbuff2 = addrs2.map((a) => platformvm.parseAddress(a)) @@ -1469,7 +1474,7 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu2: UnsignedTx = set.buildAddDelegatorTx( + const txu2: UnsignedTx = await builder.buildAddDelegatorTx( networkID, bintools.cb58Decode(blockchainID), assetID, @@ -2085,10 +2090,9 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu2: UnsignedTx = set.buildAddValidatorTx( + const txu2: UnsignedTx = await builder.buildAddValidatorTx( networkID, bintools.cb58Decode(blockchainID), - assetID, addrbuff3, addrbuff1, addrbuff2, @@ -2096,6 +2100,7 @@ describe("PlatformVMAPI", (): void => { startTime, endTime, amount, + assetID, locktime, threshold, addrbuff3, @@ -2163,7 +2168,7 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu2: UnsignedTx = lset.buildAddDelegatorTx( + const txu2: UnsignedTx = await lbuilder.buildAddDelegatorTx( networkID, bintools.cb58Decode(blockchainID), assetID, @@ -2239,10 +2244,9 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu2: UnsignedTx = lset.buildAddValidatorTx( + const txu2: UnsignedTx = await lbuilder.buildAddValidatorTx( networkID, bintools.cb58Decode(blockchainID), - assetID, addrbuff3, addrbuff1, addrbuff2, @@ -2250,6 +2254,7 @@ describe("PlatformVMAPI", (): void => { startTime, endTime, amount, + assetID, locktime, threshold, addrbuff3, @@ -2436,7 +2441,7 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu2: UnsignedTx = set.buildCreateSubnetTx( + const txu2: UnsignedTx = await builder.buildCreateSubnetTx( networkID, bintools.cb58Decode(blockchainID), addrbuff1, @@ -2499,7 +2504,7 @@ describe("PlatformVMAPI", (): void => { UnixNow() ) - const txu2: UnsignedTx = lset.buildCreateSubnetTx( + const txu2: UnsignedTx = await lbuilder.buildCreateSubnetTx( networkID, bintools.cb58Decode(blockchainID), addrbuff1, diff --git a/tests/apis/platformvm/tx.test.ts b/tests/apis/platformvm/tx.test.ts index b86b43b3b..497fac66b 100644 --- a/tests/apis/platformvm/tx.test.ts +++ b/tests/apis/platformvm/tx.test.ts @@ -22,6 +22,7 @@ import { BaseTx } from "../../../src/apis/platformvm/basetx" import { ImportTx } from "../../../src/apis/platformvm/importtx" import { ExportTx } from "../../../src/apis/platformvm/exporttx" import { DefaultPlatformChainID } from "src/utils" +import { Builder } from "../../../src/apis/platformvm/builder" describe("Transactions", (): void => { /** @@ -31,6 +32,7 @@ describe("Transactions", (): void => { const networkID: number = 12345 let set: UTXOSet + let builder: Builder let keymgr1: KeyChain let keymgr2: KeyChain let keymgr3: KeyChain @@ -93,6 +95,7 @@ describe("Transactions", (): void => { beforeEach((): void => { set = new UTXOSet() + builder = new Builder(set, false) keymgr1 = new KeyChain(avalanche.getHRP(), alias) keymgr2 = new KeyChain(avalanche.getHRP(), alias) keymgr3 = new KeyChain(avalanche.getHRP(), alias) @@ -405,9 +408,10 @@ describe("Transactions", (): void => { expect(txunew.toString()).toBe(txu.toString()) }) - test("Creation UnsignedTx Check Amount", (): void => { - expect((): void => { - set.buildBaseTx( + test("Creation UnsignedTx Check Amount", (): Promise => { + expect.assertions(1) + return builder + .buildBaseTx( netid, blockchainID, new BN(amnt * 1000), @@ -416,7 +420,7 @@ describe("Transactions", (): void => { addrs1, addrs1 ) - }).toThrow() + .catch((e) => expect(e).toBeDefined()) }) test("Creation ImportTx", (): void => { @@ -493,8 +497,8 @@ describe("Transactions", (): void => { expect(exportTx.getExportOutputs().length).toBe(exportOuts.length) }) - test("Creation Tx1 with asof, locktime, threshold", (): void => { - const txu: UnsignedTx = set.buildBaseTx( + test("Creation Tx1 with asof, locktime, threshold", async (): Promise => { + const txu: UnsignedTx = await builder.buildBaseTx( netid, blockchainID, new BN(9000), @@ -516,8 +520,8 @@ describe("Transactions", (): void => { expect(tx2.toBuffer().toString("hex")).toBe(tx.toBuffer().toString("hex")) expect(tx2.toString()).toBe(tx.toString()) }) - test("Creation Tx2 without asof, locktime, threshold", (): void => { - const txu: UnsignedTx = set.buildBaseTx( + test("Creation Tx2 without asof, locktime, threshold", async (): Promise => { + const txu: UnsignedTx = await builder.buildBaseTx( netid, blockchainID, new BN(9000), @@ -533,8 +537,8 @@ describe("Transactions", (): void => { expect(tx2.toString()).toBe(tx.toString()) }) - test("Creation Tx4 using ImportTx", (): void => { - const txu: UnsignedTx = set.buildImportTx( + test("Creation Tx4 using ImportTx", async (): Promise => { + const txu: UnsignedTx = await builder.buildImportTx( netid, blockchainID, addrs3, @@ -553,17 +557,17 @@ describe("Transactions", (): void => { expect(tx2.toBuffer().toString("hex")).toBe(tx.toBuffer().toString("hex")) }) - test("Creation Tx5 using ExportTx", (): void => { - const txu: UnsignedTx = set.buildExportTx( + test("Creation Tx5 using ExportTx", async (): Promise => { + const txu: UnsignedTx = await builder.buildExportTx( netid, blockchainID, new BN(90), avaxAssetID, addrs3, addrs1, - addrs2, bintools.cb58Decode(DefaultPlatformChainID), - undefined, + addrs2, + new BN(0), undefined, new UTF8Payload("hello world").getPayload(), UnixNow() From 0dc02d1034f4dae59ef949bb8b6f410ba0b4087b Mon Sep 17 00:00:00 2001 From: peak3d Date: Sun, 8 Jan 2023 20:21:30 +0100 Subject: [PATCH 2/2] [TX] Add DepositTx --- examples/platformvm/buildDepositTx.ts | 71 ++++++++++ src/apis/platformvm/api.ts | 73 +++++++++- src/apis/platformvm/builder.ts | 82 +++++++++++ src/apis/platformvm/depositTx.ts | 192 ++++++++++++++++++++++++++ src/apis/platformvm/registernodetx.ts | 4 +- src/common/jrpcapi.ts | 5 + 6 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 examples/platformvm/buildDepositTx.ts create mode 100644 src/apis/platformvm/depositTx.ts diff --git a/examples/platformvm/buildDepositTx.ts b/examples/platformvm/buildDepositTx.ts new file mode 100644 index 000000000..7e5def7c0 --- /dev/null +++ b/examples/platformvm/buildDepositTx.ts @@ -0,0 +1,71 @@ +import { Avalanche, Buffer } from "@c4tplatform/caminojs/dist" +import { + PlatformVMAPI, + KeyChain, + UnsignedTx, + Tx +} from "@c4tplatform/caminojs/dist/apis/platformvm" +import { OutputOwners } from "@c4tplatform/caminojs/dist/common/output" +import { + PrivateKeyPrefix, + DefaultLocalGenesisPrivateKey +} from "@c4tplatform/caminojs/dist/utils" +import { ExamplesConfig } from "../common/examplesConfig" + +const config: ExamplesConfig = require("../common/examplesConfig.json") +const avalanche: Avalanche = new Avalanche( + config.host, + config.port, + config.protocol, + config.networkID +) + +/** + * @ignore + */ +let privKey: string = `${PrivateKeyPrefix}${DefaultLocalGenesisPrivateKey}` + +let pchain: PlatformVMAPI +let pKeychain: KeyChain +let pAddressStrings: string[] + +const InitAvalanche = async () => { + await avalanche.fetchNetworkSettings() + pchain = avalanche.PChain() + pKeychain = pchain.keyChain() + // P-local18jma8ppw3nhx5r4ap8clazz0dps7rv5u9xde7p + pKeychain.importKey(privKey) + + pAddressStrings = pchain.keyChain().getAddressStrings() +} + +const main = async (): Promise => { + await InitAvalanche() + + const depositOfferID = "wVVZinZkN9x6e9dh3DNNfrmdXaHPPwKWt3Zerx2vD8Ccuo6E7" + const depositDuration = 110376000 + const memo: Buffer = Buffer.from( + "Utility function to create a DepositTx transaction" + ) + const owners = new OutputOwners( + pchain.keyChain().getAddresses(), + undefined, + 1 + ) + + const unsignedTx: UnsignedTx = await pchain.buildDepositTx( + undefined, + pAddressStrings, + pAddressStrings, + depositOfferID, + depositDuration, + owners, + memo + ) + + const tx: Tx = unsignedTx.sign(pKeychain) + const txid: string = await pchain.issueTx(tx) + console.log(`Success! TXID: ${txid}`) +} + +main() diff --git a/src/apis/platformvm/api.ts b/src/apis/platformvm/api.ts index f1b1700a3..65580babf 100644 --- a/src/apis/platformvm/api.ts +++ b/src/apis/platformvm/api.ts @@ -5,8 +5,10 @@ import { Buffer } from "buffer/" import BN from "bn.js" import AvalancheCore from "../../camino" -import { JRPCAPI } from "../../common/jrpcapi" import { RequestResponseData } from "../../common/apibase" +import { JRPCAPI } from "../../common/jrpcapi" +import { OutputOwners } from "../../common/output" + import { ErrorResponseObject, ProtocolError, @@ -2010,6 +2012,75 @@ export class PlatformVMAPI extends JRPCAPI { return builtUnsignedTx } + /** + * Build an unsigned [[DepositTx]]. + * + * @param utxoset A set of UTXOs that the transaction is built on + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. + * @param depositOfferID ID of the deposit offer. + * @param depositDuration Duration of the deposit + * @param rewardsOwner Optional The owners of the reward. If omitted, all inputs must have the same owner + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + */ + buildDepositTx = async ( + utxoset: UTXOSet, + fromAddresses: string[], + changeAddresses: string[] = undefined, + depositOfferID: string | Buffer, + depositDuration: number | Buffer, + rewardsOwner: OutputOwners = undefined, + memo: PayloadBase | Buffer = undefined, + asOf: BN = ZeroBN, + changeThreshold: number = 1 + ): Promise => { + const from: Buffer[] = this._cleanAddressArray( + fromAddresses, + "buildRegisterNodeTx" + ).map((a: string): Buffer => bintools.stringToAddress(a)) + const change: Buffer[] = this._cleanAddressArray( + changeAddresses, + "buildRegisterNodeTx" + ).map((a: string): Buffer => bintools.stringToAddress(a)) + + if (memo instanceof PayloadBase) { + memo = memo.getPayload() + } + + const avaxAssetID: Buffer = await this.getAVAXAssetID() + const networkID: number = this.core.getNetworkID() + const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) + const fee: BN = this.getTxFee() + + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildDepositTx( + networkID, + blockchainID, + from, + change, + depositOfferID, + depositDuration, + rewardsOwner, + fee, + avaxAssetID, + memo, + asOf, + changeThreshold + ) + + if (!(await this.checkGooseEgg(builtUnsignedTx, this.getCreationTxFee()))) { + /* istanbul ignore next */ + throw new GooseEggCheckError("Failed Goose Egg Check") + } + + return builtUnsignedTx + } + /** * @ignore */ diff --git a/src/apis/platformvm/builder.ts b/src/apis/platformvm/builder.ts index 4c8dd0f23..75c60b125 100644 --- a/src/apis/platformvm/builder.ts +++ b/src/apis/platformvm/builder.ts @@ -6,6 +6,7 @@ import BN from "bn.js" import { Buffer } from "buffer/" +import { OutputOwners } from "../../common/output" import { DefaultNetworkID, UnixNow } from "../../utils" import { AddressError, @@ -38,6 +39,7 @@ import { UTXO } from "." import { GenesisData } from "../avm" +import { DepositTx } from "./depositTx" export type LockMode = "Unlocked" | "Bond" | "Deposit" | "Stake" @@ -1117,6 +1119,86 @@ export class Builder { return new UnsignedTx(registerNodeTx) } + /** + * Build an unsigned [[DepositTx]]. + * + * @param networkID Networkid, [[DefaultNetworkID]] + * @param blockchainID Blockchainid, default undefined + * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} + * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs. + * @param depositOfferID ID of the deposit offer. + * @param depositDuration Duration of the deposit + * @param rewardsOwner Optional The owners of the reward. If omitted, all inputs must have the same owner + * @param fee Optional. The amount of fees to burn in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN} + * @param feeAssetID Optional. The assetID of the fees being burned + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned DepositTx created from the passed in parameters. + */ + buildDepositTx = async ( + networkID: number = DefaultNetworkID, + blockchainID: Buffer, + fromAddresses: Buffer[], + changeAddresses: Buffer[], + depositOfferID: string | Buffer, + depositDuration: number | Buffer, + rewardsOwner: OutputOwners, + fee: BN = zero, + feeAssetID: Buffer = undefined, + memo: Buffer = undefined, + asOf: BN = zero, + changeThreshold: number = 1 + ): Promise => { + let ins: TransferableInput[] = [] + let outs: TransferableOutput[] = [] + + if (this._feeCheck(fee, feeAssetID)) { + const aad: AssetAmountDestination = new AssetAmountDestination( + [], + 0, + fromAddresses, + changeAddresses, + changeThreshold + ) + + aad.addAssetAmount(feeAssetID, zero, fee) + + const minSpendableErr: Error = await this.spender.getMinimumSpendable( + aad, + asOf, + zero, + "Unlocked" + ) + if (typeof minSpendableErr === "undefined") { + ins = aad.getInputs() + outs = aad.getAllOutputs() + } else { + throw minSpendableErr + } + } + + const secpOwners = new SECPOwnerOutput( + rewardsOwner.getAddresses(), + rewardsOwner?.getLocktime(), + rewardsOwner.getThreshold() + ) + + const depositTx: DepositTx = new DepositTx( + networkID, + blockchainID, + outs, + ins, + memo, + depositOfferID, + depositDuration, + new ParseableOutput(secpOwners) + ) + + return new UnsignedTx(depositTx) + } + _feeCheck(fee: BN, feeAssetID: Buffer): boolean { return ( typeof fee !== "undefined" && diff --git a/src/apis/platformvm/depositTx.ts b/src/apis/platformvm/depositTx.ts new file mode 100644 index 000000000..0abf07625 --- /dev/null +++ b/src/apis/platformvm/depositTx.ts @@ -0,0 +1,192 @@ +/** + * @packageDocumentation + * @module API-PlatformVM-DepositTx + */ +import { Buffer } from "buffer/" +import BinTools from "../../utils/bintools" +import { PlatformVMConstants } from "./constants" +import { ParseableOutput, TransferableOutput } from "./outputs" +import { TransferableInput } from "./inputs" +import { BaseTx } from "./basetx" +import { DefaultNetworkID } from "../../utils/constants" +import { Serialization, SerializedEncoding } from "../../utils/serialization" + +/** + * @ignore + */ +const bintools: BinTools = BinTools.getInstance() +const serialization: Serialization = Serialization.getInstance() + +/** + * Class representing an unsigned DepositTx transaction. + */ +export class DepositTx extends BaseTx { + protected _typeName = "DepositTx" + protected _typeID = PlatformVMConstants.DEPOSITTX + + serialize(encoding: SerializedEncoding = "hex"): object { + let fields: object = super.serialize(encoding) + return { + ...fields, + depositOfferID: serialization.encoder( + this.depositOfferID, + encoding, + "Buffer", + "cb58" + ), + depositDuration: serialization.encoder( + this.depositDuration, + encoding, + "Buffer", + "decimalString" + ), + rewardsOwner: this.rewardsOwner.serialize(encoding) + } + } + deserialize(fields: object, encoding: SerializedEncoding = "hex") { + super.deserialize(fields, encoding) + this.depositOfferID = serialization.decoder( + fields["depositOfferID"], + encoding, + "cb58", + "Buffer", + 32 + ) + this.depositDuration = serialization.decoder( + fields["depositDuration"], + encoding, + "decimalString", + "Buffer", + 4 + ) + this.rewardsOwner.deserialize(fields["rewardsOwner"], encoding) + } + + // ID of active offer that will be used for this deposit + protected depositOfferID: Buffer = Buffer.alloc(32) + // duration of deposit (in 4 byte format) + protected depositDuration: Buffer = Buffer.alloc(4) + // Where to send staking rewards when done validating + protected rewardsOwner: ParseableOutput = undefined + + /** + * Returns the id of the [[RegisterNodeTx]] + */ + getTxType(): number { + return this._typeID + } + + /** + * Returns the depositOfferID + */ + getDepositOfferID(): Buffer { + return this.depositOfferID + } + + /** + * Returns the depositOfferID + */ + getDepositDuration(): Buffer { + return this.depositDuration + } + + /** + * Returns the depositOfferID + */ + getRewardsOwner(): ParseableOutput { + return this.rewardsOwner + } + + /** + * Takes a {@link https://github.com/feross/buffer|Buffer} containing a [[DepositTx]], parses it, populates the class, and returns the length of the [[DepositTx]] in bytes. + * + * @param bytes A {@link https://github.com/feross/buffer|Buffer} containing a raw [[DepositTx]] + * + * @returns The length of the raw [[DepositTx]] + * + * @remarks assume not-checksummed + */ + fromBuffer(bytes: Buffer, offset: number = 0): number { + offset = super.fromBuffer(bytes, offset) + this.depositOfferID = bintools.copyFrom(bytes, offset, offset + 32) + offset += 32 + this.depositDuration = bintools.copyFrom(bytes, offset, offset + 4) + offset += 4 + offset = this.rewardsOwner.fromBuffer(bytes, offset) + + return offset + } + + /** + * Returns a {@link https://github.com/feross/buffer|Buffer} representation of the [[DepositTx]]. + */ + toBuffer(): Buffer { + const superbuff: Buffer = super.toBuffer() + + let bsize: number = + superbuff.length + + this.depositOfferID.length + + this.depositDuration.length + const barr: Buffer[] = [ + superbuff, + this.depositOfferID, + this.depositDuration + ] + + barr.push(this.rewardsOwner.toBuffer()) + bsize += barr[barr.length - 1].length + + return Buffer.concat(barr, bsize) + } + + clone(): this { + const newDepositTx: DepositTx = new DepositTx() + newDepositTx.fromBuffer(this.toBuffer()) + return newDepositTx as this + } + + create(...args: any[]): this { + return new DepositTx(...args) as this + } + + /** + * Class representing an unsigned RegisterNode transaction. + * + * @param networkID Optional networkID, [[DefaultNetworkID]] + * @param blockchainID Optional blockchainID, default Buffer.alloc(32, 16) + * @param outs Optional array of the [[TransferableOutput]]s + * @param ins Optional array of the [[TransferableInput]]s + * @param memo Optional {@link https://github.com/feross/buffer|Buffer} for the memo field + * @param depositOfferID Optional ID of the deposit offer. + * @param duration Optional Duration of depositing. + * @param rewardsOwner Optional the owner of the rewards + */ + constructor( + networkID: number = DefaultNetworkID, + blockchainID: Buffer = Buffer.alloc(32, 16), + outs: TransferableOutput[] = undefined, + ins: TransferableInput[] = undefined, + memo: Buffer = undefined, + depositOfferID: string | Buffer = undefined, + depositDuration: number | Buffer = undefined, + rewardsOwner: ParseableOutput = undefined + ) { + super(networkID, blockchainID, outs, ins, memo) + if (typeof depositOfferID != "undefined") { + if (typeof depositOfferID === "string") { + this.depositOfferID = bintools.cb58Decode(depositOfferID) + } else { + this.depositOfferID = depositOfferID + } + } + if (typeof depositDuration != "undefined") { + if (typeof depositDuration === "number") { + this.depositDuration = Buffer.alloc(4) + this.depositDuration.writeUInt32BE(depositDuration, 0) + } else { + this.depositDuration = depositDuration + } + } + this.rewardsOwner = rewardsOwner + } +} diff --git a/src/apis/platformvm/registernodetx.ts b/src/apis/platformvm/registernodetx.ts index ef4604271..a4c33d3b3 100644 --- a/src/apis/platformvm/registernodetx.ts +++ b/src/apis/platformvm/registernodetx.ts @@ -25,7 +25,7 @@ const bintools: BinTools = BinTools.getInstance() const serialization: Serialization = Serialization.getInstance() /** - * Class representing an unsigned CreateChainTx transaction. + * Class representing an unsigned DepositTx transaction. */ export class RegisterNodeTx extends BaseTx { protected _typeName = "RegisterNodeTx" @@ -86,7 +86,7 @@ export class RegisterNodeTx extends BaseTx { } /** - * Takes a {@link https://github.com/feross/buffer|Buffer} containing an [[RegisterNodeTx]], parses it, populates the class, and returns the length of the [[RegisterNodeTx]] in bytes. + * Takes a {@link https://github.com/feross/buffer|Buffer} containing a [[RegisterNodeTx]], parses it, populates the class, and returns the length of the [[RegisterNodeTx]] in bytes. * * @param bytes A {@link https://github.com/feross/buffer|Buffer} containing a raw [[RegisterNodeTx]] * diff --git a/src/common/jrpcapi.ts b/src/common/jrpcapi.ts index 97f21f394..6c0683145 100644 --- a/src/common/jrpcapi.ts +++ b/src/common/jrpcapi.ts @@ -3,11 +3,16 @@ * @module Common-JRPCAPI */ +import BN from "bn.js" import { AxiosRequestConfig } from "axios" import { fetchAdapter } from "../utils" import AvalancheCore from "../camino" import { APIBase, RequestResponseData } from "./apibase" +BN.prototype.toJSON = function () { + return this.toString(10) +} + export class JRPCAPI extends APIBase { protected jrpcVersion: string = "2.0" protected rpcID = 1