# 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/`:

```bash
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/`:

```bash
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`, `tok` and `fee` aren't checked against the chain. On a buy, only `amt` is.
- **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 `sol` in 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
