@typesol/cli 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/portgen.js CHANGED
@@ -84,6 +84,34 @@ export function generatePort(idl) {
84
84
  for (const ix of idl.instructions)
85
85
  for (const g of ix.args)
86
86
  mark(g.type, "arg");
87
+ // Structs passed or returned whole (not inside another type): these can keep Anchor's Borsh bytes with @layout borsh.
88
+ const whole = new Set();
89
+ const nested = new Set();
90
+ const inside = (t) => {
91
+ if (!t || typeof t === "string")
92
+ return;
93
+ const inner = t.vec ?? t.option ?? t.coption ?? t.array?.[0];
94
+ if (inner)
95
+ return inside(inner);
96
+ if (t.defined)
97
+ nested.add(definedName(t.defined));
98
+ };
99
+ for (const ix of idl.instructions) {
100
+ for (const t of [...ix.args.map((g) => g.type), ix.returns]) {
101
+ if (t && typeof t === "object" && t.defined)
102
+ whole.add(definedName(t.defined));
103
+ else if (t && typeof t === "object")
104
+ inside(t.vec ?? t.option ?? t.coption ?? t.array?.[0]);
105
+ }
106
+ }
107
+ for (const def of idl.types ?? []) {
108
+ for (const f of (def.type.fields ?? []))
109
+ if (typeof f === "object" && f && "type" in f)
110
+ inside(f.type);
111
+ for (const v of def.type.variants ?? [])
112
+ for (const f of v.fields ?? [])
113
+ inside(typeof f === "object" && f && "type" in f ? f.type : f);
114
+ }
87
115
  const fixed = (w) => w === "account" || w === "struct" || w === "variant";
88
116
  const describe = (w) => (w === "account" ? "account data" : w === "struct" ? "a struct argument" : "a data-enum variant");
89
117
  const tsType = (t, where, at, last = false) => {
@@ -232,8 +260,10 @@ export function generatePort(idl) {
232
260
  const where = used.has("account") ? "account" : used.has("arg") ? "struct" : used.has("event") ? "event" : "account";
233
261
  if (def.type.kind === "struct") {
234
262
  const fields = (def.type.fields ?? []);
235
- // An account struct with a String, Vec or Option of its own: `@layout borsh` keeps Anchor's bytes.
236
- const borsh = accountNames.has(def.name) && !isTuple(fields) && fields.some((f) => borshOnly(f.type)) && fields.every((f) => borshField(f.type, "") !== undefined);
263
+ // An account struct, or one passed / returned whole, with a String, Vec or Option of its own: `@layout borsh`
264
+ // keeps Anchor's bytes.
265
+ const borshable = accountNames.has(def.name) || (whole.has(def.name) && !nested.has(def.name) && !eventNames.has(def.name));
266
+ const borsh = borshable && !isTuple(fields) && fields.some((f) => borshOnly(f.type)) && fields.every((f) => borshField(f.type, "") !== undefined);
237
267
  const tag = accountNames.has(def.name) ? " (account)" : eventNames.has(def.name) ? ` (event: emit<${typeName(def.name)}>({ … }))` : "";
238
268
  const doc = def.docs?.length ? esc(def.docs.join(" ")) : `${def.name}${tag}`;
239
269
  out.push(borsh ? `/**\n * ${doc}\n * @layout borsh\n */` : `/** ${doc} */`);
package/docs/errors.md CHANGED
@@ -11,7 +11,7 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
11
11
  | 2009 | not the associated token account (ConstraintAssociated) |
12
12
  | 2012 | not the expected address (ConstraintAddress) |
13
13
  | 2014 / 2015 | token account mint / owner mismatch (ConstraintTokenMint / ConstraintTokenOwner) |
14
- | 2016 / 2017 | mint authority / freeze authority mismatch |
14
+ | 2016 / 2017 / 2018 | mint authority / freeze authority / decimals mismatch (2018: an existing mint `createIfNeeded` found) |
15
15
  | 2040 | the same account passed in two writable slots (ConstraintDuplicateMutableAccount) |
16
16
  | 3000 | account already exists / initialized (AccountDiscriminatorAlreadySet) |
17
17
  | 3002 | discriminator mismatch (AccountDiscriminatorMismatch) |
@@ -21,7 +21,8 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
21
21
  | 3006 | account must be writable (AccountNotMutable) |
22
22
  | 3007 | wrong owner program (AccountOwnedByWrongProgram) |
23
23
  | 3008 / 3009 | wrong program address / not executable (InvalidProgramId / InvalidProgramExecutable) |
24
- | 3010 | account must sign (AccountNotSigner) |
24
+ | 3010 | account must sign (AccountNotSigner; also a new keypair account a `create(payer, null, …)` makes) |
25
+ | 3011 | a `SystemAccount` owned by another program (AccountNotSystemOwned) |
25
26
  | 3012 | account not initialized: empty system account where a program account was expected (AccountNotInitialized) |
26
27
  | 3016 | account would grow more than 10 KiB in one instruction, or grow without a payer (AccountReallocExceedsLimit) |
27
28
  | 4102 | cast overflow (InvalidNumericConversion) |
package/docs/language.md CHANGED
@@ -8,8 +8,8 @@ The full reference. For a tour, start with the [README](../README.md).
8
8
  | --- | --- |
9
9
  | Instruction | an exported function returning `void`, one integer / boolean / `Pubkey`, or a fixed-size struct (sent as return data, at most 1024 bytes; `returns` in the IDL, and the generated client decodes it) |
10
10
  | Account data | an exported `interface` with fixed-size fields; interfaces nest (`home: Point`, `trail: FixedArray<Point, 4>`) |
11
- | Integers | `u8` `u16` `u32` `u64` `u128` `i8` `i16` `i32` `i64` `i128` — bigint literals (`1n`), every `+ - * / % **` checked (`**` aborts on a negative exponent, as JS throws); `& \| ^` on equal types, `~` within the width, `<<` / `>>` checked (amount below the width, `<<` may not lose bits) |
12
- | Integer helpers | `mulDiv(a, b, c)` / `mulDivCeil` (u64 through a 128-bit product), `isqrt(x)` (up to u128), `min(a, b)`, `max(a, b)` |
11
+ | Integers | `u8` `u16` `u32` `u64` `u128` `i8` `i16` `i32` `i64` `i128` — bigint literals (`1n`), every `+ - * / % **` (and `+=` … `**=`) checked (`**` aborts on a negative exponent, as JS throws); `& \| ^` on equal types, `~` within the width, `<<` / `>>` checked (amount below the width, `<<` may not lose bits) |
12
+ | Integer helpers | `mulDiv(a, b, c)` / `mulDivCeil` (u64 through a 128-bit product, u128 through a 256-bit one), `isqrt(x)` (up to u128), `min(a, b)`, `max(a, b)` |
13
13
  | Other scalars | `boolean`, `Pubkey` (compare with `===` or `.equals`) |
14
14
  | Errors | `export enum MyError { Name = "message" }` + `require(cond, MyError.Name)` → codes 6000+ |
15
15
  | Casts | `u8(x)`, `u64(x)`, … abort if the value does not fit |
@@ -23,9 +23,9 @@ The full reference. For a tour, start with the [README](../README.md).
23
23
  | Pubkey constants | `const ADMIN = pubkey("4Nd1…")` (checked at compile time) or inline `pubkey("…")`; usable as seeds |
24
24
  | Events | `emit<Deposited>({ owner: owner.key, amount })` — Anchor-format `Program data:` logs, listed under `events` in the IDL; event structs may hold `string` and `Option` |
25
25
  | Enums | `export enum Status { Pending, Approved }` — unit variants stored as the Borsh u8 index; `Status.Approved`, `===` |
26
- | Options | `best: Option<u64>` (or `u64 \| null`) — `null`, `=== null`, TypeScript narrowing; `===` between options or with a value follows JS (null equals null, empty never equals a value); empty use aborts (5108); `a ?? b` takes b only when a is empty (b runs only then; the result is an Option when b can be null), `x!` is the value or aborts with 5108; `${opt}` prints the value or `null`. Borsh `option` in arguments/events, fixed `{ is_some, value }` in account data |
27
- | Vecs | `entries: Vec<Entry>` as the last account field — `get`, `set`, `push(v, payer)`, `pop()`, `clear()`, `.length`; the account is resized (realloc) and rent topped up by `payer`; `Vec<u64>` arguments are read-only views; `pop()` of a Vec of structs works as a statement |
28
- | Anchor's account bytes | `/** @layout borsh */` on an account struct stores it as Anchor does (Borsh: a `FixedString<N>` is a String, a `FixedVec<T, N>` a Vec, an `Option` one byte when empty, and the fields after them move), so a TypeSOL program can take over accounts an Anchor program wrote and the IDL lists Anchor's own types. The instruction unpacks it into the usual fixed layout when it starts (bad bytes fail with 3003) and packs it back when it returns (3004 when the account is too small for the new value), so everything else works on it unchanged. Programs invoked from the body (CPIs) see the account's bytes as they were when the instruction started. Costs more compute than the fixed layout (the whole account is read and written each time; see the benchmarks); nested structs and list elements must be fixed-size (FixedString elements allowed); not for arguments, events, `Zeroed<T>`, `loadAccount` or a growable `Vec<T>` |
26
+ | Options | `best: Option<u64>` (or `u64 \| null`) — `null`, `=== null`, TypeScript narrowing; `===` between options or with a value follows JS (null equals null, empty never equals a value); empty use aborts (5108); `acct?.field` on an optional account and `x.home?.field` on an `Option<struct>` give an Option (empty when the account was left out / the option is empty), `a ?? b` takes b only when a is empty (b runs only then; the result is an Option when b can be null), `x!` is the value or aborts with 5108; `${opt}` prints the value or `null`. Borsh `option` in arguments/events, fixed `{ is_some, value }` in account data |
27
+ | Vecs | `entries: Vec<Entry>` as the last account field — `get`, `set`, `push(v, payer)`, `pop()`, `clear()`, `.length`; the account is resized (realloc) and rent topped up by `payer`; `Vec<u64>` arguments are read-only views; `Vec<string>` arguments (Anchor's `Vec<String>`) are views of the instruction data, each checked as UTF-8 on entry (102), and go back out as Borsh through `invoke`; `pop()` of a Vec of structs works as a statement |
28
+ | Anchor's account bytes | `/** @layout borsh */` on an account struct stores it as Anchor does (Borsh: a `FixedString<N>` is a String, a `FixedVec<T, N>` a Vec, an `Option` one byte when empty, and the fields after them move), so a TypeSOL program can take over accounts an Anchor program wrote and the IDL lists Anchor's own types. The instruction unpacks it into the usual fixed layout when it starts (bad bytes fail with 3003) and packs it back when it returns (3004 when the account is too small for the new value), so everything else works on it unchanged. Programs invoked from the body (CPIs) see the account's bytes as they were when the instruction started. Costs more compute than the fixed layout (the whole account is read and written each time; see the benchmarks); nested structs and list elements must be fixed-size (FixedString elements allowed). The same struct is Borsh as a whole instruction argument (checked like Borsh deserialization, UTF-8 included), as return data and as an event; as those (not as account data) it may also hold `string` fields and data enums carrying strings, Anchor's `String` and enums in argument structs (account data takes data enums without strings); not inside list or enum arguments, `Zeroed<T>`, `loadAccount` or a growable `Vec<T>` |
29
29
  | Fixed-capacity lists | `owners: FixedVec<Pubkey, 8>` — any field, any number of them, in nested structs too: a u32 length and room for N elements (always 4 + N × size bytes), the same `get`, `set`, `push(v)`, `pop()`, `clear()`, `.length` and `for … of`; `push` on a full one fails (5104); `acct.owners = fixedVec([a, b])` writes a whole one (unused slots zeroed). Account data and returned structs only: arguments use `Vec<T>` |
30
30
  | Struct arguments | `order: Order`, `ids: FixedArray<u8, 4>`, `orders: Vec<Order>` — fixed layouts, Borsh-identical, validated on entry (bools, enums, option tags, string lengths, enum tags; 102) and read-only |
31
31
  | Remaining accounts | `remainingAccounts()` — `.length`, `.get(i)` → `key`, `owner`, `lamports` (writes check writability), `isSigner`, `isWritable`; `loadAccount<T>` / `loadAccountMut<T>` give checked struct views; pass them to `invoke`, one at a time or spread: `...remainingAccounts()` (each with the flags it was passed with), `...readonly(rest)` / `...writable(rest)` (all with that role), `rest.slice(a, b)` for part of them; a CPI lists at most 32 accounts (5113) |
@@ -36,25 +36,27 @@ The full reference. For a tour, start with the [README](../README.md).
36
36
  | Constants | top-level `const SEED = "vault"`, `const FEE: u16 = 30n`, `const ON = true` — inlined, usable as seeds (and described in the IDL) |
37
37
  | Data enums | `export type Action = { kind: "Deposit"; amount: u64 } \| { kind: "Pause" }` — `a.kind === "Deposit"`, `switch (a.kind)`, TypeScript narrowing picks the variant (checked again at runtime, 5112); Borsh enums in arguments/events, fixed `{ tag, payload }` in account data. A variant may hold a `string` (`{ kind: "Say"; text: string }`): such an enum works in arguments, events and locals (Borsh, as Anchor encodes it) but not in account data |
38
38
  | Option of structs | `home: Option<Point>` in account data — `=== null`, `p.home.x` after narrowing, `p.home = null` / `{ x, y }` |
39
- | Strings at runtime | `"hello, " + name`, `` `#${n}` `` — built on the 32 KiB heap (5110 when full) |
40
- | `switch` | on integers (up to 64 bits), enums and data-enum `.kind`, with JS `break` / fallthrough; case labels are literals, constants or enum members |
39
+ | Strings at runtime | `"hello, " + name`, `` `#${n}` ``, `` `owner ${acct.owner}` `` (a Pubkey or Hash as base58) — built on the 32 KiB heap (5110 when full) |
40
+ | `switch` | on integers (128-bit ones too), enums and data-enum `.kind`, with JS `break` / fallthrough; case labels are literals, constants or enum members |
41
41
  | Hashing & signatures | `sha256(...)`, `keccak256(...)` → `Hash`; `findProgramAddress(seeds, program)` → the canonical PDA of any program (Rust `Pubkey::find_program_address(..).0`, e.g. a Bubblegum asset id); `merkleVerify(leaf, proof: Vec<Hash>, root)` (sorted-pair keccak, OpenZeppelin convention); `verifyEd25519(instructionsSysvar, signer, message)` checks an Ed25519 precompile instruction in the same transaction; `verifySecp256k1(keccak256(...message) | ethMessageHash(message), recoveryId, r, s, ethAddress)` (the hash must be computed in the program; a passed-in hash could be forged) |
42
42
  | Oracles | `pythPrice(update: PythPriceUpdate, feedId, maxAgeSeconds)` (fully verified, feed-matched, fresh); raw `update.price / conf / exponent / publishTime`; `switchboardValue(feed: SwitchboardFeed, expectedFeed, maxStaleSlots, decimals)` (the feed must be the expected one: anyone can create feeds) (mainnet program unless `SwitchboardFeed<"devnet">`) |
43
43
  | Multiple files | `import { Side, min } from "./math"` — structs, enums, constants and helpers can live in other files; only the entry file's exported functions are instructions; names are program-wide, so imports from TypeSOL files cannot be renamed; error codes number imported files first |
44
- | Loops | `for (let i = 0n; i < n; i++)`, `while`, `for (const x of list)` over a `Vec` or `FixedArray` (each `x` is what `list.get(i)` gives: a value, or a reference into account data for structs; the length is read again every step, as in JS), `break`, `continue` — bounded by the compute budget. Remaining accounts are iterated by index |
45
- | Helpers | non-exported functions (and every function of an imported file): take values, structs, Vec arguments, `SignerSeeds` and accounts the instruction already checked (`Signer`, `AccountInfo`/`AccountMeta`, `Mut<T>`, token accounts, programs); return `void`, a scalar or a fixed-size struct (`function at(x: u64, y: u64): Point { return { x, y }; }`: the caller gets its own copy, to bind, store with `acct.home = at(…)` / `arr.set(i, at(…))` or read as `at(…).x`); no recursion. Helpers cannot grow Vecs (only instructions know the entry size) |
46
- | Arrow functions | `const fee = (x: u64): u64 => x / 100n` (or `const f = function (…) {…}`) at the top level or inside a function is a helper (parameter and return types written out). A local one capturing nothing: it reads its parameters, top-level names and other functions, not the enclosing function's variables (pass them in). Functions are called, not stored or passed |
47
- | Generic helpers | `function max<T extends bigint>(a: T, b: T): T` — compiled once per set of types it is called with (`max(a, b)` with u64 arguments, or `max<u128>(x, 1n)` when only literals tell); a type parameter stands for an integer, boolean, Pubkey or enum. Each instance is checked like any helper; one that is never called is not compiled (TypeScript still type-checks it). Arithmetic on `T` is a `bigint` to TypeScript: `return (a + b) as T` (`as` names a type the value already has; it never converts) |
44
+ | Loops | `for (let i = 0n; i < n; i++)`, `while`, `do … while`, `for (const x of list)` over a `Vec` or `FixedArray` (each `x` is what `list.get(i)` gives: a value, or a reference into account data for structs; the length is read again every step, as in JS), `break`, `continue` — bounded by the compute budget. Remaining accounts are iterated by index |
45
+ | Helpers | non-exported functions (and every function of an imported file): take values, structs, Vec arguments, `SignerSeeds` and accounts the instruction already checked (`Signer`, `AccountInfo`/`AccountMeta`, `Mut<T>`, token accounts, programs); return `void`, a scalar or a fixed-size struct (`function at(x: u64, y: u64): Point { return { x, y }; }`: the caller gets its own copy, to bind, store with `acct.home = at(…)` / `arr.set(i, at(…))` or read as `at(…).x`); no recursion. A helper that takes a struct account as `Mut<T>` can grow its Vec (`push(value, payer)`: the account's size at instruction entry is passed along) |
46
+ | Arrow functions and closures | `const fee = (x: u64): u64 => x / 100n` (or `const f = function (…) {…}`) at the top level or inside a function. One that reads or writes the enclosing function's variables (a closure: `let total = 0n; const add = (v: u64): void => { total += v; }`) is expanded where it is called: its names mean what they meant where it was declared, struct parameters are references, `return` leaves the closure; it cannot call itself. One that captures nothing becomes a helper. Parameter types may be left out on closures (taken from the arguments) |
47
+ | Functions as arguments | a helper may take a function parameter (`function applyTwice(f: (x: u64) => u64, v: u64): u64`): it is compiled once per named function passed (a helper or a local function that captures nothing). Closures and arrow literals cannot be passed to helpers (a closure only runs where it is declared) |
48
+ | List callbacks | on a `Vec`, `FixedArray`, `FixedVec` or `Vec` argument: `forEach`, `some`, `every`, `find` (an Option of the element; scalar elements), `findIndex` (i64, -1 when absent), `reduce(fn, initial)`, `includes(value)`, `indexOf(value)` (i64). The arrow written at the call is expanded into a loop (elements as `list.get(i)` gives them, structs by reference) and may read and write the function's variables |
49
+ | Generic helpers | `function max<T extends bigint>(a: T, b: T): T` — compiled once per set of types it is called with (`max(a, b)` with u64 arguments, or `max<u128>(x, 1n)` when only literals tell); a type parameter stands for an integer, boolean, Pubkey, enum or fixed-size struct (`pick<Point>(…)`, or from struct arguments; a struct `T` is copied like any struct value). Each instance is checked like any helper; one that is never called is not compiled (TypeScript still type-checks it). Arithmetic on `T` is a `bigint` to TypeScript: `return (a + b) as T` (`as` names a type the value already has; it never converts) |
48
50
  | Evaluation order | operands and arguments are read left to right, as in JS, even when a later one changes account data (`v.length + v.pop()`) |
49
51
  | `++` `--` `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` | all checked |
50
52
  | Sysvars | `now()`, `currentSlot()`, `currentEpoch()` (clock), `minimumBalance(account)`, `rentExempt(space)` (rent) |
51
53
  | Account size | `realloc(account, newSize, payer)` on a `Mut<Struct>` — growing charges the payer the extra rent (at most 10 KiB per instruction, 3016); shrinking keeps the lamports in the account; never below the struct |
52
- | Token-2022 extensions | `transferFee(mint, amount)` (current-epoch TransferFeeConfig, ceil + cap like spl-token-2022), `transferHookProgram(mint)`, `permanentDelegate(mint)`, `mintCloseAuthority(mint)` (Option<Pubkey>), `hasExtension(account, type)` |
54
+ | Token-2022 extensions | `transferFee(mint, amount)` (current-epoch TransferFeeConfig, ceil + cap like spl-token-2022), `transferHookProgram(mint)`, `transferHookAuthority(mint)`, `permanentDelegate(mint)`, `mintCloseAuthority(mint)` (Option<Pubkey>), `hasExtension(account, type)`, `isTransferring(tokenAccount)` (inside a transfer hook: whether Token-2022 is transferring from it) |
53
55
  | Events through CPI | `emitCpi<Paid>({ … }, eventAuthority, self)` with `eventAuthority: EventAuthority, self: SelfProgram` parameters — Anchor `emit_cpi!`: the event rides in a self-CPI (inner instruction), not in logs; the program accepts Anchor's event instruction (2002 / 2006 otherwise) |
54
56
  | Heap | 32 KiB by default; `const HEAP_SIZE_KIB = 64n` (32–256) lets the program use more, when clients add ComputeBudget `requestHeapFrame` |
55
57
  | Docs | JSDoc on instructions, `@param`, structs, fields and data enums becomes `docs` in the IDL |
56
- | Metaplex Core | `core: MplCoreProgram` — `createCollection`, `createAsset` (in a collection, PDA authorities via signerSeeds), `transferAsset`, `burnAsset` (`null` leaves an optional account out); plugins as a literal list after the uri (`[{ kind: "Royalties", basisPoints: 500n, creators: [{ address, percentage: 100n }] }, { kind: "PermanentFreezeDelegate", frozen: false, authority: studio.key }]`), and `addPlugin` / `updatePlugin` / `removePlugin(…, "FreezeDelegate")` with their `…CollectionPlugin` forms. Plugins: Royalties (no rule set), FreezeDelegate, BurnDelegate, TransferDelegate, UpdateDelegate, PermanentFreezeDelegate, Attributes, PermanentTransferDelegate, PermanentBurnDelegate, Edition, AddBlocker, ImmutableMetadata; `authority` is `"owner"`, `"updateAuthority"`, `"none"` or a Pubkey; `coreAssetOwner(asset)`, `coreAssetCollection(asset)` |
57
- | Compressed NFTs | `bubblegum: BubblegumProgram` — V1: `createTree` (client-allocated tree on SPL Account Compression), `mint` (MintV1, no collection/creators). V2 (trees on mpl-account-compression with mpl-noop): `createTreeV2`, `mintV2(…, { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable? }, { collection, authority, cpiSigner }?)` (into a Core collection that has the `BubblegumV2` plugin), `transferV2` and `burnV2` with the leaf `{ root, dataHash, creatorHash, assetDataHash?, flags?, nonce, index }` and its proof as `remainingAccounts()` / `rest.slice(a)` |
58
+ | Metaplex Core | `core: MplCoreProgram` — `createCollection`, `createAsset` (in a collection, PDA authorities via signerSeeds), `transferAsset`, `burnAsset` (`null` leaves an optional account out); plugins as a literal list after the uri (`[{ kind: "Royalties", basisPoints: 500n, creators: [{ address, percentage: 100n }] }, { kind: "PermanentFreezeDelegate", frozen: false, authority: studio.key }]`), and `addPlugin` / `updatePlugin` / `removePlugin(…, "FreezeDelegate")` with their `…CollectionPlugin` forms. Plugins: Royalties (with an optional `ruleSet: { programAllowList \| programDenyList: [...] }`), FreezeDelegate, BurnDelegate, TransferDelegate, UpdateDelegate, PermanentFreezeDelegate, Attributes, PermanentTransferDelegate, PermanentBurnDelegate, Edition, MasterEdition (collections: `maxSupply?`, `name?`, `uri?`), AddBlocker, ImmutableMetadata, VerifiedCreators, Autograph, BubblegumV2, FreezeExecute, PermanentFreezeExecute (Groups is kept by Core's group instructions); `authority` is `"owner"`, `"updateAuthority"`, `"none"` or a Pubkey; `coreAssetOwner(asset)`, `coreAssetCollection(asset)` |
59
+ | Compressed NFTs | `bubblegum: BubblegumProgram` — V1 (trees on SPL Account Compression): `createTree`, `mint` (MintV1, no collection/creators), `transfer`, `burn`, `delegate` (the leaf owner or delegate signs; a PDA owner signs with the seeds). V2 (trees on mpl-account-compression with mpl-noop): `createTreeV2`, `mintV2(…, { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable? }, { collection, authority, cpiSigner }?)` (into a Core collection that has the `BubblegumV2` plugin), `transferV2`, `burnV2`, `delegateV2`, `freezeV2` / `thawV2`, `setNonTransferableV2`, `updateMetadataV2(…, currentMetadata, { name?, symbol?, uri?, creators?, … })`, `verifyCreatorV2` / `unverifyCreatorV2`, `setCollectionV2`. Each takes the leaf (`{ root, dataHash, creatorHash, assetDataHash?, flags?, nonce, index }`, or `{ root, assetDataHash?, flags?, nonce, index }` with the leaf's current metadata for the metadata instructions) and its proof as `remainingAccounts()` / `rest.slice(a)` |
58
60
 
59
61
  ## Account parameters and the checks they emit
60
62
 
@@ -62,7 +64,7 @@ The full reference. For a tour, start with the [README](../README.md).
62
64
  | --- | --- |
63
65
  | `Signer` / `Mut<Signer>` | signed (and writable) |
64
66
  | `Account<T>` / `Mut<T>` | owned by this program, 8-byte discriminator matches, data large enough (and writable) |
65
- | `Init<T>` | writable; `create(payer, seeds)` derives the PDA, verifies it, funds rent, writes the discriminator (up to 10 KiB) |
67
+ | `Init<T>` | writable; `create(payer, seeds)` derives the PDA, verifies it, funds rent, writes the discriminator (up to 10 KiB); `create(payer, null)` creates a new keypair account that signs the transaction instead (3010 if it did not; a signer in the IDL); a third argument is Anchor's `space = …` (discriminator included, at least 8 + the struct's size) |
66
68
  | `InitIfNeeded<T>` | `const created = acct.createIfNeeded(payer, seeds)` — creates once; afterwards re-derives the PDA and checks owner, discriminator and size (Anchor `init_if_needed`); `acct.pdaBump` after create |
67
69
  | `HasOne<A, "owner">`, `Seeds<A, ["vault", "@owner"], "bump">`, `CloseTo<A, "owner">` | declarative constraints like Anchor's `has_one`, `seeds`/`bump` and `close`: checked before the body (2001 / 2006), close runs when the instruction succeeds; `"@name"` = account key or argument bytes, `"@acct.field"` = a Pubkey, integer or unit enum field of a struct account parameter (also the account's own: `["offer", "@maker", "@offer.id"]`), other strings are literals; recorded as `relations` / `pda` in the IDL. `acct.pdaBump` is the bump (Anchor `ctx.bumps.acct`): the stored field when one is named, else the canonical bump the check found — use it to sign as the PDA |
68
70
  | `HasOne<TokenAccount, "mint" \| "owner">`, `HasOne<Mint, "mintAuthority" \| "freezeAuthority">` | token constraints (2014 / 2015 / 2016 / 2017) against the parameter with that name |
@@ -71,10 +73,12 @@ The full reference. For a tour, start with the [README](../README.md).
71
73
  | `Address<A, "4Nd1…">` | exact address (2012; `address` in the IDL) |
72
74
  | `Owner<A, "Tokenkeg…">`, `Executable<A>` | owner program (2004), executable (2007) |
73
75
  | `Seeds<A, ["metadata", "@mint"], never, "@metadataProgram">` | the fourth argument derives the PDA under another program (a program parameter or a base58 address; `pda.program` in the IDL) |
74
- | `A \| null` / `Optional<A>` | optional account (Anchor `Option<Account>`): the client passes this program's id to leave it out; its checks and constraints run only when present; `x !== null` before use (5115 otherwise); `optional` in the IDL |
76
+ | `A \| null` / `Optional<A>` | optional account (Anchor `Option<Account>`): the client passes this program's id to leave it out; its checks and constraints run only when present; `x !== null` before use, or `x!` / `x!.field` to assert it was passed (5115 otherwise); `optional` in the IDL |
75
77
  | `ProgramData` | this program's ProgramData (address derived from the program id, upgradeable-loader owner and state checked); `upgradeAuthority`, `deploySlot` — gate admin setup on `programData.upgradeAuthority === admin.key` |
76
78
  | `EventAuthority`, `SelfProgram` | Anchor's `["__event_authority"]` PDA, and this program's own address, for `emitCpi` |
77
79
  | `Zeroed<T>` | large accounts (up to 10 MiB) the client allocated with System `CreateAccount`: owned by this program, big enough, rent-exempt, discriminator still zero — then initialized; like Anchor's zero-copy `zero` |
80
+ | `Init<Mint>`, `InitIfNeeded<Mint>` | Anchor `init` / `init_if_needed` with `mint::decimals` / `mint::authority` / `mint::freeze_authority`: `const bump = mint.create(payer, ["mint", payer.key], 6n, authority, freezeAuthority \| null)` creates the mint at that PDA of this program, or with `null` seeds as a new keypair account that signs (3010 if it did not), under the instruction's token program (exactly one token program parameter, plus `SystemProgram`). Token-2022 extensions (Anchor's `extensions::…`) go after the freeze authority: `mint.create(payer, null, 6n, authority, null, { metadataPointer: { authority, metadataAddress: mint.key }, transferHook: { authority, programId }, closeAuthority, permanentDelegate, nonTransferable: true, defaultAccountState: true, interestBearing: { rateAuthority, rate }, transferFee: { configAuthority, withdrawAuthority, basisPoints, maximumFee }, groupPointer, groupMemberPointer })` sizes the mint for them and initializes each before InitializeMint2. `createIfNeeded(…)` returns whether it created it; an existing one must be that PDA and have those decimals and authorities (2018 / 2016 / 2017) |
81
+ | `Init<TokenAccount>`, `InitIfNeeded<TokenAccount>` (no `Ata<…>`) | Anchor `init` / `init_if_needed` with `token::mint` / `token::authority`: `vault.create(payer, ["vault", mint.key], mint, authority)` (the authority may be `vault.key` itself) creates a token account at that PDA (or a keypair account with `null`), sized for the mint's extensions under Token-2022. `createIfNeeded` checks an existing one has that mint and owner (2014 / 2015) |
78
82
  | `TokenAccount` / `Mut<TokenAccount>` | owned by SPL Token or Token-2022 (extensions allowed, multisig accounts refused), initialized; `mint`, `owner`, `amount`, `delegate`, `delegatedAmount`, `isFrozen`, `isNative`, `closeAuthority`, `tokenProgram` |
79
83
  | `Mint` / `Mut<Mint>` | owned by SPL Token or Token-2022, initialized; `decimals`, `supply`, `mintAuthority`, `freezeAuthority`, `tokenProgram` |
80
84
  | `SystemProgram`, `TokenProgram`, `Token2022Program` | exact program address |
@@ -85,6 +89,7 @@ The full reference. For a tour, start with the [README](../README.md).
85
89
  | `Program<"Address…">` | exact program address + executable |
86
90
  | `UncheckedProgram` | executable only — check `.key` yourself |
87
91
  | `AccountInfo` / `Mut<AccountInfo>` | nothing (writable for `Mut`); `owner`, `executable` |
92
+ | `SystemAccount` / `Mut<SystemAccount>` | owned by the System Program, e.g. a wallet (Anchor `SystemAccount`, 3011) |
88
93
 
89
94
  Every account exposes `key`, `lamports`, `dataLen`, `isSigner`, `isWritable`. At most 32 accounts per instruction (more is refused, 5113).
90
95
 
@@ -96,10 +101,10 @@ account written through `.lamports` or `loadAccountMut` is another account of th
96
101
  - `systemProgram.transfer(from: Mut<Signer>, to, lamports)`, `systemProgram.createAccount(payer, account, lamports, space, owner, signerSeeds?)`
97
102
  (the new account signs: a keypair `Mut<Signer>` or this program's PDA), `rentExempt(space)`, `MINT_SIZE`, `TOKEN_ACCOUNT_SIZE`
98
103
  - Token programs (`TokenProgram`, `Token2022Program`, `TokenInterface`) — pass `signerSeeds` (with the bump) when the authority is a PDA:
99
- `transfer`, `transferChecked(from, mint, to, authority, amount)` (required by Token-2022 mints with fees/hooks), `mintTo`, `burn`, `closeAccount`,
104
+ `transfer`, `transferChecked(from, mint, to, authority, amount)` (required by Token-2022 mints with fees/hooks), `mintTo` (the mint may be one this instruction just created), `burn`, `closeAccount` (a token account, or a Token-2022 mint with a close authority),
100
105
  `approve(source, delegate, owner, amount)`, `revoke`, `setAuthority(target, current, AuthorityType.MintTokens, next | null)`,
101
106
  `freezeAccount` / `thawAccount(account, mint, freezeAuthority)`, `syncNative(account)`, `initializeMint2(mint, decimals, mintAuthority, freezeAuthority | null)`,
102
- `initializeAccount3(account, mint, owner)`, and on Token-2022 `initializeMetadata(mint, updateAuthority, mintAuthority, name, symbol, uri)` (metadata extension) and the extension initializers to call between `createAccount` and `initializeMint2` / `initializeAccount3`: `initializeTransferFeeConfig(mint, configAuthority | null, withdrawAuthority | null, basisPoints, maximumFee)`, `initializeMintCloseAuthority(mint, authority | null)`, `initializeNonTransferableMint(mint)`, `initializePermanentDelegate(mint, delegate)`, `initializeDefaultAccountState(mint, frozen)`, `initializeMetadataPointer` / `initializeGroupPointer` / `initializeGroupMemberPointer` / `initializeTransferHook(mint, authority | null, address | null)`, `initializeInterestBearingMint(mint, rateAuthority | null, rate)`, `initializeImmutableOwner(account)`
107
+ `initializeAccount3(account, mint, owner)`, and on Token-2022 `initializeMetadata(mint, updateAuthority, mintAuthority, name, symbol, uri)` (metadata extension) and the extension initializers to call between `createAccount` and `initializeMint2` / `initializeAccount3`: `initializeTransferFeeConfig(mint, configAuthority | null, withdrawAuthority | null, basisPoints, maximumFee)`, `initializeMintCloseAuthority(mint, authority | null)`, `initializeNonTransferableMint(mint)`, `initializePermanentDelegate(mint, delegate)`, `initializeDefaultAccountState(mint, frozen)`, `initializeMetadataPointer` / `initializeGroupPointer` / `initializeGroupMemberPointer` / `initializeTransferHook(mint, authority | null, address | null)`, `initializeInterestBearingMint(mint, rateAuthority | null, rate)`, `initializeImmutableOwner(account)`; the extension instructions `updateDefaultAccountState(mint, freezeAuthority, frozen)`, `updateTransferHook(mint, authority, programId | null)`, `enableRequiredMemoTransfers` / `disableRequiredMemoTransfers(account, owner)`, `updateInterestRate(mint, rateAuthority, rate)`, `transferCheckedWithFee(from, mint, to, authority, amount, fee)`, `harvestWithheldTokensToMint(mint, remainingAccounts())`, `withdrawWithheldTokensFromMint(mint, destination, authority)`, `setTransferFee(mint, authority, basisPoints, maximumFee)`; and the Token Metadata interface `updateMetadataField(mint, updateAuthority, field, value)` ("name" / "symbol" / "uri" or any other key), `removeMetadataKey(mint, updateAuthority, key, idempotent)`, `updateMetadataAuthority(mint, updateAuthority, next | null)`, `emitMetadata(mint)`. Each takes optional signerSeeds
103
108
  - `account.lamports` read/write on `Mut` accounts, `close(account, destination)` for program accounts
104
109
  - `requirePda(account, seeds, bump)`
105
110
 
@@ -149,10 +154,13 @@ an integer literal, an argument, `acct.key` or a scalar field of an account, the
149
154
 
150
155
  ## Not supported yet
151
156
 
152
- Growable `Vec`s anywhere but the last account field (use `FixedVec<T, N>`), FixedVec in arguments and events, growing an account by more than 10 KiB in one instruction, growing a Vec
153
- inside a helper, data-enum arguments carrying structs, Pubkeys in templates, `?.`, `**=`, `!` on anything but an Option, `switch` on
154
- 128-bit integers, `mulDiv` beyond u64, Core's MasterEdition / VerifiedCreators / Autograph plugins, royalty rule sets and external plugin adapters, Bubblegum V2 delegates / freezes / metadata updates / V1 transfers, closures (local functions reading the enclosing function's variables), functions as values (callbacks,
155
- `.map`), type parameters standing for structs, floating point (by design), `do … while`.
157
+ Growable `Vec`s anywhere but the last account field (use `FixedVec<T, N>`), FixedVec in arguments and events outside
158
+ `@layout borsh` structs, growing an account by more than 10 KiB in one instruction (Solana's limit: realloc again in
159
+ the next instruction), `?.` on anything but optional accounts and Option<struct> fields, `!` on anything but an Option
160
+ or an optional account, Core's external plugin adapters (oracles, app data, lifecycle hooks: use `invoke`), closures
161
+ stored or returned (they are called or passed to list methods), arrow literals and closures as helper arguments (pass a
162
+ named function), `map` / `filter` (they would build a new list), mint extensions on `InitIfNeeded<Mint>` (create the
163
+ mint with `create(…, extensions)`), floating point (by design).
156
164
  They are rejected at compile time rather than half-supported.
157
165
 
158
166
  `Init.create` handles a PDA address someone pre-funded: it tops up rent, then allocates and assigns instead of
@@ -31,7 +31,8 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
31
31
  | `#[derive(Accounts)] struct Ctx` + `ctx.accounts.x` | the instruction's parameters, in the same order |
32
32
  | `Signer<'info>` / `#[account(mut)] Signer` | `Signer` / `Mut<Signer>` |
33
33
  | `Account<'info, T>` / `#[account(mut)]` | `Account<T>` / `Mut<T>` |
34
- | `#[account(init, payer = p, space = …, seeds = […], bump)]` | `Init<T>`, then `const bump = acct.create(p, ["seed", other.key])` (space is computed) |
34
+ | `#[account(init, payer = p, space = …, seeds = […], bump)]` | `Init<T>`, then `const bump = acct.create(p, ["seed", other.key])` (space is computed; `acct.create(p, […], 8n + 64n)` keeps an explicit `space`) |
35
+ | `#[account(init, payer = p, space = …)]` (a keypair account) | `Init<T>`, then `acct.create(p, null)` (the account signs the transaction, as in Anchor) |
35
36
  | `init_if_needed` | `InitIfNeeded<T>`, then `const created = acct.createIfNeeded(p, seeds)` |
36
37
  | `seeds = [b"vault", owner.key().as_ref()], bump = vault.bump` | `Seeds<Mut<Vault>, ["vault", "@owner"], "bump">` |
37
38
  | `seeds = [b"offer", offer.maker.as_ref(), offer.id.to_le_bytes().as_ref()]` | `Seeds<…, ["offer", "@offer.maker", "@offer.id"], "bump">` |
@@ -43,11 +44,15 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
43
44
  | `realloc = n, realloc::payer = p` | `realloc(acct, n, p)` |
44
45
  | `Option<Account<'info, T>>` | `Mut<T> \| null` (the client passes the program id to leave it out) |
45
46
  | `UncheckedAccount` / `AccountInfo` | `AccountInfo` / `Mut<AccountInfo>`: check what you rely on yourself |
47
+ | `SystemAccount<'info>` | `SystemAccount` / `Mut<SystemAccount>` |
46
48
  | `AccountLoader<T>` with `#[account(zero)]` | `Zeroed<T>` (TypeSOL reads every account in place: no copy to avoid) |
47
49
  | `Program<'info, System>` | `SystemProgram` |
48
50
  | `Program<'info, Token>` / `Token2022` / `Interface<'info, TokenInterface>` | `TokenProgram` / `Token2022Program` / `TokenInterface` |
49
51
  | `Account<'info, TokenAccount>` / `Account<'info, Mint>` (`InterfaceAccount`) | `TokenAccount` / `Mint` (`Mut<…>` when written) |
50
52
  | `token::mint = m, token::authority = a` | `HasOne<TokenAccount, "mint" \| "owner">` against parameters with those names |
53
+ | `extensions::metadata_pointer::authority = a, …` (with `mint::…`) | `mint.create(p, …, { metadataPointer: { authority: a.key, metadataAddress: mint.key }, … })`: transferHook, closeAuthority, permanentDelegate, nonTransferable, groupPointer, groupMemberPointer and more |
54
+ | `#[account(init, payer = p, seeds = […], bump, mint::decimals = 6, mint::authority = a, mint::freeze_authority = f)]` | `Init<Mint>`, then `const bump = mint.create(p, […], 6n, a.key, f.key)` (`null` seeds: a keypair mint; `InitIfNeeded<Mint>` + `createIfNeeded`) |
55
+ | `#[account(init, payer = p, seeds = […], bump, token::mint = m, token::authority = a)]` | `Init<TokenAccount>`, then `vault.create(p, […], m, a.key)` (`InitIfNeeded<TokenAccount>` + `createIfNeeded`) |
51
56
  | `associated_token::mint = m, associated_token::authority = a` | `Ata<TokenAccount, "a", "m">`; `Ata<Init<TokenAccount>, "a", "m">` creates it |
52
57
  | `ctx.remaining_accounts` | `remainingAccounts()` |
53
58
  | `ctx.bumps.vault` | the value `create` returns, or `acct.pdaBump` |
@@ -75,7 +80,8 @@ Anchor stores it (`FixedString<N>` / `FixedVec<T, N>` / `Option<T>` become Borsh
75
80
  adds it for you. It costs some compute (the account is unpacked and packed in every instruction that uses it) but stays
76
81
  several times cheaper than Anchor. Accounts using them need a migration (or a new program) rather
77
82
  than an in-place upgrade, unless they use `@layout borsh`. Everything else, including instruction arguments (`String`, `Option`, `Vec` are Borsh there),
78
- matches byte for byte.
83
+ matches byte for byte. A `@layout borsh` struct passed as a whole argument, returned or emitted is Borsh too, so an
84
+ Anchor instruction that takes or returns a struct with `String` / `Vec` / `Option` fields keeps its client bytes.
79
85
 
80
86
  ## Code
81
87
 
@@ -96,6 +102,8 @@ matches byte for byte.
96
102
  | `invoke_signed(&ix, &accounts, &[seeds])` | `invoke(program, [writable(a), readonly(b)], [data…], seeds)` |
97
103
  | `declare_program!(other)` / CPI to another Anchor program | `typesol cpi other.json` generates typed helpers |
98
104
  | `for x in v.iter()` | `for (const x of v)` |
105
+ | `v.iter().any(\|x\| …)`, `.all`, `.position`, `.find`, `.fold(init, …)`, `.contains(&x)` | `v.some((x) => …)`, `every`, `findIndex`, `find`, `reduce((acc, x) => …, init)`, `includes(x)` |
106
+ | a closure `\|x\| x + offset` | `const f = (x: u64): u64 => x + offset` (it may read and write the function's variables) |
99
107
  | `x.unwrap_or(d)` | `x ?? d` |
100
108
  | `fn max<T: PartialOrd>(a: T, b: T) -> T` | `function max<T extends bigint>(a: T, b: T): T` (one copy per type used) |
101
109
  | `let fee = \|x: u64\| x / 100;` (no captures) | `const fee = (x: u64): u64 => x / 100n;` |