UNPKG

@ledgerhq/coin-stacks

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