From ce05a50c36e13ebcd048b9475841e3edcd40d7ae Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Mon, 2 Dec 2024 16:55:10 +0000 Subject: [PATCH 01/14] feat(aptos): replace deprecated aptos package --- libs/ledger-live-common/package.json | 2 +- pnpm-lock.yaml | 65 +++++++++++++++++----------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/libs/ledger-live-common/package.json b/libs/ledger-live-common/package.json index 90b9ad273d76..18cd1a1bf9db 100644 --- a/libs/ledger-live-common/package.json +++ b/libs/ledger-live-common/package.json @@ -121,6 +121,7 @@ }, "dependencies": { "@apollo/client": "^3.8.7", + "@aptos-labs/ts-sdk": "^1.33.0", "@blooo/hw-app-acre": "^1.1.1", "@cardano-foundation/ledgerjs-hw-app-cardano": "^7.1.2", "@celo/connect": "^3.0.1", @@ -208,7 +209,6 @@ "@zondax/ledger-filecoin": "^0.11.2", "@zondax/ledger-icp": "^0.7.0", "@zondax/ledger-stacks": "^1.0.2", - "aptos": "^1.21.0", "async": "^3.2.3", "axios": "1.7.7", "bech32": "^1.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30e9d2b779d7..804c62a8bcda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3690,6 +3690,9 @@ importers: '@apollo/client': specifier: ^3.8.7 version: 3.11.10(@types/react@18.2.73)(graphql@16.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@aptos-labs/ts-sdk': + specifier: ^1.33.0 + version: 1.33.0 '@blooo/hw-app-acre': specifier: ^1.1.1 version: 1.1.1 @@ -3951,9 +3954,6 @@ importers: '@zondax/ledger-stacks': specifier: ^1.0.2 version: 1.0.4 - aptos: - specifier: ^1.21.0 - version: 1.21.0 async: specifier: ^3.2.3 version: 3.2.5 @@ -7786,10 +7786,18 @@ packages: subscriptions-transport-ws: optional: true + '@aptos-labs/aptos-cli@1.0.2': + resolution: {integrity: sha512-PYPsd0Kk3ynkxNfe3S4fanI3DiUICCoh4ibQderbvjPFL5A0oK6F4lPEO2t0MDsQySTk2t4vh99Xjy6Bd9y+aQ==} + hasBin: true + '@aptos-labs/aptos-client@0.1.1': resolution: {integrity: sha512-kJsoy4fAPTOhzVr7Vwq8s/AUg6BQiJDa7WOqRzev4zsuIS3+JCuIZ6vUd7UBsjnxtmguJJulMRs9qWCzVBt2XA==} engines: {node: '>=15.10.0'} + '@aptos-labs/ts-sdk@1.33.0': + resolution: {integrity: sha512-svdlPH5r2dlSue2D9WXaaTslsmX18WLytAho6IRZJxQjEssglk64I6c1G9S8BTjRQj/ug6ahTwp6lx3eWuyd8Q==} + engines: {node: '>=11.0.0'} + '@aw-web-design/x-default-browser@1.4.126': resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==} hasBin: true @@ -12741,9 +12749,6 @@ packages: '@scure/bip32@1.4.0': resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} - '@scure/bip39@1.2.1': - resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} - '@scure/bip39@1.2.2': resolution: {integrity: sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==} @@ -16378,11 +16383,6 @@ packages: application-config-path@0.1.1: resolution: {integrity: sha512-zy9cHePtMP0YhwG+CfHm0bgwdnga2X3gZexpdCwEj//dpb+TKajtiC8REEUJUSq6Ab4f9cgNy2l8ObXzCXFkEw==} - aptos@1.21.0: - resolution: {integrity: sha512-PRKjoFgL8tVEc9+oS7eJUv8GNxx8n3+0byH2+m7CP3raYOD6yFKOecuwjVMIJmgfpjp6xH0P0HDMGZAXmSyU0Q==} - engines: {node: '>=11.0.0'} - deprecated: Package aptos is no longer supported, please migrate to https://www.npmjs.com/package/@aptos-labs/ts-sdk - arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} @@ -22735,6 +22735,10 @@ packages: jws@3.2.2: resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + keccak@3.0.2: resolution: {integrity: sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==} engines: {node: '>=10.0.0'} @@ -25016,6 +25020,9 @@ packages: resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} engines: {node: '>= 0.12.0'} + poseidon-lite@0.2.1: + resolution: {integrity: sha512-xIr+G6HeYfOhCuswdqcFpSX47SPhm0EpisWJ6h7fHlWwaVIvH3dLnejpatrtw6Xc6HaLrpq05y7VRfvDmDGIog==} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -30216,11 +30223,29 @@ snapshots: transitivePeerDependencies: - '@types/react' + '@aptos-labs/aptos-cli@1.0.2': + dependencies: + commander: 12.1.0 + '@aptos-labs/aptos-client@0.1.1': dependencies: axios: 1.7.4 got: 11.8.6 + '@aptos-labs/ts-sdk@1.33.0': + dependencies: + '@aptos-labs/aptos-cli': 1.0.2 + '@aptos-labs/aptos-client': 0.1.1 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + eventemitter3: 5.0.1 + form-data: 4.0.0 + js-base64: 3.7.7 + jwt-decode: 4.0.0 + poseidon-lite: 0.2.1 + '@aw-web-design/x-default-browser@1.4.126': dependencies: default-browser-id: 3.0.0 @@ -39120,11 +39145,6 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.6 - '@scure/bip39@1.2.1': - dependencies: - '@noble/hashes': 1.3.3 - '@scure/base': 1.1.6 - '@scure/bip39@1.2.2': dependencies: '@noble/hashes': 1.3.3 @@ -45078,15 +45098,6 @@ snapshots: application-config-path@0.1.1: {} - aptos@1.21.0: - dependencies: - '@aptos-labs/aptos-client': 0.1.1 - '@noble/hashes': 1.3.3 - '@scure/bip39': 1.2.1 - eventemitter3: 5.0.1 - form-data: 4.0.0 - tweetnacl: 1.0.3 - arch@2.2.0: {} archiver-utils@4.0.1: @@ -54525,6 +54536,8 @@ snapshots: jwa: 1.4.1 safe-buffer: 5.2.1 + jwt-decode@4.0.0: {} + keccak@3.0.2: dependencies: node-addon-api: 2.0.2 @@ -57492,6 +57505,8 @@ snapshots: transitivePeerDependencies: - supports-color + poseidon-lite@0.2.1: {} + possible-typed-array-names@1.0.0: {} postcss-attribute-case-insensitive@5.0.2(postcss@8.4.38): From de8af24151cca6f909eb5939922f639b430a2319 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Mon, 2 Dec 2024 16:56:21 +0000 Subject: [PATCH 02/14] feat(aptos): update aptos types --- libs/ledger-live-common/src/families/aptos/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/types.ts b/libs/ledger-live-common/src/families/aptos/types.ts index 59f1968cb765..a6dd0d349957 100644 --- a/libs/ledger-live-common/src/families/aptos/types.ts +++ b/libs/ledger-live-common/src/families/aptos/types.ts @@ -1,14 +1,14 @@ -import type { BigNumber } from "bignumber.js"; -import type { Types as AptosTypes } from "aptos"; +import type { TransactionResponse } from "@aptos-labs/ts-sdk"; import type { + Account, TransactionCommon, TransactionCommonRaw, TransactionStatusCommon, TransactionStatusCommonRaw, - Account, } from "@ledgerhq/types-live"; +import type { BigNumber } from "bignumber.js"; -export type AptosTransaction = AptosTypes.UserTransaction & { +export type AptosTransaction = TransactionResponse & { block: { height: number; hash: string; From 5c530905034742626616d42e130d8ce4a7461181 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Wed, 4 Dec 2024 10:35:04 +0000 Subject: [PATCH 03/14] chore: update aptos api --- .../src/families/aptos/api/index.ts | 170 +++++++++++------- .../src/families/aptos/js-buildTransaction.ts | 17 +- .../aptos/js-getFeesForTransaction.ts | 11 +- 3 files changed, 121 insertions(+), 77 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/api/index.ts b/libs/ledger-live-common/src/families/aptos/api/index.ts index 46e7437f2d8a..7c4712bdd27c 100644 --- a/libs/ledger-live-common/src/families/aptos/api/index.ts +++ b/libs/ledger-live-common/src/families/aptos/api/index.ts @@ -1,46 +1,55 @@ -import { AptosClient, TxnBuilderTypes } from "aptos"; import { ApolloClient, InMemoryCache } from "@apollo/client"; -import type { Types as AptosTypes } from "aptos"; -import BigNumber from "bignumber.js"; -import network from "@ledgerhq/live-network/network"; +import { + Account, + AccountData, + Aptos, + AptosConfig, + Ed25519PublicKey, + GasEstimation, + InputEntryFunctionData, + InputGenerateTransactionOptions, + Network, + SimpleTransaction, + TransactionResponse, + UserTransactionResponse, +} from "@aptos-labs/ts-sdk"; import { getEnv } from "@ledgerhq/live-env"; -import { isUndefined } from "lodash"; +import network from "@ledgerhq/live-network/network"; +import BigNumber from "bignumber.js"; +import isUndefined from "lodash/isUndefined"; import { isTestnet } from "../logic"; -import { - GetAccountTransactionsDataQuery, - GetAccountTransactionsDataQueryVariables, -} from "./graphql/types"; import { GetAccountTransactionsData, GetAccountTransactionsDataGt, GetAccountTransactionsDataLt, } from "./graphql/queries"; +import { + GetAccountTransactionsDataQuery, + GetAccountTransactionsDataQueryVariables, +} from "./graphql/types"; -import type { - AptosResource, - AptosCoinStoreResource, - AptosTransaction, - Transaction, -} from "../types"; +import type { AptosTransaction, TransactionOptions } from "../types"; -const getApiEndpoint = (currencyId: string) => - isTestnet(currencyId) ? getEnv("APTOS_TESTNET_API_ENDPOINT") : getEnv("APTOS_API_ENDPOINT"); +const getNetwork = (currencyId: string) => + isTestnet(currencyId) ? Network.TESTNET : Network.MAINNET; const getIndexerEndpoint = (currencyId: string) => isTestnet(currencyId) ? getEnv("APTOS_TESTNET_INDEXER_ENDPOINT") : getEnv("APTOS_INDEXER_ENDPOINT"); export class AptosAPI { - private apiUrl: string; + private network: Network; private indexerUrl: string; - private aptosClient: AptosClient; + private aptosConfig: AptosConfig; + private aptosClient: Aptos; private apolloClient: ApolloClient; constructor(currencyId: string) { - this.apiUrl = getApiEndpoint(currencyId); + this.network = getNetwork(currencyId); this.indexerUrl = getIndexerEndpoint(currencyId); - this.aptosClient = new AptosClient(this.apiUrl); + this.aptosConfig = new AptosConfig({ network: this.network }); + this.aptosClient = new Aptos(this.aptosConfig); this.apolloClient = new ApolloClient({ uri: this.indexerUrl, cache: new InMemoryCache(), @@ -89,7 +98,9 @@ export class AptosAPI { async richItemByVersion(version: number): Promise { try { - const tx: AptosTypes.Transaction = await this.aptosClient.getTransactionByVersion(version); + const tx: TransactionResponse = await this.aptosClient.getTransactionByVersion({ + ledgerVersion: version, + }); const block = await this.getBlock(version); return { ...tx, @@ -100,8 +111,8 @@ export class AptosAPI { } } - async getAccount(address: string): Promise { - return this.aptosClient.getAccount(address); + async getAccount(address: string): Promise { + return this.aptosClient.getAccountInfo({ accountAddress: address }); } async getAccountInfo(address: string, startAt: string) { @@ -118,82 +129,111 @@ export class AptosAPI { }; } - async estimateGasPrice(): Promise { - return this.aptosClient.estimateGasPrice(); + async estimateGasPrice(): Promise { + return this.aptosClient.getGasPriceEstimation(); } async generateTransaction( address: string, - payload: AptosTypes.EntryFunctionPayload, - options: Transaction["options"], - ): Promise { - const opts: Partial = {}; + payload: InputEntryFunctionData, + options: TransactionOptions, + ): Promise { + const opts: Partial = {}; if (!isUndefined(options.maxGasAmount)) { - opts.max_gas_amount = BigNumber(options.maxGasAmount).toString(); + opts.maxGasAmount = Number(options.maxGasAmount); } if (!isUndefined(options.gasUnitPrice)) { - opts.gas_unit_price = BigNumber(options.gasUnitPrice).toString(); + opts.gasUnitPrice = Number(options.gasUnitPrice); } if (!isUndefined(options.sequenceNumber)) { - opts.sequence_number = BigNumber(options.sequenceNumber).toString(); + opts.accountSequenceNumber = Number(options.sequenceNumber); } if (!isUndefined(options.expirationTimestampSecs)) { - opts.expiration_timestamp_secs = BigNumber(options.expirationTimestampSecs).toString(); - } - - const tx = await this.aptosClient.generateTransaction(address, payload, opts); - - let serverTimestamp = tx.expiration_timestamp_secs; - if (isUndefined(opts.expiration_timestamp_secs)) { + opts.expireTimestamp = Number(options.expirationTimestampSecs); + } else { try { const ts = (await this.aptosClient.getLedgerInfo()).ledger_timestamp; - serverTimestamp = BigInt(Math.ceil(+ts / 1_000_000 + 2 * 60)); // in microseconds + opts.expireTimestamp = Number(Math.ceil(+ts / 1_000_000 + 2 * 60)); // in microseconds } catch (_) { // skip } } - const ntx = new TxnBuilderTypes.RawTransaction( - tx.sender, - tx.sequence_number, - tx.payload, - tx.max_gas_amount, - tx.gas_unit_price, - serverTimestamp, - tx.chain_id, - ); - - return ntx; + return this.aptosClient.transaction.build.simple({ + sender: address, + data: payload, + options: opts, + }); } async simulateTransaction( - address: TxnBuilderTypes.Ed25519PublicKey, - tx: TxnBuilderTypes.RawTransaction, + address: Ed25519PublicKey, + tx: SimpleTransaction, options = { estimateGasUnitPrice: true, estimateMaxGasAmount: true, estimatePrioritizedGasUnitPrice: false, }, - ): Promise { - return this.aptosClient.simulateTransaction(address, tx, options); + ): Promise { + return this.aptosClient.transaction.simulate.simple({ + signerPublicKey: address, + transaction: tx, + options, + }); } async broadcast(signature: string): Promise { - const txBytes = Uint8Array.from(Buffer.from(signature, "hex")); - const pendingTx = await this.aptosClient.submitTransaction(txBytes); + // *** PREVIOUSLY *** + // const txBytes = Uint8Array.from(Buffer.from(signature, "hex")); + // const pendingTx = await this.aptosClient.submitTransaction(txBytes); + // return pendingTx.hash; + // *** PREVIOUSLY *** + + // const accountInfo = await this.aptosClient.getAccountInfo({ accountAddress: address }); + // const alice = Account.fromDerivationPath({ path: address, mnemonic: "" }); + const alice = Account.generate(); + + const senderAuthenticator = this.aptosClient.transaction.sign({ + signer: alice, // TODO: get sender account object + // signer: { + // accountAddress: transaction.rawTransaction.sender, + // publicKey: alice.publicKey, // TODO: get public key from account + // signingScheme: SigningScheme.Ed25519, + // }, + + transaction: {}, // TODO: Get transaction + // transaction: { + // rawTransaction: { + // sender: , + // sequence_number: accountInfo.sequence_number, + // payload + // max_gas_amount + // gas_unit_price + // expiration_timestamp_secs + // chain_id + // } + // }, + }); + const pendingTx = await this.aptosClient.transaction.submit.simple({ + senderAuthenticator, + transaction, + }); return pendingTx.hash; } private async getBalance(address: string): Promise { try { - const balanceRes = await this.aptosClient.getAccountResource( - address, - "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", - ); - const balance = (balanceRes as AptosResource).data.coin.value; + const [balanceStr] = await this.aptosClient.view<[string]>({ + payload: { + function: "0x1::coin::balance", + typeArguments: ["0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"], // TODO: or is it "0x1::aptos_coin::AptosCoin" ?? + functionArguments: [address], + }, + }); + const balance = parseInt(balanceStr, 10); return new BigNumber(balance); } catch (e: any) { return new BigNumber(0); @@ -203,13 +243,13 @@ export class AptosAPI { private async getHeight(): Promise { const { data } = await network({ method: "GET", - url: this.apiUrl, + url: this.network, }); return parseInt(data.block_height); } private async getBlock(version: number) { - const block = await this.aptosClient.getBlockByVersion(version); + const block = await this.aptosClient.getBlockByVersion({ ledgerVersion: version }); return { height: parseInt(block.block_height), hash: block.block_hash, diff --git a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts index b383065475d3..e4cbd765aafc 100644 --- a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts +++ b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts @@ -1,7 +1,6 @@ -import BigNumber from "bignumber.js"; -import { TxnBuilderTypes } from "aptos"; import type { Account } from "@ledgerhq/types-live"; - +import BigNumber from "bignumber.js"; +import { SimpleTransaction } from "@aptos-labs/ts-sdk"; import { AptosAPI } from "./api"; import { DEFAULT_GAS, DEFAULT_GAS_PRICE, normalizeTransactionOptions } from "./logic"; import type { Transaction } from "./types"; @@ -10,7 +9,7 @@ const buildTransaction = async ( account: Account, transaction: Transaction, aptosClient: AptosAPI, -): Promise => { +): Promise => { const amount = transaction.useAllAmount ? getMaxSendBalance( account.spendableBalance, @@ -21,7 +20,15 @@ const buildTransaction = async ( const txPayload = getPayload(transaction.recipient, amount); const txOptions = normalizeTransactionOptions(transaction.options); - const tx = await aptosClient.generateTransaction(account.freshAddress, txPayload, txOptions); + const tx = await aptosClient.generateTransaction( + account.freshAddress, + { + function: txPayload.function as `${string}::${string}::${string}`, + functionArguments: txPayload.arguments, + typeArguments: txPayload.type_arguments, + }, + txOptions, + ); return tx; }; diff --git a/libs/ledger-live-common/src/families/aptos/js-getFeesForTransaction.ts b/libs/ledger-live-common/src/families/aptos/js-getFeesForTransaction.ts index f40194f6f066..cfa741d2e959 100644 --- a/libs/ledger-live-common/src/families/aptos/js-getFeesForTransaction.ts +++ b/libs/ledger-live-common/src/families/aptos/js-getFeesForTransaction.ts @@ -1,12 +1,11 @@ -import BigNumber from "bignumber.js"; -import { HexString, TxnBuilderTypes } from "aptos"; +import { Ed25519PublicKey } from "@aptos-labs/ts-sdk"; +import { log } from "@ledgerhq/logs"; import type { Account } from "@ledgerhq/types-live"; - +import BigNumber from "bignumber.js"; import { AptosAPI } from "./api"; import buildTransaction from "./js-buildTransaction"; import { DEFAULT_GAS, DEFAULT_GAS_PRICE, ESTIMATE_GAS_MUL } from "./logic"; import type { Transaction, TransactionErrors } from "./types"; -import { log } from "@ledgerhq/logs"; type IGetEstimatedGasReturnType = { fees: BigNumber; @@ -50,9 +49,7 @@ export const getFee = async ( if (account.xpub) { try { - const publickKey = account.xpub as string; - const pubKeyUint = new HexString(publickKey).toUint8Array(); - const publicKeyEd = new TxnBuilderTypes.Ed25519PublicKey(pubKeyUint); + const publicKeyEd = new Ed25519PublicKey(account.xpub as string); const tx = await buildTransaction(account, transaction, aptosClient); const simulation = await aptosClient.simulateTransaction(publicKeyEd, tx); const completedTx = simulation[0]; From 5865e7d506d0314bb10c57f044ece8f66c39c667 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Tue, 10 Dec 2024 09:55:20 +0000 Subject: [PATCH 04/14] feat(aptos): update aptos package --- .../src/families/aptos/LedgerAccount.ts | 76 ++++++++++--------- .../src/families/aptos/api/index.ts | 70 +++++++---------- .../src/families/aptos/js-broadcast.ts | 6 +- .../src/families/aptos/js-buildTransaction.ts | 4 +- .../src/families/aptos/logic.ts | 19 +++-- 5 files changed, 79 insertions(+), 96 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts index 29d6e262a44f..781427512e7f 100644 --- a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts +++ b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts @@ -1,21 +1,24 @@ -import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; -import Transport from "@ledgerhq/hw-transport"; -import HwAptos from "@ledgerhq/hw-app-aptos"; import { - AptosAccount, - HexString, - MaybeHexString, - TransactionBuilder, - TxnBuilderTypes, - BCS, -} from "aptos"; + Account, + AccountAuthenticatorEd25519, + Ed25519PublicKey, + Ed25519Signature, + Hex, + RawTransaction, + SimpleTransaction, + generateSignedTransaction, + generateSigningMessageForTransaction, +} from "@aptos-labs/ts-sdk"; +import HwAptos from "@ledgerhq/hw-app-aptos"; +import Transport from "@ledgerhq/hw-transport"; +import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; export default class LedgerAccount { private readonly hdPath: string; private client?: HwAptos; private publicKey: Buffer = Buffer.from([]); - private accountAddress: HexString = new HexString(""); + private accountAddress: Hex = Hex.fromHexString(""); static async fromLedgerConnection(transport: Transport, path: string): Promise { const account = new LedgerAccount(path); @@ -23,14 +26,14 @@ export default class LedgerAccount { return account; } - toAptosAccount(): AptosAccount { - return this as unknown as AptosAccount; + toAptosAccount(): Account { + return this as unknown as Account; } constructor(path: string, pubKey?: string) { this.hdPath = path; if (pubKey) { - this.publicKey = Buffer.from(HexString.ensure(pubKey).toUint8Array()); + this.publicKey = Buffer.from(Hex.fromHexString(pubKey).toUint8Array()); this.accountAddress = this.authKey(); } } @@ -39,7 +42,7 @@ export default class LedgerAccount { this.client = new HwAptos(transport); if (!this.publicKey.length && !display) { const response = await this.client.getAddress(this.hdPath, display); - this.accountAddress = new HexString(response.address); + this.accountAddress = Hex.fromHexString(response.address); this.publicKey = response.publicKey; } } @@ -48,49 +51,48 @@ export default class LedgerAccount { return this.hdPath; } - address(): HexString { + address(): Hex { return this.accountAddress; } - authKey(): HexString { + authKey(): Hex { const hash = sha3Hash.create(); - hash.update(this.publicKey); + hash.update(this.publicKey.toString()); hash.update("\x00"); - return HexString.fromBuffer(hash.digest()); + return Hex.fromHexString(hash.digest().toString()); } - pubKey(): HexString { - return HexString.fromBuffer(this.publicKey); + pubKey(): Hex { + return Hex.fromHexString(this.publicKey.toString()); } - async asyncSignBuffer(buffer: Uint8Array): Promise { + async asyncSignBuffer(buffer: Uint8Array): Promise { if (!this.client) { throw new Error("LedgerAccount not initialized"); } const response = await this.client.signTransaction(this.hdPath, Buffer.from(buffer)); - return HexString.fromBuffer(response.signature); + return Hex.fromHexString(response.signature.toString()); } - async asyncSignHexString(hexString: MaybeHexString): Promise { - const toSign = HexString.ensure(hexString).toUint8Array(); + async asyncSignHexString(hexString: Hex): Promise { + const toSign = hexString.toUint8Array(); return this.asyncSignBuffer(toSign); } - async rawToSigned( - rawTxn: TxnBuilderTypes.RawTransaction, - ): Promise { - const signingMessage = TransactionBuilder.getSigningMessage(rawTxn); + async signTransaction(rawTxn: RawTransaction): Promise { + const signingMessage = generateSigningMessageForTransaction({ + rawTransaction: rawTxn, + } as SimpleTransaction); const sigHexStr = await this.asyncSignBuffer(signingMessage); - const signature = new TxnBuilderTypes.Ed25519Signature(sigHexStr.toUint8Array()); - const authenticator = new TxnBuilderTypes.TransactionAuthenticatorEd25519( - new TxnBuilderTypes.Ed25519PublicKey(this.publicKey), + const signature = new Ed25519Signature(sigHexStr.toUint8Array()); + const authenticator = new AccountAuthenticatorEd25519( + new Ed25519PublicKey(this.publicKey.toString()), signature, ); - return new TxnBuilderTypes.SignedTransaction(rawTxn, authenticator); - } - - async signTransaction(rawTxn: TxnBuilderTypes.RawTransaction): Promise { - return BCS.bcsToBytes(await this.rawToSigned(rawTxn)); + return generateSignedTransaction({ + transaction: { rawTransaction: rawTxn } as SimpleTransaction, + senderAuthenticator: authenticator, + }); } } diff --git a/libs/ledger-live-common/src/families/aptos/api/index.ts b/libs/ledger-live-common/src/families/aptos/api/index.ts index 7c4712bdd27c..be56555eb633 100644 --- a/libs/ledger-live-common/src/families/aptos/api/index.ts +++ b/libs/ledger-live-common/src/families/aptos/api/index.ts @@ -1,14 +1,17 @@ import { ApolloClient, InMemoryCache } from "@apollo/client"; import { - Account, AccountData, Aptos, + AptosApiType, AptosConfig, Ed25519PublicKey, GasEstimation, InputEntryFunctionData, InputGenerateTransactionOptions, + MimeType, Network, + post, + RawTransaction, SimpleTransaction, TransactionResponse, UserTransactionResponse, @@ -137,7 +140,7 @@ export class AptosAPI { address: string, payload: InputEntryFunctionData, options: TransactionOptions, - ): Promise { + ): Promise { const opts: Partial = {}; if (!isUndefined(options.maxGasAmount)) { opts.maxGasAmount = Number(options.maxGasAmount); @@ -162,16 +165,21 @@ export class AptosAPI { } } - return this.aptosClient.transaction.build.simple({ - sender: address, - data: payload, - options: opts, - }); + return this.aptosClient.transaction.build + .simple({ + sender: address, + data: payload, + options: opts, + }) + .then(t => t.rawTransaction) + .catch((error: any) => { + throw error; + }); } async simulateTransaction( address: Ed25519PublicKey, - tx: SimpleTransaction, + tx: RawTransaction, options = { estimateGasUnitPrice: true, estimateMaxGasAmount: true, @@ -180,48 +188,22 @@ export class AptosAPI { ): Promise { return this.aptosClient.transaction.simulate.simple({ signerPublicKey: address, - transaction: tx, + transaction: { rawTransaction: tx } as SimpleTransaction, options, }); } async broadcast(signature: string): Promise { - // *** PREVIOUSLY *** - // const txBytes = Uint8Array.from(Buffer.from(signature, "hex")); - // const pendingTx = await this.aptosClient.submitTransaction(txBytes); - // return pendingTx.hash; - // *** PREVIOUSLY *** - - // const accountInfo = await this.aptosClient.getAccountInfo({ accountAddress: address }); - // const alice = Account.fromDerivationPath({ path: address, mnemonic: "" }); - const alice = Account.generate(); - - const senderAuthenticator = this.aptosClient.transaction.sign({ - signer: alice, // TODO: get sender account object - // signer: { - // accountAddress: transaction.rawTransaction.sender, - // publicKey: alice.publicKey, // TODO: get public key from account - // signingScheme: SigningScheme.Ed25519, - // }, - - transaction: {}, // TODO: Get transaction - // transaction: { - // rawTransaction: { - // sender: , - // sequence_number: accountInfo.sequence_number, - // payload - // max_gas_amount - // gas_unit_price - // expiration_timestamp_secs - // chain_id - // } - // }, - }); - const pendingTx = await this.aptosClient.transaction.submit.simple({ - senderAuthenticator, - transaction, + const txBytes = Uint8Array.from(Buffer.from(signature, "hex")); + const pendingTx = await post({ + contentType: MimeType.BCS_SIGNED_TRANSACTION, + aptosConfig: this.aptosClient.config, + body: txBytes, + path: "/v1/transactions", + type: AptosApiType.FULLNODE, + originMethod: "", }); - return pendingTx.hash; + return (pendingTx.data as TransactionResponse).hash; } private async getBalance(address: string): Promise { diff --git a/libs/ledger-live-common/src/families/aptos/js-broadcast.ts b/libs/ledger-live-common/src/families/aptos/js-broadcast.ts index 3a2e14b6c93f..12627c40b024 100644 --- a/libs/ledger-live-common/src/families/aptos/js-broadcast.ts +++ b/libs/ledger-live-common/src/families/aptos/js-broadcast.ts @@ -9,9 +9,9 @@ const broadcast = async ({ signedOperation: SignedOperation; account: Account; }): Promise => { - const { signature, operation } = signedOperation; - const hash = await new AptosAPI(account.currency.id).broadcast(signature); - return patchOperationWithHash(operation, hash); + // const { signature, operation } = signedOperation; + const hash = await new AptosAPI(account.currency.id).broadcast(signedOperation, account); + return patchOperationWithHash(signedOperation.operation, hash); }; export default broadcast; diff --git a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts index e4cbd765aafc..cc0e26e8a500 100644 --- a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts +++ b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts @@ -1,6 +1,6 @@ +import { RawTransaction } from "@aptos-labs/ts-sdk"; import type { Account } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; -import { SimpleTransaction } from "@aptos-labs/ts-sdk"; import { AptosAPI } from "./api"; import { DEFAULT_GAS, DEFAULT_GAS_PRICE, normalizeTransactionOptions } from "./logic"; import type { Transaction } from "./types"; @@ -9,7 +9,7 @@ const buildTransaction = async ( account: Account, transaction: Transaction, aptosClient: AptosAPI, -): Promise => { +): Promise => { const amount = transaction.useAllAmount ? getMaxSendBalance( account.spendableBalance, diff --git a/libs/ledger-live-common/src/families/aptos/logic.ts b/libs/ledger-live-common/src/families/aptos/logic.ts index 7ddcabf54a02..504f6e54870e 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.ts @@ -1,19 +1,18 @@ +import { EntryFunction } from "@aptos-labs/ts-sdk"; import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; -import BigNumber from "bignumber.js"; - -import type { Types as AptosTypes } from "aptos"; import type { Operation, OperationType } from "@ledgerhq/types-live"; -import type { AptosTransaction, Transaction } from "./types"; +import BigNumber from "bignumber.js"; import { encodeOperationId } from "../../operation"; +import type { AptosTransaction, Transaction } from "./types"; import { - TRANSFER_TYPES, - DELEGATION_POOL_TYPES, - BATCH_TRANSFER_TYPES, - TX_TYPE, - APTOS_OBJECT_TRANSFER, APTOS_DELEGATION_WITHDRAW, + APTOS_OBJECT_TRANSFER, + BATCH_TRANSFER_TYPES, + DELEGATION_POOL_TYPES, DIRECTION, + TRANSFER_TYPES, + TX_TYPE, } from "./constants"; export const DEFAULT_GAS = 5; @@ -100,7 +99,7 @@ export const txsToOps = (info: any, id: string, txs: (AptosTransaction | null)[] const op: Operation = getBlankOperation(tx, id); op.fee = new BigNumber(tx.gas_used).multipliedBy(BigNumber(tx.gas_unit_price)); - const payload = tx.payload as AptosTypes.EntryFunctionPayload; + const payload = tx.payload as EntryFunction; let type; if ("function" in payload) { From d0475bf83c315d0492899a86487a8cd57636709e Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Tue, 10 Dec 2024 12:38:00 +0000 Subject: [PATCH 05/14] feat(aptos): update aptos package --- libs/ledger-live-common/src/families/aptos/js-broadcast.ts | 6 +++--- libs/ledger-live-common/src/families/aptos/logic.ts | 3 +-- libs/ledger-live-common/src/families/aptos/types.ts | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/js-broadcast.ts b/libs/ledger-live-common/src/families/aptos/js-broadcast.ts index 12627c40b024..3a2e14b6c93f 100644 --- a/libs/ledger-live-common/src/families/aptos/js-broadcast.ts +++ b/libs/ledger-live-common/src/families/aptos/js-broadcast.ts @@ -9,9 +9,9 @@ const broadcast = async ({ signedOperation: SignedOperation; account: Account; }): Promise => { - // const { signature, operation } = signedOperation; - const hash = await new AptosAPI(account.currency.id).broadcast(signedOperation, account); - return patchOperationWithHash(signedOperation.operation, hash); + const { signature, operation } = signedOperation; + const hash = await new AptosAPI(account.currency.id).broadcast(signature); + return patchOperationWithHash(operation, hash); }; export default broadcast; diff --git a/libs/ledger-live-common/src/families/aptos/logic.ts b/libs/ledger-live-common/src/families/aptos/logic.ts index 504f6e54870e..c239f6ba1b33 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.ts @@ -1,4 +1,3 @@ -import { EntryFunction } from "@aptos-labs/ts-sdk"; import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; import type { Operation, OperationType } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; @@ -99,7 +98,7 @@ export const txsToOps = (info: any, id: string, txs: (AptosTransaction | null)[] const op: Operation = getBlankOperation(tx, id); op.fee = new BigNumber(tx.gas_used).multipliedBy(BigNumber(tx.gas_unit_price)); - const payload = tx.payload as EntryFunction; + const payload = tx.payload; let type; if ("function" in payload) { diff --git a/libs/ledger-live-common/src/families/aptos/types.ts b/libs/ledger-live-common/src/families/aptos/types.ts index a6dd0d349957..ef56df863962 100644 --- a/libs/ledger-live-common/src/families/aptos/types.ts +++ b/libs/ledger-live-common/src/families/aptos/types.ts @@ -1,4 +1,4 @@ -import type { TransactionResponse } from "@aptos-labs/ts-sdk"; +import type { UserTransactionResponse } from "@aptos-labs/ts-sdk"; import type { Account, TransactionCommon, @@ -8,7 +8,7 @@ import type { } from "@ledgerhq/types-live"; import type { BigNumber } from "bignumber.js"; -export type AptosTransaction = TransactionResponse & { +export type AptosTransaction = UserTransactionResponse & { block: { height: number; hash: string; From 266106cb756872ab2658863e62b877bb2e3b6969 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Tue, 10 Dec 2024 14:50:04 +0000 Subject: [PATCH 06/14] fix(aptos): get network and indexer api url from network --- .../src/families/aptos/api/index.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/api/index.ts b/libs/ledger-live-common/src/families/aptos/api/index.ts index be56555eb633..4a4ecd37c13c 100644 --- a/libs/ledger-live-common/src/families/aptos/api/index.ts +++ b/libs/ledger-live-common/src/families/aptos/api/index.ts @@ -10,13 +10,14 @@ import { InputGenerateTransactionOptions, MimeType, Network, + NetworkToIndexerAPI, + NetworkToNodeAPI, post, RawTransaction, SimpleTransaction, TransactionResponse, UserTransactionResponse, } from "@aptos-labs/ts-sdk"; -import { getEnv } from "@ledgerhq/live-env"; import network from "@ledgerhq/live-network/network"; import BigNumber from "bignumber.js"; import isUndefined from "lodash/isUndefined"; @@ -36,10 +37,6 @@ import type { AptosTransaction, TransactionOptions } from "../types"; const getNetwork = (currencyId: string) => isTestnet(currencyId) ? Network.TESTNET : Network.MAINNET; -const getIndexerEndpoint = (currencyId: string) => - isTestnet(currencyId) - ? getEnv("APTOS_TESTNET_INDEXER_ENDPOINT") - : getEnv("APTOS_INDEXER_ENDPOINT"); export class AptosAPI { private network: Network; @@ -50,7 +47,7 @@ export class AptosAPI { constructor(currencyId: string) { this.network = getNetwork(currencyId); - this.indexerUrl = getIndexerEndpoint(currencyId); + this.indexerUrl = NetworkToIndexerAPI[this.network]; this.aptosConfig = new AptosConfig({ network: this.network }); this.aptosClient = new Aptos(this.aptosConfig); this.apolloClient = new ApolloClient({ @@ -225,7 +222,7 @@ export class AptosAPI { private async getHeight(): Promise { const { data } = await network({ method: "GET", - url: this.network, + url: NetworkToNodeAPI[this.network], }); return parseInt(data.block_height); } From cace33fb849a79e8f472c9b2fa1d278fbf66d980 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Tue, 10 Dec 2024 17:35:46 +0000 Subject: [PATCH 07/14] fix(aptos): account address issues --- .../src/families/aptos/LedgerAccount.ts | 27 ++++++++++--------- .../src/families/aptos/api/index.ts | 4 +-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts index 781427512e7f..250e9c9f7e40 100644 --- a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts +++ b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts @@ -1,5 +1,6 @@ import { Account, + AccountAddress, AccountAuthenticatorEd25519, Ed25519PublicKey, Ed25519Signature, @@ -18,7 +19,7 @@ export default class LedgerAccount { private client?: HwAptos; private publicKey: Buffer = Buffer.from([]); - private accountAddress: Hex = Hex.fromHexString(""); + private accountAddress: AccountAddress = new AccountAddress(new Uint8Array(32)); static async fromLedgerConnection(transport: Transport, path: string): Promise { const account = new LedgerAccount(path); @@ -33,7 +34,7 @@ export default class LedgerAccount { constructor(path: string, pubKey?: string) { this.hdPath = path; if (pubKey) { - this.publicKey = Buffer.from(Hex.fromHexString(pubKey).toUint8Array()); + this.publicKey = Buffer.from(AccountAddress.from(pubKey).toUint8Array()); this.accountAddress = this.authKey(); } } @@ -42,7 +43,7 @@ export default class LedgerAccount { this.client = new HwAptos(transport); if (!this.publicKey.length && !display) { const response = await this.client.getAddress(this.hdPath, display); - this.accountAddress = Hex.fromHexString(response.address); + this.accountAddress = AccountAddress.from(response.address); this.publicKey = response.publicKey; } } @@ -51,19 +52,19 @@ export default class LedgerAccount { return this.hdPath; } - address(): Hex { + address(): AccountAddress { return this.accountAddress; } - authKey(): Hex { + authKey(): AccountAddress { const hash = sha3Hash.create(); - hash.update(this.publicKey.toString()); + hash.update(this.publicKey.toString("hex")); hash.update("\x00"); - return Hex.fromHexString(hash.digest().toString()); + return AccountAddress.from(hash.digest()); } - pubKey(): Hex { - return Hex.fromHexString(this.publicKey.toString()); + pubKey(): AccountAddress { + return AccountAddress.from(this.publicKey.toString("hex")); } async asyncSignBuffer(buffer: Uint8Array): Promise { @@ -71,10 +72,12 @@ export default class LedgerAccount { throw new Error("LedgerAccount not initialized"); } const response = await this.client.signTransaction(this.hdPath, Buffer.from(buffer)); - return Hex.fromHexString(response.signature.toString()); + return Hex.fromHexString(response.signature.toString("hex")); } - async asyncSignHexString(hexString: Hex): Promise { + async asyncSignHexString(hexString: AccountAddress): Promise { + const isValidAddress = AccountAddress.isValid({ input: hexString }); + if (!isValidAddress) throw new Error("Invalid account address"); const toSign = hexString.toUint8Array(); return this.asyncSignBuffer(toSign); } @@ -86,7 +89,7 @@ export default class LedgerAccount { const sigHexStr = await this.asyncSignBuffer(signingMessage); const signature = new Ed25519Signature(sigHexStr.toUint8Array()); const authenticator = new AccountAuthenticatorEd25519( - new Ed25519PublicKey(this.publicKey.toString()), + new Ed25519PublicKey(this.publicKey.toString("hex")), signature, ); diff --git a/libs/ledger-live-common/src/families/aptos/api/index.ts b/libs/ledger-live-common/src/families/aptos/api/index.ts index 4a4ecd37c13c..b3711a1d800f 100644 --- a/libs/ledger-live-common/src/families/aptos/api/index.ts +++ b/libs/ledger-live-common/src/families/aptos/api/index.ts @@ -196,7 +196,7 @@ export class AptosAPI { contentType: MimeType.BCS_SIGNED_TRANSACTION, aptosConfig: this.aptosClient.config, body: txBytes, - path: "/v1/transactions", + path: "transactions", type: AptosApiType.FULLNODE, originMethod: "", }); @@ -208,7 +208,7 @@ export class AptosAPI { const [balanceStr] = await this.aptosClient.view<[string]>({ payload: { function: "0x1::coin::balance", - typeArguments: ["0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"], // TODO: or is it "0x1::aptos_coin::AptosCoin" ?? + typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [address], }, }); From ec41ea469bc3a255f77a0c0506c45ffa9dc61d36 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Thu, 12 Dec 2024 09:38:12 +0000 Subject: [PATCH 08/14] refactor: set private methods --- .../src/families/aptos/api/index.ts | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/api/index.ts b/libs/ledger-live-common/src/families/aptos/api/index.ts index b3711a1d800f..9ba6cdcec456 100644 --- a/libs/ledger-live-common/src/families/aptos/api/index.ts +++ b/libs/ledger-live-common/src/families/aptos/api/index.ts @@ -59,58 +59,6 @@ export class AptosAPI { }); } - async fetchTransactions(address: string, lt?: string, gt?: string) { - if (!address) { - return []; - } - - // WORKAROUND: Where is no way to pass optional bigint var to query - let query = GetAccountTransactionsData; - if (lt) { - query = GetAccountTransactionsDataLt; - } - if (gt) { - query = GetAccountTransactionsDataGt; - } - - const queryResponse = await this.apolloClient.query< - GetAccountTransactionsDataQuery, - GetAccountTransactionsDataQueryVariables - >({ - query, - variables: { - address, - limit: 1000, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - lt, - gt, - }, - fetchPolicy: "network-only", - }); - - return Promise.all( - queryResponse.data.address_version_from_move_resources.map(({ transaction_version }) => { - return this.richItemByVersion(transaction_version); - }), - ); - } - - async richItemByVersion(version: number): Promise { - try { - const tx: TransactionResponse = await this.aptosClient.getTransactionByVersion({ - ledgerVersion: version, - }); - const block = await this.getBlock(version); - return { - ...tx, - block, - } as AptosTransaction; - } catch (error) { - return null; - } - } - async getAccount(address: string): Promise { return this.aptosClient.getAccountInfo({ accountAddress: address }); } @@ -219,6 +167,58 @@ export class AptosAPI { } } + private async fetchTransactions(address: string, lt?: string, gt?: string) { + if (!address) { + return []; + } + + // WORKAROUND: Where is no way to pass optional bigint var to query + let query = GetAccountTransactionsData; + if (lt) { + query = GetAccountTransactionsDataLt; + } + if (gt) { + query = GetAccountTransactionsDataGt; + } + + const queryResponse = await this.apolloClient.query< + GetAccountTransactionsDataQuery, + GetAccountTransactionsDataQueryVariables + >({ + query, + variables: { + address, + limit: 1000, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + lt, + gt, + }, + fetchPolicy: "network-only", + }); + + return Promise.all( + queryResponse.data.address_version_from_move_resources.map(({ transaction_version }) => { + return this.richItemByVersion(transaction_version); + }), + ); + } + + private async richItemByVersion(version: number): Promise { + try { + const tx: TransactionResponse = await this.aptosClient.getTransactionByVersion({ + ledgerVersion: version, + }); + const block = await this.getBlock(version); + return { + ...tx, + block, + } as AptosTransaction; + } catch (error) { + return null; + } + } + private async getHeight(): Promise { const { data } = await network({ method: "GET", From 87f5d72945e6eb259efbb41f9f7675ee128df9df Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Thu, 12 Dec 2024 10:04:57 +0000 Subject: [PATCH 09/14] refactor: build transaction module and api --- .../src/families/aptos/LedgerAccount.ts | 7 +++++-- .../src/families/aptos/js-buildTransaction.ts | 18 +++++------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts index 250e9c9f7e40..236b53ac3269 100644 --- a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts +++ b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts @@ -19,7 +19,9 @@ export default class LedgerAccount { private client?: HwAptos; private publicKey: Buffer = Buffer.from([]); - private accountAddress: AccountAddress = new AccountAddress(new Uint8Array(32)); + private accountAddress: AccountAddress = new AccountAddress( + new Uint8Array(AccountAddress.LENGTH), + ); static async fromLedgerConnection(transport: Transport, path: string): Promise { const account = new LedgerAccount(path); @@ -72,7 +74,8 @@ export default class LedgerAccount { throw new Error("LedgerAccount not initialized"); } const response = await this.client.signTransaction(this.hdPath, Buffer.from(buffer)); - return Hex.fromHexString(response.signature.toString("hex")); + // return Hex.fromHexString(response.signature.toString("hex")); + return new Hex(new Uint8Array(response.signature)); } async asyncSignHexString(hexString: AccountAddress): Promise { diff --git a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts index cc0e26e8a500..2ad270126723 100644 --- a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts +++ b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts @@ -1,4 +1,4 @@ -import { RawTransaction } from "@aptos-labs/ts-sdk"; +import { InputEntryFunctionData, RawTransaction } from "@aptos-labs/ts-sdk"; import type { Account } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; import { AptosAPI } from "./api"; @@ -20,15 +20,7 @@ const buildTransaction = async ( const txPayload = getPayload(transaction.recipient, amount); const txOptions = normalizeTransactionOptions(transaction.options); - const tx = await aptosClient.generateTransaction( - account.freshAddress, - { - function: txPayload.function as `${string}::${string}::${string}`, - functionArguments: txPayload.arguments, - typeArguments: txPayload.type_arguments, - }, - txOptions, - ); + const tx = await aptosClient.generateTransaction(account.freshAddress, txPayload, txOptions); return tx; }; @@ -40,11 +32,11 @@ const getMaxSendBalance = (amount: BigNumber, gas: BigNumber, gasPrice: BigNumbe return amount; }; -const getPayload = (sendTo: string, amount: BigNumber) => { +const getPayload = (sendTo: string, amount: BigNumber): InputEntryFunctionData => { return { function: "0x1::aptos_account::transfer_coins", - type_arguments: ["0x1::aptos_coin::AptosCoin"], - arguments: [sendTo, amount.toString()], + typeArguments: ["0x1::aptos_coin::AptosCoin"], + functionArguments: [sendTo, amount.toString()], }; }; From f6f8f3eca698853d7a0e2ba476ed01ea84a6d62c Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Thu, 12 Dec 2024 11:04:05 +0000 Subject: [PATCH 10/14] refactor: fix logic and constants --- .../src/families/aptos/LedgerAccount.ts | 1 - .../src/families/aptos/api/index.ts | 3 +- .../src/families/aptos/constants.ts | 56 ++--- .../src/families/aptos/js-buildTransaction.ts | 3 +- .../src/families/aptos/logic.ts | 214 ++++++++++++------ 5 files changed, 161 insertions(+), 116 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts index 236b53ac3269..29d949fc6a2a 100644 --- a/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts +++ b/libs/ledger-live-common/src/families/aptos/LedgerAccount.ts @@ -74,7 +74,6 @@ export default class LedgerAccount { throw new Error("LedgerAccount not initialized"); } const response = await this.client.signTransaction(this.hdPath, Buffer.from(buffer)); - // return Hex.fromHexString(response.signature.toString("hex")); return new Hex(new Uint8Array(response.signature)); } diff --git a/libs/ledger-live-common/src/families/aptos/api/index.ts b/libs/ledger-live-common/src/families/aptos/api/index.ts index 9ba6cdcec456..d6405c8b3f3b 100644 --- a/libs/ledger-live-common/src/families/aptos/api/index.ts +++ b/libs/ledger-live-common/src/families/aptos/api/index.ts @@ -33,6 +33,7 @@ import { GetAccountTransactionsDataQueryVariables, } from "./graphql/types"; +import { APTOS_ASSET_ID } from "../constants"; import type { AptosTransaction, TransactionOptions } from "../types"; const getNetwork = (currencyId: string) => @@ -156,7 +157,7 @@ export class AptosAPI { const [balanceStr] = await this.aptosClient.view<[string]>({ payload: { function: "0x1::coin::balance", - typeArguments: ["0x1::aptos_coin::AptosCoin"], + typeArguments: [APTOS_ASSET_ID], functionArguments: [address], }, }); diff --git a/libs/ledger-live-common/src/families/aptos/constants.ts b/libs/ledger-live-common/src/families/aptos/constants.ts index 1b1fe0e70870..e97564afa963 100644 --- a/libs/ledger-live-common/src/families/aptos/constants.ts +++ b/libs/ledger-live-common/src/families/aptos/constants.ts @@ -1,51 +1,27 @@ export const LOAD_LIMIT = 10; -export enum TX_TYPE { - REGISTER = "register", - TRANSFER = "transfer", - RECEIVE = "receive", - ALLOW_RECEIVE_NFT = "opt_in_direct_transfer", - FAUCET = "faucet", - SWAP = "swap", - ADD_LIQUIDITY = "add_liquidity", - REGISTER_POOL_AND_ADD_LIQUIDITY = "register_pool_and_add_liquidity", - REMOVE_LIQUIDITY = "remove_liquidity", - TRANSFER_NFT = "transfer_with_opt_in", - RECEIVE_NFT = "receive_nft", - OFFER_NFT = "offer_script", - CANCEL_OFFER_NFT = "cancel_offer_script", - BUY_NFT = "buy", - LIST_NFT = "list", - SELL_NFT = "fill", - EDIT_NFT = "edit", - DELIST_NFT = "delist", - DELETE_NFT = "delete_nft", - CLAIM_NFT = "claim_script", - - STAKE_APTOS = "stake_aptos", - INSTANT_UNSTAKE_APTOS = "instant_unstake", - DEPOSIT = "deposit", - WITHDRAW = "withdraw", - CLAIM_THL = "claim_thl_rewards", - - APPROVE = "approve", - CONTRACT_ADDRESS_CREATED = "contract_address_created", - - UNKNOWN = "unknown", -} - export enum TX_STATUS { PENDING = "pending", FAIL = "fail", SUCCESS = "success", } -export const TRANSFER_TYPES = ["transfer", "transfer_coins"]; -export const BATCH_TRANSFER_TYPES = ["batch_transfer", "batch_transfer_coins"]; -export const DELEGATION_POOL_TYPES = ["add_stake", "withdraw"]; - -export const APTOS_DELEGATION_WITHDRAW = "0x1::delegation_pool::withdraw"; -export const APTOS_OBJECT_TRANSFER = "0x1::object::transfer"; +export const TRANSFER_TYPES = [ + "0x1::aptos_account::transfer", + "0x1::aptos_account::transfer_coins", + "0x1::coin::transfer", +]; +export const BATCH_TRANSFER_TYPES = [ + "0x1::aptos_account::batch_transfer", + "0x1::aptos_account::batch_transfer_coins", +]; +export const DELEGATION_POOL_TYPES = [ + "0x1::delegation_pool::add_stake", + "0x1::delegation_pool::withdraw", +]; + +export const APTOS_ASSET_ID = "0x1::aptos_coin::AptosCoin"; +export const APTOS_COIN_CHANGE = `0x1::coin::CoinStore<${APTOS_ASSET_ID}>`; export enum DIRECTION { IN = "IN", diff --git a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts index 2ad270126723..8a578b602499 100644 --- a/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts +++ b/libs/ledger-live-common/src/families/aptos/js-buildTransaction.ts @@ -2,6 +2,7 @@ import { InputEntryFunctionData, RawTransaction } from "@aptos-labs/ts-sdk"; import type { Account } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; import { AptosAPI } from "./api"; +import { APTOS_ASSET_ID } from "./constants"; import { DEFAULT_GAS, DEFAULT_GAS_PRICE, normalizeTransactionOptions } from "./logic"; import type { Transaction } from "./types"; @@ -35,7 +36,7 @@ const getMaxSendBalance = (amount: BigNumber, gas: BigNumber, gasPrice: BigNumbe const getPayload = (sendTo: string, amount: BigNumber): InputEntryFunctionData => { return { function: "0x1::aptos_account::transfer_coins", - typeArguments: ["0x1::aptos_coin::AptosCoin"], + typeArguments: [APTOS_ASSET_ID], functionArguments: [sendTo, amount.toString()], }; }; diff --git a/libs/ledger-live-common/src/families/aptos/logic.ts b/libs/ledger-live-common/src/families/aptos/logic.ts index c239f6ba1b33..c4d3be0fb1e2 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.ts @@ -1,18 +1,22 @@ +import { + EntryFunctionPayloadResponse, + Event, + TransactionPayloadResponse, + WriteSetChange, + WriteSetChangeWriteResource, +} from "@aptos-labs/ts-sdk"; import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; import type { Operation, OperationType } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; import { encodeOperationId } from "../../operation"; -import type { AptosTransaction, Transaction } from "./types"; - import { - APTOS_DELEGATION_WITHDRAW, - APTOS_OBJECT_TRANSFER, + APTOS_COIN_CHANGE, BATCH_TRANSFER_TYPES, DELEGATION_POOL_TYPES, DIRECTION, TRANSFER_TYPES, - TX_TYPE, } from "./constants"; +import type { AptosTransaction, Transaction } from "./types"; export const DEFAULT_GAS = 5; export const DEFAULT_GAS_PRICE = 100; @@ -61,7 +65,6 @@ export function normalizeTransactionOptions( } return v; }; - return { maxGasAmount: check(options.maxGasAmount), gasUnitPrice: check(options.gasUnitPrice), @@ -90,89 +93,45 @@ const getBlankOperation = ( hasFailed: false, }); -export const txsToOps = (info: any, id: string, txs: (AptosTransaction | null)[]) => { +export const txsToOps = ( + info: { address: string }, + id: string, + txs: (AptosTransaction | null)[], +): Operation[] => { const { address } = info; const ops: Operation[] = []; + txs.forEach(tx => { if (tx !== null) { const op: Operation = getBlankOperation(tx, id); - op.fee = new BigNumber(tx.gas_used).multipliedBy(BigNumber(tx.gas_unit_price)); + op.fee = new BigNumber(tx.gas_used).multipliedBy(new BigNumber(tx.gas_unit_price)); const payload = tx.payload; - let type; - if ("function" in payload) { - type = payload.function.split("::").at(-1) as TX_TYPE; - } else if ("type" in payload) { - type = (payload as any).type as TX_TYPE; + const function_address = getFunctionAddress(payload); + + if (!function_address) { + return; // skip transaction without functions in payload } - // TRANSFER & RECEIVE - if ( - (TRANSFER_TYPES.includes(type) || DELEGATION_POOL_TYPES.includes(type)) && - "arguments" in payload - ) { - // main DELEGATION_POOL functions have identic semantic to TRANSFER_TYPES so we can parse them in the same way - // avoid v2 parse - if ("function" in payload && payload.function === APTOS_OBJECT_TRANSFER) { - op.type = DIRECTION.UNKNOWN; - } else { - if ("function" in payload && payload.function === APTOS_DELEGATION_WITHDRAW) { - // for withdraw function signer should be recipient of the coins - op.recipients.push(tx.sender); - op.senders.push(payload.arguments[0]); - } else { - op.recipients.push(payload.arguments[0]); - op.senders.push(tx.sender); - } - op.value = op.value.plus(payload.arguments[1]); - if (compareAddress(op.recipients[0], address)) { - op.type = DIRECTION.IN; - } else { - op.type = DIRECTION.OUT; - } - } + const { amount_in, amount_out } = getAptosAmounts(tx, address); + op.value = calculateAmount(tx.sender, address, op.fee, amount_in, amount_out); + op.type = compareAddress(tx.sender, address) ? DIRECTION.OUT : DIRECTION.IN; + op.senders.push(tx.sender); - op.hasFailed = !tx.success; - op.id = encodeOperationId(id, tx.hash, op.type); - if (op.type !== DIRECTION.UNKNOWN) ops.push(op); - } else if (BATCH_TRANSFER_TYPES.includes(type) && "arguments" in payload) { - // batch transfers has a list of recipients so we need to find `our` record to show - op.senders.push(tx.sender); + processRecipients(payload, address, op, function_address); + if (op.value.isZero()) { + // skip transaction that result no Aptos change op.type = DIRECTION.UNKNOWN; - if (compareAddress(tx.sender, address)) { - op.type = DIRECTION.OUT; - for (const amount of payload.arguments[1]) { - op.value = op.value.plus(amount); - } - } else { - for (const recipient_num in payload.arguments[0]) { - if (compareAddress(payload.arguments[0][recipient_num], address)) { - op.recipients.push(payload.arguments[0][recipient_num]); - op.value = op.value.plus(payload.arguments[1][recipient_num]); - op.type = DIRECTION.IN; - } - } - } - op.hasFailed = !tx.success; - op.id = encodeOperationId(id, tx.hash, op.type); - if (op.type !== DIRECTION.UNKNOWN) ops.push(op); - } else { - // This is the place where we want to process events - // TODO: implement generig parsing of events - op.type = DIRECTION.UNKNOWN; - op.id = encodeOperationId(id, tx.hash, op.type); - - if (compareAddress(tx.sender, address)) { - op.type = DIRECTION.OUT; - } else { - op.type = DIRECTION.IN; - } - ops.push(op); } + + op.hasFailed = !tx.success; + op.id = encodeOperationId(id, tx.hash, op.type); + if (op.type !== DIRECTION.UNKNOWN) ops.push(op); } }); + return ops; }; @@ -182,3 +141,112 @@ export function compareAddress(addressA: string, addressB: string) { addressB.replace(CLEAN_HEX_REGEXP, "").toLowerCase() ); } + +export function getFunctionAddress(payload: TransactionPayloadResponse): string | undefined { + if ("function" in payload) { + const parts = payload.function.split("::"); + return parts.length === 3 ? parts[0] : undefined; + } + return undefined; +} + +export function processRecipients( + payload: TransactionPayloadResponse, + address: string, + op: Operation, + function_address: string, +): void { + // get recipients buy 3 groups + if ( + (TRANSFER_TYPES.includes((payload as EntryFunctionPayloadResponse).function) || + DELEGATION_POOL_TYPES.includes((payload as EntryFunctionPayloadResponse).function)) && + "arguments" in payload + ) { + // 1. Transfer like functions (includes some delegation pool functions) + op.recipients.push(payload.arguments[1]); + } else if ( + BATCH_TRANSFER_TYPES.includes((payload as EntryFunctionPayloadResponse).function) && + "arguments" in payload + ) { + // 2. Batch function, to validate we are in the recipients list + if (!compareAddress(op.senders[0], address)) { + for (const recipient of payload.arguments[1]) { + if (compareAddress(recipient, address)) { + op.recipients.push(recipient); + } + } + } + } else { + // 3. other smart contracts, in this case smart contract will be treated as a recipient + op.recipients.push(function_address); + } +} + +function checkWriteSets(tx: AptosTransaction, event: Event, event_name: string): boolean { + return tx.changes.some(change => { + return isChangeOfAptos(change, event, event_name); + }); +} + +export function isChangeOfAptos(change: WriteSetChange, event: Event, event_name: string): boolean { + // to validate the event is related to Aptos Tokens we need to find change of type "write_resource" + // with the same guid as event + if (change.type == "write_resource") { + const change_data = (change as WriteSetChangeWriteResource).data; + if (change_data.type === APTOS_COIN_CHANGE) { + const change_event_data = change_data.data[event_name]; + if ( + change_event_data && + change_event_data.guid.id.addr === event.guid.account_address && + change_event_data.guid.id.creation_num === event.guid.creation_number + ) { + return true; + } + } + } + return false; +} + +export function getAptosAmounts( + tx: AptosTransaction, + address: string, +): { amount_in: BigNumber; amount_out: BigNumber } { + let amount_in = new BigNumber(0); + let amount_out = new BigNumber(0); + // collect all events related to the address and calculate the overall amounts + tx.events.forEach(event => { + if (compareAddress(event.guid.account_address, address)) { + switch (event.type) { + case "0x1::coin::WithdrawEvent": + if (checkWriteSets(tx, event, "withdraw_events")) { + amount_out = amount_out.plus(event.data.amount); + } + break; + case "0x1::coin::DepositEvent": + if (checkWriteSets(tx, event, "deposit_events")) { + amount_in = amount_in.plus(event.data.amount); + } + break; + } + } + }); + return { amount_in, amount_out }; +} + +export function calculateAmount( + sender: string, + address: string, + fee: BigNumber, + amount_in: BigNumber, + amount_out: BigNumber, +): BigNumber { + const is_sender: boolean = compareAddress(sender, address); + // Include fees if our address is the sender + if (is_sender) { + amount_out = amount_out.plus(fee); + } + // LL negates the amount for SEND transactions + // to show positive amount on the send transaction (ex: in "cancel" tx, when amount will be returned to our account) + // we need to make it negative + return is_sender ? amount_out.minus(amount_in) : amount_in.minus(amount_out); +} From ca0f83c99b4baa6cebfd9c235e2bb785229fe798 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Thu, 12 Dec 2024 12:09:40 +0000 Subject: [PATCH 11/14] fix: set aptos client with our url paths. --- .../src/families/aptos/api/index.ts | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/api/index.ts b/libs/ledger-live-common/src/families/aptos/api/index.ts index d6405c8b3f3b..ef4aece41a42 100644 --- a/libs/ledger-live-common/src/families/aptos/api/index.ts +++ b/libs/ledger-live-common/src/families/aptos/api/index.ts @@ -9,20 +9,19 @@ import { InputEntryFunctionData, InputGenerateTransactionOptions, MimeType, - Network, - NetworkToIndexerAPI, - NetworkToNodeAPI, post, RawTransaction, SimpleTransaction, TransactionResponse, UserTransactionResponse, } from "@aptos-labs/ts-sdk"; +import { getEnv } from "@ledgerhq/live-env"; import network from "@ledgerhq/live-network/network"; import BigNumber from "bignumber.js"; import isUndefined from "lodash/isUndefined"; - +import { APTOS_ASSET_ID } from "../constants"; import { isTestnet } from "../logic"; +import type { AptosTransaction, TransactionOptions } from "../types"; import { GetAccountTransactionsData, GetAccountTransactionsDataGt, @@ -33,23 +32,27 @@ import { GetAccountTransactionsDataQueryVariables, } from "./graphql/types"; -import { APTOS_ASSET_ID } from "../constants"; -import type { AptosTransaction, TransactionOptions } from "../types"; - -const getNetwork = (currencyId: string) => - isTestnet(currencyId) ? Network.TESTNET : Network.MAINNET; +const getApiEndpoint = (currencyId: string) => + isTestnet(currencyId) ? getEnv("APTOS_TESTNET_API_ENDPOINT") : getEnv("APTOS_API_ENDPOINT"); +const getIndexerEndpoint = (currencyId: string) => + isTestnet(currencyId) + ? getEnv("APTOS_TESTNET_INDEXER_ENDPOINT") + : getEnv("APTOS_INDEXER_ENDPOINT"); export class AptosAPI { - private network: Network; + private apiUrl: string; private indexerUrl: string; private aptosConfig: AptosConfig; private aptosClient: Aptos; private apolloClient: ApolloClient; constructor(currencyId: string) { - this.network = getNetwork(currencyId); - this.indexerUrl = NetworkToIndexerAPI[this.network]; - this.aptosConfig = new AptosConfig({ network: this.network }); + this.apiUrl = getApiEndpoint(currencyId); + this.indexerUrl = getIndexerEndpoint(currencyId); + this.aptosConfig = new AptosConfig({ + fullnode: this.apiUrl, + indexer: this.indexerUrl, + }); this.aptosClient = new Aptos(this.aptosConfig); this.apolloClient = new ApolloClient({ uri: this.indexerUrl, @@ -223,7 +226,7 @@ export class AptosAPI { private async getHeight(): Promise { const { data } = await network({ method: "GET", - url: NetworkToNodeAPI[this.network], + url: this.apiUrl, }); return parseInt(data.block_height); } From a809ab5ea558e14492b201a46f9eff3199f21c69 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Thu, 12 Dec 2024 12:22:10 +0000 Subject: [PATCH 12/14] test: fix imports from old package --- .../src/families/aptos/logic.test.ts | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/logic.test.ts b/libs/ledger-live-common/src/families/aptos/logic.test.ts index 41e828268f39..6f0bf440b657 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.test.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.test.ts @@ -1,10 +1,8 @@ +import { Event, EntryFunctionPayloadResponse, WriteSetChange } from "@aptos-labs/ts-sdk"; +import type { Operation, OperationType } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; import { calculateAmount, getAptosAmounts, getFunctionAddress, isChangeOfAptos } from "./logic"; - import { processRecipients, compareAddress } from "./logic"; -import type { Operation, OperationType } from "@ledgerhq/types-live"; - -import type { Types as AptosTypes } from "aptos"; import type { AptosTransaction } from "./types"; describe("Aptos sync logic ", () => { @@ -37,7 +35,7 @@ describe("Aptos sync logic ", () => { /////////////////////////////////////////// describe("getFunctionAddress", () => { it("should return the function address when payload contains a function", () => { - const payload: AptosTypes.EntryFunctionPayload = { + const payload: EntryFunctionPayloadResponse = { function: "0x1::coin::transfer", type_arguments: [], arguments: [], @@ -52,14 +50,14 @@ describe("Aptos sync logic ", () => { function: "", type_arguments: [], arguments: [], - } as AptosTypes.EntryFunctionPayload; + } as EntryFunctionPayload; const result = getFunctionAddress(payload); expect(result).toBeUndefined(); }); it("should return undefined when payload is empty", () => { - const payload = {} as AptosTypes.EntryFunctionPayload; + const payload = {} as EntryFunctionPayloadResponse; const result = getFunctionAddress(payload); expect(result).toBeUndefined(); @@ -89,7 +87,7 @@ describe("Aptos sync logic ", () => { }); it("should add recipient for transfer-like functions from LL account", () => { - const payload: AptosTypes.EntryFunctionPayload = { + const payload: EntryFunctionPayloadResponse = { function: "0x1::coin::transfer", type_arguments: [], arguments: ["0x12", "0x13", 1], //from: &signer, to: address, amount: u64 @@ -100,7 +98,7 @@ describe("Aptos sync logic ", () => { }); it("should add recipient for transfer-like functions from external account", () => { - const payload: AptosTypes.EntryFunctionPayload = { + const payload: EntryFunctionPayloadResponse = { function: "0x1::coin::transfer", type_arguments: [], arguments: ["0x13", "0x12", 1], //from: &signer, to: address, amount: u64 @@ -111,7 +109,7 @@ describe("Aptos sync logic ", () => { }); it("should add recipients for batch transfer functions", () => { - const payload: AptosTypes.EntryFunctionPayload = { + const payload: EntryFunctionPayloadResponse = { function: "0x1::aptos_account::batch_transfer_coins", type_arguments: ["0x1::aptos_coin::AptosCoin"], arguments: ["0x11", ["0x12", "0x13"], [1, 2]], @@ -123,7 +121,7 @@ describe("Aptos sync logic ", () => { }); it("should add function address as recipient for other smart contracts", () => { - const payload: AptosTypes.EntryFunctionPayload = { + const payload: EntryFunctionPayloadResponse = { function: "0x2::other::contract", type_arguments: [], arguments: ["0x11", ["0x12"], [1]], @@ -151,7 +149,7 @@ describe("Aptos sync logic ", () => { }, }, }, - } as unknown as AptosTypes.WriteSetChange; + } as unknown as WriteSetChange; const event = { guid: { @@ -159,7 +157,7 @@ describe("Aptos sync logic ", () => { creation_number: "2", }, type: "0x1::coin::WithdrawEvent", - } as AptosTypes.Event; + } as Event; const result = isChangeOfAptos(change, event, "withdraw_events"); expect(result).toBe(true); @@ -181,7 +179,7 @@ describe("Aptos sync logic ", () => { }, }, }, - } as unknown as AptosTypes.WriteSetChange; + } as unknown as WriteSetChange; const event = { guid: { @@ -189,7 +187,7 @@ describe("Aptos sync logic ", () => { creation_number: "1", }, type: "0x1::coin::WithdrawEvent", - } as AptosTypes.Event; + } as Event; const result = isChangeOfAptos(change, event, "withdraw_events"); expect(result).toBe(false); @@ -199,7 +197,7 @@ describe("Aptos sync logic ", () => { const change = { type: "write_module", data: {}, - } as unknown as AptosTypes.WriteSetChange; + } as unknown as WriteSetChange; const event = { guid: { @@ -207,7 +205,7 @@ describe("Aptos sync logic ", () => { creation_number: "1", }, type: "0x1::coin::WithdrawEvent", - } as AptosTypes.Event; + } as Event; const result = isChangeOfAptos(change, event, "withdraw_events"); expect(result).toBe(false); @@ -229,7 +227,7 @@ describe("Aptos sync logic ", () => { }, }, }, - } as unknown as AptosTypes.WriteSetChange; + } as unknown as WriteSetChange; const event = { guid: { @@ -237,7 +235,7 @@ describe("Aptos sync logic ", () => { creation_number: "2", }, type: "0x1::coin::WithdrawEvent", - } as AptosTypes.Event; + } as Event; const result = isChangeOfAptos(change, event, "withdraw_events"); expect(result).toBe(false); From e5560d9090916b67d69773d561ce5992aaad0570 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Thu, 12 Dec 2024 12:24:21 +0000 Subject: [PATCH 13/14] fix: payload type --- libs/ledger-live-common/src/families/aptos/logic.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ledger-live-common/src/families/aptos/logic.test.ts b/libs/ledger-live-common/src/families/aptos/logic.test.ts index 6f0bf440b657..beaaadc8f5ff 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.test.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.test.ts @@ -121,7 +121,7 @@ describe("Aptos sync logic ", () => { }); it("should add function address as recipient for other smart contracts", () => { - const payload: EntryFunctionPayloadResponse = { + const payload: PayloadResponse = { function: "0x2::other::contract", type_arguments: [], arguments: ["0x11", ["0x12"], [1]], From dce6c5fb642b79ace395529a00443e0c835011a2 Mon Sep 17 00:00:00 2001 From: Pedro Semeano Date: Thu, 12 Dec 2024 15:31:01 +0000 Subject: [PATCH 14/14] fix: logic code and tests --- .../src/families/aptos/logic.test.ts | 70 ++++++++++--------- .../src/families/aptos/logic.ts | 42 +++++++---- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/libs/ledger-live-common/src/families/aptos/logic.test.ts b/libs/ledger-live-common/src/families/aptos/logic.test.ts index beaaadc8f5ff..af64d55b625f 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.test.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.test.ts @@ -1,8 +1,15 @@ -import { Event, EntryFunctionPayloadResponse, WriteSetChange } from "@aptos-labs/ts-sdk"; +import { Event, InputEntryFunctionData, WriteSetChange } from "@aptos-labs/ts-sdk"; import type { Operation, OperationType } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; -import { calculateAmount, getAptosAmounts, getFunctionAddress, isChangeOfAptos } from "./logic"; -import { processRecipients, compareAddress } from "./logic"; +import { APTOS_ASSET_ID, APTOS_COIN_CHANGE } from "./constants"; +import { + calculateAmount, + compareAddress, + getAptosAmounts, + getFunctionAddress, + isChangeOfAptos, + processRecipients, +} from "./logic"; import type { AptosTransaction } from "./types"; describe("Aptos sync logic ", () => { @@ -32,13 +39,12 @@ describe("Aptos sync logic ", () => { }); }); - /////////////////////////////////////////// describe("getFunctionAddress", () => { it("should return the function address when payload contains a function", () => { - const payload: EntryFunctionPayloadResponse = { + const payload: InputEntryFunctionData = { function: "0x1::coin::transfer", - type_arguments: [], - arguments: [], + typeArguments: [], + functionArguments: [], }; const result = getFunctionAddress(payload); @@ -47,23 +53,23 @@ describe("Aptos sync logic ", () => { it("should return undefined when payload does not contain a function", () => { const payload = { - function: "", - type_arguments: [], - arguments: [], - } as EntryFunctionPayload; + function: "::::", + typeArguments: [], + functionArguments: [], + } as InputEntryFunctionData; const result = getFunctionAddress(payload); expect(result).toBeUndefined(); }); it("should return undefined when payload is empty", () => { - const payload = {} as EntryFunctionPayloadResponse; + const payload = {} as InputEntryFunctionData; const result = getFunctionAddress(payload); expect(result).toBeUndefined(); }); }); - /////////////////////////////////////////// + describe("processRecipients", () => { let op: Operation; @@ -87,10 +93,10 @@ describe("Aptos sync logic ", () => { }); it("should add recipient for transfer-like functions from LL account", () => { - const payload: EntryFunctionPayloadResponse = { + const payload: InputEntryFunctionData = { function: "0x1::coin::transfer", - type_arguments: [], - arguments: ["0x12", "0x13", 1], //from: &signer, to: address, amount: u64 + typeArguments: [], + functionArguments: ["0x12", "0x13", 1], // from: &signer, to: address, amount: u64 }; processRecipients(payload, "0x13", op, "0x1"); @@ -98,10 +104,10 @@ describe("Aptos sync logic ", () => { }); it("should add recipient for transfer-like functions from external account", () => { - const payload: EntryFunctionPayloadResponse = { + const payload: InputEntryFunctionData = { function: "0x1::coin::transfer", - type_arguments: [], - arguments: ["0x13", "0x12", 1], //from: &signer, to: address, amount: u64 + typeArguments: [], + functionArguments: ["0x13", "0x12", 1], // from: &signer, to: address, amount: u64 }; processRecipients(payload, "0x13", op, "0x1"); @@ -109,10 +115,10 @@ describe("Aptos sync logic ", () => { }); it("should add recipients for batch transfer functions", () => { - const payload: EntryFunctionPayloadResponse = { + const payload: InputEntryFunctionData = { function: "0x1::aptos_account::batch_transfer_coins", - type_arguments: ["0x1::aptos_coin::AptosCoin"], - arguments: ["0x11", ["0x12", "0x13"], [1, 2]], + typeArguments: [APTOS_ASSET_ID], + functionArguments: ["0x11", ["0x12", "0x13"], [1, 2]], }; op.senders.push("0x11"); @@ -121,23 +127,23 @@ describe("Aptos sync logic ", () => { }); it("should add function address as recipient for other smart contracts", () => { - const payload: PayloadResponse = { + const payload: InputEntryFunctionData = { function: "0x2::other::contract", - type_arguments: [], - arguments: ["0x11", ["0x12"], [1]], + typeArguments: [], + functionArguments: ["0x11", ["0x12"], [1]], }; processRecipients(payload, "0x11", op, "0x2"); expect(op.recipients).toContain("0x2"); }); }); - /////////////////////////////////////////// + describe("isChangeOfAptos", () => { it("should return true for a valid change of Aptos", () => { const change = { type: "write_resource", data: { - type: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", + type: APTOS_COIN_CHANGE, data: { withdraw_events: { guid: { @@ -167,7 +173,7 @@ describe("Aptos sync logic ", () => { const change = { type: "write_resource", data: { - type: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", + type: APTOS_COIN_CHANGE, data: { withdraw_events: { guid: { @@ -241,7 +247,7 @@ describe("Aptos sync logic ", () => { expect(result).toBe(false); }); }); - /////////////////////////////////////////// + describe("getAptosAmounts", () => { it("should calculate the correct amounts for withdraw and deposit events", () => { const tx = { @@ -271,7 +277,7 @@ describe("Aptos sync logic ", () => { { type: "write_resource", data: { - type: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", + type: APTOS_COIN_CHANGE, data: { withdraw_events: { guid: { @@ -330,7 +336,7 @@ describe("Aptos sync logic ", () => { { type: "write_resource", data: { - type: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", + type: APTOS_COIN_CHANGE, data: { withdraw_events: { guid: { @@ -384,7 +390,7 @@ describe("Aptos sync logic ", () => { expect(result.amount_out).toEqual(new BigNumber(0)); }); }); - /////////////////////////////////////////// + describe("calculateAmount", () => { it("should calculate the correct amount when the address is the sender", () => { const address = "0x11"; diff --git a/libs/ledger-live-common/src/families/aptos/logic.ts b/libs/ledger-live-common/src/families/aptos/logic.ts index c4d3be0fb1e2..ae08f974abf1 100644 --- a/libs/ledger-live-common/src/families/aptos/logic.ts +++ b/libs/ledger-live-common/src/families/aptos/logic.ts @@ -1,7 +1,7 @@ import { EntryFunctionPayloadResponse, Event, - TransactionPayloadResponse, + InputEntryFunctionData, WriteSetChange, WriteSetChangeWriteResource, } from "@aptos-labs/ts-sdk"; @@ -93,6 +93,14 @@ const getBlankOperation = ( hasFailed: false, }); +const convertFunctionPayloadResponseToInputEntryFunctionData = ( + payload: EntryFunctionPayloadResponse, +): InputEntryFunctionData => ({ + function: payload.function, + typeArguments: payload.type_arguments, + functionArguments: payload.arguments, +}); + export const txsToOps = ( info: { address: string }, id: string, @@ -106,7 +114,9 @@ export const txsToOps = ( const op: Operation = getBlankOperation(tx, id); op.fee = new BigNumber(tx.gas_used).multipliedBy(new BigNumber(tx.gas_unit_price)); - const payload = tx.payload; + const payload = convertFunctionPayloadResponseToInputEntryFunctionData( + tx.payload as EntryFunctionPayloadResponse, + ); const function_address = getFunctionAddress(payload); @@ -142,37 +152,41 @@ export function compareAddress(addressA: string, addressB: string) { ); } -export function getFunctionAddress(payload: TransactionPayloadResponse): string | undefined { +export function getFunctionAddress(payload: InputEntryFunctionData): string | undefined { if ("function" in payload) { const parts = payload.function.split("::"); - return parts.length === 3 ? parts[0] : undefined; + return parts.length === 3 && parts[0].length ? parts[0] : undefined; } return undefined; } export function processRecipients( - payload: TransactionPayloadResponse, + payload: InputEntryFunctionData, address: string, op: Operation, function_address: string, ): void { // get recipients buy 3 groups if ( - (TRANSFER_TYPES.includes((payload as EntryFunctionPayloadResponse).function) || - DELEGATION_POOL_TYPES.includes((payload as EntryFunctionPayloadResponse).function)) && - "arguments" in payload + (TRANSFER_TYPES.includes(payload.function) || + DELEGATION_POOL_TYPES.includes(payload.function)) && + payload.functionArguments && + payload.functionArguments.length > 1 && + typeof payload.functionArguments[1] === "string" ) { // 1. Transfer like functions (includes some delegation pool functions) - op.recipients.push(payload.arguments[1]); + op.recipients.push(payload.functionArguments[1].toString()); } else if ( - BATCH_TRANSFER_TYPES.includes((payload as EntryFunctionPayloadResponse).function) && - "arguments" in payload + BATCH_TRANSFER_TYPES.includes(payload.function) && + payload.functionArguments && + payload.functionArguments.length > 1 && + Array.isArray(payload.functionArguments[1]) ) { // 2. Batch function, to validate we are in the recipients list if (!compareAddress(op.senders[0], address)) { - for (const recipient of payload.arguments[1]) { - if (compareAddress(recipient, address)) { - op.recipients.push(recipient); + for (const recipient of payload.functionArguments[1]) { + if (recipient && compareAddress(recipient.toString(), address)) { + op.recipients.push(recipient.toString()); } } }