@typesol/cli 0.16.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/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,8 +23,10 @@ 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 |
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
+ | 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>` |
28
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 |
29
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) |
30
32
  | Struct literals | `book.entries.push({ who: who.key, amount, status: Status.Pending, note }, who)` — every field required |
@@ -34,23 +36,27 @@ The full reference. For a tour, start with the [README](../README.md).
34
36
  | Constants | top-level `const SEED = "vault"`, `const FEE: u16 = 30n`, `const ON = true` — inlined, usable as seeds (and described in the IDL) |
35
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 |
36
38
  | Option of structs | `home: Option<Point>` in account data — `=== null`, `p.home.x` after narrowing, `p.home = null` / `{ x, y }` |
37
- | Strings at runtime | `"hello, " + name`, `` `#${n}` `` — built on the 32 KiB heap (5110 when full) |
38
- | `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 |
39
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) |
40
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">`) |
41
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 |
42
- | 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 |
43
- | 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) |
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) |
44
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()`) |
45
51
  | `++` `--` `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` | all checked |
46
52
  | Sysvars | `now()`, `currentSlot()`, `currentEpoch()` (clock), `minimumBalance(account)`, `rentExempt(space)` (rent) |
47
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 |
48
- | 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) |
49
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) |
50
56
  | Heap | 32 KiB by default; `const HEAP_SIZE_KIB = 64n` (32–256) lets the program use more, when clients add ComputeBudget `requestHeapFrame` |
51
57
  | Docs | JSDoc on instructions, `@param`, structs, fields and data enums becomes `docs` in the IDL |
52
- | Metaplex Core | `core: MplCoreProgram` — `createCollection`, `createAsset` (in a collection, PDA authorities via signerSeeds), `transferAsset`, `burnAsset` (no plugins; `null` leaves an optional account out); `coreAssetOwner(asset)`, `coreAssetCollection(asset)` |
53
- | Compressed NFTs | `bubblegum: BubblegumProgram` — `createTree` (client-allocated tree on SPL Account Compression), `mint` (MintV1, no collection/creators) |
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)` |
54
60
 
55
61
  ## Account parameters and the checks they emit
56
62
 
@@ -58,7 +64,7 @@ The full reference. For a tour, start with the [README](../README.md).
58
64
  | --- | --- |
59
65
  | `Signer` / `Mut<Signer>` | signed (and writable) |
60
66
  | `Account<T>` / `Mut<T>` | owned by this program, 8-byte discriminator matches, data large enough (and writable) |
61
- | `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) |
62
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 |
63
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 |
64
70
  | `HasOne<TokenAccount, "mint" \| "owner">`, `HasOne<Mint, "mintAuthority" \| "freezeAuthority">` | token constraints (2014 / 2015 / 2016 / 2017) against the parameter with that name |
@@ -67,20 +73,23 @@ The full reference. For a tour, start with the [README](../README.md).
67
73
  | `Address<A, "4Nd1…">` | exact address (2012; `address` in the IDL) |
68
74
  | `Owner<A, "Tokenkeg…">`, `Executable<A>` | owner program (2004), executable (2007) |
69
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) |
70
- | `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 |
71
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` |
72
78
  | `EventAuthority`, `SelfProgram` | Anchor's `["__event_authority"]` PDA, and this program's own address, for `emitCpi` |
73
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) |
74
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` |
75
83
  | `Mint` / `Mut<Mint>` | owned by SPL Token or Token-2022, initialized; `decimals`, `supply`, `mintAuthority`, `freezeAuthority`, `tokenProgram` |
76
84
  | `SystemProgram`, `TokenProgram`, `Token2022Program` | exact program address |
77
85
  | `TokenInterface` | either token program |
78
86
  | `AssociatedTokenProgram` | `create` / `createIdempotent` CPIs; `associatedTokenAddress(owner, mint, tokenProgram)` |
