@typesol/cli 0.16.0 → 0.17.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,52 @@ 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.17.0 — 2026-10-28
9
+
10
+ Anchor's account bytes on request (`@layout borsh`, so a TypeSOL program can take over an Anchor program's accounts
11
+ without a migration), lists with a fixed capacity anywhere in account data (`FixedVec`), arrow functions and generic
12
+ helpers, Metaplex collection verification, Core plugins and Bubblegum V2, and a faster UTF-8 check. Existing programs
13
+ keep their layouts and error codes (3004 is new, raised only by `@layout borsh` accounts). Not externally audited (see
14
+ SECURITY.md).
15
+
16
+ ### Added
17
+ - `/** @layout borsh */` on an account struct stores it with Anchor's bytes (Borsh String / Vec / Option for
18
+ `FixedString` / `FixedVec` / `Option`), so a TypeSOL program can take over an Anchor program's accounts without a
19
+ migration; its IDL lists Anchor's own types. An instruction unpacks the account into the fixed layout when it starts
20
+ and packs it back when it returns (3003 for bytes that are not a valid value, 3004 when the account is too small).
21
+ Checked byte for byte against an Anchor twin, including switching programs in the middle of a sequence.
22
+ `typesol port` marks Anchor account structs that need it.
23
+ - `FixedVec<T, N>`: a list with room for N elements (u32 length + N slots, like `FixedString<N>`) as any account field,
24
+ several per struct and in nested structs, with `get` / `set` / `push` / `pop` / `clear` / `.length` / `for … of`;
25
+ `push` on a full one fails with 5104, `fixedVec([…])` writes a whole one. The IDL describes each as a
26
+ `FixedVec<N>_<T>` struct `{ len, items }`; the generated client reads and writes them as arrays. The
27
+ basics/favorites port uses one.
28
+ - Bubblegum V2: `createTreeV2`, `mintV2` (optionally into a Metaplex Core collection), `transferV2` and `burnV2`, which
29
+ take the leaf (root, data and creator hashes, nonce, index) and pass its Merkle proof on from the remaining accounts;
30
+ the Core `BubblegumV2` collection plugin. Checked against Bubblegum, mpl-account-compression, mpl-noop and Core
31
+ (`pnpm fetch:programs` now fetches the mpl-account-compression pair too).
32
+ - Metaplex Core plugins: a literal plugin list in `createAsset` / `createCollection` (Royalties, freeze / burn / transfer /
33
+ update delegates and their permanent forms, Attributes, Edition, AddBlocker, ImmutableMetadata, each with an optional
34
+ authority), and `addPlugin`, `updatePlugin`, `removePlugin` with their collection forms, checked against the real Core
35
+ program (a frozen asset cannot move until it is thawed).
36
+ - Arrow functions: `const f = (x: u64): u64 => …` (and `const f = function (…) {…}`) at the top level or inside a
37
+ function is a helper. Local ones capture nothing (a closure over the enclosing function's variables is refused with
38
+ the name it reads); functions are called, not passed around.
39
+ - Generic helpers: `function max<T extends bigint>(a: T, b: T): T` compiles once per set of types it is called with,
40
+ inferred from the arguments or written out (`max<u128>(x, 1n)`); type parameters stand for integers, booleans,
41
+ Pubkeys or enums.
42
+ - `x as T` (and `satisfies`): names a type the value already has, never a conversion; mainly `return (a + b) as T`
43
+ in a generic helper.
44
+ - Metaplex collections: `createV3`'s `isCollection: true` (CollectionDetails::V1), `createMasterEditionV3(…, maxSupply | null)`,
45
+ `verifyCollection(…)` (Verify with CollectionV1) and `masterEditionAddress(mint)`. The tokens/nft-operations port now
46
+ uses them instead of hand-encoded `invoke`s, checked against the real Token Metadata program.
47
+
48
+ ### Changed
49
+ - IDLs no longer list `FixedString*` / `FixedOption_*` / `FixedVec*` helper types that nothing in them refers to
50
+ (for example an `Option` used only as an instruction argument, which is Borsh there).
51
+ - UTF-8 checks of strings run eight ASCII bytes at a time: every official-example instruction with a string argument
52
+ gets cheaper (create_token_mint 40,919 → 39,828 CU, set_favorites 4,282 → 4,195).
53
+
8
54
  ## 0.16.0 — 2026-10-28
9
55
 
