Payment links and invoices take a chain argument, Base or Arc testnet, and
pay_link settles on whichever chain the link was created on. Adds Arc to the
chain registry as a pay-only entry, so the x402 client and the pay tools share
one provider. my_wallet now names the gas asset per chain, since Arc bills gas
in USDC rather than ETH.
Subscriptions stay on Base. The contract is deployed on Arc but no indexer or
keeper runs there, so a subscription would never charge; the tools reject that
chain instead of writing the offer to Base.
Adds the merchant surface on Base: create payment links and itemized
invoices, publish subscription offers, read the dashboard, manage API keys
and signed webhooks. Adds the payer side too, so an agent can settle a link
or invoice in USDC on-chain and manage its own subscriptions.
Funds settle to the QIAN_KEY wallet, which signs a short-lived session. The
subscribe approval covers three periods rather than an unlimited amount.
Comments trimmed to one-liners before publish.
Notable changes to `@qianexchange/mcp`. Versions match the npm releases.
## 0.4.0 (2026-07-19)
- QianPay works on Arc testnet. `create_payment_link` and `create_invoice` take a `chain` argument (`base` or `arc-testnet`), and `pay_link` settles on whichever chain the link was created on.
- Arc joins the chain registry as a pay-only entry, so the x402 client and the QianPay tools share one provider.
-`my_wallet` names the gas asset per chain. Arc bills gas in USDC rather than ETH, so reporting every balance as ETH was wrong.
-`create_subscription_offer` now takes `chain` and rejects any chain without a keeper. It previously ignored the argument and wrote the offer to Base.
- Subscriptions stay on Base. The contract is deployed on Arc, but no indexer or keeper runs there, so a subscription would never charge.
- Adds the payer side: `pay_link`, `subscribe`, `cancel_subscription`, `charge_subscription`, plus the public reads `get_payment_link`, `get_subscription_offer`, `get_subscriptions`.
- Funds settle to the `QIAN_KEY` wallet, which signs a short-lived session per call. The subscribe approval covers three periods rather than an unlimited amount.
## 0.2.0 (2026-07-18)
- Adds x402 payment tools. `x402_pay` pays any x402-gated API in USDC over EIP-3009 on Base or Arc testnet, with a per-call spend cap. `x402_wallet` reports the payment wallet and its balances.
- Signing key resolution: `X402_PRIVATE_KEY`, then `QIAN_KEY`, then a burner generated at `~/.qian/mcp-wallet.json`.
- Restricts the published tarball to a `files` allowlist.
## 0.1.0 to 0.1.5 (2026-07-17 to 2026-07-18)
- First release. Read tools for tokens, trades, holders and positions, all keyless.
- Trading and launching on Robinhood Chain, Base and Ethereum: `buy`, `sell`, `launch_token`, `quote_buy`, `quote_sell`, `pending_fees`, `collect_fees`.
MCP server for [Qian](https://qian.ag), the multichain DEX and launchpad. Any MCP-capable agent can launch tokens, trade, and read market data on Robinhood Chain, Base, and Ethereum.
MCP server for [Qian](https://qian.ag), a multichain exchange and payments layer for every asset. Any MCP-capable agent can launch tokens, trade, and read market data on every chain Qian supports (Robinhood Chain, Base, Ethereum), trade tokenized stocks on Robinhood Chain, and pay x402-gated APIs in USDC.
Non-custodial: the server runs on your machine and signs with your key. Qian never holds keys or funds. Every launched token's liquidity is locked forever in QianLocker.
Non-custodial: the server runs on your machine and signs with your key. Qian never holds keys or funds. Every launched token's liquidity is locked forever in QianLocker.
@@ -30,13 +36,50 @@ Omit `QIAN_KEY` for read-only mode (market data still works). Use a dedicated ho
| `my_wallet` | Your address + ETH balance per chain |
| `my_wallet` | Your address + ETH balance per chain |
| `my_positions` | Holdings, ETH value, and PnL for any address across chains |
| `my_positions` | Holdings, ETH value, and PnL for any address across chains |
| `quote_buy` / `quote_sell` | Preview a trade, read-only (no key or approval) |
| `quote_buy` / `quote_sell` | Preview a trade, read-only (no key or approval) |
| `buy` / `sell` | Trade via QianSwapRouter (plain ETH in/out, auto-approve, slippage-protected) |
| `get_payment_link` / `get_subscription_offer` / `get_subscriptions` | Read link/offer/subscription state (no key) |
| `pay_link` | Pay a QianPay link or invoice in USDC on-chain |
| `subscribe` / `cancel_subscription` / `charge_subscription` | Subscribe to an offer, cancel, or force a charge |
## Tokenized stocks
Robinhood Chain carries tokenized stocks (NVDA, TSLA, SPY, and more) issued by a third party (Robinhood Assets); Qian is a non-custodial venue, not the issuer. US persons are restricted from trading them. The read tools (`list_stocks`, `quote_stock`) work for everyone, but `buy_stock` and `sell_stock` are for non-US persons only. The server is headless and cannot verify location, so that restriction is on you to honor.
## x402 payments
`x402_pay` lets an agent pay for x402-gated APIs (HTTP 402 + USDC, Base mainnet or Arc testnet). It fetches the URL; on a 402 it signs an EIP-3009 USDC authorization and retries with the `X-PAYMENT` header. It never pays above the `max_amount_usdc` you pass per call.
Signing key resolution: `X402_PRIVATE_KEY`, else `QIAN_KEY`, else the server generates a burner wallet at `~/.qian/mcp-wallet.json` on first use and prints its address. Fund that wallet with a few USDC on Base and treat it as float. Never store more there than you're willing to let the agent spend.
## QianPay
An agent can run the full QianPay merchant surface: create payment links and
itemized invoices, publish recurring subscription offers, read its dashboard
(paid/unpaid links, subscribers, MRR), manage API keys and signed webhooks.
Payment links and invoices work on Base and on Arc testnet, chosen with the
`chain` argument. Subscriptions are Base only, because no keeper runs on Arc
yet and a subscription there would never charge. Funds settle to the `QIAN_KEY` wallet. The server signs a
short-lived session with that key, so nothing is custodial.
It can also pay from the agent side: `pay_link` settles a link or invoice in
USDC on-chain, and `subscribe` / `cancel_subscription` manage subscriptions
(the subscribe approval is 3× the period amount, never infinite). Payment links
settle in USDC through the QianPay contract, a different rail from x402.
"description":"MCP server for the Qian multichain DEX & launchpad (qian.ag) — launch and trade tokens from any MCP-capable agent",
"description":"MCP server for Qian (qian.ag): trade tokens and stocks, launch tokens, run QianPay payment links and invoices on Base and Arc, and pay x402 APIs in USDC from any MCP-capable agent",
// Advisory surfaced on the trade tools; the server is headless and can't gate by location.
constRWA_RESTRICTED='US persons are restricted from trading tokenized stocks (they are securities). This tool cannot verify location. Calling buy_stock/sell_stock confirms you are not a US person and not trading from the US. US users may view and quote only. Tokens are issued by a third party (Robinhood Assets); Qian is a non-custodial venue, not the issuer.'
description:'Preview buying a token with ETH via QianSwapRouter (1% router fee included). Read-only — no wallet or key needed. Returns expected tokens out.',
description:'Preview buying a token with ETH via QianSwapRouter (1% router fee included). Read-only, no wallet or key needed. Returns expected tokens out.',
inputSchema:{chain:chainSlug,address,eth_amount:z.string().describe('ETH to spend, e.g. "0.05"')},
inputSchema:{chain:chainSlug,address,eth_amount:z.string().describe('ETH to spend, e.g. "0.05"')},
description:'Preview selling a token for ETH via QianSwapRouter (1% router fee included). Read-only — no wallet, key, or approval needed. Returns expected ETH out.',
description:'Preview selling a token for ETH via QianSwapRouter (1% router fee included). Read-only. No wallet, key, or approval needed. Returns expected ETH out.',
inputSchema:{chain:chainSlug,address,token_amount:z.string().describe('Tokens to sell in whole tokens, e.g. "1000000"')},
inputSchema:{chain:chainSlug,address,token_amount:z.string().describe('Tokens to sell in whole tokens, e.g. "1000000"')},
// Quoters revert when the pool can't cover the WETH out (thin/one-sided liquidity).
if(e?.code==='CALL_EXCEPTION')thrownewError(`can't sell ${token_amount} tokens — the pool doesn't have enough WETH liquidity for a sell this size (try a smaller amount)`)
'Launch a new token on Qian: 1B supply, full supply as locked one-sided liquidity (LP owned forever by QianLocker — no rug possible), fees stream to you as creator (70%) and the platform (30%).',
'Launch a new token on Qian: 1B supply, full supply as locked one-sided liquidity (LP owned forever by QianLocker, no rug possible), fees stream to you as creator (70%) and the platform (30%).',
'Costs the deploy fee (~0.0005 ETH) + optional dev_buy_eth. Tiers set starting FDV: 1=1 ETH, 2=2, 3=5, 4=10.',
'Costs the deploy fee (~0.0005 ETH) + optional dev_buy_eth. Tiers set starting FDV: 1=1 ETH, 2=2, 3=5, 4=10.',
'creator_fee_pct is the trading tax (0.5%–20%) you earn on every swap — it is the V4 pool fee, immutable once launched. There are no anti-snipe / dead-window / max-wallet options in V2.',
'creator_fee_pct is the trading tax (0.5%–20%) you earn on every swap. It is the V4 pool fee, immutable once launched. There are no anti-snipe / dead-window / max-wallet options in V2.',
description:'Collect accrued trading fees for a token via QianFeeRouter (permissionless — anyone can trigger; payout always goes 70% to the token creator, 30% to the platform).',
description:'Collect accrued trading fees for a token via QianFeeRouter (permissionless, anyone can trigger; payout always goes 70% to the token creator, 30% to the platform).',
conststockArg=z.string().describe('ticker (e.g. "NVDA") or the token address')
server.registerTool('list_stocks',{
description:'List tokenized stocks tradeable on Qian (Robinhood Chain). Tokens issued by a third party (Robinhood Assets); Qian is a non-custodial venue. Some are shown but not yet tradeable (no liquidity pool).',
inputSchema:{},
},async()=>json({
chain:'robinhood',
disclaimer:RWA_RESTRICTED,
stocks:RWA_CATALOG.map((s)=>({
symbol:s.symbol,name:s.name,address:s.address,
tradeable:!s.soon,
url:`https://qian.ag/rwa`,
})),
}))
server.registerTool('quote_stock',{
description:'Preview a tokenized-stock trade (Robinhood Chain, 1% router fee included). Read-only, no wallet or key needed. buy: amount is ETH to spend; sell: amount is tokens to sell.',
inputSchema:{stock:stockArg,side:z.enum(['buy','sell']),amount:z.string().describe('ETH for buy, tokens for sell, e.g. "0.1"')},
},async({stock,side,amount})=>{
consts=resolveStock(stock)
if(s.soon)thrownewError(`${s.symbol} is not tradeable yet — no liquidity pool`)
description:`Buy a tokenized stock with plain ETH via QianRWARouter (Robinhood Chain). Simulates first, applies slippage to set min-out, then sends. ⚠️ ${RWA_RESTRICTED}`,
inputSchema:{
stock:stockArg,
eth_amount:z.string().describe('ETH to spend, e.g. "0.1"'),
description:`Sell a tokenized stock for ETH via QianRWARouter (Robinhood Chain; approves the router if needed). amount "all" sells your full balance. ⚠️ ${RWA_RESTRICTED}`,
inputSchema:{
stock:stockArg,
amount:z.string().describe('token amount in whole tokens, or "all"'),
description:'The x402 payment wallet: address, key source, and USDC balances on Base + Arc testnet. If freshly generated, fund it with a few USDC on Base — treat it as a burner float, never main funds.',
balances[net]=`error: ${e.message}`// network may be unreachable; report, don't hide
}
}
returnjson({address:w.address,key_source:source,usdc:balances,note:'burner float only — fund with a few USDC on Base'})
})
server.registerTool('x402_pay',{
description:'Fetch a URL; if it responds 402 (x402 protocol), pay the requested USDC (Base or Arc testnet) by signing an EIP-3009 authorization and retry. Never pays more than max_amount_usdc. Returns the response plus what was paid.',
inputSchema:{
url:z.string().url(),
method:z.enum(['GET','POST']).default('GET'),
body:z.string().optional().describe('request body for POST (sent as application/json)'),
max_amount_usdc:z.string().describe('hard cap for this call, e.g. "0.05" — payment above this is refused'),
if(!req)thrownewError(`no payable option: server accepts ${JSON.stringify(accepts.map((a)=>`${a.scheme}/${a.network}`))}, we support exact on ${Object.keys(X402_NETWORKS).join('/')}`)
constnet=X402_NETWORKS[req.network]
if(req.asset&&req.asset.toLowerCase()!==net.usdc.toLowerCase())thrownewError(`asset ${req.asset} on ${req.network} is not USDC — refusing`)
constamount=BigInt(req.maxAmountRequired)
constcap=ethers.parseUnits(max_amount_usdc,6)
if(amount>cap)thrownewError(`payment required ${ethers.formatUnits(amount,6)} USDC exceeds your cap ${max_amount_usdc} — refused`)
description:'Create a QianPay payment link on Base or Arc testnet. Funds go to your QIAN_KEY wallet; the payer settles in USDC. Returns the shareable pay.qian.ag link.',
description:'Create a QianPay invoice on Base or Arc testnet: line items summed to a total, payable in USDC at pay.qian.ag. Funds go to your QIAN_KEY wallet.',
description:'Create a recurring subscription offer (Base): payers subscribe on-chain and are charged usdc_amount every period. Returns the offer ref + subscribe link.',
inputSchema:{
name:z.string().min(1).max(80),
usdc_amount:usdcAmount,
period_seconds:z.number().int().min(3600).max(365*86_400).describe('billing period, 1 hour to 365 days'),
description:'Create a QianPay API key (qp_…) for embedding in merchant pages / checkout.js. Returned once. Keys can create payment links/invoices pinned to your wallet — nothing else.',
description:'Generate a new whsec_ webhook signing secret (returned once). Sign-verify inbound webhooks with it: HMAC-SHA256 over the raw body, x-qianpay-signature header.',
description:'List subscriptions for an address (both roles, all chains): asMerchant (you get paid) and asPayer (you pay). Defaults to your QIAN_KEY wallet.',
description:'Pay a QianPay payment link / invoice in USDC from your QIAN_KEY wallet, on whichever chain the link was created on (Base or Arc testnet). Approves USDC if needed, then settles on-chain. Refuses if already paid.',
inputSchema:{ref:payRef},
},async({ref})=>{
constrow=awaitapi(`/pay/intents/${ref}`)
constcfg=payCfg(row.chain)// the link's chain decides; throws if we can't pay there
description:'Subscribe to a QianPay offer (Base) from your QIAN_KEY wallet. Charges the first period in the same tx; approves 3× the period amount (not infinite). Returns the on-chain subId.',
inputSchema:{offer_ref:payRef},
},async({offer_ref})=>{
consto=awaitapi(`/pay/offers/${offer_ref}`)
constsubsAddr=subsCfg(o.chain).subs
constamount=BigInt(o.usdc_amount)
awaitensureUsdcAllowance(o.chain,subsAddr,amount*3n)// UI convention: amount×3, re-approve later
description:'Trigger a due subscription charge by subId (Base, permissionless). Normally the Qian keeper does this every 5 min; use only to force a charge. Reverts if not yet due.',
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.