UNPKG

@0xsequence/connect

Version:
187 lines 8.21 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 returnedTransactions = []; 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) { const triggerEmbeddedWalletPromise = async () => { // 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; }; returnedTransactions.push(triggerEmbeddedWalletPromise); // Sequence-Based Connector } else if (isSequenceUniversalWallet) { const triggerSequenceUniversalWalletPromise = async () => { 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; }; returnedTransactions.push(triggerSequenceUniversalWalletPromise); // Other connectors (metamask, eip-6963, etc...) } else { const triggerOtherConnectorTransaction = async ({ transaction, isLastTransaction }) => { const txnHash = await walletClient.sendTransaction({ account: senderAddress, to: transaction.to, value: transaction?.value, data: transaction?.data, chain: undefined }); if (!isLastTransaction || (isLastTransaction && waitConfirmationForLastTransaction)) { const { txnStatus } = await waitForTransactionReceipt({ indexerClient, txnHash, publicClient, confirmations: transactionConfirmations }); if (txnStatus === TransactionStatus.FAILED) { throw new Error('Transaction failed'); } } return txnHash; }; for (const [index, transaction] of transactions.entries()) { const isLastTransaction = index === transactions.length - 1; const triggerOtherConnectorTransactionPromise = () => triggerOtherConnectorTransaction({ transaction, isLastTransaction }); returnedTransactions.push(triggerOtherConnectorTransactionPromise); } } return returnedTransactions; }; export const waitForTransactionReceipt = async ({ indexerClient, txnHash, publicClient, confirmations }) => { const RECEIPT_MAX_WAIT_MINUTES = 3; const WAIT_TIME_BETWEEN_REQUESTS_MS = 3000; const startTime = Date.now(); const maxWaitTime = RECEIPT_MAX_WAIT_MINUTES * 60 * 1000; let receipt; while (Date.now() - startTime < maxWaitTime && !receipt) { try { const response = await indexerClient.fetchTransactionReceipt({ txnHash, maxBlockWait: 400 }); receipt = response.receipt; } catch (e) { // Wait a little bit between request in case of very small block times await new Promise(resolve => setTimeout(resolve, WAIT_TIME_BETWEEN_REQUESTS_MS)); } } if (!receipt) { throw new Error('Transaction receipt not found'); } 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; }; export const isTxRejected = (error) => { const errorWithCode = error; // error 4001 is documented in EIP-1193 // https://eips.ethereum.org/EIPS/eip-1193#provider-errors if (errorWithCode?.code == 4001) { return true; } return false; }; //# sourceMappingURL=transactions.js.map