@abstract-foundation/agw-client
Version:
Abstract Global Wallet Client SDK
245 lines • 10.6 kB
JavaScript
import { assertCurrentChain, createPublicClient, createWalletClient, custom, fromHex, hexToBigInt, hexToNumber, isHex, toHex, } from "viem";
import { parseAccount, toAccount } from "viem/accounts";
import { createAbstractClient } from "./clients/abstractClient.js";
import { agwCapabilities, getReceiptStatus, } from "./eip5792.js";
import { validChains } from "./exports/index.js";
import { getSmartAccountAddressFromInitialSigner, VALID_CHAINS, } from "./utils.js";
async function getAgwAddressFromInitialSigner(chain, transport, signer) {
const publicClient = createPublicClient({
chain,
transport,
});
return await getSmartAccountAddressFromInitialSigner(signer, publicClient);
}
async function getAgwSigner(provider, method = "eth_accounts") {
const accounts = await provider.request({ method });
return accounts?.[0];
}
async function getAgwClient(account, chain, transport, isPrivyCrossApp, overrideTransport, customPaymasterHandler) {
const wallet = createWalletClient({
account,
transport,
});
const signer = toAccount({
address: account,
signMessage: wallet.signMessage,
signTransaction: wallet.signTransaction,
signTypedData: wallet.signTypedData,
});
const abstractClient = await createAbstractClient({
chain,
signer,
transport,
isPrivyCrossApp,
publicTransport: overrideTransport,
customPaymasterHandler,
});
return abstractClient;
}
export function transformEIP1193Provider(options) {
const { provider, chain, transport: overrideTransport, isPrivyCrossApp = false, customPaymasterHandler, } = options;
const transport = custom(provider);
const handler = async (e) => {
const { method, params } = e;
switch (method) {
case "eth_requestAccounts": {
const signer = await getAgwSigner(provider, method);
if (!signer) {
return [];
}
const smartAccount = await getAgwAddressFromInitialSigner(chain, transport, signer);
return [smartAccount, signer];
}
case "eth_accounts": {
const signer = await getAgwSigner(provider);
if (!signer) {
return [];
}
const smartAccount = await getAgwAddressFromInitialSigner(chain, transport, signer);
return [smartAccount, signer];
}
case "eth_signTypedData_v4": {
const account = await getAgwSigner(provider);
if (!account) {
throw new Error("Account not found");
}
if (params[0] === account) {
return provider.request(e);
}
const abstractClient = await getAgwClient(account, chain, transport, isPrivyCrossApp, overrideTransport, customPaymasterHandler);
return abstractClient.signTypedData(JSON.parse(params[1]));
}
case "personal_sign": {
const account = await getAgwSigner(provider);
if (!account) {
throw new Error("Account not found");
}
if (params[1] === account) {
return provider.request(e);
}
const abstractClient = await getAgwClient(account, chain, transport, isPrivyCrossApp, overrideTransport, customPaymasterHandler);
return await abstractClient.signMessage({
message: {
raw: params[0],
},
});
}
case "eth_signTransaction":
case "eth_sendTransaction": {
const account = await getAgwSigner(provider);
if (!account) {
throw new Error("Account not found");
}
const transaction = params[0];
if (transaction.from === account) {
return await provider.request(e);
}
const abstractClient = await getAgwClient(account, chain, transport, isPrivyCrossApp, overrideTransport, customPaymasterHandler);
// 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?.paymasterParams) {
transaction.paymaster =
transaction.eip712Meta.paymasterParams.paymaster;
transaction.paymasterInput = toHex(transaction.eip712Meta.paymasterParams.paymasterInput);
}
if (method === "eth_signTransaction") {
return (await abstractClient.signTransaction(transaction));
}
else if (method === "eth_sendTransaction") {
return await abstractClient.sendTransaction(transaction);
}
throw new Error("Should not have reached this point");
}
case "wallet_sendCalls": {
const account = await getAgwSigner(provider);
if (!account) {
throw new Error("Account not found");
}
const sendCallsParams = params[0];
if (sendCallsParams.from === account) {
return await provider.request(e);
}
if (sendCallsParams.version === "1.0" ||
sendCallsParams.version === undefined) {
sendCallsParams.calls.forEach((call) => {
if (call.chainId) {
assertCurrentChain({
chain,
currentChainId: fromHex(call.chainId, "number"),
});
}
});
}
if (sendCallsParams.version === "2.0.0") {
if (fromHex(sendCallsParams.chainId, "number") !== chain.id) {
return {
code: 5710,
message: "Chain not supported",
};
}
}
const abstractClient = await getAgwClient(account, chain, transport, isPrivyCrossApp, overrideTransport, customPaymasterHandler);
if (sendCallsParams.from !== parseAccount(abstractClient.account).address) {
return {
code: 4001,
message: "Unauthorized",
};
}
const calls = [];
for (const call of sendCallsParams.calls) {
if (!call.to) {
return {
code: -32602,
message: "Invalid call to unspecified address",
};
}
calls.push({
to: call.to,
value: call.value ? hexToBigInt(call.value) : 0n,
data: call.data ?? "0x",
});
}
const txHash = await abstractClient.sendTransactionBatch({
calls,
});
if (sendCallsParams.version === undefined ||
sendCallsParams.version === "1.0") {
return txHash;
}
return {
id: txHash,
};
}
case "wallet_getCallsStatus": {
const receipt = await provider.request({
method: "eth_getTransactionReceipt",
params,
});
return {
version: "2.0.0",
id: params[0],
chainId: toHex(chain.id),
status: getReceiptStatus(receipt ?? undefined),
atomic: true, // AGW will always process multiple calls as an atomic batch
receipts: receipt != null ? [receipt] : undefined,
};
}
case "wallet_addEthereumChain":
case "wallet_switchEthereumChain": {
const request = params[0];
const chainIdHex = request.chainId;
if (!chainIdHex) {
throw new Error("Chain ID is required");
}
// chainId is hex most likely, convert to number
const chainId = isHex(chainIdHex)
? hexToNumber(chainIdHex)
: chainIdHex;
const chain = Object.values(validChains).find((c) => c.id === chainId);
if (!chain) {
throw new Error(`Chain ${chainId} not supported`);
}
return await provider.request(e);
}
case "wallet_showCallsStatus": {
// not implemented
return undefined;
}
case "wallet_getCapabilities": {
const account = await getAgwSigner(provider);
if (!account) {
throw new Error("Account not found");
}
if (params[0] === account) {
return await provider.request(e);
}
const chainIds = params[1];
if (chainIds) {
const filteredCapabilities = {};
for (const chainId of chainIds) {
if (VALID_CHAINS[fromHex(chainId, "number")]) {
filteredCapabilities[chainId] = agwCapabilities;
}
}
return filteredCapabilities;
}
else {
return Object.keys(VALID_CHAINS).reduce((acc, chainId) => {
acc[toHex(Number(chainId))] = agwCapabilities;
return acc;
}, {});
}
}
default: {
return await provider.request(e);
}
}
};
return {
...provider,
on: provider.on,
removeListener: provider.removeListener,
request: handler,
};
}
//# sourceMappingURL=transformEIP1193Provider.js.map