@ledgerhq/coin-stacks
Version:
Ledger Stacks Coin integration
189 lines • 9.43 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTxToBroadcast = exports.applySignatureToTransaction = exports.createTransaction = exports.createStxTransferTransaction = exports.createTokenTransferTransaction = exports.createTokenTransferPostConditions = exports.createTokenTransferFunctionArgs = exports.getTokenContractDetails = void 0;
const transactions_1 = require("@stacks/transactions");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const api_1 = require("../../network/api");
const memoUtils_1 = require("./memoUtils");
const specialConditionCode = new Set([
"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.auto-alex-v3::auto-alex-v3",
"SM26NBC8SFHNW4P1Y4DFH27974P56WN86C92HPEHH.token-lqstx::lqstx",
"SP673Z4BPB4R73359K9HE55F2X91V5BJTN5SXZ5T.token-liabtc::liabtc",
]);
// Type guard: validates rawData has all required token transfer fields
function isTokenTransferRawData(data) {
return (typeof data.contractAddress === "string" &&
typeof data.contractName === "string" &&
typeof data.assetName === "string" &&
typeof data.anchorMode === "number" &&
typeof data.network === "string" &&
typeof data.xpub === "string");
}
// Extracts and validates base fields from rawData (throws if invalid)
function extractBaseRawData(data) {
const anchorMode = data.anchorMode;
const network = data.network;
const xpub = data.xpub;
if (typeof anchorMode !== "number" || typeof network !== "string" || typeof xpub !== "string") {
throw new Error("Invalid raw data: missing or invalid base fields");
}
return { anchorMode, network, xpub };
}
// Extracts contract address, name, and asset name from TokenAccount
// CARE: usage of asset name is case-sensitive
// Returns null if subAccount is undefined
const getTokenContractDetails = (subAccount) => {
if (!subAccount)
return null;
// Contract address format: "ADDRESS.CONTRACT-NAME::ASSET-NAME"
const tokenId = subAccount.token.contractAddress;
const contractAddress = tokenId.split(".").shift()?.toUpperCase() ?? "";
const contractName = tokenId.split(".").pop()?.split("::")[0] ?? "";
const assetName = tokenId.split(".").pop()?.split("::")[1] ?? "";
return { contractAddress, contractName, assetName };
};
exports.getTokenContractDetails = getTokenContractDetails;
// Creates SIP-010 function arguments: [amount, sender, recipient, memo]
const createTokenTransferFunctionArgs = (amount, senderAddress, recipientAddress, memo) => {
return [
(0, transactions_1.uintCV)(amount.toFixed()), // Amount
(0, transactions_1.standardPrincipalCV)(senderAddress), // Sender
(0, transactions_1.standardPrincipalCV)(recipientAddress), // Recipient
(0, memoUtils_1.memoToBufferCV)(memo), // Memo (optional)
];
};
exports.createTokenTransferFunctionArgs = createTokenTransferFunctionArgs;
// Creates post conditions for token transfer
// Uses LessEqual for special tokens (auto-alex-v3, lqstx, liabtc), Equal for others
const createTokenTransferPostConditions = (senderAddress, amount, contractAddress, contractName, assetName) => {
let conditionCode = transactions_1.FungibleConditionCode.Equal;
if (specialConditionCode.has(`${contractAddress}.${contractName}::${assetName}`)) {
conditionCode = transactions_1.FungibleConditionCode.LessEqual;
}
return [
{
type: transactions_1.StacksMessageType.PostCondition,
conditionType: transactions_1.PostConditionType.Fungible,
principal: (0, transactions_1.createStandardPrincipal)(senderAddress),
conditionCode,
amount: BigInt(amount.toFixed()),
assetInfo: (0, transactions_1.createAssetInfo)(contractAddress, contractName, assetName),
},
];
};
exports.createTokenTransferPostConditions = createTokenTransferPostConditions;
// Creates unsigned SIP-010 token transfer transaction
const createTokenTransferTransaction = async (options) => {
const { contractAddress, contractName, assetName, amount, senderAddress, recipientAddress, anchorMode, network, publicKey, fee, nonce, memo, } = options;
const functionArgs = (0, exports.createTokenTransferFunctionArgs)(amount, senderAddress, recipientAddress, memo);
const postConditions = (0, exports.createTokenTransferPostConditions)(senderAddress, amount, contractAddress, contractName, assetName);
const contractCallOptions = {
contractAddress,
contractName,
functionName: "transfer",
functionArgs,
anchorMode,
network: api_1.StacksNetwork[network],
publicKey,
postConditions,
};
if (fee) {
contractCallOptions.fee = fee.toFixed();
}
if (nonce) {
contractCallOptions.nonce = nonce.toFixed();
}
return await (0, transactions_1.makeUnsignedContractCall)(contractCallOptions);
};
exports.createTokenTransferTransaction = createTokenTransferTransaction;
// Creates unsigned STX transfer transaction
const createStxTransferTransaction = async (amount, recipientAddress, anchorMode, network, publicKey, fee, nonce, memo) => {
const options = {
amount: amount.toFixed(),
recipient: recipientAddress,
anchorMode,
network: api_1.StacksNetwork[network],
memo,
publicKey,
};
if (fee) {
options.fee = fee.toFixed();
}
if (nonce) {
options.nonce = nonce.toFixed();
}
return (0, transactions_1.makeUnsignedSTXTokenTransfer)(options);
};
exports.createStxTransferTransaction = createStxTransferTransaction;
// Creates either token or STX transfer transaction based on subAccount presence
const createTransaction = async (transaction, senderAddress, publicKey, subAccount, fee, nonce) => {
const { recipient, anchorMode, network, memo, amount } = transaction;
const tokenDetails = (0, exports.getTokenContractDetails)(subAccount);
if (tokenDetails) {
// Token transfer transaction
const { contractAddress, contractName, assetName } = tokenDetails;
return (0, exports.createTokenTransferTransaction)({
contractAddress,
contractName,
assetName,
amount,
senderAddress,
recipientAddress: recipient,
anchorMode,
network,
publicKey,
fee,
nonce,
memo,
});
}
else {
// Regular STX transfer
return (0, exports.createStxTransferTransaction)(amount, recipient, anchorMode, network, publicKey, fee, nonce, memo);
}
};
exports.createTransaction = createTransaction;
// Applies signature to transaction and returns serialized buffer
const applySignatureToTransaction = (tx, signature) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - TS doesn't recognize spendingCondition.signature property
tx.auth.spendingCondition.signature = (0, transactions_1.createMessageSignature)(signature);
return Buffer.from(tx.serialize());
};
exports.applySignatureToTransaction = applySignatureToTransaction;
// Recreates transaction from operation and applies signature for broadcast
// Used by the broadcast function - rawData type comes from internal ledger-live types
const getTxToBroadcast = async (operation, signature, rawData) => {
const { value, recipients, senders, fee, extra: { memo }, } = operation;
if (isTokenTransferRawData(rawData)) {
// TypeScript now knows rawData has all token transfer fields
const { anchorMode, network, xpub, contractAddress, contractName, assetName } = rawData;
// Create the function arguments for the SIP-010 transfer function
const functionArgs = (0, exports.createTokenTransferFunctionArgs)(new bignumber_js_1.default(value), senders[0], recipients[0], memo !== undefined && memo !== null ? String(memo) : undefined);
const postConditions = (0, exports.createTokenTransferPostConditions)(senders[0], new bignumber_js_1.default(value), contractAddress, contractName, assetName);
const tx = await (0, transactions_1.makeUnsignedContractCall)({
contractAddress,
contractName,
functionName: "transfer",
functionArgs,
anchorMode,
network: api_1.StacksNetwork[network],
publicKey: xpub,
fee: fee.toFixed(),
nonce: operation.transactionSequenceNumber?.toString() ?? "0",
postConditions,
});
return (0, exports.applySignatureToTransaction)(tx, signature);
}
else {
// STX transfer - extract base fields
const { anchorMode, network, xpub } = extractBaseRawData(rawData);
const tx = await (0, exports.createStxTransferTransaction)(new bignumber_js_1.default(value).minus(fee), recipients[0], anchorMode, network, xpub, new bignumber_js_1.default(fee), new bignumber_js_1.default(operation.transactionSequenceNumber ?? 0), memo !== undefined && memo !== null ? String(memo) : undefined);
return (0, exports.applySignatureToTransaction)(tx, signature);
}
};
exports.getTxToBroadcast = getTxToBroadcast;
//# sourceMappingURL=transactions.js.map