Docs · dry run

How Passavant works

Declare one fixed price for a whole bag on one chain and get paid in USDC on another. The Officer wallet collects both sides before it pays anyone, so a deal clears in full or comes back in full.

Any token chain to any USDC chain
Fee0.5%Only on cleared deals
ChainsEVM and Solana
Expiry1h to 7dYou pick it when you post
AccountYour walletNo sign up, no email
Introduction

01Introduction#

Passavant is a cross-chain OTC desk. You post one fixed price for a whole bag of tokens on one chain and pick the chain where you want to be paid in USDC. Another wallet takes the whole pass, and the Passavant Officer wallet settles both sides.

A passavant is an old customs paper: it lets goods cross a border in transit without stopping at every toll. Here the paper is a short block of plain text your wallet signs, and the toll you skip is the pool. A big bag sold through a pool walks the price down with every swap. A pass is sold at the price written on it.

Fixed price, no slippageThe deal never touches an AMM pool, so a fat bag does not move the chart.
Two chains in one dealSell a token on Solana and get USDC on Base, or any other route in the table.
Both sides or nothingNobody is paid until both sides are collected, otherwise the first side goes back.

02Quick start#

  1. Open Make offer and search the token. Search reads DexScreener on Solana, Ethereum, BNB Chain and Base.
  2. Set the amount and one price per token. The page shows the live market price and how far your price sits from it.
  3. Pick the chain where the money lands and how long the pass stays open.
  4. Sign the plain text. The pass shows up in the book right away.

To take a pass instead, open Markets, press Buy or Sell on a row, read the terms and sign. You watch the Officer run all four legs and stamp the result.

Everything the desk does is also in a small module you can import from this site:

import * as P from "https://passavant.vercel.app/lib/passavant.js";

const pass = P.createPass({
  side: "sell", tokenSymbol: "BONK", tokenChain: "solana",
  tokenAddress: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
  amount: 50_000_000, price: 0.00002, payChain: "base", expiry: "3d"
});

console.log(pass.id);              // PV-SOL-BASE-…
console.log(P.terms(pass));        // the exact text your wallet signs
console.log(P.legs(pass));         // the four transfers the Officer runs

03Dry run#

The desk is open in dry run. Searching, live market prices, the book, plain-text terms and wallet signatures are all real. Settlement runs every check and every leg in your browser, but the Officer sends nothing on chain. Every deal in the ledger says so.

PartIn dry run
Token search and market priceLive, DexScreener
Wallet connectLive, reads your address only
Signing termsLive, Phantom signMessage or EIP-191 personal_sign
The bookSample passes repriced against the live market every hour, plus the passes you file from this browser
Settlement legsSimulated by settle(), each leg gets a dry-… id instead of a transaction hash
NOTENo approve and no transaction is ever requested in dry run. If a page asks your wallet for anything other than a text signature, it is not this desk.

04How a deal works#

File MAKER
  1. 1Connect a walletYour address is your account
  2. 2Set the termsToken, amount, one price, USDC chain, expiry
  3. 3SignPlain text, nothing moves yet
Take TAKER
  1. 4Find the passEvery open pass sits in one book
  2. 5Take it wholeSign the other side, the pass is Accepted
Stamp OFFICER
  1. 6Collect both sidesToken and USDC move to the Officer
  2. 7Pay outToken to the buyer, USDC minus 0.5% to the seller

On an Ask the maker sells the token and the taker pays USDC. On a Bid the maker pays USDC and the taker brings the token. A pass is all or nothing: there are no partial fills.

05Settlement#

When a pass is taken, the Officer runs four transfers and pays nobody until both sides are in.

#LegChain
1Token seller → Officer, the whole bagtoken chain
2USDC payer → Officer, the deal totalpay chain
3Officer → USDC payer, the whole bagtoken chain
4Officer → token seller, total minus 0.5%pay chain

If leg 2 does not arrive, leg 1 is refunded and the deal ends as failed. In the book you can see this happen: tick “Dry run a bounce” before you sign a take.

WARNINGOn chain you trade with the Officer wallet, not with the other wallet. The Officer holds both sides for a moment and is trusted to pay them out. Read Risks and trust.

06What you sign#

Filing, taking and cancelling each start with your wallet signing a short block of plain text. A signature cannot move funds. It proves the terms came from your wallet, and a random nonce ties it to one pass only.

Solana wallets sign the bytes with signMessage (ed25519). EVM wallets sign with personal_sign (EIP-191). Without a wallet you can still file and take as a guest, which only works in dry run.

07What you authorize#

When the desk goes on chain, a pass goes live only after your wallet lets the Officer collect exactly your side at settlement time:

  • EVM chains: one approve of the Officer for the token. A new approve replaces the old one and covers all your live passes on that token.
  • Solana: the Officer becomes the delegate of your token account for the amount you owe. Tokens stay in your wallet until the deal settles.

You can revoke either one at any time. In dry run neither is requested.

08Chains and routes#

A route is the token's chain plus the chain where USDC is paid. Every pair of the four chains is open, same chain included.

Dollar contracts

The only dollar a deal accepts on each chain:

09Fees#

0.5% of the deal total, taken from the USDC paid to the seller, and only when a deal clears.

Deal total10,000 USDC
Buyer pays10,000 USDC
Fee, 0.5%50 USDC
Seller gets9,950 USDC
Filing, cancelling, expiringFree
Smallest deal$25

10Order states#

