@arbius/aa-wallet
Version:
A secure and flexible Account Abstraction wallet implementation for Arbitrum One chain applications.
181 lines (180 loc) • 5.84 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupTransactionQueue = setupTransactionQueue;
exports.sendTransaction = sendTransaction;
const uuid_1 = require("uuid");
const types_1 = require("../types");
// import { getConfig } from './init'; // unused
const broadcastChannel_1 = require("../utils/broadcastChannel");
/**
* Simple mutex implementation for queue processing
*/
class Mutex {
constructor() {
this.locked = false;
this.waitQueue = [];
}
async acquire() {
if (!this.locked) {
this.locked = true;
return Promise.resolve();
}
return new Promise(resolve => {
this.waitQueue.push(resolve);
});
}
release() {
if (this.waitQueue.length > 0) {
const resolve = this.waitQueue.shift();
resolve();
}
else {
this.locked = false;
}
}
}
// Store pending transactions
const transactionQueue = [];
let isProcessing = false;
const mutex = new Mutex();
/**
* Set up the transaction queue and broadcast channel
*/
function setupTransactionQueue() {
setupBroadcastChannelListener();
}
/**
* Send a transaction through the queue
* @param txParams Transaction parameters
* @returns Promise resolving to the transaction hash
*/
async function sendTransaction(txParams) {
const txId = (0, uuid_1.v4)();
// Create a transaction object
const transaction = {
id: txId,
status: types_1.TransactionStatus.PENDING,
method: txParams.method,
params: txParams.params,
chainId: txParams.chainId,
createdAt: Date.now(),
updatedAt: Date.now(),
};
// Add to queue
transactionQueue.push(transaction);
// Broadcast the transaction
(0, broadcastChannel_1.broadcastTxUpdate)(transaction);
// Process the queue
processQueue();
// Return a promise that resolves when the transaction is processed
return new Promise((resolve, reject) => {
const checkStatus = setInterval(() => {
const tx = transactionQueue.find(tx => tx.id === txId);
if (!tx) {
clearInterval(checkStatus);
reject(new Error('Transaction removed from queue'));
return;
}
if (tx.status === types_1.TransactionStatus.SUCCESS) {
clearInterval(checkStatus);
resolve(tx.hash);
}
else if (tx.status === types_1.TransactionStatus.ERROR) {
clearInterval(checkStatus);
reject(tx.error);
}
}, 100);
});
}
/**
* Process the transaction queue
*/
async function processQueue() {
// Use mutex to prevent concurrent processing
if (isProcessing) {
return;
}
await mutex.acquire();
try {
isProcessing = true;
while (transactionQueue.length > 0) {
const tx = transactionQueue[0];
// Skip already processed transactions
if (tx.status !== types_1.TransactionStatus.PENDING) {
transactionQueue.shift();
continue;
}
try {
if (!window.ethereum) {
throw new Error('Ethereum provider not found');
}
// Process the transaction
const result = await window.ethereum.request({
method: tx.method,
params: tx.params,
});
// Update the transaction with the result
tx.hash = result;
tx.status = types_1.TransactionStatus.SUCCESS;
tx.updatedAt = Date.now();
// Broadcast the update
(0, broadcastChannel_1.broadcastTxUpdate)(tx);
}
catch (error) {
// Handle specific error types
if (isNonceError(error)) {
// For nonce errors, we want to retry
console.log('Nonce error, retrying transaction');
continue;
}
// For other errors, mark as failed
tx.status = types_1.TransactionStatus.ERROR;
tx.error = error;
tx.updatedAt = Date.now();
// Broadcast the update
(0, broadcastChannel_1.broadcastTxUpdate)(tx);
}
// Remove the transaction from the queue
transactionQueue.shift();
}
}
finally {
isProcessing = false;
mutex.release();
}
}
/**
* Check if an error is a nonce error
* @param error The error to check
* @returns True if it's a nonce error, false otherwise
*/
function isNonceError(error) {
return (error.message?.includes('nonce') ||
error.message?.includes('transaction underpriced') ||
error.code === -32000);
}
/**
* Set up the broadcast channel listener
*/
function setupBroadcastChannelListener() {
if (typeof BroadcastChannel === 'undefined') {
console.warn('BroadcastChannel not supported. Cross-tab communication disabled.');
return;
}
const channel = new BroadcastChannel('aa-wallet-tx-queue');
channel.onmessage = (event) => {
const { transaction } = event.data;
// Find the transaction in our queue
const existingTxIndex = transactionQueue.findIndex(tx => tx.id === transaction.id);
if (existingTxIndex >= 0) {
// Update the existing transaction
transactionQueue[existingTxIndex] = transaction;
}
else {
// Add the transaction to our queue
transactionQueue.push(transaction);
}
// Process the queue
processQueue();
};
}