Skip to content

Quickstart (Builders)

Get from zero to a signed, submitted sweep in under 15 minutes.

Prerequisites

  • Node.js 22+, pnpm or npm
  • A builder account + API key — self-serve at dashboard.sweepster.xyz
  • viem (for signing): npm install viem

Step 1 — Get a builder account

Self-serve signup is at dashboard.sweepster.xyz:

  1. Connect the wallet that will own your payout address.
  2. Sign in with SIWE (Sign-In With Ethereum).
  3. If your wallet isn't a builder yet, you'll get a "Create a builder account" form — pick a name.
  4. You receive your API key once (save it) and land in the dashboard.

No admin step is required. New builders start at zero commission (builderBps = 0); to earn a markup, contact Sweepster to raise your cap. The returned builder looks like:

json
{
  "builder": {
    "id": "bld_abc123",
    "name": "Acme Wallet",
    "builderBps": 0,
    "maxBuilderBps": 0,
    "payoutAddress": "0xYourPayoutAddress"
  },
  "apiKey": "sk_live_..."
}

Store the API key securely. It is shown only once. You can rotate it from the dashboard (the new key is also shown once). See the Builder Dashboard guide.

Step 2 — Request a quote

typescript
// POST https://api.sweepster.xyz/quote
const response = await fetch("https://api.sweepster.xyz/quote", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    owner: "0xUserWalletAddress",
    tokenIn: "0xTokenAddressOnSourceChain",
    amountIn: "1000000",           // in token's smallest unit (e.g. 1 USDC = 1000000)
    tokenOut: "0xTokenAddressOnDestChain",
    destination: "0xDestinationWallet",
    destChainId: 42161,            // Arbitrum
    tier: "tier1",
    subscriptionActive: false,
    userId: "user_unique_id",
    apiKey: "sk_live_...",         // your builder API key (optional — enables attribution)
  }),
});

const { quote, order } = await response.json();
console.log("Quote expires at:", new Date(quote.expiresAtMs));
console.log("Total fees:", quote.total, "in token units");

Step 3 — Sign two things off-chain (EIP-7702, gasless)

The gasless sweep needs the user to sign two off-chain messages — no gas, no approval, no allowance. See the Signing guide for buildDelegateTypedData.

typescript
import { createWalletClient, custom } from "viem";
import { base } from "viem/chains";
import { buildDelegateTypedData } from "./delegate"; // see Signing guide

const walletClient = createWalletClient({ chain: base, transport: custom(window.ethereum) });
const chainId = 8453; // source chain (Base)

// 1. EIP-712 signTypedData the SweepOrder (becomes the `bytes signature`)
const signature = await walletClient.signTypedData({
  account: order.owner,
  ...buildDelegateTypedData(order, chainId),
});

// 2. Sign the EIP-7702 authorization delegating the EOA to the SweepDelegate impl
const signedAuth = await walletClient.signAuthorization({
  account: walletClient.account,
  contractAddress: quote.settlementAddress, // per-chain SweepDelegate impl
  chainId,
});
const authorization = {
  address: signedAuth.address,
  chainId: signedAuth.chainId,
  nonce: signedAuth.nonce,
  r: signedAuth.r,
  s: signedAuth.s,
  yParity: signedAuth.yParity ?? 0,
};

Step 4 — Submit

Submit the quote id, the order-digest signature, and the EIP-7702 authorization. The relayer (Kryard) broadcasts the type-4 set-code transaction and pays the gas.

typescript
// POST https://api.sweepster.xyz/intents
const submitRes = await fetch("https://api.sweepster.xyz/intents", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ quoteId: quote.id, signature, authorization }),
});

const intent = await submitRes.json();
console.log("Intent ID:", intent.id, "Status:", intent.status);

The authorization is requiredPOST /intents returns 422 without it. There is no Permit2 fallback.

Step 5 — Poll status

typescript
// GET https://api.sweepster.xyz/intents/:id
async function pollIntent(id: string) {
  const TERMINAL = ["delivered", "failed"];
  while (true) {
    const res = await fetch(`https://api.sweepster.xyz/intents/${id}`);
    const intent = await res.json();
    console.log(intent.status, intent.txHash);
    if (TERMINAL.includes(intent.status)) break;
    await new Promise((r) => setTimeout(r, 3000));
  }
}

await pollIntent(intent.id);

What's next

Non-custodial · Atomic · Audit + bug bounty planned before mainnet value.