open→accepted→pending→completed
open→expired·cancelledpending →failed
StateMeaning
openFiled, anyone can take it
expiredNobody took it before the deadline
cancelledThe maker withdrew it
acceptedTaken, settlement is about to start
pendingThe Officer is collecting and paying
failedA side did not arrive, anything collected went back
completedBoth sides paid out

canMove(from, to) in the library holds this exact graph, and move() throws BAD_STATE on anything else.

11Markets#

One book for every route. Filter by token or address, by chain (a pass matches if either side is on it), by minimum size, and by Asks or Bids. Vs market compares the pass price to the live DexScreener price of the deepest pool: green means a good take for you, red means you pay over the odds.

Recent clearances and the route list sit under the book. A row marked YOURS is a pass you filed from this browser, and the desk will not let you take it.

12Make offer#

Three steps: token, size and price, where you get paid. The pass on the right fills in as you type: what you give, what you get after the fee, its number and expiry. The address field fills from your connected wallet for that chain and checks the format before it lets you sign.

13Deals#

The Officer's ledger: every taken pass with its state, total and fee. Open a row to see the legs and their dry run ids. Sample rows come from the desk's own dry run book.

14My passes#

Your connected addresses, the passes you filed with Cancel (one more signature), and the deals you took.

15passavant.js#

The rules of the desk live in one file with no dependencies: /lib/passavant.js. Every page of this site imports it, so what the docs say is what the desk does. It runs in browsers and in Node 18+.

ConstantValue
FEE_BPS50
MIN_TOTAL25
EXPIRY{ "1h": 3600, "1d": 86400, "3d": 259200, "7d": 604800 }
CHAIN_IDS["solana", "ethereum", "bnb", "base"]
STATESthe seven states above

16API reference#

FunctionReturns
createPass(o)A pass with id, total, fee, expiresAt, nonce, state: "open". Throws PassError.
quote(amount, price){ total, fee, sellerGets, buyerPays }
terms(pass, action)The plain text to sign, action is create, take or cancel
parseTerms(text)The fields back from that text
legs(pass)The four transfers, in order
settle(pass, { onStep, failAt, delay })Promise of the final pass, dry run
route(tokenChain, payChain){ open, label, dollar, dollarAddress }
vsMarket(price, market)Percent above or below market
marketPrice(chain, address)Live price from the deepest DexScreener pool
searchTokens(q, chain?)Up to 12 tokens on the four chains, deepest first
isAddress(chain, a)Format check, base58 or 0x
canMove(from, to), move(pass, to)The state machine
import { createPass, settle } from "https://passavant.vercel.app/lib/passavant.js";

const p = createPass({ side: "sell", tokenSymbol: "WIF", tokenChain: "solana",
  tokenAddress: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", amount: 2000, price: 1.1, payChain: "ethereum" });

const done = await settle(p, {
  failAt: 2,                                   // the USDC never shows up
  onStep: e => console.log(e.state, e.leg?.kind ?? "")
});
console.log(done.state);                         // "failed", leg 1 refunded

17Try it#

This block imports the same file and runs it here. Change a field and the pass, its text and its legs update. Press settle to watch a dry run.


        

18Pass format#

A pass number is PV-<token chain>-<pay chain>-<7 chars>. The tail is an FNV-1a hash of side, token, amount, price, pay chain and nonce in base 36, so the same terms with a new nonce get a new number.

FieldTypeNotes
side"sell" | "buy"From the maker's side
tokenAddressstringMint on Solana, 0x contract elsewhere
amount, pricenumberWhole tokens, USD per token
payChainchain idWhere the dollars move
makerReceivestringWhere the maker is paid, defaults to the maker
expiresAtmscreatedAt + EXPIRY[expiry] × 1000
nonce16 hexRandom, from crypto.getRandomValues

19Errors#

CodeWhen
MISSING_FIELDA required field is empty
BAD_SIDEside is not sell or buy
ROUTE_CLOSEDA chain is not one of the four
TOO_SMALLTotal under $25
BAD_EXPIRYExpiry is not 1h, 1d, 3d or 7d
BAD_STATEA state change the graph does not allow
BAD_TERMSText that does not start with PASSAVANT: … pass

20Risks and trust#

  • The Officer is trusted. It holds both sides for a moment and pays them out. That is the whole design, and it is why the desk can cross chains without a bridge.
  • A pass does not lock funds. Balances and approvals are checked again at settlement, which is why a side can fail to arrive.
  • The maker sets the price. Every row shows how far it sits from the market. Check it before you take one.
  • Look-alike tokens exist. Match the address, not the symbol. Search shows both.

21Limits#

Whole bags only, four chains, USDC as the only dollar, $25 minimum, seven days maximum. Market prices come from the deepest DexScreener pool and can be missing for fresh tokens, in which case the book shows n/a.

22$PASSAVANT#

The desk's own token. Its contract is published here and on the main page the moment it exists.

Ticker$PASSAVANT
ChainSolana
Contractsoon
LinksBuy · Chart · X

23FAQ#

Do I need an account?

No. Your wallet address is your account. Without a wallet you get a guest id on this browser, which works in dry run.

Can someone take only part of my pass?

No. A pass is taken whole or not at all.

Can I cancel?

Any time while it is open, in My passes. One signature, no fee.

What if the other side does not pay?

The Officer never pays anyone before both sides are in. If the second side fails, the first goes back and the deal is marked failed.

Can I take my own pass?

No. The desk refuses a take from the wallet that filed it.

Where does the market price come from?

DexScreener, the pool with the most liquidity for that token on its chain.

Is there a minimum size?

$25 total. Below that the Officer does not get out of the chair.

Ready to declare?

File a pass in a minute, or take one from the book.