Protocol
Signed orders
The exact bytes you sign, and what binds them to one batch and chain.
An order is an intent: a fixed-layout message the owner signs with their wallet’s Ed25519 key. The same bytes are built by the app, the SDK and the program, and all three must agree to the byte.
#The preimage
The canonical preimage is 336 bytes, in this order:
| Bytes | Field | Encoding |
|---|---|---|
| 15 | Domain tag | ASCII CHORD_INTENT_V1 |
| 32 | Program | Chord’s program ID |
| 32 | Chain domain | SHA-256 of the cluster’s genesis hash string, fixed in the config |
| 32 | Config | The config PDA |
| 32 | Batch | The batch account |
| 32 | Owner | The signing wallet |
| 32 | Source | Owner’s token account for the sell mint |
| 32 | Destination | Owner’s token account for the buy mint |
| 32 | Sell mint | |
| 32 | Buy mint | |
| 8 | Sell amount | u64, little-endian |
| 8 | Minimum buy amount | u64, little-endian |
| 8 | Nonce | u64, little-endian |
| 8 | Expiry slot | u64, little-endian |
| 1 | Allow partial | 0 or 1 |
#The digest
The wallet signs SHA-256(preimage), 32 bytes, not the preimage itself. This keeps the on-chain verification small while still committing to every field above.
import { encodeIntent, intentHash, signIntent } from "@chord/sdk";
const bytes = encodeIntent(intent, domain); // 336 bytes
const digest = intentHash(intent, domain); // hex SHA-256 of those bytes
const envelope = signIntent(intent, domain, secretKey); // { intent, signature }Browser wallets sign the digest with signMessage. The signature is 64 bytes, sent to the collector as base64.
#What binds a signature
- Program, config and chain domain stop a signature from being used on another deployment or another cluster. The app checks that its RPC’s genesis hash produces the collector’s chain domain before it asks you to sign.
- Batch stops it from being used in any other batch.
- Owner, source and destination fix where tokens come from and go to.
- Nonce makes every order unique per owner. The program writes a permanent receipt for each nonce it fills or cancels, so a nonce can’t be used twice.
- Expiry slot must cover the batch’s settlement deadline. The program refuses fills after it.
#On-chain verification
settle_fill requires the instruction just before it to be a native Ed25519 verification with exactly one signature, laid out self-contained: public key at offset 16, signature at 48, message at 112, a 32-byte message, and all three instruction indices 0xFFFF. The program then checks that the public key is the order’s owner and the message is the digest it computes itself. The Ed25519 program checks the signature; Chord checks what was signed.