/** * Uniswap: Permit2 signature-based approvals * * This example demonstrates how to use Permit2 for signature-based token approvals. * Permit2 allows users to approve tokens via signatures instead of on-chain transactions. */ import { createWalletRpcClient } from '../../src/utils/chain-config.js'; import { getPermit2Address, getUniswapSwapRouter02 } from '../../src/utils/addresses.js'; import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js'; import { getPermit2Domain, getPermit2TransferTypes, createPermit2Deadline } from '../../src/utils/permit2.js'; import { waitForTransaction } from '../../src/utils/rpc.js'; import { signTypedData } from 'viem'; import type { Address } from 'viem'; const CHAIN_ID = 1; // Mainnet const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`; // Permit2 ABI for permit transfer const PERMIT2_ABI = [ { name: 'permitTransferFrom', type: 'function', stateMutability: 'nonpayable', inputs: [ { components: [ { components: [ { name: 'token', type: 'address' }, { name: 'amount', type: 'uint256' }, ], name: 'permitted', type: 'tuple', }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], name: 'permit', type: 'tuple', }, { components: [ { name: 'to', type: 'address' }, { name: 'requestedAmount', type: 'uint256' }, ], name: 'transferDetails', type: 'tuple', }, { name: 'signature', type: 'bytes' }, ], outputs: [], }, ] as const; async function permit2Approval() { const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY); const publicClient = walletClient as any; const account = walletClient.account?.address; if (!account) { throw new Error('No account available'); } const permit2Address = getPermit2Address(CHAIN_ID); const token = getTokenMetadata(CHAIN_ID, 'USDC'); const amount = parseTokenAmount('1000', token.decimals); const spender = getUniswapSwapRouter02(CHAIN_ID); // Example: approve Uniswap router console.log(`Creating Permit2 signature for ${amount} ${token.symbol}`); console.log(`Permit2: ${permit2Address}`); console.log(`Spender: ${spender}`); console.log(`Account: ${account}`); // Step 1: Get nonce from Permit2 // In production, query the Permit2 contract for the user's current nonce const nonce = 0n; // TODO: Read from Permit2 contract // Step 2: Create permit data const deadline = createPermit2Deadline(3600); // 1 hour const domain = getPermit2Domain(CHAIN_ID); const types = getPermit2TransferTypes(); const permit = { permitted: { token: token.address, amount, }, nonce, deadline, }; const transferDetails = { to: spender, requestedAmount: amount, }; // Step 3: Sign the permit console.log('\n1. Signing Permit2 permit...'); const signature = await signTypedData(walletClient, { domain, types, primaryType: 'PermitTransferFrom', message: { permitted: permit.permitted, spender, nonce: permit.nonce, deadline: permit.deadline, }, }); console.log(`Signature: ${signature}`); // Step 4: Execute permitTransferFrom (this would typically be done by a router/contract) // Note: In practice, Permit2 permits are usually used within larger transaction flows // (e.g., Universal Router uses them automatically) console.log('\n2. Permit2 signature created successfully!'); console.log('Use this signature in your transaction (e.g., Universal Router)'); console.log('\nExample usage with Universal Router:'); console.log(' - Universal Router will call permitTransferFrom on Permit2'); console.log(' - Then execute the swap/transfer'); console.log(' - All in one transaction'); } // Run if executed directly if (import.meta.url === `file://${process.argv[1]}`) { permit2Approval().catch(console.error); } export { permit2Approval };