UNPKG

@ledgerhq/coin-stacks

Version:
303 lines 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.findNextNonce = exports.sip010OpToParentOp = exports.sip010TxnToOperation = exports.mapTxToOps = exports.mapPendingTxToOps = exports.getAddress = exports.getUnit = exports.getTxToBroadcast = void 0; exports.reconciliatePublicKey = reconciliatePublicKey; const bignumber_js_1 = require("bignumber.js"); const transactions_1 = require("@stacks/transactions"); const index_1 = require("@ledgerhq/coin-framework/account/index"); const index_2 = require("../../network/index"); const currencies_1 = require("@ledgerhq/cryptoassets/currencies"); const operation_1 = require("@ledgerhq/coin-framework/operation"); const logs_1 = require("@ledgerhq/logs"); const memoUtils_1 = require("./memoUtils"); const getTxToBroadcast = async (operation, signature, rawData) => { const { value, recipients, senders, fee, extra: { memo }, } = operation; const { anchorMode, network, xpub, contractAddress, contractName, assetName } = rawData; if (contractAddress && contractName && assetName) { // Create the function arguments for the SIP-010 transfer function const functionArgs = [ (0, transactions_1.uintCV)(value.toFixed()), // Amount (0, transactions_1.standardPrincipalCV)(senders[0]), // Sender (0, transactions_1.standardPrincipalCV)(recipients[0]), // Recipient memo ? (0, transactions_1.someCV)((0, transactions_1.stringAsciiCV)(memo)) : (0, transactions_1.noneCV)(), // Memo (optional) ]; const tx = await (0, transactions_1.makeUnsignedContractCall)({ contractAddress, contractName, functionName: "transfer", functionArgs, anchorMode, network: index_2.StacksNetwork[network], publicKey: xpub, fee: fee.toFixed(), nonce: operation.transactionSequenceNumber?.toString() ?? "0", postConditions: [ { type: transactions_1.StacksMessageType.PostCondition, conditionType: transactions_1.PostConditionType.Fungible, principal: (0, transactions_1.createStandardPrincipal)(senders[0]), conditionCode: transactions_1.FungibleConditionCode.Equal, amount: BigInt(value.toFixed()), assetInfo: (0, transactions_1.createAssetInfo)(contractAddress, contractName, assetName), }, ], }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore need to ignore the TS error here tx.auth.spendingCondition.signature = (0, transactions_1.createMessageSignature)(signature); return Buffer.from(tx.serialize()); } else { const options = { amount: (0, bignumber_js_1.BigNumber)(value).minus(fee).toFixed(), recipient: recipients[0], anchorMode, memo, network: index_2.StacksNetwork[network], publicKey: xpub, fee: (0, bignumber_js_1.BigNumber)(fee).toFixed(), nonce: operation.transactionSequenceNumber?.toString() ?? "0", }; const tx = await (0, transactions_1.makeUnsignedSTXTokenTransfer)(options); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore need to ignore the TS error here tx.auth.spendingCondition.signature = (0, transactions_1.createMessageSignature)(signature); return Buffer.from(tx.serialize()); } }; exports.getTxToBroadcast = getTxToBroadcast; const getUnit = () => (0, currencies_1.getCryptoCurrencyById)("stacks").units[0]; exports.getUnit = getUnit; const getAddress = (account) => ({ address: account.freshAddress, derivationPath: account.freshAddressPath }); exports.getAddress = getAddress; const mapPendingTxToOps = (accountID, address) => (tx) => { const { sender_address, receipt_time, fee_rate, tx_id, token_transfer, tx_status, nonce } = tx; if (tx.tx_type !== "token_transfer" || tx_status !== "pending") { return []; } const memo = (0, memoUtils_1.hexMemoToString)(token_transfer.memo); const feeToUse = new bignumber_js_1.BigNumber(fee_rate || "0"); const date = new Date(receipt_time * 1000); const operationCommons = { hash: tx_id, fee: feeToUse, accountId: accountID, senders: [sender_address], recipients: [token_transfer.recipient_address], transactionSequenceNumber: new bignumber_js_1.BigNumber(nonce), value: new bignumber_js_1.BigNumber(token_transfer.amount).plus(feeToUse), date, extra: { memo, }, blockHeight: null, blockHash: null, }; const isSending = address === sender_address; const isReceiving = token_transfer.recipient_address === address; const ops = []; if (isSending) { const type = "OUT"; ops.push({ ...operationCommons, id: (0, operation_1.encodeOperationId)(accountID, tx_id, type), type, }); } else if (isReceiving) { const type = "IN"; ops.push({ ...operationCommons, id: (0, operation_1.encodeOperationId)(accountID, tx_id, type), type, }); } return ops; }; exports.mapPendingTxToOps = mapPendingTxToOps; const mapTxToOps = (accountID, address) => (tx) => { try { const { tx_id, fee_rate, nonce, block_height, burn_block_time, sender_address, block_hash: blockHash, } = tx.tx; const { stx_received: receivedValue, stx_sent: sentValue } = tx; let recipients = []; if (tx.tx.tx_type === "token_transfer" && tx.tx.token_transfer) { recipients = [tx.tx.token_transfer.recipient_address]; } const memoHex = tx.tx.token_transfer?.memo; const memo = (0, memoUtils_1.hexMemoToString)(memoHex ?? ""); const ops = []; const date = new Date(burn_block_time * 1000); const feeToUse = new bignumber_js_1.BigNumber(fee_rate || "0"); const isSending = sentValue !== "0" && receivedValue === "0"; const isReceiving = receivedValue !== "0"; const operationCommons = { hash: tx_id, blockHeight: block_height, blockHash, fee: feeToUse, accountId: accountID, senders: [sender_address], transactionSequenceNumber: new bignumber_js_1.BigNumber(nonce), date, extra: { memo, }, }; if (isSending) { const type = "OUT"; let internalOperations = undefined; if (tx.tx.tx_type === "contract_call" && tx.tx.contract_call) { internalOperations = []; const deserialized = (0, transactions_1.deserializeCV)(tx.tx.contract_call.function_args[0].hex); const decodedArgs = (0, transactions_1.cvToJSON)(deserialized); for (const [idx, t] of decodedArgs.value.entries()) { internalOperations.push({ ...operationCommons, id: (0, operation_1.encodeSubOperationId)(accountID, tx_id, type, idx), contract: "send-many", type, value: new bignumber_js_1.BigNumber(t.value.ustx.value), senders: [sender_address], recipients: [t.value.to.value], extra: { memo: (0, memoUtils_1.hexMemoToString)(t.value.memo?.value ?? ""), }, }); } } ops.push({ ...operationCommons, id: (0, operation_1.encodeOperationId)(accountID, tx_id, type), value: new bignumber_js_1.BigNumber(sentValue), recipients, type, internalOperations, }); } if (isReceiving) { const type = "IN"; ops.push({ ...operationCommons, id: (0, operation_1.encodeOperationId)(accountID, tx_id, type), value: new bignumber_js_1.BigNumber(receivedValue), recipients: recipients.length ? recipients : [address], type, }); } return ops; } catch (err) { (0, logs_1.log)("warn", "mapTxToOps failed for stacks", err); return []; } }; exports.mapTxToOps = mapTxToOps; const sip010TxnToOperation = (tx, address, accountId) => { try { const { tx_id, fee_rate, nonce, block_height, burn_block_time, block_hash: blockHash } = tx.tx; if (!tx.tx.contract_call) { (0, logs_1.log)("error", "contract_call is not defined", tx); return []; } const contractCallData = tx.tx.contract_call; const contractCallArgs = contractCallData.function_args; if (contractCallArgs.length !== 4) { (0, logs_1.log)("error", "contractCallArgs len is not sip010 token transfer", tx); return []; } const [valueArg, senderArg, receiverArg, memoArg] = contractCallArgs; const sender = (0, transactions_1.cvToJSON)((0, transactions_1.deserializeCV)(senderArg.hex)).value; const receiver = (0, transactions_1.cvToJSON)((0, transactions_1.deserializeCV)(receiverArg.hex)).value; const valueStr = (0, transactions_1.cvToJSON)((0, transactions_1.deserializeCV)(valueArg.hex)).value; const memoJson = (0, transactions_1.cvToJSON)((0, transactions_1.deserializeCV)(memoArg.hex)).value; const memo = (0, memoUtils_1.bufferMemoToString)(memoJson); const value = new bignumber_js_1.BigNumber(valueStr); const recipients = [receiver]; const senders = [sender]; const ops = []; const date = new Date(burn_block_time * 1000); const fee = new bignumber_js_1.BigNumber(fee_rate || "0"); const hasFailed = tx.tx.tx_status !== "success"; let type = ""; if (address === sender) { type = "OUT"; } else if (address === receiver) { type = "IN"; } if (type === "") { (0, logs_1.log)("error", "op type is not known", tx); return []; } ops.push({ hash: tx_id, blockHeight: block_height, blockHash, fee, accountId, senders, recipients, transactionSequenceNumber: (0, bignumber_js_1.BigNumber)(nonce), date, value, hasFailed, extra: { memo, }, type, id: (0, operation_1.encodeOperationId)(accountId, tx_id, type), }); return ops; } catch (e) { (0, logs_1.log)("error", "stacks error converting sip010 transaction to operation", e); return []; } }; exports.sip010TxnToOperation = sip010TxnToOperation; function reconciliatePublicKey(publicKey, initialAccount) { if (publicKey) return publicKey; if (initialAccount) { const { xpubOrAddress } = (0, index_1.decodeAccountId)(initialAccount.id); return xpubOrAddress; } throw new Error("publicKey wasn't properly restored"); } const sip010OpToParentOp = (tokenOps, parentAccountId) => { return tokenOps.map(op => ({ ...op, accountId: parentAccountId, value: (0, bignumber_js_1.BigNumber)(0), subOperations: [op], id: (0, operation_1.encodeOperationId)(parentAccountId, op.hash, op.type), })); }; exports.sip010OpToParentOp = sip010OpToParentOp; const findNextNonce = async (senderAddress, pendingOps) => { let nextNonce = (0, bignumber_js_1.BigNumber)(0); for (const op of pendingOps) { const nonce = op.transactionSequenceNumber ? op.transactionSequenceNumber : new bignumber_js_1.BigNumber(0); if (nonce.gt(nextNonce)) { nextNonce = nonce; } } const allMempoolTxns = await (0, index_2.fetchFullMempoolTxs)(senderAddress); for (const tx of allMempoolTxns) { const nonce = (0, bignumber_js_1.BigNumber)(tx.nonce); if (nonce.gt(nextNonce)) { nextNonce = nonce; } } if (!nextNonce.eq(0)) { nextNonce = nextNonce.plus(1); } const nonceResp = await (0, index_2.fetchNonce)(senderAddress); const possibleNextNonce = new bignumber_js_1.BigNumber(nonceResp.possible_next_nonce); if (possibleNextNonce.gt(nextNonce)) { nextNonce = possibleNextNonce; } return nextNonce; }; exports.findNextNonce = findNextNonce; //# sourceMappingURL=misc.js.map