Signing (EIP-7702)
After receiving a quote, the user signs two things off-chain — both gasless, with no on-chain approval and no token allowance:
- The order — an EIP-712 typed-data signature (
signTypedData) over theSweepOrder. This becomes thebytes signaturepassed to the on-chainsweep(). - An EIP-7702 authorization — delegates the user's EOA to the per-chain
SweepDelegateimplementation so the EOA can run the settlement as if it were a smart account.
The client then submits { quoteId, signature, authorization } to POST /intents. A relayer (Kryard) broadcasts the EIP-7702 type-4 ("set-code") transaction and pays the gas.
EIP-7702-only. The on-chain
SweepRouter/ Permit2 contracts still exist but are not used by the product — Permit2 needs an on-chainapprove, which costs gas. The two off-chain signatures below are the only signing path.
Step 1 — Sign the order (EIP-712)
The SweepOrder is signed as EIP-712 typed data, not a blind hash — so hardware wallets render every field readably. The on-chain delegate verifies it as:
ECDSA.recover(toTypedDataHash(domainSeparator(), structHash(order)), sig) == address(this)Because sweep() runs with address(this) == the user's EOA == order.owner, the recovered signer must be the user. Build the typed-data payload exactly as the on-chain SweepDelegate defines its EIP-712 scheme:
import type { SweepOrderWire } from "./types"; // the order from POST /quote
type Hex = `0x${string}`;
/**
* EIP-712 type definition for the SweepOrder — all 14 fields, in the order that
* matches the on-chain SWEEP_ORDER_TYPEHASH.
*/
export const SWEEP_ORDER_TYPES = {
SweepOrder: [
{ name: "owner", type: "address" },
{ name: "tokenIn", type: "address" },
{ name: "amountIn", type: "uint256" },
{ name: "feeAmount", type: "uint256" },
{ name: "builder", type: "address" },
{ name: "builderFee", type: "uint256" },
{ name: "tokenOut", type: "address" },
{ name: "minAmountOut", type: "uint256" },
{ name: "destination", type: "address" },
{ name: "swapTarget", type: "address" },
{ name: "swapCalldata", type: "bytes" },
{ name: "deadline", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "destChainId", type: "uint256" },
],
} as const;
/**
* Build the full EIP-712 typed-data payload the EOA signs via
* `walletClient.signTypedData(...)`. Mirrors the on-chain SweepDelegate scheme:
*
* domain = { name: "Sweepster", version: "1",
* chainId: <source chain id>,
* verifyingContract: order.owner }
* primaryType = "SweepOrder"
*
* `verifyingContract` is the delegated EOA (== order.owner) and `chainId` is the
* SOURCE chain id, because `sweep` runs as the user's EOA on the source chain.
*/
export function buildDelegateTypedData(order: SweepOrderWire, sourceChainId: number) {
return {
domain: {
name: "Sweepster",
version: "1",
chainId: BigInt(sourceChainId),
verifyingContract: order.owner as Hex,
},
types: SWEEP_ORDER_TYPES,
primaryType: "SweepOrder",
message: {
owner: order.owner,
tokenIn: order.tokenIn,
amountIn: BigInt(order.amountIn),
feeAmount: BigInt(order.feeAmount),
builder: order.builder,
builderFee: BigInt(order.builderFee),
tokenOut: order.tokenOut,
minAmountOut: BigInt(order.minAmountOut),
destination: order.destination,
swapTarget: order.swapTarget,
swapCalldata: order.swapCalldata,
deadline: BigInt(order.deadline),
nonce: BigInt(order.nonce),
destChainId: BigInt(order.destChainId),
},
} as const;
}Field order is critical. The
SWEEP_ORDER_TYPESfield list mirrors the on-chainSWEEP_ORDER_TYPEHASHexactly. AnySweepOrderfield change must be reflected in both the contract typehash and this type definition, or the recovered signer will not match.
Sign the typed data
Sign the typed data with signTypedData — viem computes the EIP-712 digest (keccak256(0x1901 ‖ domainSeparator ‖ structHash)) that the on-chain toTypedDataHash recovers against.
import { createWalletClient, custom } from "viem";
import { base } from "viem/chains";
const walletClient = createWalletClient({
chain: base,
transport: custom(window.ethereum),
});
const signature = await walletClient.signTypedData({
account: order.owner as Hex,
...buildDelegateTypedData(order, 8453), // 8453 = source chain (Base)
});With wagmi (React):
import { useSignTypedData } from "wagmi";
const { signTypedDataAsync } = useSignTypedData();
const signature = await signTypedDataAsync(
buildDelegateTypedData(order, sourceChainId),
);Step 2 — Sign the EIP-7702 authorization
The authorization delegates the user's EOA to the per-chain SweepDelegate implementation contract. The contractAddress comes from the quote (quote.settlementAddress, preferred) or a configured per-chain fallback.
const signedAuth = await walletClient.signAuthorization({
account: walletClient.account,
contractAddress: sweepDelegateAddress, // the per-chain SweepDelegate impl
chainId, // the source chain id
});
// Serialize for the orchestrator (POST /intents `authorization`):
const authorization = {
address: signedAuth.address, // the SweepDelegate impl
chainId: signedAuth.chainId,
nonce: signedAuth.nonce,
r: signedAuth.r,
s: signedAuth.s,
yParity: signedAuth.yParity ?? 0,
};The authorization is required.
POST /intentsreturns422ifauthorizationis absent — there is no fallback. EIP-7702 signing requires viem >= 2.x and a Pectra-capable chain + wallet; all supported chains qualify.
What the user is authorizing
The EIP-712 order signature binds every field of the SweepOrder. By signing, the user authorizes Sweepster to:
- Pull exactly
amountInoftokenInfrom their wallet (self-pull, via the 7702 delegation) - Swap and skim
feeAmount(protocol fee) +builderFee - Deliver
minAmountOutor more oftokenOuttodestination - Execute via
swapTargetusingswapCalldata - Expire at
deadline
The relayer cannot change any of these without invalidating the signature.
Displaying the order to the user
Before signing, display the key order fields clearly:
You will send: 1,000 USDC (Base)
Fee: 8 USDC (protocol: 5, builder: 3)
You will receive: ≥ 992 USDC (Arbitrum)
To: 0xYourDestination
Expires: 2026-06-13T14:30:00ZA robust integration also re-validates the returned order against the user's original request (owner, token, amount, destination, chain, fees, slippage floor) before signing — to prevent blind-signing an order the orchestrator may have altered.