79
- | `TokenMetadataProgram` | Metaplex `createV3(...)` (CreateMetadataAccountV3), optionally with `{ creators: [{ address, verified, share }], collection: mint }` after `isMutable` (1–5 creators, shares adding up to 100; only the update authority, signing, can be `verified: true`; the collection is written unverified); `metadataAddress(mint)` |
87
+ | `TokenMetadataProgram` | Metaplex `createV3(...)` (CreateMetadataAccountV3), optionally with `{ creators: [{ address, verified, share }], collection: mint }` after `isMutable` (1–5 creators, shares adding up to 100; only the update authority, signing, can be `verified: true`; the collection is written unverified; `isCollection: true` makes the NFT a sized collection); `createMasterEditionV3(edition, mint, updateAuthority, mintAuthority, payer, metadata, tokenProgram, systemProgram, maxSupply \| null)`; `verifyCollection(authority, metadata, collectionMint, collectionMetadata, collectionMasterEdition, systemProgram, instructionsSysvar)` (Verify with CollectionV1); `metadataAddress(mint)`, `masterEditionAddress(mint)` |
80
88
  | `InstructionsSysvar`, `PythPriceUpdate`, `SwitchboardFeed` | exact address / owner program + discriminator |
81
89
  | `Program<"Address…">` | exact program address + executable |
82
90
  | `UncheckedProgram` | executable only — check `.key` yourself |
83
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) |
84
93
 
85
94
  Every account exposes `key`, `lamports`, `dataLen`, `isSigner`, `isWritable`. At most 32 accounts per instruction (more is refused, 5113).
86
95
 
@@ -92,10 +101,10 @@ account written through `.lamports` or `loadAccountMut` is another account of th
92
101
  - `systemProgram.transfer(from: Mut<Signer>, to, lamports)`, `systemProgram.createAccount(payer, account, lamports, space, owner, signerSeeds?)`
93
102
  (the new account signs: a keypair `Mut<Signer>` or this program's PDA), `rentExempt(space)`, `MINT_SIZE`, `TOKEN_ACCOUNT_SIZE`
94
103
  - Token programs (`TokenProgram`, `Token2022Program`, `TokenInterface`) — pass `signerSeeds` (with the bump) when the authority is a PDA:
95
- `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),
96
105
  `approve(source, delegate, owner, amount)`, `revoke`, `setAuthority(target, current, AuthorityType.MintTokens, next | null)`,
97
106
  `freezeAccount` / `thawAccount(account, mint, freezeAuthority)`, `syncNative(account)`, `initializeMint2(mint, decimals, mintAuthority, freezeAuthority | null)`,
98
- `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
99
108
  - `account.lamports` read/write on `Mut` accounts, `close(account, destination)` for program accounts
100
109
  - `requirePda(account, seeds, bump)`
101
110
 
@@ -145,10 +154,13 @@ an integer literal, an argument, `acct.key` or a scalar field of an account, the
145
154
 
146
155
  ## Not supported yet
147
156
 
148
- Vecs anywhere but the last account field, growing an account by more than 10 KiB in one instruction, growing a Vec
149
- inside a helper, data-enum arguments carrying structs, Pubkeys in templates, `?.`, `**=`, `!` on anything but an Option, `switch` on
150
- 128-bit integers, `mulDiv` beyond u64, Token Metadata collection verification, Core plugins, Bubblegum v2 / collections /
151
- transfers, 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).
152
164
  They are rejected at compile time rather than half-supported.
153
165
 
154
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` |
@@ -60,15 +65,23 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
60
65
  | `u8` … `u128`, `i8` … `i128`, `bool`, `Pubkey` | `u8` … `u128`, `i8` … `i128`, `boolean`, `Pubkey` |
61
66
  | `[T; N]` | `FixedArray<T, N>` |
62
67
  | `String` with `#[max_len(n)]` | `FixedString<N>`: a u32 length and N bytes, always N bytes long |
