Appearance
EIP-7702 accounts
Live deployment
Use INK_PUBLIC_PAYMASTER_V08 for EIP-7702 UserOperations. It is pinned to canonical EntryPoint v0.8 and quote version 3. The original INK_PUBLIC_PAYMASTER deployment remains on v0.7 and cannot accept EIP-7702 operations.
EIP-7702 lets an EOA temporarily behave like a smart account by authorizing a delegate implementation. EntryPoint v0.8 applies the authorization before validating the UserOperation. The paymaster still pays native ETH from its EntryPoint deposit and separately precharges the EOA's USDC; EIP-7702 does not turn USDC into Ink's protocol gas token.
Before the first prepaid operation
The public endpoint does not bootstrap the USDC approval. Before requesting sponsorship, the EOA must hold native Ink USDC and have approved the new v0.8 paymaster to spend a finite amount. That approval must be sent with ordinary gas or another sponsorship mechanism.
The delegate implementation must also be deployed on Ink. Viem's default Simple7702Account implementation is deployed on Ink, but production integrations should explicitly pin and verify the implementation address they intend to authorize.
Viem setup
Create the client with the SDK's pinned v0.8 deployment so it cannot silently send a 7702 operation to the v0.7 endpoint.
ts
import {createPublicClient, defineChain, http} from "viem";
import {privateKeyToAccount} from "viem/accounts";
import {
createBundlerClient,
toSimple7702SmartAccount,
} from "viem/account-abstraction";
import {
createInkPublicPaymasterClient,
createInkViemPaymaster,
encodePublicPaymasterApproval,
INK_PUBLIC_PAYMASTER_V08,
INK_SIMPLE_7702_DELEGATE,
} from "@hellomoon/ink-paymaster";
const inkRpcUrl = process.env.INK_RPC_URL!;
const ink = defineChain({
id: 57_073,
name: "Ink Mainnet",
nativeCurrency: {name: "Ether", symbol: "ETH", decimals: 18},
rpcUrls: {default: {http: [inkRpcUrl]}},
});
const publicClient = createPublicClient({chain: ink, transport: http(inkRpcUrl)});
const owner = privateKeyToAccount("0x...");
const delegate = INK_SIMPLE_7702_DELEGATE;
const account = await toSimple7702SmartAccount({
client: publicClient,
entryPoint: "0.8",
implementation: delegate,
owner,
});
const deployment = INK_PUBLIC_PAYMASTER_V08;
const service = createInkPublicPaymasterClient({deployment});
// Use the same deployment when encoding the separately owner-paid approval.
const approvalCalldata = encodePublicPaymasterApproval(100_000n, deployment);
const bundler = createBundlerClient({
account,
chain: ink,
client: publicClient,
transport: http(service.bundlerUrl),
paymaster: createInkViemPaymaster(service),
paymasterContext: {
maxTokenCost: 100_000n,
idempotencyKey: "checkout-order-123",
// Viem does not pass its authorization object into the paymaster hook.
// The service signs this delegate into quote v3 and the contract verifies it.
eip7702Delegate: delegate,
},
});
const authorization = await owner.signAuthorization({
address: delegate,
chainId: ink.id,
nonce: await publicClient.getTransactionCount({
address: owner.address,
blockTag: "pending",
}),
});
const hash = await bundler.sendUserOperation({
authorization,
calls: [{to: destination, value: 0n, data: transferCalldata}],
});For an already-delegated EOA, continue supplying eip7702Delegate in paymasterContext. The UserOperation may no longer need the 0x7702 factory marker, but the quote remains bound to the delegate currently installed on the sender.
Safety checks
Quote v3 signs the expected delegate address. Onchain validation reads the sender's EIP-7702 code designator and rejects a mismatch, so a quote cannot be replayed after changing the delegate. The SDK also rejects:
- a
0x7702factory marker withoutpaymasterContext.eip7702Delegate; - a 7702 operation configured against EntryPoint v0.7;
- a v0.8 deployment that reports quote version 2; and
- a quote whose encoded delegate differs from its metadata or requested delegate.