txHash and a minted slotTokenId. This is the how-to companion to the Quickstart: the same flow, now in your own project, shown both ways, with the failure cases a first run skips.
Before you start
- A throwaway testnet key in
CONSUMER_PK(environment only). It signs; it never broadcasts and needs no ETH. pnpm installat the repository root.- TUSDC in that key’s address from the faucet, so it can pay for a capability.
- A relayer to submit to: run your own with one command, or point
CAPLANE_RELAYER_URLat a hosted one. New to all of this? Run the Quickstart first.
AllocationAdapter on Robinhood testnet (chainId 46630), the guaranteed floor with no external dependency. The generic path at the end brokers any capability.
Path A: the connect() SDK
connect() is the developer on-ramp. It loads a chain’s registry, caches the EIP-712 domain and the broker address, and brokers in one call. It holds no key: you pass your own LocalAccount, it signs, and it never broadcasts.
buyAllocation returns { txHash, orderHash, slotTokenId, explorerUrl, feeCollected }. Under the hood it runs the four steps in Path B for you: discover the live quote, build the order, sign the EIP-3009 authorization (nonce = orderHash), submit, and decode the receipt.
For any other capability (gas, timeboost, attested), the generic primitive mirrors the broker exactly. You supply the adapter, the encoded params, and the amount:
connect() creates a read-only client for discovery and never a wallet client. The consumer is gas-poor: it only signs the authorization. The relayer is msg.sender. No key ever leaves your process; only the signature does.Path B: the raw primitives
For a developer integrating into an existing client (or reimplementing in another language), the four stepsconnect() wraps are exported directly. Resolve everything from the registry first, exactly as the consumer CLI does:
Discover the price
Read
adapter.quote(params) on-chain. It is view, so no gas and no state change. The price and payToken it returns become the order’s amount and payToken.Build the order
Assemble the
Order from the quote and your choices. payee defaults to the consumer (self-allocation); order.nonce defaults to a random salt.Sign the authorization (the binding)
Sign an EIP-3009 See the binding for why USDC itself then rejects any tampered order, and Order encoding for the byte-exact layout the hash commits to.
TransferWithAuthorization whose nonce is the order hash. signOrder sets nonce = orderHash = keccak256(abi.encode(order)) for you, so one signature ties the payment to the adapter, the params, the amount, the payee, and the fee.Submit to a relayer
POST the order, exactly as signed, to a relayer. Any mutation of a field breaks the orderHash, so send it verbatim. On success, decode the Allocation receipt to the minted slot id.
submit(relayerUrl, order, sig, chainId) returns the RelaySuccess on 200, or throws a typed SubmitError(code) you can branch on.A real settlement
Running either path against the deployedAllocationAdapter produces the printed machine truth below. This is the settlement the Quickstart lands (Allocation slot #1 on Robinhood), shown here as the raw path prints it. Every value is real and on-chain.
5.000000 TUSDC (6 decimals). At feeBps 100 the relayer earns 50000 (0.050000 TUSDC) and the provider receives 4.950000. Confirm it on the explorer: 0xb89a56…abd845. The Brokered event carries this orderHash, and the slot ERC721 is owned by your address.
When a relayer rejects the order
submit throws SubmitError(code) so you branch on the typed code, not on a message string. Every code below is a real RelayErrorCode from the relayer’s pipeline.
| Code | HTTP | What it means | What to do |
|---|---|---|---|
FEE_BELOW_FLOOR | 402 | Your feeBps is under this relayer’s local floor. | Raise feeBps, or shop a relayer with a lower floor via GET /info. |
AUTH_EXPIRED | 422 | The signed window (validBefore) has passed. | Re-sign with a fresh window. |
WOULD_REVERT | 422 | The order would revert on-chain (sold out, insufficient balance). The relayer caught it in pre-simulation, gaslessly. | Fix the inputs: fund the key, pick an open vault. |
ORDERHASH_MISMATCH | 400 | Your client hash does not match the relayer’s recomputed hash. | Re-encode the order. This is the encoding footgun: see Order encoding. |
MALFORMED_ORDER | 400 | The request body failed validation. | Check the field types (feeBps is a uint16; amount and nonce cross the wire as decimal strings). |
INSUFFICIENT_RELAYER_GAS | 503 | The relayer is low on gas. | Try another relayer, or run your own. |
UPSTREAM_RPC | 502 | The relayer’s RPC failed. | Retry, or try another relayer. |
SubmitError("BAD_RESPONSE"). The full catalog is in the errors reference.
The gotchas worth stating once
No signup, no API key, no dashboard. You hold your key, you sign one order, and a permissionless relayer settles it. There is no one in the path to coordinate, censor, or fake it.Related
- The binding and how a brokerage settles: why the order is tamper-proof and what happens in the one transaction.
- Order encoding,
brokerCapability, and the errors reference: the exact machinery this lane links instead of restating. - Sell a capability behind your own adapter, or run a relayer and earn the fee.

