You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/content/tutorial/6-gasless-transfers/7-usdc-permit/content.md
+61-23Lines changed: 61 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,18 +13,31 @@ terminal:
13
13
14
14
# USDC with EIP-2612 Permit
15
15
16
-
In Lesson 6 you built gasless USDT transfers using meta-transactions. USDC uses a different approval method called **EIP-2612 Permit** - a standardized way to approve token spending with signatures. This lesson shows how to adapt your gasless pipeline for USDC.
16
+
In Lesson 6 you built gasless USDT transfers using a custom meta-transaction approval. USDC ships with a standardized approval flow called **EIP-2612 Permit**. This lesson explains what that standard changes, why it exists, and how to adapt the gasless pipeline you already wrote so that USDC transfers go through the same relay infrastructure.
17
17
18
18
---
19
19
20
20
## Learning Goals
21
21
22
-
- Understand EIP-2612 Permit vs meta-transaction approvals
23
-
- Sign permit messages with version-based domain separators
24
-
-Use `transferWithPermit` instead of `transferWithApproval`
25
-
-Adapt fee calculation for USDC-specific parameters
22
+
- Understand when to prefer EIP-2612 Permit instead of custom meta-transaction approvals.
23
+
- Sign permit messages that use the version + chainId domain separator defined by EIP-2612.
24
+
-Swap the `transferWithApproval` call for the USDC-specific `transferWithPermit`.
25
+
-Adjust fee calculations to respect USDC's 6-decimal precision and Polygon USD pricing.
26
26
27
-
The core flow stays the same: discover relays, calculate fees, sign approval, submit to relay. Only the approval signature changes.
27
+
You already know the broader flow: discover relays, compute fees, sign an approval, relay the transaction. The only moving pieces are the approval signature and the calldata that consumes it. Everything else stays intact.
28
+
29
+
---
30
+
31
+
## Background: What EIP-2612 Adds
32
+
33
+
EIP-2612 is an extension of ERC-20 that lets a token holder authorize spending via an off-chain signature instead of an on-chain `approve()` transaction. The signature uses the shared EIP-712 typed-data format:
34
+
35
+
-**Domain separator** includes the token name, version, chainId, and contract address so signatures cannot be replayed across chains or forks.
36
+
-**Permit struct** defines the spender, allowance value, and deadline in a predictable shape.
37
+
38
+
Tokens like USDC, DAI, and WETH adopted the standard because it enables wallets and relayers to cover approval gas costs while staying interoperable with any contract that understands permits (for example, Uniswap routers or Aave).
39
+
40
+
Older tokens such as USDT predate EIP-2612, so they expose custom meta-transaction logic instead. That is why Lesson 6 had to sign the entire `transferWithApproval` function payload, whereas USDC only needs the numeric values that describe the allowance.
USDC uses a different transfer contract and Uniswap pool than USDT. The method selector also changes.
113
+
USDC relies on a different relay contract and Uniswap pool than USDT on Polygon. Updating the constants up front prevents subtle bugs later on. For example, querying gas data against the wrong selector yields an optimistic fee that fails on-chain.
const { r, s, v } =ethers.utils.splitSignature(signature)
142
156
```
143
157
158
+
Key callouts:
159
+
160
+
- Fetch the **nonce** from the USDC contract itself. EIP-2612 uses a per-owner nonce to prevent replay.
161
+
- Calculate the **approval value** as `transfer + fee`. A permit is just an allowance, so the relay must be allowed to withdraw both the payment to the recipient and its compensation.
162
+
- USDC accepts a `MaxUint256` deadline, but production systems usually set a shorter deadline (for example `Math.floor(Date.now() / 1000) + 3600`) to minimize replay windows.
Notice the `deadline` parameter - this replaces USDT's `approval` parameter (which was the approval amount).
187
+
`transferWithPermit` consumes the permit signature directly. Compare this to the USDT version: instead of passing an encoded `approve()` call, you now hand the relay the raw signature components plus a deadline.
188
+
189
+
If you changed the `deadline` value when signing the permit, make sure the same variable is used here. Hardcoding `MaxUint256` in one place and not the other invalidates the signature.
The gas limit for `transferWithPermit` is typically the same as `transferWithApproval` (~72,000), but query the contract to be sure.
208
+
-`getRequiredRelayGas` evaluates the gas buffer the forwarder demands for `transferWithPermit`. It usually matches the USDT value (~72,000 gas) but querying removes guesswork.
209
+
- USDC keeps **6 decimals**, so multiply/divide by `1_000_000` when converting between POL and USDC. Avoid using `ethers.utils.parseUnits(..., 18)` out of habit.
210
+
- The `polPerUsdc` helper should target the USDC/WMATIC pool; pricing against USDT would skew the fee at times when the two stablecoins diverge.
187
211
188
212
---
189
213
@@ -197,6 +221,8 @@ After building the transfer calldata, the rest is identical to Lesson 6:
197
221
4. Submit to relay via `HttpClient`
198
222
5. Broadcast transaction
199
223
224
+
If you already wrapped these steps in helper functions, you should not need to touch them. The permit signature simply slots into the existing request payload where the USDT approval bytes previously sat.
225
+
200
226
---
201
227
202
228
## ABI Changes
@@ -210,6 +236,8 @@ const TRANSFER_ABI = [
210
236
]
211
237
```
212
238
239
+
`transferWithPermit` mirrors OpenZeppelin's relay helper, so the ABI change is straightforward. Keeping the ABI narrowly scoped makes tree-shaking easier if you bundle the tutorial for production later.
240
+
213
241
---
214
242
215
243
## Why Two Approval Methods?
@@ -228,23 +256,33 @@ Most modern tokens (DAI, USDC, WBTC on some chains) support EIP-2612. Older toke
228
256
229
257
---
230
258
259
+
## Testing and Troubleshooting
260
+
261
+
-**Signature mismatch**: Double-check that `domain.name` exactly matches the on-chain token name. For USDC on Polygon it is `"USD Coin"`; capitalization matters.
262
+
-**Invalid deadline**: If the relay says the permit expired, inspect the value you passed to `deadline` and ensure your local clock is not skewed.
263
+
-**Allowance too low**: If the recipient receives funds but the relay reverts, print the computed `feeAmount` and make sure the permit covered both transfer and fee.
264
+
265
+
Running `npm run usdc` after each change keeps the feedback loop tight and mirrors how the Nimiq wallet tests the same flow.
266
+
267
+
---
268
+
231
269
## Production Considerations
232
270
233
271
1.**Check token support**: Not all ERC20s have permit. Fallback to standard `approve()` + `transferFrom()` if needed.
234
272
2.**Deadline vs MaxUint256**: Production systems often use block-based deadlines (e.g., `currentBlock + 100`) for tighter security.
235
273
3.**Domain parameters**: Always verify `name` and `version` match the token contract - wrong values = invalid signature.
236
274
4.**Method selector lookup**: Store selectors in config per token to avoid hardcoding.
275
+
5.**Permit reuse policy**: Decide whether to reuse a permit for multiple transfers or issue a fresh one per relay request. Fresh permits simplify accounting but require re-signing each time.
237
276
238
277
---
239
278
240
279
## Wrap-Up
241
280
242
-
You've now implemented gasless transfers for both USDT (meta-transaction) and USDC (EIP-2612 permit). The key takeaways:
281
+
You now support gasless transfers for both USDT (custom meta-transaction) and USDC (EIP-2612 permit). Keep these takeaways in mind:
243
282
244
-
- ✅ Approval strategies vary by token implementation
245
-
- ✅ EIP-2612 is the standard, but many tokens predate it
246
-
- ✅ Domain separators differ (salt vs version+chainId)
247
-
- ✅ Transfer method changes (`transferWithApproval` vs `transferWithPermit`)
248
-
- ✅ Fee calculation and relay logic stay consistent
283
+
- ✅ Approval strategies vary across tokens; detect the capability before deciding on the flow.
284
+
- ✅ EIP-2612 standardizes the permit format: domain fields and struct definitions must match exactly.
285
+
- ✅ `transferWithPermit` lets you drop the bulky encoded function signature and pass raw signature parts instead.
286
+
- ✅ Fee and relay logic remain unchanged once the calldata is assembled correctly.
249
287
250
288
You now have a complete gasless transaction system matching the Nimiq wallet implementation, ready for production use on Polygon mainnet.
0 commit comments