UNPKG

@trezor/connect

Version:

High-level javascript interface for Trezor hardware wallet.

225 lines 13.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.fetchAccountOwnerAndTokenInfoForAddress = exports.buildTokenTransferTransaction = exports.getMinimumRequiredTokenAccountsForTransfer = exports.buildCreateAssociatedTokenAccountInstruction = exports.getAssociatedTokenAccountAddress = exports.buildTokenTransferInstruction = exports.buildTransferTransaction = exports.dummyPriorityFeesForFeeEstimation = exports.getLamportsFromSol = exports.SOLANA_BASE_FEE = void 0; exports.createTransactionShim = createTransactionShim; exports.createTransactionShimFromHex = createTransactionShimFromHex; const ts_belt_1 = require("@mobily/ts-belt"); const blockchain_link_utils_1 = require("@trezor/blockchain-link-utils"); const bigNumber_1 = require("@trezor/utils/lib/bigNumber"); const { SYSTEM_PROGRAM_PUBLIC_KEY, tokenProgramsInfo } = blockchain_link_utils_1.solanaUtils; const loadSolanaLib = async () => await Promise.resolve().then(() => __importStar(require('@solana/kit'))); const loadSolanaComputeBudgetProgramLib = async () => await Promise.resolve().then(() => __importStar(require('@solana-program/compute-budget'))); const loadSolanaSystemProgramLib = async () => await Promise.resolve().then(() => __importStar(require('@solana-program/system'))); const loadSolanaTokenProgramLib = async (tokenProgramName) => { switch (tokenProgramName) { case 'spl-token': return await Promise.resolve().then(() => __importStar(require('@solana-program/token'))); case 'spl-token-2022': return await Promise.resolve().then(() => __importStar(require('@solana-program/token-2022'))); default: throw new Error(`Unsupported token program: ${tokenProgramName}`); } }; exports.SOLANA_BASE_FEE = 5000; const getLamportsFromSol = (amountInSol) => BigInt(new bigNumber_1.BigNumber(amountInSol).times(10 ** 9).toString()); exports.getLamportsFromSol = getLamportsFromSol; exports.dummyPriorityFeesForFeeEstimation = { computeUnitPrice: '100000', computeUnitLimit: '200000', }; async function createTransactionShimCommon(transaction) { const { getBase16Codec, getTransactionEncoder } = await loadSolanaLib(); return { addSignature(signerPubKey, signatureHex) { if (signerPubKey in transaction.signatures) { const signatureBytes = getBase16Codec().encode(signatureHex); transaction = Object.freeze({ ...transaction, signatures: Object.freeze({ ...transaction.signatures, [signerPubKey]: signatureBytes, }), }); } }, serializeMessage() { return getBase16Codec().decode(transaction.messageBytes); }, serialize() { return (0, ts_belt_1.pipe)(transaction, getTransactionEncoder().encode, getBase16Codec().decode); }, }; } async function createTransactionShim(message) { const { compileTransaction } = await loadSolanaLib(); const transaction = compileTransaction(message); return createTransactionShimCommon(transaction); } async function createTransactionShimFromHex(rawTx) { const { getBase16Encoder, getTransactionDecoder } = await loadSolanaLib(); const txByteArray = getBase16Encoder().encode(rawTx); const transaction = getTransactionDecoder().decode(txByteArray); return createTransactionShimCommon(transaction); } const addPriorityFees = async (message, priorityFees) => { const [{ prependTransactionMessageInstructions }, { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction },] = await Promise.all([loadSolanaLib(), loadSolanaComputeBudgetProgramLib()]); return (0, ts_belt_1.pipe)(message, m => prependTransactionMessageInstructions([ getSetComputeUnitLimitInstruction({ units: parseInt(priorityFees.computeUnitLimit, 10), }), getSetComputeUnitPriceInstruction({ microLamports: parseInt(priorityFees.computeUnitPrice, 10), }), ], m)); }; const buildTransferTransaction = async (fromAddress, toAddress, amountInSol, blockhash, lastValidBlockHeight, priorityFees) => { const [{ address, appendTransactionMessageInstruction, createTransactionMessage, lamports, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageFeePayer, createNoopSigner, }, { getTransferSolInstruction },] = await Promise.all([loadSolanaLib(), loadSolanaSystemProgramLib()]); const message = await (0, ts_belt_1.pipe)(createTransactionMessage({ version: 'legacy' }), m => setTransactionMessageFeePayer(address(fromAddress), m), m => setTransactionMessageLifetimeUsingBlockhash({ blockhash: blockhash, lastValidBlockHeight: BigInt(lastValidBlockHeight ?? '0xFFFFFFFFFFFFFFFF'), }, m), m => appendTransactionMessageInstruction(getTransferSolInstruction({ amount: lamports((0, exports.getLamportsFromSol)(amountInSol)), destination: address(toAddress), source: createNoopSigner(address(fromAddress)), }), m), m => addPriorityFees(m, priorityFees)); return await createTransactionShim(message); }; exports.buildTransferTransaction = buildTransferTransaction; const buildTokenTransferInstruction = async (from, to, owner, amount, mint, decimals, tokenProgramName) => { const [{ address, createNoopSigner }, { getTransferCheckedInstruction },] = await Promise.all([loadSolanaLib(), loadSolanaTokenProgramLib(tokenProgramName)]); return getTransferCheckedInstruction({ amount: BigInt(amount.toString()), authority: createNoopSigner(address(owner)), decimals, destination: address(to), mint: address(mint), source: address(from), }); }; exports.buildTokenTransferInstruction = buildTokenTransferInstruction; const getAssociatedTokenAccountAddress = async (baseAddress, tokenMintAddress, tokenProgramName) => { const [{ address }, { findAssociatedTokenPda },] = await Promise.all([loadSolanaLib(), loadSolanaTokenProgramLib(tokenProgramName)]); const [pdaAddress] = await findAssociatedTokenPda({ mint: address(tokenMintAddress), owner: address(baseAddress), tokenProgram: address(tokenProgramsInfo[tokenProgramName].publicKey), }); return pdaAddress; }; exports.getAssociatedTokenAccountAddress = getAssociatedTokenAccountAddress; const buildCreateAssociatedTokenAccountInstruction = async (funderAddress, newOwnerAddress, tokenMintAddress, tokenProgramName) => { const [{ address, createNoopSigner }, { getCreateAssociatedTokenInstruction },] = await Promise.all([loadSolanaLib(), loadSolanaTokenProgramLib(tokenProgramName)]); const associatedTokenAccountAddress = await (0, exports.getAssociatedTokenAccountAddress)(newOwnerAddress, tokenMintAddress, tokenProgramName); const txInstruction = getCreateAssociatedTokenInstruction({ ata: associatedTokenAccountAddress, mint: address(tokenMintAddress), owner: address(newOwnerAddress), payer: createNoopSigner(address(funderAddress)), }); txInstruction.data = new Uint8Array([]); return [txInstruction, associatedTokenAccountAddress]; }; exports.buildCreateAssociatedTokenAccountInstruction = buildCreateAssociatedTokenAccountInstruction; const getMinimumRequiredTokenAccountsForTransfer = (tokenAccounts, requiredAmount) => { let accumulatedBalance = new bigNumber_1.BigNumber('0'); const requiredAccounts = ts_belt_1.F.toMutable((0, ts_belt_1.pipe)(tokenAccounts, ts_belt_1.A.sort((a, b) => new bigNumber_1.BigNumber(b.balance).comparedTo(new bigNumber_1.BigNumber(a.balance)) ?? 0), ts_belt_1.A.takeWhile(tokenAccount => { const needMoreAccounts = accumulatedBalance.lt(requiredAmount); accumulatedBalance = accumulatedBalance.plus(tokenAccount.balance); return needMoreAccounts; }))); return requiredAccounts; }; exports.getMinimumRequiredTokenAccountsForTransfer = getMinimumRequiredTokenAccountsForTransfer; const buildTokenTransferTransaction = async (fromAddress, toAddress, toAddressOwner, tokenMint, tokenUiAmount, tokenDecimals, fromTokenAccounts, toTokenAccount, blockhash, lastValidBlockHeight, priorityFees, tokenProgramName) => { const { address, appendTransactionMessageInstruction, appendTransactionMessageInstructions, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, } = await loadSolanaLib(); let message = await (0, ts_belt_1.pipe)(createTransactionMessage({ version: 'legacy' }), m => setTransactionMessageFeePayer(address(fromAddress), m), m => setTransactionMessageLifetimeUsingBlockhash({ blockhash: blockhash, lastValidBlockHeight: BigInt(lastValidBlockHeight ?? '0xFFFFFFFFFFFFFFFF'), }, m), m => addPriorityFees(m, priorityFees)); const tokenAmount = new bigNumber_1.BigNumber(tokenUiAmount).times(10 ** tokenDecimals); const requiredAccounts = (0, exports.getMinimumRequiredTokenAccountsForTransfer)(fromTokenAccounts, tokenAmount.toString()); const isReceiverAddressSystemAccount = toAddressOwner === SYSTEM_PROGRAM_PUBLIC_KEY; let finalReceiverAddress = toAddress; if (isReceiverAddressSystemAccount) { if (toTokenAccount) { finalReceiverAddress = toTokenAccount.publicKey; } else { const [createAccountInstruction, associatedTokenAccountAddress] = await (0, exports.buildCreateAssociatedTokenAccountInstruction)(fromAddress, toAddress, tokenMint, tokenProgramName); message = appendTransactionMessageInstruction(createAccountInstruction, message); finalReceiverAddress = associatedTokenAccountAddress; } } let remainingAmount = tokenAmount; const instructionPromises = requiredAccounts.map(async (tokenAccount) => { const transferAmount = bigNumber_1.BigNumber.min(remainingAmount, new bigNumber_1.BigNumber(tokenAccount.balance)); const transferInstruction = await (0, exports.buildTokenTransferInstruction)(tokenAccount.publicKey, finalReceiverAddress, fromAddress, transferAmount, tokenMint, tokenDecimals, tokenProgramName); remainingAmount = remainingAmount.minus(transferAmount); return transferInstruction; }); message = appendTransactionMessageInstructions(await Promise.all(instructionPromises), message); return { transaction: await createTransactionShim(message), destinationAddress: finalReceiverAddress, tokenAccountInfo: isReceiverAddressSystemAccount ? { baseAddress: toAddress, tokenProgram: tokenProgramsInfo[tokenProgramName].publicKey, tokenMint, tokenAccount: finalReceiverAddress, } : undefined, }; }; exports.buildTokenTransferTransaction = buildTokenTransferTransaction; const fetchAccountOwnerAndTokenInfoForAddress = async (blockchain, address, mint, tokenProgram) => { let accountOwner; let tokenInfo; const accountInfoResponse = await blockchain.getAccountInfo({ descriptor: address, details: 'tokens', }); if (accountInfoResponse) { const associatedTokenAccount = await (0, exports.getAssociatedTokenAccountAddress)(address, mint, tokenProgram); accountOwner = accountInfoResponse?.misc?.owner; tokenInfo = accountInfoResponse?.tokens ?.find(token => token.contract === mint) ?.accounts?.find(account => associatedTokenAccount.toString() === account.publicKey); } return [accountOwner, tokenInfo]; }; exports.fetchAccountOwnerAndTokenInfoForAddress = fetchAccountOwnerAndTokenInfoForAddress; //# sourceMappingURL=solanaUtils.js.map