Swaps
Permit2
Permit2 replaces "approve the Executor for every token" with one approval to Permit2 per token, then an off-chain signature per swap. The canonical Permit2 (0x000000000022D473030F116dDEE9F6B43aC78BA3) is deployed on Elysium testnet and mainnet.
ERC20 inputs only. Native HYPE needs no approval.
Why use it
| Classic approve | Permit2 | |
|---|---|---|
| First swap of a token | approve tx + swap tx | approve-Permit2 tx (once) + signature + swap tx |
| Next swaps | swap tx (if the allowance remains) | signature + swap tx |
| Allowance left behind | Whatever you approved, on the Executor | Infinite on Permit2, but each signature is single-use, bound to an amount, a deadline, the Executor and your address |
The two-call flow
Call 1: get the data to sign
curl "$API/quote?in=$USDC&out=HYPE&amount=25000000&recipient=$ME&permit2=true"- If
approvalis not null: it approves Permit2 (unlimited, once per token). Send it, then repeat call 1. permit2.typedDatais an EIP-712PermitTransferFrom:
"permit2": { "typedData": {
"domain": { "name": "Permit2", "chainId": 99801, "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3" },
"types": {
"PermitTransferFrom": [ { "name": "permitted", "type": "TokenPermissions" }, { "name": "spender", "type": "address" },
{ "name": "nonce", "type": "uint256" }, { "name": "deadline", "type": "uint256" } ],
"TokenPermissions": [ { "name": "token", "type": "address" }, { "name": "amount", "type": "uint256" } ]
},
"primaryType": "PermitTransferFrom",
"message": { "permitted": { "token": "0x7D29…", "amount": "25000000" }, "spender": "0xExecutor…", "nonce": "8172…", "deadline": "1762450000" }
} }tx.datais0xandsimulation.okisfalsewith "sign permit2.typedData, then re-quote…". That is expected.
Sign it
const signature = await wallet.signTypedData({ account, ...q.permit2.typedData });Call 2: get the transaction
Repeat the quote with the signature, and the same nonce and deadline as in the message you signed:
curl "$API/quote?in=$USDC&out=HYPE&amount=25000000&recipient=$ME&permit2=true\
&signature=0x…&permit2Nonce=8172…&permit2Deadline=1762450000"tx now calls Executor.swapWithPermit2(...) and is simulated as usual. Send it.
Rules enforced on-chain
- The permit's
spenderis the Executor, and the Executor passesmsg.senderas the owner to Permit2. A signature is useless to anyone except the address that signed it, even if it leaks. - Nonces are unordered and single-use (the API picks a random 192-bit one). A signature can't be replayed.
amountmust equal the swap'samountIn.permit2Deadlinebounds the signature; the swap's own 5-minutedeadlinestill applies.- If the route changes between call 1 and call 2, the signature stays valid: it only covers the token, the amount and the spender, not the route.
Next: Fees