@arbius/aa-wallet
Version:
A secure and flexible Account Abstraction wallet implementation for Arbitrum One chain applications.
265 lines (264 loc) • 10.2 kB
JavaScript
;
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.AAWalletProvider = exports.AAWalletContext = void 0;
const react_1 = __importStar(require("react"));
const types_1 = require("../types");
const init_1 = require("../core/init");
const walletReducer_1 = require("../reducers/walletReducer");
const walletActions_1 = require("../reducers/walletActions");
const broadcastChannel_1 = require("../utils/broadcastChannel");
exports.AAWalletContext = (0, react_1.createContext)({
isConnected: false,
address: null,
chainId: null,
transactions: [],
connect: async () => '',
disconnect: () => { },
switchChain: async () => { },
sendTransaction: async () => '',
});
const AAWalletProvider = ({ children }) => {
const [state, dispatch] = (0, react_1.useReducer)(walletReducer_1.walletReducer, walletReducer_1.initialState);
(0, react_1.useEffect)(() => {
if (!(0, init_1.isInitialized)()) {
console.warn('AA Wallet not initialized. Call init() before rendering AAWalletProvider.');
return () => { };
}
const config = (0, init_1.getConfig)();
if (config) {
dispatch({ type: 'WALLET_SET_CONFIG', payload: config });
if (config.ui?.autoConnectOnInit) {
connect();
}
}
if (!window.ethereum) {
return () => { };
}
const handleAccountsChanged = (accounts) => {
if (accounts.length === 0) {
dispatch({ type: walletActions_1.WALLET_DISCONNECT });
}
else {
const newState = {
address: accounts[0],
chainId: state.chainId ?? null,
isConnected: true,
transactions: state.transactions
};
dispatch({ type: walletActions_1.WALLET_CONNECT, payload: newState });
}
};
const handleChainChanged = (chainIdHex) => {
const chainId = parseInt(chainIdHex, 16);
dispatch({ type: walletActions_1.WALLET_SWITCH_CHAIN, payload: chainId });
};
window.ethereum.on('accountsChanged', handleAccountsChanged);
window.ethereum.on('chainChanged', handleChainChanged);
return () => {
window.ethereum?.removeListener('accountsChanged', handleAccountsChanged);
window.ethereum?.removeListener('chainChanged', handleChainChanged);
};
}, []);
(0, react_1.useEffect)(() => {
if (typeof BroadcastChannel === 'undefined') {
return () => { };
}
const channel = new BroadcastChannel('aa-wallet-state');
channel.onmessage = (event) => {
const { state: newState } = event.data;
dispatch({ type: 'WALLET_SET_STATE', payload: newState });
};
return () => {
channel.close();
};
}, []);
(0, react_1.useEffect)(() => {
if (typeof BroadcastChannel === 'undefined') {
return () => { };
}
const channel = new BroadcastChannel('aa-wallet-tx-queue');
channel.onmessage = (event) => {
const { transaction } = event.data;
const existingTx = state.transactions.find(tx => tx.id === transaction.id);
if (existingTx) {
dispatch({ type: walletActions_1.TRANSACTION_UPDATE, payload: transaction });
}
else {
dispatch({ type: walletActions_1.TRANSACTION_ADD, payload: transaction });
}
};
return () => {
channel.close();
};
}, [state.transactions]);
const connect = (0, react_1.useCallback)(async () => {
if (!window.ethereum) {
throw new Error('No Ethereum provider found');
}
try {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts.length === 0) {
throw new Error('No accounts found');
}
const chainIdHex = await window.ethereum.request({ method: 'eth_chainId' });
const chainId = parseInt(chainIdHex, 16);
const newState = {
address: accounts[0],
chainId,
isConnected: true,
transactions: state.transactions
};
dispatch({
type: walletActions_1.WALLET_CONNECT,
payload: newState
});
(0, broadcastChannel_1.broadcastWalletState)(newState);
return accounts[0];
}
catch (error) {
const err = error;
console.error('Error connecting to wallet:', err);
throw err;
}
}, [state.transactions]);
const disconnect = (0, react_1.useCallback)(() => {
const newState = {
isConnected: false,
address: null,
chainId: null,
transactions: state.transactions
};
dispatch({ type: walletActions_1.WALLET_DISCONNECT });
(0, broadcastChannel_1.broadcastWalletState)(newState);
}, [state.transactions]);
const switchChain = (0, react_1.useCallback)(async (chainId) => {
if (!window.ethereum) {
throw new Error('No Ethereum provider found');
}
if (!state.isConnected) {
throw new Error('Wallet not connected');
}
try {
const chainIdHex = `0x${chainId.toString(16)}`;
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainIdHex }],
});
const newState = {
...state,
chainId
};
dispatch({ type: walletActions_1.WALLET_SWITCH_CHAIN, payload: chainId });
(0, broadcastChannel_1.broadcastWalletState)(newState);
}
catch (error) {
const err = error;
if (err.code === 4902) {
throw new Error('Chain not supported by wallet');
}
throw error;
}
}, [state]);
const sendTransaction = (0, react_1.useCallback)(async (transaction) => {
if (!window.ethereum) {
throw new Error('No Ethereum provider found');
}
if (!state.isConnected) {
throw new Error('Wallet not connected');
}
const txId = `tx-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const tx = {
id: txId,
status: types_1.TransactionStatus.PENDING,
method: transaction.method,
params: transaction.params,
chainId: transaction.chainId,
createdAt: Date.now(),
updatedAt: Date.now(),
};
const newState = {
...state,
transactions: [...state.transactions, tx]
};
dispatch({ type: walletActions_1.TRANSACTION_ADD, payload: tx });
try {
const hash = await window.ethereum.request({
method: transaction.method,
params: transaction.params,
});
const updatedTx = {
...tx,
hash: hash,
status: types_1.TransactionStatus.SUCCESS,
updatedAt: Date.now(),
};
const finalState = {
...newState,
transactions: newState.transactions.map(t => t.id === txId ? updatedTx : t)
};
dispatch({ type: walletActions_1.TRANSACTION_UPDATE, payload: updatedTx });
(0, broadcastChannel_1.broadcastWalletState)(finalState);
return hash;
}
catch (error) {
const err = error;
const updatedTx = {
...tx,
status: types_1.TransactionStatus.ERROR,
error: err,
updatedAt: Date.now(),
};
const finalState = {
...newState,
transactions: newState.transactions.map(t => t.id === txId ? updatedTx : t)
};
dispatch({ type: walletActions_1.TRANSACTION_UPDATE, payload: updatedTx });
(0, broadcastChannel_1.broadcastWalletState)(finalState);
throw err;
}
}, [state]);
const contextValue = (0, react_1.useMemo)(() => ({
...state,
connect,
disconnect,
switchChain,
sendTransaction,
}), [state, connect, disconnect, switchChain, sendTransaction]);
return (<exports.AAWalletContext.Provider value={contextValue}>
{children}
</exports.AAWalletContext.Provider>);
};
exports.AAWalletProvider = AAWalletProvider;