import { createWalletClient, createPublicClient, http, toHex, keccak256, encodeAbiParameters, encodeFunctionData } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { secp256k1 } from '@noble/curves/secp256k1';
// 1) Fill these from the published deployments:
// Base Sepolia deployments (example)
export const EIP7702PROXY_TEMPLATE_ADDRESS = '0x7702cb554e6bFb442cb743A7dF23154544a7176C' as const;
export const CBSW_IMPLEMENTATION_ADDRESS = '0x000100abaad02f1cfC8Bbe32bD5a564817339E72' as const;
export const VALIDATOR_ADDRESS = '0x79A33f950b90C7d07E66950daedf868BD0cDcF96' as const;
export const NONCE_TRACKER_ADDRESS = '0xD0Ff13c28679FDd75Bc09c0a430a0089bf8b95a8' as const;
// Standard ERC-1967 implementation slot
export const ERC1967_IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' as const;
// 2) Setup signer and clients
const eoa = privateKeyToAccount('0xPRIVATEKEY');
const walletClient = createWalletClient({ account: eoa, chain: baseSepolia, transport: http(CDP_RPC_URL) });
const publicClient = createPublicClient({ chain: baseSepolia, transport: http(CDP_RPC_URL) });
// 3) Create EIP-7702 authorization to delegate to the EIP7702Proxy (NOT to the implementation)
const authorization = await walletClient.signAuthorization({
account: eoa,
contractAddress: EIP7702PROXY_TEMPLATE_ADDRESS,
// optionally: executor: eoa.address
});
// 4) Prepare initialization calldata for CoinbaseSmartWallet.initialize
// Minimal example: make the EOA the initial owner. You can also encode passkeys if desired.
const ownerArg = encodeAbiParameters([{ type: 'address' }], [eoa.address]);
const initArgs = encodeFunctionData({
abi: [{ type: 'function', name: 'initialize', inputs: [{ type: 'bytes[]', name: 'owners' }], outputs: [], stateMutability: 'payable' }],
functionName: 'initialize',
args: [[ownerArg]],
});
// 5) Read NonceTracker nonce and current ERC-1967 implementation at your EOA address
const nonce: bigint = await publicClient.readContract({
address: NONCE_TRACKER_ADDRESS,
abi: [{ type: 'function', name: 'nonces', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' }],
functionName: 'nonces',
args: [eoa.address],
});
const implSlot = await publicClient.getStorageAt({ address: eoa.address, slot: ERC1967_IMPLEMENTATION_SLOT });
const currentImplementation = (`0x${implSlot.slice(-40)}`) as `0x${string}`;
// 6) Create the EIP7702Proxy.setImplementation hash and sign it (raw secp256k1, no Eth prefix)
const TYPE_STRING =
'EIP7702ProxyImplementationSet(uint256 chainId,address proxy,uint256 nonce,address currentImplementation,address newImplementation,bytes callData,address validator,uint256 expiry)';
const typeHash = keccak256(toHex(TYPE_STRING));
const callDataHash = keccak256(initArgs);
const expiry = 2n ** 256n - 1n; // or your own expiry
const encodedForHash = encodeAbiParameters(
[
{ type: 'bytes32' }, // typehash
{ type: 'uint256' }, // chainId
{ type: 'address' }, // proxy
{ type: 'uint256' }, // nonce
{ type: 'address' }, // currentImplementation
{ type: 'address' }, // newImplementation
{ type: 'bytes32' }, // keccak256(callData)
{ type: 'address' }, // validator
{ type: 'uint256' }, // expiry
],
[
typeHash,
BigInt(baseSepolia.id),
EIP7702PROXY_TEMPLATE_ADDRESS,
nonce,
currentImplementation,
CBSW_IMPLEMENTATION_ADDRESS,
callDataHash,
VALIDATOR_ADDRESS,
expiry,
],
);
const toSign = keccak256(encodedForHash);
// sign raw 32-byte hash with the EOA private key (no prefix)
const priv = eoa.source instanceof Uint8Array ? eoa.source : Buffer.from(eoa.source.slice(2), 'hex');
const sig = secp256k1.sign(Buffer.from(toSign.slice(2), 'hex'), priv);
const r = `0x${sig.r.toString(16).padStart(64, '0')}`;
const s = `0x${sig.s.toString(16).padStart(64, '0')}`;
const v = 27 + sig.recovery; // 27 or 28
const signature = (`0x${r.slice(2)}${s.slice(2)}${v.toString(16).padStart(2, '0')}`) as `0x${string}`;
// 7) Encode EIP7702Proxy.setImplementation(newImpl, initArgs, validator, expiry, signature, allowCrossChainReplay=false)
const setImplCalldata = encodeFunctionData({
abi: [{
type: 'function',
name: 'setImplementation',
inputs: [
{ name: 'newImplementation', type: 'address' },
{ name: 'callData', type: 'bytes' },
{ name: 'validator', type: 'address' },
{ name: 'expiry', type: 'uint256' },
{ name: 'signature', type: 'bytes' },
{ name: 'allowCrossChainReplay', type: 'bool' },
],
outputs: [],
stateMutability: 'payable',
}],
functionName: 'setImplementation',
args: [CBSW_IMPLEMENTATION_ADDRESS, initArgs, VALIDATOR_ADDRESS, expiry, signature, false],
});
// 8) Send one transaction that includes the 7702 authorization and calls setImplementation
const hash = await walletClient.sendTransaction({
account: eoa,
to: eoa.address,
data: setImplCalldata,
authorizationList: [authorization],
});