Receipts
Receipts are Solana memo transactions that record a wallet's knot changes and trades, so anyone can check from chain alone that the knots held. They cover only wallets an operator flags, and none are flagged by default.
What a receipt is #
A receipt is one SPL Memo transaction, signed and paid for by the receipts key (TRADE_RECEIPTS_KEYPAIR), which signs nothing else. Receipts don't move money or stop anything. If anchoring fails, the trade still goes ahead and the problem is logged; for a swap, a withdrawal or a presale action, the checker then reports the missing receipt.
Source: trade/server/receipts.ts
Which wallets are covered #
Only flagged wallets. The receipts column on the wallets table defaults to 0. There's no trader command for this. An operator runs, in trade/:
npm run receipts -- on <wallet address>
npm run receipts -- off <wallet address>
npm run receipts -- status
Turning a wallet on first writes what's already in force (tied knots, pending loosenings, the saved address), then every event from then on. Those first memos predate receipts, so they're Koine's word.
A flagged wallet's rules and trades become public. Each memo carries the wallet address, and some carry the saved withdrawal address. Memos carry no Telegram id.
Source: trade/scripts/receipts.ts, trade/server/db.ts, trade/package.json
Which events are written #
e |
When | Fields |
|---|---|---|
change |
A knot or a position's exit is tied, tightened, loosened or untied | id, k, from, to, dir (tighten or loosen), eff (when it takes effect) |
cancel |
A pending change won't land | id |
land |
A pending change took effect | id |
limit |
A limit buy was set | id, mint, amt (lamports), price (USD), exp |
block |
A knot refused a buy or presale deposit | k, mint, amt (lamports), msg, and presale: true for a deposit |
exit |
An exit is about to sell, once per try | k, mint, qty, price and peak (lamports per whole token), try |
fill |
A swap landed | side, order, sig, mint, amt, fee, sol, tok, kind (manual, the exit that sold it, limit:<id> for a limit buy or open:<id> for a buy at open), scan on a buy |
openbuy |
A buy at open was set | id, mint, amt, at |
presale |
A presale action landed | kind, mint, sig, sol, tok |
addr |
A saved address was set or changed | to, eff |
addr_cancel |
A pending address change won't land | none |
wd |
A withdrawal went out | id, to, sol, sig |
state |
When a wallet is flagged: a knot already tied | k, to |
On one position, k is rung:<id>, stop:<id> or own:<mint>:<opened_at>.
Source: trade/server/receipts.ts, trade/server/knots.ts, trade/server/trading.ts, trade/server/orders.ts, trade/server/exits.ts, trade/server/limits.ts, trade/server/presale.ts, trade/server/withdraw.ts
The memo format #
A memo is JSON:
| Field | Meaning |
|---|---|
v |
Format version, 1 |
n |
Its place in the chain, starting at 1 |
p |
The first 32 hex characters of the SHA-256 of the previous memo's exact text; null for the first |
w |
The wallet |
e |
The event, with its fields |
at |
The server's clock, in unix milliseconds. Koine's word; the checker uses block time |
A msg is cut to 200 characters. A memo still over 700 bytes loses msg and scan and gains cut: 1.
Source: trade/server/receipts.ts (memoFor, MAX_MEMO)
The chain #
There's one chain for the trading service, across every flagged wallet, so one wallet's n values have gaps. A memo is recorded in the same database transaction as most events it describes and sent within seconds; memos go out side by side, so n and p order them, not their slots. An expired send is re-signed with a fresh blockhash and the same text.
The chain shows a dropped, repeated or reordered memo, except a drop of the newest ones. It can't show an event Koine never wrote.
Source: trade/server/receipts.ts (anchor, sendReceipts)
The checker #
trade/server/verify.ts checks a wallet from chain alone, without Koine's database. Run it in trade/:
npm run receipts:check -- <wallet> [--key <receipts key address>] [--out receipts.json]
It needs an RPC (TRADE_RPC_URL, or a Helius key) and exits 1 if any check fails. Each check is trustless (it follows from memos and the wallet's transactions) or attested (it rests on something only Koine saw).
| Check | Strength | What fails it |
|---|---|---|
chain |
trustless | A memo missing or repeated, or p not matching the memo before |
unreadable |
trustless | The RPC couldn't return a transaction |
swap |
trustless | A Jupiter transaction since the first memo that changed one of the wallet's token balances and has no fill |
amount |
trustless | A buy's amt is more than the SOL that left the wallet |
buy |
trustless | The buy breaks a knot in force at its block, rerun with the same knot code |
untie_delay |
trustless | A land memo's time is before its request's block plus the delay, less 60 seconds of grace |
presale |
trustless | An Alpha Vault transaction (presale deposit, withdrawal, claim or refund) the wallet paid for, since the first memo, with no presale memo naming it, or a deposit whose sol is more than the SOL and wrapped SOL that left the wallet |
withdrawal |
trustless | SOL left in a transaction the wallet paid for that isn't a Jupiter swap or a presale action, with no wd, or went anywhere but the saved address in force |
block |
attested | A refusal names a knot Koine didn't say was tied |
exit |
attested | An automatic sale has no matching exit, a different quantity, or a knot that wasn't tied |
Source: trade/server/verify.ts, trade/server/verify.test.ts, trade/scripts/receipts-check.ts
What the checker doesn't prove #
- Memos are Koine's account. The trader signs nothing. Compare the memos with your own Knots screen.
- The receipts key sits on the trading server. Whoever controls that server can write memos.
- Refusals and exit prices are Koine's word.
- Swaps with no token change are skipped, and failed transactions are skipped. A plain token transfer out is caught only if it also moves SOL out of the wallet.
- On a sell,
sol,tokandfeearen't checked against the chain. On a buy, onlyamtis. - Nothing before the first memo is checked.
- Presale actions are checked for a receipt, not traced. The checker doesn't rerun knots on a deposit, and doesn't check where a presale transaction's SOL went beyond the deposit amount. A claim adds its tokens to the rebuilt position at the
solin its memo, as the ledger does.
A swap that landed seconds ago can fail as "no receipt" until its memo lands. Run the checker again before reading a recent failure as real.
Source: trade/server/verify.ts