Files
237-combo/examples/ts/uniswap-permit2.ts
2026-02-09 21:51:30 -08:00

132 lines
4.1 KiB
TypeScript

/**
* 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 };