@0xsequence/connect
Version:
Connect package for Sequence Web SDK
187 lines • 8.21 kB
JavaScript
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