@abstract-foundation/agw-react
Version:
Abstract Global Wallet React Components
178 lines • 6.88 kB
JavaScript
import { useCrossAppAccounts, usePrivy, } from '@privy-io/react-auth';
import { useCallback, useMemo } from 'react';
import { createPublicClient, fromHex, http, toHex, } from 'viem';
import { AGW_APP_ID } from '../constants.js';
export const usePrivyCrossAppProvider = ({ chain, transport = http(undefined, {
batch: true,
}), }) => {
const { loginWithCrossAppAccount, linkCrossAppAccount, sendTransaction, signMessage, signTypedData, } = useCrossAppAccounts();
const { user, authenticated, ready: privyReady } = usePrivy();
const passthroughMethods = {
web3_clientVersion: true,
web3_sha3: true,
net_listening: true,
net_peerCount: true,
net_version: true,
eth_blobBaseFee: true,
eth_blockNumber: true,
eth_call: true,
eth_chainId: true,
eth_coinbase: true,
eth_estimateGas: true,
eth_feeHistory: true,
eth_gasPrice: true,
eth_getBalance: true,
eth_getBlockByHash: true,
eth_getBlockByNumber: true,
eth_getBlockTransactionCountByHash: true,
eth_getBlockTransactionCountByNumber: true,
eth_getCode: true,
eth_getFilterChanges: true,
eth_getFilterLogs: true,
eth_getLogs: true,
eth_getProof: true,
eth_getStorageAt: true,
eth_getTransactionByBlockHashAndIndex: true,
eth_getTransactionByBlockNumberAndIndex: true,
eth_getTransactionByHash: true,
eth_getTransactionCount: true,
eth_getTransactionReceipt: true,
eth_getUncleByBlockHashAndIndex: true,
eth_getUncleByBlockNumberAndIndex: true,
eth_getUncleCountByBlockHash: true,
eth_getUncleCountByBlockNumber: true,
eth_maxPriorityFeePerGas: true,
eth_newBlockFilter: true,
eth_newFilter: true,
eth_newPendingTransactionFilter: true,
eth_protocolVersion: true,
eth_sendRawTransaction: true,
eth_uninstallFilter: true,
};
const passthrough = (method) => !!passthroughMethods[method];
const publicClient = createPublicClient({
chain,
transport,
});
const getAddressesFromUser = (user) => {
if (!user) {
return {
smartAccount: undefined,
signer: undefined,
};
}
const crossAppAccount = user.linkedAccounts.find((account) => account.type === 'cross_app' && account.providerApp.id === AGW_APP_ID);
const smartAccount = crossAppAccount?.smartWallets?.[0]?.address;
const signer = crossAppAccount?.embeddedWallets?.[0]?.address;
return {
smartAccount: smartAccount ? smartAccount : undefined,
signer: signer ? signer : undefined,
};
};
const getAccounts = useCallback(async (promptLogin) => {
if (!ready) {
return [];
}
let contextUser = user;
if (promptLogin) {
if (!contextUser && !authenticated) {
contextUser = await loginWithCrossAppAccount({
appId: AGW_APP_ID,
});
}
else if (!contextUser && authenticated) {
contextUser = await linkCrossAppAccount({ appId: AGW_APP_ID });
}
}
const { signer, smartAccount } = getAddressesFromUser(contextUser);
if (signer && smartAccount) {
return [smartAccount, signer];
}
else {
return [];
}
}, [
user,
authenticated,
privyReady,
loginWithCrossAppAccount,
linkCrossAppAccount,
]);
const eventListeners = new Map();
const handleRequest = useCallback(async (request) => {
const { method, params } = request;
if (passthrough(method)) {
return publicClient.request(request);
}
switch (method) {
case 'eth_requestAccounts': {
return await getAccounts(true);
}
case 'eth_accounts': {
return await getAccounts(false);
}
case 'wallet_switchEthereumChain':
// TODO: do we need to do anything here?
return null;
case 'wallet_revokePermissions':
// TODO: do we need to do anything here?
return null;
case 'eth_signTransaction':
throw new Error('eth_signTransaction is not supported');
case 'eth_sendTransaction': {
const transaction = params[0];
// Undo the automatic formatting applied by Wagmi's eth_signTransaction
// Formatter: https://github.com/wevm/viem/blob/main/src/zksync/formatters.ts#L114
if (transaction.eip712Meta &&
transaction.eip712Meta.paymasterParams) {
transaction.paymaster =
transaction.eip712Meta.paymasterParams.paymaster;
transaction.paymasterInput = toHex(transaction.eip712Meta.paymasterParams.paymasterInput);
}
return await sendTransaction({
...transaction,
chainId: chain.id,
}, {
address: transaction.from,
});
}
case 'eth_signTypedData_v4':
return await signTypedData(JSON.parse(params[1]), { address: params[0], chainId: chain.id });
case 'eth_sign':
throw new Error('eth_sign is unsafe and not supported');
case 'personal_sign': {
return await signMessage(fromHex(params[0], 'string'), {
address: params[1],
chainId: chain.id,
});
}
default:
throw new Error(`Unsupported request: ${method}`);
}
}, [passthrough, publicClient, getAccounts, signMessage]);
const provider = useMemo(() => {
return {
on: (event, listener) => {
eventListeners.set(event, [
...(eventListeners.get(event) ?? []),
listener,
]);
},
removeListener: (event, listener) => {
eventListeners.set(event, (eventListeners.get(event) ?? []).filter((l) => l !== listener));
},
request: handleRequest,
};
}, [handleRequest]);
const ready = useMemo(() => {
return (privyReady &&
user &&
authenticated &&
user.linkedAccounts.some((account) => account.type === 'cross_app' && account.providerApp.id === AGW_APP_ID));
}, [privyReady, user, authenticated]);
return {
ready,
provider,
};
};
//# sourceMappingURL=usePrivyCrossAppProvider.js.map