@ledgerhq/coin-stacks
Version: 
Ledger Stacks Coin integration
203 lines • 8.23 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findNextNonce = 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 getTxToBroadcast = async (operation, signature, rawData) => {
    const { value, recipients, fee, extra: { memo }, } = operation;
    const { anchorMode, network, xpub } = rawData;
    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 ?? 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 getMemo = (memoHex) => {
    if (memoHex?.substring(0, 2) === "0x") {
        // eslint-disable-next-line no-control-regex
        return Buffer.from(memoHex.substring(2), "hex").toString().replace(/\x00/g, "");
        // NOTE: couldn't use replaceAll because it's not supported in node 14
    }
    return "";
};
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 = getMemo(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: 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 = getMemo(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: 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: getMemo(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;
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 findNextNonce = async (senderAddress, pendingOps) => {
    let nextNonce = (0, bignumber_js_1.BigNumber)(0);
    for (const op of pendingOps) {
        const nonce = op.transactionSequenceNumber
            ? new bignumber_js_1.BigNumber(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