@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/CHANGELOG.md CHANGED
@@ -5,6 +5,127 @@ until 1.0, a minor version may change the language, the IDL layout or error code
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.18.0 — 2026-10-28
9
+
10
+ The remaining Anchor differences, closed: Anchor's argument structs (Strings, Vecs, Options and enums in Borsh),
11
+ keypair `init` with `space`, `init` of mints with Token-2022 extensions and of token accounts, `Vec<String>`
12
+ arguments, `SystemAccount`, string return values, and the Token-2022 and Token Metadata instructions programs call.
13
+ Closures, list callbacks and more everyday TypeScript; the rest of Bubblegum and Core. 26 of the 46 official
14
+ examples are now full ports. Existing programs keep their layouts and error codes (2018 and 3011 are new, raised only
15
+ by the new `Init<Mint>` and `SystemAccount` checks). Not externally audited (see SECURITY.md).
16
+
17
+ ### Added
18
+ Anchor accounts and arguments:
19
+ - `@layout borsh` structs as whole instruction arguments, return values and events, in Anchor's Borsh bytes (a
20
+ FixedString is a String, a FixedVec a Vec, an Option one byte when empty). Arguments are checked as Borsh
21
+ deserialization does, UTF-8 included (102 otherwise). `return vault` returns a whole struct account.
22
+ - `@layout borsh` structs used as arguments, return values or events may hold `string` fields and data enums (with or
23
+ without strings), as Anchor argument structs do; Borsh accounts may hold data enums without strings.
24
+ - `acct.create(payer, null)` on `Init<T>`: Anchor's `init` of a keypair account (it must sign; 3010 otherwise), and an
25
+ optional third argument for Anchor's `space = …` on `create` / `createIfNeeded`.
26
+ - Mint extensions on `Init<Mint>`: `mint.create(payer, seeds | null, decimals, authority, freeze, { … })` with
27
+ transferFee, closeAuthority, defaultAccountState, nonTransferable, interestBearing, permanentDelegate, transferHook,
28
+ metadataPointer, groupPointer and groupMemberPointer (Anchor's `extensions::…`).
29
+ - `Init<Mint>` / `InitIfNeeded<Mint>` with `mint.create(payer, seeds | null, decimals, mintAuthority, freezeAuthority)`
30
+ and `Init<TokenAccount>` / `InitIfNeeded<TokenAccount>` (without `Ata<…>`) with `vault.create(payer, seeds | null,
31
+ mint, authority)`: Anchor's `init` with `mint::` / `token::` constraints, at a PDA of this program or (with `null`) a
32
+ new keypair account that signs, under SPL Token or Token-2022 (token accounts sized with GetAccountDataSize). The
33
+ `createIfNeeded` forms check an existing account like Anchor's `init_if_needed` (2014 / 2015 / 2016 / 2017 and the
34
+ new 2018 ConstraintMintDecimals). The IDL marks keypair-created accounts as signers.
35
+ - `SystemAccount` / `Mut<SystemAccount>`: Anchor's `SystemAccount`, refused with 3011 (AccountNotSystemOwned) when
36
+ another program owns it.
37
+ - `Vec<string>` instruction arguments (Anchor's `Vec<String>`): `.get(i)`, `.length`, `for … of`, list callbacks,
38
+ helper parameters, storing into `FixedVec<FixedString<N>, M>`, and re-encoding through `invoke`; each string is
39
+ checked as UTF-8 on entry (102).
40
+ - Helpers and instructions can return `string` (an instruction's is Borsh String return data).
41
+ - `acct.key` of an `Init<…>` account before it is created (the address is known; Anchor seeds use it that way).
42
+ - `mint.create(…, tokenProgram)` / `vault.create(…, tokenProgram)`: an explicit token program, for instructions that
43
+ take more than one.
44
+
45
+ Language:
46
+ - Closures: a local function that reads or writes the enclosing function's variables is expanded where it is called,
47
+ with the names it was declared with (a block at the call that declares the same name does not change it), struct
48
+ parameters by reference and `return` leaving the closure. Recursion and storing / returning closures are refused.
49
+ - List callbacks on Vec, FixedArray, FixedVec and Vec arguments: `forEach`, `some`, `every`, `find`, `findIndex`,
50
+ `reduce`, `includes`, `indexOf`, expanded into loops with the arrow written at the call.
51
+ - Helpers can take function parameters (`f: (x: u64) => u64`): compiled once per named function passed.
52
+ - `do … while` loops (`continue` goes to the test).
53
+ - `x **= n` (the target must not call a function: it is read and written).
54
+ - `switch` on 128-bit integers (the case is picked with comparisons, then switched on as usual, fallthrough included).
55
+ - `?.` on optional accounts (`acct?.field`) and on `Option<struct>` fields (`x.home?.field`): an Option, empty when the
56
+ account was left out or the option is empty; use it with `??`.
57
+ - `acct!` on optional accounts (fails with 5115 when it was left out), Pubkeys / Hashes in template strings (base58),
58
+ and data-enum arguments whose variants carry fixed-size structs or arrays.
59
+ - Type parameters standing for fixed-size structs (`pick<Point>(…)`, or taken from struct arguments), and `cond ? a : b`
60
+ for struct values (places, literals or calls).
61
+ - Growable `Vec<T>` `push` / `pop` / `clear` inside helpers that take the account as `Mut<T>` (with its payer and
62
+ SystemProgram, as in instructions).
63
+ - `mulDiv` / `mulDivCeil` on u128 through a 256-bit product (all three u128; literals take that type).
64
+
65
+ CPI:
66
+ - Token-2022 extension instructions: `updateDefaultAccountState`, `updateTransferHook`, `enableRequiredMemoTransfers` /
67
+ `disableRequiredMemoTransfers`, `updateInterestRate`, `transferCheckedWithFee`, `harvestWithheldTokensToMint`,
68
+ `withdrawWithheldTokensFromMint`, `setTransferFee`; the Token Metadata interface: `updateMetadataField`,
69
+ `removeMetadataKey`, `updateMetadataAuthority`, `emitMetadata`; `closeAccount` of a mint; `mintTo` /
70
+ `initializeMetadata` on a mint created earlier in the instruction; `isTransferring(tokenAccount)` and
71
+ `transferHookAuthority(mint)`.
72
+ - Metaplex Core plugin values: MasterEdition, VerifiedCreators, Autograph, FreezeExecute, PermanentFreezeExecute, and
73
+ royalty rule sets (`ruleSet: { programAllowList | programDenyList }`).
74
+ - Bubblegum: `delegateV2`, `freezeV2`, `thawV2`, `setNonTransferableV2`, `updateMetadataV2`, `verifyCreatorV2`,
75
+ `unverifyCreatorV2`, `setCollectionV2`, and the V1 `transfer`, `burn`, `delegate`. Checked on the real programs with
76
+ every leaf recomputed: verify → delegate → freeze (a frozen leaf is refused) → thaw → rename, unverify, moving a leaf
77
+ into a collection, soulbinding a collection's leaf, and a V1 transfer signed by a PDA owner, delegate and burn.
78
+
79
+ ### Fixed
80
+ - Bubblegum leaf fields that may be null at run time (`assetDataHash: Hash | null`, `flags`) are sent as Borsh Options;
81
+ a null aborted the instruction before.
82
+
83
+ ## 0.17.0 — 2026-10-28
84
+
85
+ Anchor's account bytes on request (`@layout borsh`, so a TypeSOL program can take over an Anchor program's accounts
86
+ without a migration), lists with a fixed capacity anywhere in account data (`FixedVec`), arrow functions and generic
87
+ helpers, Metaplex collection verification, Core plugins and Bubblegum V2, and a faster UTF-8 check. Existing programs
88
+ keep their layouts and error codes (3004 is new, raised only by `@layout borsh` accounts). Not externally audited (see
89
+ SECURITY.md).
90
+
91
+ ### Added
92
+ - `/** @layout borsh */` on an account struct stores it with Anchor's bytes (Borsh String / Vec / Option for
93
+ `FixedString` / `FixedVec` / `Option`), so a TypeSOL program can take over an Anchor program's accounts without a
94
+ migration; its IDL lists Anchor's own types. An instruction unpacks the account into the fixed layout when it starts
95
+ and packs it back when it returns (3003 for bytes that are not a valid value, 3004 when the account is too small).
96
+ Checked byte for byte against an Anchor twin, including switching programs in the middle of a sequence.
97
+ `typesol port` marks Anchor account structs that need it.
98
+ - `FixedVec<T, N>`: a list with room for N elements (u32 length + N slots, like `FixedString<N>`) as any account field,
99
+ several per struct and in nested structs, with `get` / `set` / `push` / `pop` / `clear` / `.length` / `for … of`;
100
+ `push` on a full one fails with 5104, `fixedVec([…])` writes a whole one. The IDL describes each as a
101
+ `FixedVec<N>_<T>` struct `{ len, items }`; the generated client reads and writes them as arrays. The
102
+ basics/favorites port uses one.
103
+ - Bubblegum V2: `createTreeV2`, `mintV2` (optionally into a Metaplex Core collection), `transferV2` and `burnV2`, which
104
+ take the leaf (root, data and creator hashes, nonce, index) and pass its Merkle proof on from the remaining accounts;
105
+ the Core `BubblegumV2` collection plugin. Checked against Bubblegum, mpl-account-compression, mpl-noop and Core
106
+ (`pnpm fetch:programs` now fetches the mpl-account-compression pair too).
107
+ - Metaplex Core plugins: a literal plugin list in `createAsset` / `createCollection` (Royalties, freeze / burn / transfer /
108
+ update delegates and their permanent forms, Attributes, Edition, AddBlocker, ImmutableMetadata, each with an optional
109
+ authority), and `addPlugin`, `updatePlugin`, `removePlugin` with their collection forms, checked against the real Core
110
+ program (a frozen asset cannot move until it is thawed).
111
+ - Arrow functions: `const f = (x: u64): u64 => …` (and `const f = function (…) {…}`) at the top level or inside a
112
+ function is a helper. Local ones capture nothing (a closure over the enclosing function's variables is refused with
113
+ the name it reads); functions are called, not passed around.
114
+ - Generic helpers: `function max<T extends bigint>(a: T, b: T): T` compiles once per set of types it is called with,
115
+ inferred from the arguments or written out (`max<u128>(x, 1n)`); type parameters stand for integers, booleans,
116
+ Pubkeys or enums.
117
+ - `x as T` (and `satisfies`): names a type the value already has, never a conversion; mainly `return (a + b) as T`
118
+ in a generic helper.
119
+ - Metaplex collections: `createV3`'s `isCollection: true` (CollectionDetails::V1), `createMasterEditionV3(…, maxSupply | null)`,
120
+ `verifyCollection(…)` (Verify with CollectionV1) and `masterEditionAddress(mint)`. The tokens/nft-operations port now
121
+ uses them instead of hand-encoded `invoke`s, checked against the real Token Metadata program.
122
+
123
+ ### Changed
124
+ - IDLs no longer list `FixedString*` / `FixedOption_*` / `FixedVec*` helper types that nothing in them refers to
125
+ (for example an `Option` used only as an instruction argument, which is Borsh there).
126
+ - UTF-8 checks of strings run eight ASCII bytes at a time: every official-example instruction with a string argument
127
+ gets cheaper (create_token_mint 40,919 → 39,828 CU, set_favorites 4,282 → 4,195).
128
+
8
129
  ## 0.16.0 — 2026-10-28
