Skip to main content

Permit & Meta-Transactions

ERC-20 tokens deployed with ERC20_VOTES or ERC20_SOLADY include EIP-2612 Permit support, enabling gasless approvals via off-chain signatures. Urblock also supports Uniswap's Permit2 for universal gasless approvals.

When to Use

  • Gasless UX — let users approve token spending without paying gas
  • Single-transaction flows — approve + transfer in one call
  • Payment protocols — users sign off-chain, relayer submits on-chain
  • Subscription services — time-limited permits with automatic expiry

How Permit Works

Traditional flow (2 transactions):
1. User calls approve(spender, amount) — pays gas
2. Spender calls transferFrom(owner, spender, amount)

Permit flow (1 transaction):
1. User signs EIP-712 message off-chain — FREE
2. Spender calls permit(owner, spender, value, deadline, v, r, s)
+ transferFrom(owner, spender, amount) — in one tx

Deploy a Permit-Enabled Token

// ERC20_VOTES includes EIP-2612 Permit
const token = await urblock.tokens.create({
name: "Permit Token",
symbol: "PMT",
standard: "ERC20_VOTES",
network: "polygon_amoy",
initial_supply: "1000000000000000000000000",
idempotency_key: "permit-token-001",
});

Get Permit Info

Retrieve the nonce and EIP-712 domain separator needed to build a permit signature:

curl https://api.urblock.io/v1/tokens/tok_abc123/permit_info \
-H "Authorization: Bearer sk_test_..."
{
"nonce": "0",
"domain_separator": "0x...",
"name": "Permit Token",
"version": "1",
"chain_id": 80002,
"verifying_contract": "0xTokenAddress..."
}

Sign a Permit (Off-Chain)

The token holder signs an EIP-712 typed message off-chain using their wallet (e.g., ethers.js, viem). This is a client-side operation — the backend does not sign permits:

import { ethers } from "ethers";

// Build the EIP-712 domain from permit_info
const domain = {
name: permitInfo.name,
version: permitInfo.version,
chainId: permitInfo.chain_id,
verifyingContract: permitInfo.verifying_contract,
};

const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
};

const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour
const value = { owner, spender, value: amount, nonce: permitInfo.nonce, deadline };

const signature = await signer.signTypedData(domain, types, value);
const { v, r, s } = ethers.Signature.from(signature);

Submit a Permit On-Chain

The spender (or relayer) submits the signed permit via the Urblock API:

curl -X POST https://api.urblock.io/v1/tokens/tok_abc123/permit \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"owner": "0xTokenHolder...",
"spender": "0xSpenderContract...",
"value": "1000000000000000000000",
"deadline": 1718238167,
"v": 28,
"r": "0x...",
"s": "0x...",
"idempotency_key": "permit-submit-001"
}'

After the permit is confirmed on-chain, the spender has the allowance and can call transferFrom.

Permit2 (Universal Permits)

Permit2 extends gasless transfers to any ERC-20 token, not just those with built-in EIP-2612 support. Urblock exposes a permit2_transfer endpoint that executes a gasless transfer via the Permit2 contract:

curl -X POST https://api.urblock.io/v1/tokens/tok_abc123/permit2_transfer \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"from": "0xTokenHolder...",
"to": "0xRecipient...",
"amount": "1000000000000000000000",
"idempotency_key": "permit2-transfer-001"
}'
SDK Support

The SDK does not yet have wrapper methods for permit endpoints. Use the HTTP API directly as shown above, or use urblock.request() for a typed call.

Permit vs Permit2

FeatureEIP-2612 PermitPermit2
Requires token supportYes (ERC20_VOTES, ERC20_SOLADY)No (any ERC-20)
On-chain contractBuilt into tokenSeparate Permit2 contract
Nonce managementPer-address counterBitmap-based
Signed transferNoYes (permitTransferFrom)
Batch permitsNoYes

Supported Standards

StandardEIP-2612 PermitPermit2 Compatible
ERC20_VOTES
ERC20_SOLADY
ERC20
ERC1363
ERC3643❌ (compliance checks)

Error Codes

CodeMeaning
permit_expiredDeadline has passed
invalid_signaturev/r/s values don't match the signer
invalid_nonceNonce already used (replay protection)
permit_not_supportedToken standard does not support EIP-2612

Best Practices

  1. Set short deadlines — permits should expire within hours, not days
  2. Track nonces — each address has an incrementing nonce; use the SDK to auto-resolve
  3. Prefer Permit2 for universal support — works with any ERC-20 token
  4. Verify signatures server-side before submitting to avoid wasting gas on invalid permits

Next Steps