Skip to content

Approval Flows

CommissionRoad is designed to be as frictionless as possible. How you handle token approvals is a key part of the user experience.

This guide explains the three main strategies available to you.

Best for: Most modern dApps, ensuring a smooth UX with off-chain signatures.

CommissionRoad has native integration with Uniswap's Permit2. This allows users to "approve" tokens by signing a message, rather than submitting an on-chain transaction.

How it works

  1. User signs a typed data message (EIP-712) authorizing the transfer.
  2. The signature is passed into CommissionRoad.
  3. CommissionRoad calls Permit2 to pull the tokens using your signature.

Example

typescript
// 1. Get the signature
const { domain, types, values } = PERMIT2_TypedData;
const signature = await wallet.signTypedData(domain, types, values);

// 2. Include in your batch call
const permitCall = encodeFunctionData({
  abi: permit2Abi,
  functionName: "permitTransferFrom",
  args: [permit, transferDetails, user, signature]
});

// 3. Execute Commission Call
commissionRoad.write.commissionCall([
  [{ target: permit2Addy, callData: permitCall, ... }, ...otherCalls],
  ...
])

2. Standard approve() (Legacy / Simple)

Best for: Simple integrations, legacy tokens that don't support Permit, or quick prototypes.

This is the classic ERC-20 flow. It works with everything but adds friction because the user must pay gas to approve before they can transact.

How it works

  1. User sends a transaction: token.approve(CommissionRoad, amount).
  2. User waits for the transaction to confirm.
  3. User sends the commissionCall transaction.

Example

typescript
// 1. Approve (Triggers a wallet transaction)
await token.write.approve([commissionRoadAddress, amount]);

// 2. Execute
await commissionRoad.write.commissionCall(...);

3. EIP-7702 (Gasless / Atomic)

Best for: Smart Wallets, Relayer configurations, and "Invisible" UX.

With EIP-7702, specific wallets can authorize the CommissionRoadExecutor code to run strictly within their own account context. This means the user is the executor.

Why it's powerful

Since the user is the one executing the transfer logic (via the delegated code), no approval is needed to move their own tokens. The code just calls transfer.

Example

typescript
// 1. Authorize the Executor (Sign message)
const auth = await wallet.signAuthorization({ contract: executorAddress, ... });

// 2. Broadcast Transaction (Relayer or User)
// The transaction 'targets' the user's own address but runs the Executor logic.
await client.sendTransaction({
  to: userAddress,
  authorizationList: [auth],
  data: encodeFunctionData({ functionName: 'batchCall', ... })
});