@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