9
130
 
10
131
  Everyday TypeScript that Anchor developers reach for (`for … of`, `??`, destructuring, `**`, structs returned from
package/README.md CHANGED
@@ -189,10 +189,10 @@ Runtime failures point back to your source: `Error 6000 Unauthorized (counter.ts
189
189
 
190
190
  | | |
191
191
  | --- | --- |
192
- | **Accounts** | `Signer`, `Account<T>`, `Mut<T>`, `Init<T>`, `InitIfNeeded<T>` (PDAs and associated token accounts), `Zeroed<T>` (up to 10 MiB), optional accounts (`A \| null`), token accounts and mints, programs, sysvars |
192
+ | **Accounts** | `Signer`, `Account<T>`, `Mut<T>`, `Init<T>`, `InitIfNeeded<T>` (PDAs or keypair accounts, with Anchor's `space`; mints, token accounts and associated token accounts), `Zeroed<T>` (up to 10 MiB), optional accounts (`A \| null`), token accounts and mints, `SystemAccount`, programs, sysvars |
193
193
  | **Constraints** | `HasOne`, `Seeds` (also from account fields and under another program; `pdaBump` like `ctx.bumps`), `CloseTo`, `Ata`, `Address`, `Owner`, `Executable`. They are checked before the body runs and recorded in the IDL |
194
- | **Data** | `u8`…`u128`, `i8`…`i128`, `boolean`, `Pubkey`, `Hash`, nested structs, `FixedArray`, `FixedString`, `Option`, unit enums, data enums (discriminated unions), `Vec` that resizes its account |
195
- | **Control flow** | `if`, `for`, `for … of`, `while`, `switch` (with narrowing on data enums), ternaries, `??`, destructuring, helper functions (returning structs too) across multiple files |
194
+ | **Data** | `u8`…`u128`, `i8`…`i128`, `boolean`, `Pubkey`, `Hash`, nested structs, `FixedArray`, `FixedString`, `Option`, unit enums, data enums (discriminated unions), `Vec` that resizes its account, `FixedVec` lists anywhere, and Anchor's own byte layout (`@layout borsh`) to take over existing Anchor accounts and to pass, return and emit structs with String / Vec / Option fields as Anchor does |
195
+ | **Control flow** | `if`, `for`, `for … of`, `while`, `switch` (with narrowing on data enums), ternaries, `??`, destructuring, helper functions (returning structs too, arrow functions, closures, function arguments, generics), list callbacks (`some`, `find`, `reduce`, …) across multiple files |
196
196
  | **CPIs** | System, SPL Token, Token-2022 (including extension reads and metadata), ATA, Metaplex Token Metadata, Core, Bubblegum. Generic `invoke` with PDA signing, return data, and typed bindings generated from any Anchor IDL (`typesol cpi`) |
197
197
  | **Events** | `emit` (log-based) and `emitCpi` (Anchor `emit_cpi!`, through a self-CPI) |
198
198
  | **Crypto & oracles** | `sha256`, `keccak256`, Merkle proofs, Ed25519 and secp256k1 signature checks, Pyth pull oracle, Switchboard |
@@ -306,7 +306,7 @@ Details are in [docs/testing.md](docs/testing.md).
306
306
 
307
307
  ## Status
308
308
 
309
- **TypeSOL is pre-1.0 (0.16).** Releases follow the organization's
309
+ **TypeSOL is pre-1.0 (0.18).** Releases follow the organization's
310
310
  [versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md); [CHANGELOG.md](CHANGELOG.md) lists every
311
311
  change.
312
312
  - The compiler, runtime and tooling have been through internal security reviews and are covered by the tests above.
package/dist/clientgen.js CHANGED
@@ -39,16 +39,19 @@ export const TYPESOL_ERRORS = {
39
39
  2015: "ConstraintTokenOwner",
40
40
  2016: "ConstraintMintMintAuthority",
41
41
  2017: "ConstraintMintFreezeAuthority",
42
+ 2018: "ConstraintMintDecimals",
42
43
  2040: "ConstraintDuplicateMutableAccount",
43
44
  3000: "AccountDiscriminatorAlreadySet",
44
45
  3002: "AccountDiscriminatorMismatch",
45
46
  3003: "AccountDidNotDeserialize",
47
+ 3004: "AccountDidNotSerialize",
46
48
  3005: "AccountNotEnoughKeys",
47
49
  3006: "AccountNotMutable",
48
50
  3007: "AccountOwnedByWrongProgram",
49
51
  3008: "InvalidProgramId",
50
52
  3009: "InvalidProgramExecutable",
51
53
  3010: "AccountNotSigner",
54
+ 3011: "AccountNotSystemOwned",
52
55
  3012: "AccountNotInitialized",
53
56
  3016: "AccountReallocExceedsLimit",
54
57
  4102: "InvalidNumericConversion",
@@ -383,6 +386,8 @@ export function generateClient(idl, opts = {}) {
383
386
  const arrayLen = (t) => (typeof t === "object" && t.array ? Number(t.array[1]) : NaN);
384
387
  if (/^FixedString\d+$/.test(n) && f[0].name === "len" && Number.isInteger(arrayLen(f[1].type)))
385
388
  return { kind: "fstring", cap: arrayLen(f[1].type) };
389
+ if (/^FixedVec\d+_/.test(n) && f[0].name === "len" && f[1].name === "items" && Number.isInteger(arrayLen(f[1].type)))
390
+ return { kind: "fvec", elem: f[1].type.array[0], cap: arrayLen(f[1].type) };
386
391
  if (n.startsWith("FixedOption_") && f[0].name === "is_some")
387
392
  return { kind: "foption", inner: f[1].type };
388
393
  if (n.startsWith("FixedEnum_") && f[0].name === "tag" && defs.has(n.slice("FixedEnum_".length)) && Number.isInteger(arrayLen(f[1].type)))
@@ -476,6 +481,8 @@ export function generateClient(idl, opts = {}) {
476
481
  const s = special(n);
477
482
  if (s?.kind === "fstring")
478
483
  return "string";
484
+ if (s?.kind === "fvec")
485
+ return `Array<${tsType(s.elem, dir)}>`;
479
486
  if (s?.kind === "foption")
480
487
  return `${tsType(s.inner, dir)} | null`;
481
488
  if (s?.kind === "fenum")
@@ -484,6 +491,7 @@ export function generateClient(idl, opts = {}) {
484
491
  }
485
492
  let needsFixedString = false;
486
493
  let needsFixedOption = false;
494
+ let needsFixedVec = false;
487
495
  let needsUnitEnum = false;
488
496
  function codec(t) {
489
497
  if (typeof t === "string") {
@@ -525,6 +533,11 @@ export function generateClient(idl, opts = {}) {
525
533
  const s = special(n);
526
534
  if (s?.kind === "fstring")
527
535
  return ((needsFixedString = true), `fixedString(${s.cap})`);
536
+ if (s?.kind === "fvec") {
537
+ if (idlSize(s.elem) === undefined)
538
+ throw new ClientIdlError(`${n}: the elements have no fixed size`);
539
+ return ((needsFixedVec = true), `fixedVec(${codec(s.elem)}, ${idlSize(s.elem)}, ${s.cap})`);
540
+ }
528
541
  if (s?.kind === "foption") {
529
542
  const size = idlSize(s.inner);
530
543
  if (size === undefined)
@@ -560,6 +573,10 @@ export function generateClient(idl, opts = {}) {
560
573
  const sp = special(n);
561
574
  if (sp?.kind === "fstring")
562
575
  return 4 + sp.cap;
576
+ if (sp?.kind === "fvec") {
577
+ const e = idlSize(sp.elem);
578
+ return e === undefined ? undefined : 4 + e * sp.cap;
579
+ }
563
580
  if (sp?.kind === "fenum")
564
581
  return sp.size;
565
582
  if (sp?.kind === "foption") {
@@ -982,6 +999,28 @@ export function generateClient(idl, opts = {}) {
982
999
  ` });`,
983
1000
  ],
984
1001
  });
1002
+ if (needsFixedVec)
1003
+ helperDefs.push({
1004
+ name: "fixedVec",
1005
+ lines: [
1006
+ `/** TypeSOL FixedVec<T, N> in account data: u32 length, then room for N elements (unused ones zero). */`,
1007
+ `const fixedVec = <From, To extends From>(item: Codec<From, To>, size: number, cap: number): Codec<From[], To[]> =>`,
1008
+ ` createCodec<From[], To[]>({`,
1009
+ ` fixedSize: 4 + size * cap,`,
1010
+ ` write: (value, bytes, offset) => {`,
1011
+ ` if (value.length > cap) throw new Error(value.length + " elements; the field holds " + cap);`,
1012
+ ` bytes.fill(0, offset, offset + 4 + size * cap);`,
1013
+ ` getU32Codec().write(value.length, bytes, offset);`,
1014
+ ` value.forEach((v, i) => item.write(v, bytes, offset + 4 + i * size));`,
1015
+ ` return offset + 4 + size * cap;`,
1016
+ ` },`,
1017
+ ` read: (bytes, offset) => {`,
1018
+ ` const n = Math.min(getU32Codec().read(bytes, offset)[0], cap);`,
1019
+ ` return [Array.from({ length: n }, (_, i) => item.read(bytes, offset + 4 + i * size)[0]), offset + 4 + size * cap];`,
1020
+ ` },`,
1021
+ ` });`,
1022
+ ],
1023
+ });
985
1024
  if (needsUnitEnum)
986
1025
  helperDefs.push({
987
1026
  name: "unitEnum",
@@ -1,6 +1,6 @@
1
1
  import ts from "typescript";
2
2
  import { Reporter } from "./diagnostics.js";
3
- import { type DataEnumDef, type EnumDef, type ProgramModel, type ScalarType, type StructDef, type ValueType, isScalar } from "./model.js";
3
+ import { type DataEnumDef, type EnumDef, type GenericHelper, type Helper, type ProgramModel, type ScalarType, type StructDef, type ValueType, isScalar } from "./model.js";
4
4
  export declare const LIB_PATH: string;
5
5
  /** Why a name cannot be used as a C member, or undefined when it can. */
6
6
  export declare function cNameProblem(name: string): string | undefined;
@@ -11,6 +11,8 @@ export declare const MAX_RETURN_DATA = 1024;
11
11
  export declare const MAX_CPI_ALLOCATION: number;
12
12
  export interface Analysis {
13
13
  model: ProgramModel;
14
+ /** A generic helper's instance for these type arguments (analyzed once, then kept in `model.helpers`). */
15
+ instantiate(g: GenericHelper, typeArgs: TypeArg[], fnArgs?: string[]): Helper;
14
16
  /** TypeScript's checker: used for narrowing (which variant a data-enum value is in a branch). */
15
17
  checker: ts.TypeChecker;
16
18
  sourceFile: ts.SourceFile;
@@ -20,9 +22,20 @@ export interface Analysis {
20
22
  imports: Map<string, string>;
21
23
  reporter: Reporter;
22
24
  }
25
+ /** What a generic helper's type parameter stands for: a scalar, or a fixed-size struct. */
26
+ export type TypeArg = ScalarType | {
27
+ kind: "struct";
28
+ name: string;
29
+ };
30
+ /** A struct named by a type reference, through the type parameters of a generic helper's instance. */
31
+ export declare function structNameOf(node: ts.TypeNode, scope: Pick<TypeScope, "structArgs">): string | undefined;
23
32
  /** What type resolution needs: imports, unit enums, and where to record Option<T> uses. */
24
33
  export interface TypeScope {
25
34
  imports: Map<string, string>;
35
+ /** Inside a generic helper's instance: type parameter -> the type it stands for. */
36
+ typeArgs?: Map<string, ScalarType>;
37
+ /** Inside a generic helper's instance: type parameter -> the struct it stands for. */
38
+ structArgs?: Map<string, string>;
26
39
  enums: Map<string, EnumDef>;
27
40
  dataEnums?: Map<string, DataEnumDef>;
28
41
  options: Map<string, ScalarType | {
@@ -49,4 +62,6 @@ export declare function resolveOptionType(node: ts.TypeNode, scope: TypeScope):
49
62
  }) | undefined;
50
63
  /** Values a local, argument or helper parameter can hold. */
51
64
  export declare function resolveValueType(node: ts.TypeNode, scope: TypeScope, allowVec?: boolean): ValueType | undefined;
65
+ /** The helper (or generic) a call's callee names: a local function by its declaration, others by name. */
66
+ export declare function calleeName(callee: ts.Identifier, decls: Map<ts.Node, string>, checker: ts.TypeChecker): string | undefined;
52
67
  export { isScalar };