10
56
  Everyday TypeScript that Anchor developers reach for (`for … of`, `??`, destructuring, `**`, structs returned from
package/README.md CHANGED
@@ -191,8 +191,8 @@ Runtime failures point back to your source: `Error 6000 Unauthorized (counter.ts
191
191
  | --- | --- |
192
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 |
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 |
195
+ | **Control flow** | `if`, `for`, `for … of`, `while`, `switch` (with narrowing on data enums), ternaries, `??`, destructuring, helper functions (returning structs too, arrow functions, generics) 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.17).** 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
@@ -43,6 +43,7 @@ export const TYPESOL_ERRORS = {
43
43
  3000: "AccountDiscriminatorAlreadySet",
44
44
  3002: "AccountDiscriminatorMismatch",
45
45
  3003: "AccountDidNotDeserialize",
46
+ 3004: "AccountDidNotSerialize",
46
47
  3005: "AccountNotEnoughKeys",
47
48
  3006: "AccountNotMutable",
48
49
  3007: "AccountOwnedByWrongProgram",
@@ -383,6 +384,8 @@ export function generateClient(idl, opts = {}) {
383
384
  const arrayLen = (t) => (typeof t === "object" && t.array ? Number(t.array[1]) : NaN);
384
385
  if (/^FixedString\d+$/.test(n) && f[0].name === "len" && Number.isInteger(arrayLen(f[1].type)))
385
386
  return { kind: "fstring", cap: arrayLen(f[1].type) };
387
+ if (/^FixedVec\d+_/.test(n) && f[0].name === "len" && f[1].name === "items" && Number.isInteger(arrayLen(f[1].type)))
388
+ return { kind: "fvec", elem: f[1].type.array[0], cap: arrayLen(f[1].type) };
386
389
  if (n.startsWith("FixedOption_") && f[0].name === "is_some")
387
390
  return { kind: "foption", inner: f[1].type };
388
391
  if (n.startsWith("FixedEnum_") && f[0].name === "tag" && defs.has(n.slice("FixedEnum_".length)) && Number.isInteger(arrayLen(f[1].type)))
@@ -476,6 +479,8 @@ export function generateClient(idl, opts = {}) {
476
479
  const s = special(n);
477
480
  if (s?.kind === "fstring")
478
481
  return "string";
482
+ if (s?.kind === "fvec")
483
+ return `Array<${tsType(s.elem, dir)}>`;
479
484
  if (s?.kind === "foption")
480
485
  return `${tsType(s.inner, dir)} | null`;
481
486
  if (s?.kind === "fenum")
@@ -484,6 +489,7 @@ export function generateClient(idl, opts = {}) {
484
489
  }
485
490
  let needsFixedString = false;
486
491
  let needsFixedOption = false;
492
+ let needsFixedVec = false;
487
493
  let needsUnitEnum = false;
488
494
  function codec(t) {
489
495
  if (typeof t === "string") {
@@ -525,6 +531,11 @@ export function generateClient(idl, opts = {}) {
525
531
  const s = special(n);
526
532
  if (s?.kind === "fstring")
527
533
  return ((needsFixedString = true), `fixedString(${s.cap})`);
534
+ if (s?.kind === "fvec") {
535
+ if (idlSize(s.elem) === undefined)
536
+ throw new ClientIdlError(`${n}: the elements have no fixed size`);
537
+ return ((needsFixedVec = true), `fixedVec(${codec(s.elem)}, ${idlSize(s.elem)}, ${s.cap})`);
538
+ }
528
539
  if (s?.kind === "foption") {
529
540
  const size = idlSize(s.inner);
530
541
  if (size === undefined)
@@ -560,6 +571,10 @@ export function generateClient(idl, opts = {}) {
560
571
  const sp = special(n);
561
572
  if (sp?.kind === "fstring")
562
573
  return 4 + sp.cap;
574
+ if (sp?.kind === "fvec") {
575
+ const e = idlSize(sp.elem);
576
+ return e === undefined ? undefined : 4 + e * sp.cap;
577
+ }
563
578
  if (sp?.kind === "fenum")
564
579
  return sp.size;
565
580
  if (sp?.kind === "foption") {
@@ -982,6 +997,28 @@ export function generateClient(idl, opts = {}) {
982
997
  ` });`,
983
998
  ],
984
999
  });
1000
+ if (needsFixedVec)
1001
+ helperDefs.push({
1002
+ name: "fixedVec",
1003
+ lines: [
1004
+ `/** TypeSOL FixedVec<T, N> in account data: u32 length, then room for N elements (unused ones zero). */`,
1005
+ `const fixedVec = <From, To extends From>(item: Codec<From, To>, size: number, cap: number): Codec<From[], To[]> =>`,
1006
+ ` createCodec<From[], To[]>({`,
1007
+ ` fixedSize: 4 + size * cap,`,
1008
+ ` write: (value, bytes, offset) => {`,
1009
+ ` if (value.length > cap) throw new Error(value.length + " elements; the field holds " + cap);`,
1010
+ ` bytes.fill(0, offset, offset + 4 + size * cap);`,
1011
+ ` getU32Codec().write(value.length, bytes, offset);`,
1012
+ ` value.forEach((v, i) => item.write(v, bytes, offset + 4 + i * size));`,
1013
+ ` return offset + 4 + size * cap;`,
1014
+ ` },`,
1015
+ ` read: (bytes, offset) => {`,
1016
+ ` const n = Math.min(getU32Codec().read(bytes, offset)[0], cap);`,
1017
+ ` return [Array.from({ length: n }, (_, i) => item.read(bytes, offset + 4 + i * size)[0]), offset + 4 + size * cap];`,
1018
+ ` },`,
1019
+ ` });`,
1020
+ ],
1021
+ });
985
1022
  if (needsUnitEnum)
986
1023
  helperDefs.push({
987
1024
  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: ScalarType[]): 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;
@@ -23,6 +25,8 @@ export interface Analysis {
23
25
  /** What type resolution needs: imports, unit enums, and where to record Option<T> uses. */
24
26
  export interface TypeScope {
25
27
  imports: Map<string, string>;
28
+ /** Inside a generic helper's instance: type parameter -> the type it stands for. */
29
+ typeArgs?: Map<string, ScalarType>;
26
30
  enums: Map<string, EnumDef>;
27
31
  dataEnums?: Map<string, DataEnumDef>;
28
32
  options: Map<string, ScalarType | {
@@ -49,4 +53,6 @@ export declare function resolveOptionType(node: ts.TypeNode, scope: TypeScope):
49
53
  }) | undefined;
50
54
  /** Values a local, argument or helper parameter can hold. */
51
55
  export declare function resolveValueType(node: ts.TypeNode, scope: TypeScope, allowVec?: boolean): ValueType | undefined;
56
+ /** The helper (or generic) a call's callee names: a local function by its declaration, others by name. */
57
+ export declare function calleeName(callee: ts.Identifier, decls: Map<ts.Node, string>, checker: ts.TypeChecker): string | undefined;
52
58
  export { isScalar };