@ledgerhq/coin-stacks
Version:
Ledger Stacks Coin integration
122 lines • 5.68 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractContractTransactions = exports.findFinalTokenId = exports.extractSendManyTransactions = exports.extractTokenTransferTransactions = void 0;
const api_1 = require("./api");
const state_1 = require("@ledgerhq/cryptoassets/state");
// Extracts token transfer transactions from a transaction list
const extractTokenTransferTransactions = (transactions) => {
return transactions.filter(t => t.tx?.tx_type === "token_transfer");
};
exports.extractTokenTransferTransactions = extractTokenTransferTransactions;
// Extracts send-many transactions from a transaction list
const extractSendManyTransactions = (transactions) => {
return transactions.filter(t => t.tx?.tx_type === "contract_call" && t.tx.contract_call?.function_name === "send-many");
};
exports.extractSendManyTransactions = extractSendManyTransactions;
// Fetches asset identifier from contract ID using metadata API
// Returns undefined if metadata fetch fails or no matching token found
const getAssetIdFromContractId = async (contractId) => {
const metadata = await (0, api_1.fetchFungibleTokenMetadataCached)(contractId);
if (metadata.results.length === 1) {
// If there's only one result, use its asset_identifier
return metadata.results[0].asset_identifier;
}
if (metadata.results.length > 1) {
// If multiple results, find which one exists in the token registry
for (const result of metadata.results) {
const token = await (0, state_1.getCryptoAssetsStore)().findTokenByAddressInCurrency(result.asset_identifier, "stacks");
if (token) {
return result.asset_identifier;
}
}
}
// No metadata found or no matching token in registry
return undefined;
};
// Resolves token ID to canonical form by checking cache, registry, and metadata
// Format: CONTRACT_ID::ASSET_NAME. Returns original if no resolution found
const findFinalTokenId = async (tokenId, prevRecords) => {
// Check if we've already resolved this token ID
if (prevRecords[tokenId]) {
return prevRecords[tokenId];
}
// Check if token exists in the local registry
const registeredToken = await (0, state_1.getCryptoAssetsStore)().findTokenByAddressInCurrency(tokenId, "stacks");
if (registeredToken) {
return tokenId;
}
// Parse the token ID to extract contract and asset information
const [contractId, assetName] = tokenId.split("::");
const [contractAddress, _] = contractId.split(".");
// Fetch metadata from the blockchain (cached for performance)
const metadata = await (0, api_1.fetchFungibleTokenMetadataCached)(contractAddress);
// If only one result, use it as the canonical identifier
if (metadata.results.length === 1) {
return metadata.results[0].asset_identifier;
}
// Multiple results: find the one that matches our asset name and is in the registry
for (const result of metadata.results) {
const [, resultAssetName] = result.asset_identifier.split("::");
// Check if this result matches our criteria:
// 1. It exists in the token registry
// 2. The asset name matches what we're looking for
const isRegistered = await (0, state_1.getCryptoAssetsStore)().findTokenByAddressInCurrency(result.asset_identifier, "stacks");
const isMatchingAsset = resultAssetName === assetName;
if (isRegistered && isMatchingAsset) {
return result.asset_identifier;
}
}
// No better match found, return the original token ID
return tokenId;
};
exports.findFinalTokenId = findFinalTokenId;
// Checks if transaction is a valid transfer call (not send-many)
const isValidTransferTransaction = (tx) => {
if (tx.tx?.tx_type !== "contract_call")
return false;
if (tx.tx.contract_call?.function_name === "send-many")
return false;
return tx.tx.contract_call?.function_name === "transfer";
};
// Extracts asset name from transaction post_conditions
const getAssetNameFromPostConditions = (tx) => {
return tx.tx.post_conditions?.find(p => p.type === "fungible")?.asset.asset_name;
};
// Determines token ID from contract ID and asset name
const resolveTokenId = async (contractId, assetName) => {
if (assetName) {
return `${contractId}::${assetName}`;
}
// If no asset name from post_conditions, try fetching metadata
return getAssetIdFromContractId(contractId);
};
// Adds transaction to the map grouped by token ID
const addTransactionToMap = (map, tokenId, tx) => {
if (!map[tokenId]) {
map[tokenId] = [];
}
map[tokenId].push(tx);
};
// Extracts and groups contract transactions by token ID
const extractContractTransactions = async (transactions) => {
const contractTxsMap = {};
const finalTokenIdMap = {};
for (const tx of transactions) {
if (!isValidTransferTransaction(tx))
continue;
const contractId = tx.tx.contract_call?.contract_id;
if (!contractId)
continue;
const assetName = getAssetNameFromPostConditions(tx);
const tokenId = await resolveTokenId(contractId, assetName);
if (!tokenId)
continue;
// Resolve to final token ID
const finalTokenId = await (0, exports.findFinalTokenId)(tokenId, finalTokenIdMap);
finalTokenIdMap[tokenId] = finalTokenId;
addTransactionToMap(contractTxsMap, finalTokenId.toLowerCase(), tx);
}
return contractTxsMap;
};
exports.extractContractTransactions = extractContractTransactions;
//# sourceMappingURL=transformers.js.map