63
- | `Vec<T>` with `#[max_len(n)]` | `Vec<T>` as the account's last field; it grows with `push(v, payer)` |
68
+ | `Vec<T>` with `#[max_len(n)]` | `Vec<T>` as the account's last field (it grows with `push(v, payer)`), or `FixedVec<T, n>` anywhere: a u32 length and room for n, always 4 + n × size bytes |
64
69
  | `Option<T>` | `Option<T>`: fixed size (1 + size of T) |
65
70
  | `#[derive(AnchorSerialize…)] enum` | `export enum` (unit variants) or a data enum `type A = { kind: "X"; … } \| …` |
66
71
 
67
- TypeSOL keeps account data at a fixed layout, so three Anchor layouts differ: a Borsh `String` is variable-length (TypeSOL
68
- uses `FixedString<N>`), a Borsh `Option` takes 1 byte when empty (TypeSOL always reserves the value), and a `Vec` can be
69
- anywhere in an Anchor struct (TypeSOL keeps it last). Accounts using them need a migration (or a new program) rather
70
- than an in-place upgrade. Everything else, including instruction arguments (`String`, `Option`, `Vec` are Borsh there),
71
- matches byte for byte.
72
+ TypeSOL keeps account data at a fixed layout by default, so three Anchor layouts differ: a Borsh `String` is
73
+ variable-length (TypeSOL uses `FixedString<N>`), a Borsh `Option` takes 1 byte when empty (TypeSOL always reserves the
74
+ value), and a `Vec` can be anywhere in an Anchor struct (TypeSOL keeps a growable one last; a `FixedVec<T, N>` elsewhere
75
+ always takes its full capacity, where Anchor packs the elements and moves the fields after them).
76
+
77
+ To keep an Anchor program's existing accounts, mark the struct `/** @layout borsh */`: it is then stored exactly as
78
+ Anchor stores it (`FixedString<N>` / `FixedVec<T, N>` / `Option<T>` become Borsh String / Vec / Option, N being the
79
+ `#[max_len]`), its IDL type is Anchor's, and the upgraded program reads and writes the accounts in place. `typesol port`
80
+ adds it for you. It costs some compute (the account is unpacked and packed in every instruction that uses it) but stays
81
+ several times cheaper than Anchor. Accounts using them need a migration (or a new program) rather
82
+ than an in-place upgrade, unless they use `@layout borsh`. Everything else, including instruction arguments (`String`, `Option`, `Vec` are Borsh there),
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.
72
85
 
73
86
  ## Code
74
87
 
@@ -89,7 +102,11 @@ matches byte for byte.
89
102
  | `invoke_signed(&ix, &accounts, &[seeds])` | `invoke(program, [writable(a), readonly(b)], [data…], seeds)` |
90
103
  | `declare_program!(other)` / CPI to another Anchor program | `typesol cpi other.json` generates typed helpers |
91
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) |
92
107
  | `x.unwrap_or(d)` | `x ?? d` |
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) |
109
+ | `let fee = \|x: u64\| x / 100;` (no captures) | `const fee = (x: u64): u64 => x / 100n;` |
93
110
 
94
111
  ## Tooling
95
112
 
@@ -41,7 +41,7 @@ it("counts", async () => {
41
41
  | --- | --- |
42
42
  | `t.ok(res)` | the successful result, or throws with the program's logs |
43
43
  | `t.errorCode(res)` | the custom error code of a failed transaction: 6000 + the member's index in your error enum, or a framework code ([errors.md](errors.md)) |
44
- | `t.account(type, address)` | the account decoded by its IDL type (bigints, address strings), or `null` once closed |
44
+ | `t.account(type, address)` | the account decoded by its IDL type (bigints, address strings), or `null` once closed. TypeSOL fixed layouts come back as their IDL structs: a `FixedString<N>` as `{ len, bytes }`, a `FixedVec<T, N>` as `{ len, items }` (the generated client gives strings and arrays) |
45
45
  | `t.returned(instruction, res)` | the instruction's return data: an integer, boolean, Pubkey or struct |
46
46
  | `t.balance(address)` | lamports |
47
47