Skip to content

Swap: quote through SwapPro (keyless, every venue at once) - #3

Merged
sktbrd merged 2 commits into
mainfrom
swappro-quotes
Sep 3, 2026
Merged

Swap: quote through SwapPro (keyless, every venue at once)#3
sktbrd merged 2 commits into
mainfrom
swappro-quotes

Conversation

@sktbrd

@sktbrd sktbrd commented Sep 3, 2026

Copy link
Copy Markdown
Member

What

The /swap page keeps its UI and its two-step flow (price while typing, firm quote on click). Behind /api/0x/price and /api/0x/quote, a single SwapPro GET /quote now routes across 0x, CoW, LI.FI, Relay and more and returns one firm quote with the transaction to sign. No API keyZEROX_API_KEY is retired.

  • src/lib/swappro.ts — pure translation between SwapPro's answer and the shape the widget already reads, with unit tests (6 passing).
  • src/lib/swapproRoute.ts — the one handler behind both routes; fee recipient still comes from src/lib/config.ts, server-side.
  • SwapWidget.tsx — five lines: it passes sellDecimals / buyDecimals so the proxy converts base units ⇄ human decimals. Nothing visual changes.
  • docs/integrations/swap.md rewritten.

What the user gets

  • Every quote priced across every venue at once, not just 0x.
  • minBuyAmount is the floor the transaction enforces on chain.
  • No 0x key to rotate, no per-request 0x pricing.
  • Cross-chain and BTC-native routes are one parameter away when the DAO wants them.

The one thing to decide before merging — the treasury fee

The "Support Gnars treasury (0.5%)" checkbox still works and is still opt-in per request. What changes is how the fee is collected, and it depends on the venue SwapPro picks:

Venue Partner fee
CoW on-chain volume fee to the treasury address, per swap
0x, LI.FI rides inside SwapPro's fee, settled to the treasury address from SwapPro's request log
Relay and other same-chain venues cannot carry one

Every quote reports this in a partnerFee block (collected, paidToPartner), passed through verbatim. SwapPro also takes its own 30 bps on same-chain EVM routes. With direct 0x the fee landed in the split contract on every swap; with SwapPro it does on some venues and is settled off-chain on others. That is a policy call for the DAO, which is why this is a proposal.

How to test on the preview

  1. Open /swap, connect, pick ETH → USDC or ETH → GNARS, type an amount: the price should appear with the venue named in the response (route).
  2. Sell an ERC-20 (USDC → ETH): the Approve button should appear when allowance is short; approval is exact-amount.
  3. Swap: the transaction comes from SwapPro's quote; the wallet receives at least minBuyAmount.
  4. Optimism in the chain switcher answers "no liquidity" with UNSUPPORTED_CHAIN — SwapPro does not route it yet.

Checks run locally: vitest (6/6 on the new module), eslint and prettier clean on touched files, tsc --noEmit with no errors in touched files. Same change as r4topunk#297, rebased onto this repo's main.

Reference: https://www.swaps.pro/docs/api/quote

🤖 Generated with Claude Code

https://claude.ai/code/session_018aKwE1cu2NvM7Gj7ykdcgD

…e at once

The /swap page keeps its UI and its two-step flow; behind /api/0x/price and
/api/0x/quote a single SwapPro /quote now routes across 0x, CoW, LI.FI,
Relay and more and answers with a firm quote and the transaction to sign.
No API key: ZEROX_API_KEY is retired.

src/lib/swappro.ts is the pure translation between SwapPro's answer (human
decimals, approval, tx, provider) and the shape the widget already reads
(liquidityAvailable, base-unit amounts, issues.allowance, transaction),
with unit tests. The widget passes sellDecimals/buyDecimals so the proxy
can convert both ways; nothing else in it changes.

The affiliate fee is still opt-in per request and still set from config
server-side, sent as SwapPro's partner + partnerFeeBps. How it is collected
depends on the venue SwapPro picks — on-chain per swap on CoW, settled
off-chain on 0x and LI.FI, not at all on Relay — and every quote reports
it in a partnerFee block. docs/integrations/swap.md spells this out; it is
the one thing the DAO should decide on before merging.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018aKwE1cu2NvM7Gj7ykdcgD
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
gnars.com Ready Ready Preview Sep 3, 2026 8:39pm UTC

Request Review

…e it can be paid

The picker offered Optimism, which SwapPro does not route — every quote on it
came back UNSUPPORTED_CHAIN. It did not offer BNB Chain, Avalanche or Robinhood
Chain, which SwapPro does route. chains.test.ts now fails when the picker and
the router disagree, so the next chain SwapPro adds is a failing test rather
than silence.

Token addresses and decimals come from SwapPro's /tokens registry and were read
back from chain before being written down: BNB Chain's USDT and USDC are 18
decimals, not Ethereum's six, and Avalanche's Tether is USDt.

The treasury fee is now gated on the chain. eth_getCode on 2026-09-03 found the
treasury's split holding 89 bytes on Base and zero on the other five, so asking
for 50 bps there would take it from the user and park it at an address with
nothing behind it. A chain missing from GNARS_SWAP_PAYOUT asks for no fee and
does not show the checkbox — the swap simply costs less. The old rule sent every
non-Base fee to the SOPA x COINMASTERSGUILD split, under a checkbox that says
"Support Gnars treasury".

And SwapPro's error body is about to become an object with the message inside
it. toWidgetError read `error` as a string, which would have put "[object
Object]" on the screen without throwing; it now reads both shapes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nXkNtBMRY446o2pHHo9H8
@sktbrd
sktbrd merged commit 560b296 into main Sep 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant