132 lines
4.1 KiB
TypeScript
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 };
|
|
|