UNPKG

@0xsequence/connect

Version:
164 lines 6.86 kB
import { sequence } from '0xsequence'; import { TransactionStatus } from '@0xsequence/indexer'; import { TRANSACTION_CONFIRMATIONS_DEFAULT } from '../constants/index.js'; import { compareAddress } from '../utils/helpers.js'; class FeeOptionInsufficientFundsError extends Error { feeOptions; constructor(message, feeOptions) { super(message); this.name = 'FeeOptionInsufficientFundsError'; this.feeOptions = feeOptions; } } export const sendTransactions = async ({ chainId, senderAddress, publicClient, walletClient, connector, transactions, indexerClient, transactionConfirmations = TRANSACTION_CONFIRMATIONS_DEFAULT, waitConfirmationForLastTransaction = true }) => { const walletClientChainId = await walletClient.getChainId(); if (walletClientChainId !== chainId) { throw new Error('The Wallet Client is using the wrong network'); } if (publicClient.chain?.id !== chainId) { throw new Error('The Public Client is using the wrong network'); } const sequenceWaaS = connector?.['sequenceWaas']; const isEmbeddedWallet = !!sequenceWaaS; const isSequenceUniversalWallet = !!connector?._wallet?.isSequenceBased; // Sequence WaaS if (isEmbeddedWallet) { // waas connector logic const resp = await sequenceWaaS.feeOptions({ transactions: transactions, network: chainId }); const isSponsored = resp.data.feeOptions.length == 0; let transactionsFeeOption; const transactionsFeeQuote = resp.data.feeQuote; const balances = await indexerClient.getTokenBalancesDetails({ filter: { accountAddresses: [senderAddress], omitNativeBalances: false } }); for (const feeOption of resp.data.feeOptions) { const isNativeToken = feeOption.token.contractAddress == null; if (isNativeToken) { const nativeTokenBalance = balances.nativeBalances?.[0].balance || '0'; if (BigInt(nativeTokenBalance) >= BigInt(feeOption.value)) { transactionsFeeOption = feeOption; break; } } else { const erc20TokenBalance = balances.balances.find(b => compareAddress(b.contractAddress, feeOption.token.contractAddress || '')); const erc20TokenBalanceValue = erc20TokenBalance?.balance || '0'; if (BigInt(erc20TokenBalanceValue) >= BigInt(feeOption.value)) { transactionsFeeOption = feeOption; break; } } } if (!transactionsFeeOption && !isSponsored) { throw new FeeOptionInsufficientFundsError(`Transaction fee option with valid user balance not found: ${resp.data.feeOptions.map(f => f.token.symbol).join(', ')}`, resp.data.feeOptions); } const response = await sequenceWaaS.sendTransaction({ transactions, transactionsFeeOption, transactionsFeeQuote, network: chainId }); if (response.code === 'transactionFailed') { throw new Error(response.data.error); } const txnHash = response.data.txHash; if (waitConfirmationForLastTransaction) { const { txnStatus } = await waitForTransactionReceipt({ indexerClient, txnHash: txnHash, publicClient, confirmations: transactionConfirmations }); if (txnStatus === TransactionStatus.FAILED) { throw new Error('Transaction failed'); } } return txnHash; // Sequence-Based Connector } else if (isSequenceUniversalWallet) { const wallet = sequence.getWallet(); const signer = wallet.getSigner(); const response = await signer.sendTransaction(transactions); if (waitConfirmationForLastTransaction) { const { txnStatus } = await waitForTransactionReceipt({ indexerClient, txnHash: response.hash, publicClient, confirmations: transactionConfirmations }); if (txnStatus === TransactionStatus.FAILED) { throw new Error('Transaction failed'); } } return response.hash; // Other connectors (metamask, eip-6963, etc...) } else { let txHash = ''; // We fire the transactions one at a time since the cannot be batched for (const [index, transaction] of transactions.entries()) { const txnHash = await walletClient.sendTransaction({ account: senderAddress, to: transaction.to, value: transaction?.value, data: transaction?.data, chain: undefined }); const isLastTransaction = index === transactions.length - 1; if (!isLastTransaction || (isLastTransaction && waitConfirmationForLastTransaction)) { const { txnStatus } = await waitForTransactionReceipt({ indexerClient, txnHash, publicClient, confirmations: transactionConfirmations }); if (txnStatus === TransactionStatus.FAILED) { throw new Error('Transaction failed'); } } // The transaction hash of the last transaction is the one that should be returned txHash = txnHash; } return txHash; } }; export const waitForTransactionReceipt = async ({ indexerClient, txnHash, publicClient, confirmations }) => { const receiptPromise = new Promise(async (resolve, reject) => { await indexerClient.subscribeReceipts({ filter: { txnHash } }, { onMessage: ({ receipt }) => { resolve(receipt); }, onError: () => { reject('Transaction receipt not found'); } }); }); const receipt = await receiptPromise; if (confirmations) { const blockConfirmationPromise = new Promise(resolve => { const unwatch = publicClient.watchBlocks({ onBlock: ({ number: currentBlockNumber }) => { const confirmedBlocknumber = receipt.blockNumber + confirmations; if (currentBlockNumber >= confirmedBlocknumber) { unwatch(); resolve(); } } }); }); await blockConfirmationPromise; } return receipt; }; //# sourceMappingURL=transactions.js.map