UNPKG

@ledgerhq/coin-aptos

Version:
281 lines 10.8 kB
import { ApolloClient, InMemoryCache } from "@apollo/client"; import { Aptos, AptosConfig, Ed25519PublicKey, MimeType, Hex, postAptosFullNode, Network, } from "@aptos-labs/ts-sdk"; import { getEnv } from "@ledgerhq/live-env"; import network from "@ledgerhq/live-network"; import BigNumber from "bignumber.js"; import isUndefined from "lodash/isUndefined"; import { APTOS_ASSET_ID, DEFAULT_GAS, DEFAULT_GAS_PRICE, ESTIMATE_GAS_MUL, TOKEN_TYPE, } from "../constants"; import { GetAccountTransactionsData, GetAccountTransactionsDataGt } from "./graphql/queries"; import { log } from "@ledgerhq/logs"; import { transactionsToOperations } from "../logic/transactionsToOperations"; import { isTestnet } from "../logic/isTestnet"; import { normalizeAddress } from "../logic/normalizeAddress"; const getApiEndpoint = (currencyId) => isTestnet(currencyId) ? getEnv("APTOS_TESTNET_API_ENDPOINT") : getEnv("APTOS_API_ENDPOINT"); const getIndexerEndpoint = (currencyId) => isTestnet(currencyId) ? getEnv("APTOS_TESTNET_INDEXER_ENDPOINT") : getEnv("APTOS_INDEXER_ENDPOINT"); const getNetwork = (currencyId) => isTestnet(currencyId) ? Network.TESTNET : Network.MAINNET; export class AptosAPI { aptosConfig; aptosClient; apolloClient; constructor(currencyIdOrSettings) { const appVersion = getEnv("LEDGER_CLIENT_VERSION"); if (typeof currencyIdOrSettings === "string") { this.aptosConfig = new AptosConfig({ network: getNetwork(currencyIdOrSettings), fullnode: getApiEndpoint(currencyIdOrSettings), indexer: getIndexerEndpoint(currencyIdOrSettings), clientConfig: { HEADERS: { "X-Ledger-Client-Version": appVersion, }, }, }); } else { this.aptosConfig = new AptosConfig(currencyIdOrSettings); } this.aptosClient = new Aptos(this.aptosConfig); this.apolloClient = new ApolloClient({ uri: this.aptosConfig.indexer ?? "", cache: new InMemoryCache(), headers: { "X-Ledger-Client-Version": appVersion, }, }); } async getAccount(address) { return this.aptosClient.getAccountInfo({ accountAddress: address }); } async getAccountInfo(address, startAt) { const [balance, transactions, blockHeight] = await Promise.all([ this.getBalances(address, APTOS_ASSET_ID), this.fetchTransactions(address, startAt), this.getHeight(), ]); return { balance: balance[0]?.amount ?? BigNumber(0), transactions, blockHeight, }; } async estimateGasPrice() { return this.aptosClient.getGasPriceEstimation(); } async generateTransaction(address, payload, options) { const opts = {}; if (!isUndefined(options.maxGasAmount)) { opts.maxGasAmount = Number(options.maxGasAmount); } if (!isUndefined(options.gasUnitPrice)) { opts.gasUnitPrice = Number(options.gasUnitPrice); } try { const { ledger_timestamp } = await this.aptosClient.getLedgerInfo(); opts.expireTimestamp = Number(Math.ceil(+ledger_timestamp / 1_000_000 + 2 * 60)); // in milliseconds } catch { // skip } return this.aptosClient.transaction.build .simple({ sender: address, data: payload, options: opts, }) .then(t => t.rawTransaction) .catch(error => { throw error; }); } async simulateTransaction(address, tx, options = { estimateGasUnitPrice: true, estimateMaxGasAmount: true, estimatePrioritizedGasUnitPrice: false, }) { return this.aptosClient.transaction.simulate.simple({ signerPublicKey: address, transaction: { rawTransaction: tx }, options, }); } async broadcast(tx) { const txBytes = Hex.fromHexString(tx).toUint8Array(); const pendingTx = await postAptosFullNode({ aptosConfig: this.aptosClient.config, body: txBytes, path: "transactions", originMethod: "", contentType: MimeType.BCS_SIGNED_TRANSACTION, }); return pendingTx.data.hash; } async getLastBlock() { const { block_height } = await this.aptosClient.getLedgerInfo(); const block = await this.aptosClient.getBlockByHeight({ blockHeight: Number(block_height) }); return { height: Number(block.block_height), hash: block.block_hash, time: new Date(Number(block.block_timestamp) / 1_000), }; } async estimateFees(transactionIntent) { const publicKeyEd = new Ed25519PublicKey(transactionIntent?.senderPublicKey ?? ""); const txPayload = { function: "0x1::aptos_account::transfer_coins", typeArguments: [APTOS_ASSET_ID], functionArguments: [transactionIntent.recipient, transactionIntent.amount], }; if (transactionIntent.asset.type === "token") { const { standard } = transactionIntent.asset; if (standard === TOKEN_TYPE.FUNGIBLE_ASSET) { txPayload.function = "0x1::primary_fungible_store::transfer"; txPayload.typeArguments = ["0x1::fungible_asset::Metadata"]; txPayload.functionArguments = [ transactionIntent.asset.contractAddress, transactionIntent.recipient, transactionIntent.amount, ]; } else if (standard === TOKEN_TYPE.COIN) { txPayload.function = "0x1::aptos_account::transfer_coins"; txPayload.typeArguments = [transactionIntent.asset.contractAddress]; } } const txOptions = { maxGasAmount: DEFAULT_GAS.toString(), gasUnitPrice: DEFAULT_GAS_PRICE.toString(), }; const tx = await this.generateTransaction(transactionIntent.sender, txPayload, txOptions); const simulation = await this.simulateTransaction(publicKeyEd, tx); const completedTx = simulation[0]; const gasLimit = new BigNumber(completedTx.gas_used) .multipliedBy(ESTIMATE_GAS_MUL) .integerValue(); const gasPrice = new BigNumber(completedTx.gas_unit_price); const expectedGas = gasPrice.multipliedBy(gasLimit); return { value: BigInt(expectedGas.toString()), parameters: { storageLimit: BigInt(0), gasLimit: BigInt(gasLimit.toString()), gasPrice: BigInt(gasPrice.toString()), }, }; } async getNextUnlockTime(stakingPoolAddress) { const resourceType = "0x1::stake::StakePool"; try { const resource = await this.aptosClient.getAccountResource({ accountAddress: stakingPoolAddress, resourceType, }); return resource.locked_until_secs; } catch (error) { log("error", "Failed to fetch StakePool resource:", { error }); } } async getDelegatorBalanceInPool(poolAddress, delegatorAddress) { try { // Query the delegator balance in the pool return await this.aptosClient.view({ payload: { function: "0x1::delegation_pool::get_stake", typeArguments: [], functionArguments: [poolAddress, delegatorAddress], }, }); } catch (error) { log("error", "Failed to fetch delegation_pool::get_stake", { error }); return ["0", "0", "0"]; } } async listOperations(rawAddress, pagination) { const address = normalizeAddress(rawAddress); const transactions = await this.getAccountInfo(address, pagination.minHeight.toString()); const newOperations = transactionsToOperations(address, transactions.transactions); return [newOperations, ""]; } async fetchTransactions(address, gt) { if (!address) { return []; } let query = GetAccountTransactionsData; if (gt) { query = GetAccountTransactionsDataGt; } const queryResponse = await this.apolloClient.query({ query, variables: { address, limit: 1000, gt, }, fetchPolicy: "network-only", }); return Promise.all(queryResponse.data.account_transactions.map(({ transaction_version }) => { return this.richItemByVersion(transaction_version); })); } async richItemByVersion(version) { try { const tx = await this.aptosClient.getTransactionByVersion({ ledgerVersion: version, }); const block = await this.getBlock(version); return { ...tx, block, }; } catch (error) { log("error", "richItemByVersion", { error, }); return null; } } async getHeight() { const { data } = await network({ method: "GET", url: this.aptosConfig.fullnode ?? "", }); return parseInt(data.block_height); } async getBlock(version) { const block = await this.aptosClient.getBlockByVersion({ ledgerVersion: version }); return { height: parseInt(block.block_height), hash: block.block_hash, }; } async getBalances(address, contractAddress) { try { const whereCondition = { owner_address: { _eq: address }, }; if (contractAddress !== undefined && contractAddress !== "") { whereCondition.asset_type = { _eq: contractAddress }; } const response = await this.aptosClient.getCurrentFungibleAssetBalances({ options: { where: whereCondition, }, }); return response.map(x => ({ contractAddress: x.asset_type ?? "", amount: BigNumber(x.amount), })); } catch (error) { log("error", "getCoinBalance", { error, }); return [{ amount: BigNumber(0), contractAddress: "" }]; } } } //# sourceMappingURL=client.js.map