@typesol/cli 0.15.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +11 -7
  3. package/dist/cli.js +147 -20
  4. package/dist/clientgen.js +37 -0
  5. package/dist/compiler/analyze.d.ts +11 -1
  6. package/dist/compiler/analyze.js +307 -37
  7. package/dist/compiler/emit/base.d.ts +7 -0
  8. package/dist/compiler/emit/base.js +62 -2
  9. package/dist/compiler/emit/borsh.d.ts +13 -0
  10. package/dist/compiler/emit/borsh.js +232 -0
  11. package/dist/compiler/emit/calls.js +50 -15
  12. package/dist/compiler/emit/cpi.d.ts +32 -8
  13. package/dist/compiler/emit/cpi.js +706 -25
  14. package/dist/compiler/emit/expressions.d.ts +11 -0
  15. package/dist/compiler/emit/expressions.js +126 -4
  16. package/dist/compiler/emit/function.js +45 -12
  17. package/dist/compiler/emit/program.js +24 -6
  18. package/dist/compiler/emit/shared.d.ts +12 -0
  19. package/dist/compiler/emit/shared.js +22 -4
  20. package/dist/compiler/emit/statements.d.ts +13 -0
  21. package/dist/compiler/emit/statements.js +141 -7
  22. package/dist/compiler/emit/storage.d.ts +6 -1
  23. package/dist/compiler/emit/storage.js +82 -17
  24. package/dist/compiler/idl.js +61 -18
  25. package/dist/compiler/model.d.ts +56 -4
  26. package/dist/compiler/model.js +8 -2
  27. package/dist/compiler/toolchain.d.ts +2 -0
  28. package/dist/compiler/toolchain.js +27 -1
  29. package/dist/onchain.d.ts +30 -0
  30. package/dist/onchain.js +53 -0
  31. package/dist/portgen.d.ts +87 -0
  32. package/dist/portgen.js +367 -0
  33. package/dist/templates.js +49 -154
  34. package/dist/testing.d.ts +66 -0
  35. package/dist/testing.js +337 -0
  36. package/dist/workspace.js +11 -1
  37. package/docs/architecture.md +3 -0
  38. package/docs/cli.md +16 -2
  39. package/docs/errors.md +3 -2
  40. package/docs/language.md +19 -14
  41. package/docs/mainnet.md +5 -4
  42. package/docs/migrate-from-anchor.md +113 -0
  43. package/docs/test-your-program.md +52 -0
  44. package/docs/testing.md +1 -1
  45. package/docs/troubleshooting.md +7 -0
  46. package/lib/typesol.d.ts +325 -13
  47. package/package.json +19 -1
  48. package/runtime/typesol_rt.h +54 -9
  49. package/ts-plugin/index.js +112 -0
  50. package/ts-plugin/package.json +4 -0
@@ -0,0 +1,113 @@
1
+ # Migrate from Anchor
2
+
3
+ TypeSOL programs speak Anchor's wire format: the same instruction and account discriminators, Borsh arguments, an
4
+ Anchor-format IDL, the same error numbering and event logs. A ported program can keep its clients. This page maps
5
+ Anchor's constructs to TypeSOL's and shows the porting workflow.
6
+
7
+ ## Start from the IDL
8
+
9
+ ```sh
10
+ anchor build # in the Anchor project: target/idl/my_program.json
11
+ npx @typesol/cli init my-program && cd my-program
12
+ rm programs/my_program.ts tests/my_program.test.ts # the counter template's program and test
13
+ npx typesol port ../anchor/target/idl/my_program.json --out programs/my_program.ts
14
+ ```
15
+
16
+ `typesol port` writes the account structs, enums, errors and one function per instruction with its accounts and
17
+ arguments, each body failing with `NotPorted` until you write it. It keeps instruction discriminators, account names
18
+ and error codes. Its notes (on stderr) list what an existing Anchor client would see differently; see
19
+ [Account data](#account-data) below. Then, for each instruction:
20
+
21
+ 1. Give each account its real type: `Account<T>` / `Mut<T>` for your structs, `Init<T>` for one the instruction creates,
22
+ token types for token accounts (the table below).
23
+ 2. Move the constraints from `#[account(...)]` into the parameter types or the body.
24
+ 3. Port the body. Integers are `bigint`s with `n` literals, and every `+ - * / %` is checked.
25
+ 4. Test with `typesol/testing` ([test your program](test-your-program.md)), then compare the IDLs.
26
+
27
+ ## Accounts and constraints
28
+
29
+ | Anchor | TypeSOL |
30
+ | --- | --- |
31
+ | `#[derive(Accounts)] struct Ctx` + `ctx.accounts.x` | the instruction's parameters, in the same order |
32
+ | `Signer<'info>` / `#[account(mut)] Signer` | `Signer` / `Mut<Signer>` |
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) |
35
+ | `init_if_needed` | `InitIfNeeded<T>`, then `const created = acct.createIfNeeded(p, seeds)` |
36
+ | `seeds = [b"vault", owner.key().as_ref()], bump = vault.bump` | `Seeds<Mut<Vault>, ["vault", "@owner"], "bump">` |
37
+ | `seeds = [b"offer", offer.maker.as_ref(), offer.id.to_le_bytes().as_ref()]` | `Seeds<…, ["offer", "@offer.maker", "@offer.id"], "bump">` |
38
+ | `has_one = owner` | `HasOne<Mut<T>, "owner">` |
39
+ | `close = destination` | `CloseTo<Mut<T>, "destination">`, or `close(acct, destination)` in the body |
40
+ | `address = ADMIN` | `Address<AccountInfo, "Admin1111…">` |
41
+ | `owner = program`, `executable` | `Owner<A, "…">`, `Executable<A>` |
42
+ | `constraint = expr @ MyError::X` | `require(expr, MyError.X)` at the start of the body |
43
+ | `realloc = n, realloc::payer = p` | `realloc(acct, n, p)` |
44
+ | `Option<Account<'info, T>>` | `Mut<T> \| null` (the client passes the program id to leave it out) |
45
+ | `UncheckedAccount` / `AccountInfo` | `AccountInfo` / `Mut<AccountInfo>`: check what you rely on yourself |
46
+ | `AccountLoader<T>` with `#[account(zero)]` | `Zeroed<T>` (TypeSOL reads every account in place: no copy to avoid) |
47
+ | `Program<'info, System>` | `SystemProgram` |
48
+ | `Program<'info, Token>` / `Token2022` / `Interface<'info, TokenInterface>` | `TokenProgram` / `Token2022Program` / `TokenInterface` |
49
+ | `Account<'info, TokenAccount>` / `Account<'info, Mint>` (`InterfaceAccount`) | `TokenAccount` / `Mint` (`Mut<…>` when written) |
50
+ | `token::mint = m, token::authority = a` | `HasOne<TokenAccount, "mint" \| "owner">` against parameters with those names |
51
+ | `associated_token::mint = m, associated_token::authority = a` | `Ata<TokenAccount, "a", "m">`; `Ata<Init<TokenAccount>, "a", "m">` creates it |
52
+ | `ctx.remaining_accounts` | `remainingAccounts()` |
53
+ | `ctx.bumps.vault` | the value `create` returns, or `acct.pdaBump` |
54
+
55
+ ## Account data
56
+
57
+ | Anchor | TypeSOL |
58
+ | --- | --- |
59
+ | `#[account] pub struct T { … }` | `export interface T { … }` |
60
+ | `u8` … `u128`, `i8` … `i128`, `bool`, `Pubkey` | `u8` … `u128`, `i8` … `i128`, `boolean`, `Pubkey` |
61
+ | `[T; N]` | `FixedArray<T, N>` |
62
+ | `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)`), or `FixedVec<T, n>` anywhere: a u32 length and room for n, always 4 + n × size bytes |
64
+ | `Option<T>` | `Option<T>`: fixed size (1 + size of T) |
65
+ | `#[derive(AnchorSerialize…)] enum` | `export enum` (unit variants) or a data enum `type A = { kind: "X"; … } \| …` |
66
+
67
+ TypeSOL keeps account data at a fixed layout by default, so three Anchor layouts differ: a Borsh `String` is
68
+ variable-length (TypeSOL uses `FixedString<N>`), a Borsh `Option` takes 1 byte when empty (TypeSOL always reserves the
69
+ value), and a `Vec` can be anywhere in an Anchor struct (TypeSOL keeps a growable one last; a `FixedVec<T, N>` elsewhere
70
+ always takes its full capacity, where Anchor packs the elements and moves the fields after them).
71
+
72
+ To keep an Anchor program's existing accounts, mark the struct `/** @layout borsh */`: it is then stored exactly as
73
+ Anchor stores it (`FixedString<N>` / `FixedVec<T, N>` / `Option<T>` become Borsh String / Vec / Option, N being the
74
+ `#[max_len]`), its IDL type is Anchor's, and the upgraded program reads and writes the accounts in place. `typesol port`
75
+ adds it for you. It costs some compute (the account is unpacked and packed in every instruction that uses it) but stays
76
+ several times cheaper than Anchor. Accounts using them need a migration (or a new program) rather
77
+ 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.
79
+
80
+ ## Code
81
+
82
+ | Anchor | TypeSOL |
83
+ | --- | --- |
84
+ | `pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()>` | `export function deposit(owner: Mut<Signer>, vault: Mut<Vault>, …, amount: u64): void` |
85
+ | `-> Result<u64>` / a struct | `: u64` / a struct: sent as return data, listed in the IDL |
86
+ | `#[error_code] enum MyError { #[msg("…")] X }` | `export enum MyError { X = "…" }` (codes from 6000, in order) |
87
+ | `require!(c, MyError::X)`, `err!(MyError::X)` | `require(c, MyError.X)`, `require(false, MyError.X)` |
88
+ | `require_keys_eq!(a, b)` | `require(a === b, …)` |
89
+ | `a.checked_add(b).ok_or(…)?` | `a + b` (every operation is checked; overflow aborts with 5100) |
90
+ | `emit!(Ev { … })` / `emit_cpi!` | `emit<Ev>({ … })` / `emitCpi<Ev>({ … }, eventAuthority, self)` |
91
+ | `msg!("…")` | `log("…")` |
92
+ | `Clock::get()?.unix_timestamp`, `.slot` | `now()`, `currentSlot()` |
93
+ | `Rent::get()?.minimum_balance(n)` | `rentExempt(n)`, `minimumBalance(acct)` |
94
+ | `system_program::transfer(CpiContext::new(…), lamports)` | `systemProgram.transfer(from, to, lamports)` |
95
+ | `token::transfer_checked(CpiContext::new_with_signer(…, seeds), amount, decimals)` | `tokenProgram.transferChecked(from, mint, to, authority, amount, ["seed", key, bump])` |
96
+ | `invoke_signed(&ix, &accounts, &[seeds])` | `invoke(program, [writable(a), readonly(b)], [data…], seeds)` |
97
+ | `declare_program!(other)` / CPI to another Anchor program | `typesol cpi other.json` generates typed helpers |
98
+ | `for x in v.iter()` | `for (const x of v)` |
99
+ | `x.unwrap_or(d)` | `x ?? d` |
100
+ | `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
+ | `let fee = \|x: u64\| x / 100;` (no captures) | `const fee = (x: u64): u64 => x / 100n;` |
102
+
103
+ ## Tooling
104
+
105
+ | Anchor | TypeSOL |
106
+ | --- | --- |
107
+ | `anchor init` | `typesol init` (`--template vault` / `escrow`) |
108
+ | `anchor build` | `typesol build` (writes the IDL and a TypeScript client next to the binary) |
109
+ | `anchor test` (mocha, bankrun) | `npm test` with `typesol/testing` in LiteSVM |
110
+ | `anchor deploy` | `typesol deploy` ([deploy to mainnet](mainnet.md)) |
111
+ | `anchor idl init` / `upgrade` | `typesol idl publish` |
112
+ | `anchor verify` | `typesol verify` (reads the deployed program and IDL from the cluster) |
113
+ | `@coral-xyz/anchor` `Program` client | the generated `<name>.client.ts` on `@solana/kit`, or Anchor's client with the IDL |
@@ -0,0 +1,52 @@
1
+ # Test your program
2
+
3
+ `typesol/testing` runs your program in LiteSVM, an in-process Solana runtime: no validator, no SOL, a few
4
+ milliseconds per transaction. Projects from `typesol init` already use it in `tests/<name>.test.ts`.
5
+
6
+ ```ts
7
+ import { testProgram, type TestProgram } from "typesol/testing";
8
+ import { beforeAll, expect, it } from "vitest";
9
+
10
+ let t: TestProgram;
11
+ beforeAll(async () => {
12
+ t = await testProgram("counter", import.meta.url); // builds the program of typesol.json and loads it
13
+ });
14
+
15
+ it("counts", async () => {
16
+ const alice = await t.wallet(); // a funded keypair (10 SOL)
17
+ const counter = await t.pda("counter", alice); // seeds: text, addresses or keypairs, bytes, bigints (u64)
18
+ t.ok(await t.send(alice, t.ix("initialize", {}, { authority: alice, counter })));
19
+ t.ok(await t.send(alice, t.ix("increment", { by: 5n }, { authority: alice, counter })));
20
+ expect((await t.account<{ count: bigint }>("Counter", counter))?.count).toBe(5n);
21
+ });
22
+ ```
23
+
24
+ ## Instructions
25
+
26
+ `t.ix(name, args, accounts, remaining?)` builds one instruction.
27
+
28
+ - **Accounts by name**, in camelCase or snake_case. Their signer and writable flags come from the IDL, and accounts
29
+ with a fixed address (the System Program, token programs, sysvars) are filled in, as are PDAs whose seeds are
30
+ constants, arguments or other accounts' keys (`t.ix("initialize", {}, { authority: alice })` derives `counter`). Pass a keypair for an account that
31
+ signs, an address otherwise, and `null` to leave an optional account out.
32
+ - **Arguments as plain values:** bigints or numbers for integers, address strings for Pubkeys, `null` for an empty
33
+ Option, objects for structs (camelCase or snake_case fields), arrays for Vecs.
34
+ - **Remaining accounts:** `[{ address, writable?, signer? }]`.
35
+
36
+ `t.send(payer, ...ixs)` sends them in one transaction; keypairs given as accounts sign too.
37
+
38
+ ## Results
39
+
40
+ | Helper | Gives |
41
+ | --- | --- |
42
+ | `t.ok(res)` | the successful result, or throws with the program's logs |
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. 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
+ | `t.returned(instruction, res)` | the instruction's return data: an integer, boolean, Pubkey or struct |
46
+ | `t.balance(address)` | lamports |
47
+
48
+ `t.svm`, `t.programId`, `t.idl` and `t.coder` are there for anything else, e.g. writing a mint straight into the VM
49
+ with `t.svm.setAccount` (see the escrow template). `programHarness(svm, programId, idl)` gives the same helpers for a
50
+ program you loaded yourself.
51
+
52
+ The module needs `litesvm` and `@anchor-lang/core`, which `typesol init` adds to `devDependencies`.
package/docs/testing.md CHANGED
@@ -5,7 +5,7 @@ run prints its seed; reproduce with `FUZZ_SEED=<n>`, widen with `FUZZ_RUNS` / `F
5
5
 
6
6
  | Check | Oracle | What must hold |
7
7
  | --- | --- | --- |
8
- | `test/fuzz-arith.test.ts` | BigInt | `+ - * / %`, `& \| ^ << >> ~`, unary minus, comparisons and casts at every width u8…u128 / i8…i128, plus `mulDiv`, `mulDivCeil`, `isqrt`, `min`, `max`, return the same value or abort with the same code (5100/5101/4102), for edge and random inputs |
8
+ | `test/fuzz-arith.test.ts` | BigInt | `+ - * / % **`, `& \| ^ << >> ~`, unary minus, comparisons and casts at every width u8…u128 / i8…i128, plus `mulDiv`, `mulDivCeil`, `isqrt`, `min`, `max`, return the same value or abort with the same code (5100/5101/4102), for edge and random inputs |
9
9
  | `test/fuzz-state.test.ts` | TypeScript state model | random honest + hostile actions on vault and escrow: every outcome as predicted, balances equal the model, SOL conserved up to fees, token supply exactly conserved; random filings/decisions/resets on ledgerbook: Vec contents, account size and rent exemption equal the model |
10
10
  | `test/differential.test.ts` | Anchor twins in `bench/anchor` | identical transactions to TypeSOL and Anchor programs: same outcomes, same account contents, same wallet and token balances after every step |
11
11
  | `test/ports-*.test.ts` | the Anchor programs of solana-developers/program-examples and their tests | TypeSOL rewrites under `test/ports/` run the originals' flows and failure paths with the same outcomes, error codes and balances; each BLOCKED probe fails once its construct starts compiling, meaning that program can then be ported ([test/ports/README.md](../test/ports/README.md)) |
@@ -26,3 +26,10 @@ or declare `const HEAP_SIZE_KIB = 64n` and add a ComputeBudget `requestHeapFrame
26
26
  **`typesol idl publish` fails on a local validator with "invalid instruction data"** — the System Program of
27
27
  `solana-test-validator` (Agave 3.1 included) rejects a System instruction that the current Program Metadata program
28
28
  uses. Publishing works on mainnet-beta. The error shows the failing transaction's last log lines.
29
+
30
+ **"instruction X needs N bytes of stack; SBF gives each function 4096"** — every function on Solana gets a 4 KiB
31
+ stack frame, and going past it corrupts memory, so the build refuses it. Large structs and arrays copied into typed
32
+ locals (`const q: Big = acct.big`) are the usual cause: bind them without a type (`const q = acct.big`, a reference
33
+ into the account data) or move the work into helpers. Helpers are often inlined into their instruction, so their
34
+ locals count toward its frame.
35
+
package/lib/typesol.d.ts CHANGED
@@ -36,6 +36,8 @@ export interface FixedArray<T, N extends number> {
36
36
  get(index: bigint): T;
37
37
  set(index: bigint, value: T): void;
38
38
  readonly __length?: N;
39
+ /** `for (const x of arr)`: each element in order, as `arr.get(i)` gives it. */
40
+ [Symbol.iterator](): Iterator<T>;
39
41
  }
40
42
 
41
43
  /** Create a local fixed-length array filled with `fill`: `const xs = fixedArray<u64, 4>(0n)`. */
@@ -71,8 +73,28 @@ export interface Vec<T> {
71
73
  pop(): T;
72
74
  /** Remove every element, shrinking the account. */
73
75
  clear(): void;
76
+ /** `for (const x of vec)`: each element in order, as `vec.get(i)` gives it; the length is read again every step. */
77
+ [Symbol.iterator](): Iterator<T>;
74
78
  }
75
79
 
80
+ /**
81
+ * A list stored inline with room for N elements: a u32 length, then N slots (Anchor `Vec<T>` with `#[max_len(N)]`, but
82
+ * always 4 + N × size(T) bytes). Unlike `Vec<T>` it can be any field, any number of times, and in nested structs;
83
+ * `push` fails (5104) when it is full. Write a whole one with `fixedVec([a, b])`.
84
+ */
85
+ export interface FixedVec<T, N extends number> {
86
+ readonly length: u64;
87
+ get(index: bigint): T;
88
+ set(index: bigint, value: T): void;
89
+ push(value: T): void;
90
+ pop(): T;
91
+ clear(): void;
92
+ [Symbol.iterator](): Iterator<T>;
93
+ readonly __capacity?: N;
94
+ }
95
+ /** A FixedVec value holding these elements (`acct.owners = fixedVec([a, b])`, `{ owners: fixedVec([]) }`). */
96
+ export declare function fixedVec<T>(items: T[]): FixedVec<T, never>;
97
+
76
98
  /** One extra account passed after the declared ones. Nothing about it is checked until you check it. */
77
99
  export interface RemainingAccount {
78
100
  readonly key: Pubkey;
@@ -204,6 +226,13 @@ export interface AssociatedTokenProgram extends AccountMeta {
204
226
  /** The associated token account address of `owner` for `mint` under `tokenProgram`. */
205
227
  export declare function associatedTokenAddress(owner: Pubkey, mint: Pubkey, tokenProgram: Pubkey): Pubkey;
206
228
 
229
+ /** A creator in Metaplex metadata. `verified` can only be true for the update authority when it signs. */
230
+ export interface MetadataCreator {
231
+ address: Pubkey;
232
+ verified: boolean;
233
+ share: u8;
234
+ }
235
+
207
236
  /** Metaplex Token Metadata (metaqbxx…). Its address is checked. */
208
237
  export interface TokenMetadataProgram extends AccountMeta {
209
238
  readonly __mpl_metadata: void;
@@ -222,7 +251,58 @@ export interface TokenMetadataProgram extends AccountMeta {
222
251
  isMutable: boolean,
223
252
  signerSeeds?: Seed[] | SignerSeeds,
224
253
  ): void;
254
+ /**
255
+ * With creators (1–5, shares adding up to 100), an unverified collection mint, and/or `isCollection: true`
256
+ * (CollectionDetails::V1: this NFT is a sized collection other NFTs are verified into).
257
+ */
258
+ createV3(
259
+ metadata: AccountMeta,
260
+ mint: AccountMeta,
261
+ mintAuthority: AccountMeta,
262
+ payer: Mut<Signer>,
263
+ updateAuthority: AccountMeta,
264
+ systemProgram: SystemProgram,
265
+ name: string,
266
+ symbol: string,
267
+ uri: string,
268
+ sellerFeeBasisPoints: u16,
269
+ isMutable: boolean,
270
+ extras: { creators?: MetadataCreator[]; collection?: Pubkey; isCollection?: boolean },
271
+ signerSeeds?: Seed[] | SignerSeeds,
272
+ ): void;
273
+ /**
274
+ * CreateMasterEditionV3: makes `mint` (supply 1, decimals 0) a master edition; the mint and freeze authorities move
275
+ * to the edition. `maxSupply` 0n is a one of one, `null` allows unlimited prints.
276
+ */
277
+ createMasterEditionV3(
278
+ edition: AccountMeta,
279
+ mint: AccountMeta,
280
+ updateAuthority: AccountMeta,
281
+ mintAuthority: AccountMeta,
282
+ payer: Mut<Signer>,
283
+ metadata: AccountMeta,
284
+ tokenProgram: TokenProgram,
285
+ systemProgram: SystemProgram,
286
+ maxSupply: u64 | null,
287
+ signerSeeds?: Seed[] | SignerSeeds,
288
+ ): void;
289
+ /**
290
+ * Verify with CollectionV1: marks `metadata`'s collection (set with createV3's `collection`) verified. `authority` is
291
+ * the collection's update authority; the collection is a master edition created with `isCollection: true`.
292
+ */
293
+ verifyCollection(
294
+ authority: AccountMeta,
295
+ metadata: AccountMeta,
296
+ collectionMint: AccountMeta,
297
+ collectionMetadata: AccountMeta,
298
+ collectionMasterEdition: AccountMeta,
299
+ systemProgram: SystemProgram,
300
+ instructionsSysvar: AccountMeta,
301
+ signerSeeds?: Seed[] | SignerSeeds,
302
+ ): void;
225
303
  }
304
+ /** The Metaplex master edition address of a mint (PDA ["metadata", program, mint, "edition"]). */
305
+ export declare function masterEditionAddress(mint: Pubkey): Pubkey;
226
306
  /** The canonical program address of `seeds` under `program` (Rust `Pubkey::find_program_address(seeds, program).0`). */
227
307
  export declare function findProgramAddress(seeds: Seed[], program: Pubkey): Pubkey;
228
308
  /** The Metaplex metadata account address of a mint. */
@@ -446,9 +526,36 @@ export declare function emitCpi<T>(event: T, eventAuthority: EventAuthority, sel
446
526
  * account out. Accounts that sign (the new asset/collection, authorities) are keypair Signers, or PDAs of this
447
527
  * program covered by `signerSeeds`.
448
528
  */
529
+ /**
530
+ * Who manages a Metaplex Core plugin: `"owner"`, `"updateAuthority"`, `"none"` (nobody: fixed), or an address. Left out,
531
+ * the plugin type's default (the owner for owner-managed plugins, the update authority for the others).
532
+ */
533
+ export type CorePluginAuthority = "none" | "owner" | "updateAuthority" | Pubkey;
534
+ /** A Metaplex Core plugin, written as an object literal (Core's Plugin enum, with its authority when it is added). */
535
+ export type CorePlugin = (
536
+ | { kind: "Royalties"; basisPoints: u16; creators: { address: Pubkey; percentage: u8 }[] }
537
+ | { kind: "FreezeDelegate"; frozen: boolean }
538
+ | { kind: "BurnDelegate" }
539
+ | { kind: "TransferDelegate" }
540
+ | { kind: "UpdateDelegate"; additionalDelegates?: Pubkey[] }
541
+ | { kind: "PermanentFreezeDelegate"; frozen: boolean }
542
+ | { kind: "Attributes"; attributes: { key: string; value: string }[] }
543
+ | { kind: "PermanentTransferDelegate" }
544
+ | { kind: "PermanentBurnDelegate" }
545
+ | { kind: "Edition"; number: u32 }
546
+ | { kind: "AddBlocker" }
547
+ | { kind: "ImmutableMetadata" }
548
+ /** On a collection: lets Bubblegum mint compressed NFTs into it (mintV2 with a collection). */
549
+ | { kind: "BubblegumV2" }
550
+ ) & { authority?: CorePluginAuthority };
551
+ export type CorePluginKind = CorePlugin["kind"] | "MasterEdition" | "VerifiedCreators" | "Autograph";
552
+
449
553
  export interface MplCoreProgram extends AccountMeta {
450
554
  readonly __mpl_core: void;
451
- /** CreateV1. In a collection, `authority` is the collection's update authority; `owner` defaults to the payer. */
555
+ /**
556
+ * CreateV1. In a collection, `authority` is the collection's update authority; `owner` defaults to the payer.
557
+ * `plugins` (a literal list, one of each kind) are created with the asset.
558
+ */
452
559
  createAsset(
453
560
  asset: AccountMeta,
454
561
  collection: AccountMeta | null,
@@ -460,6 +567,18 @@ export interface MplCoreProgram extends AccountMeta {
460
567
  uri: string,
461
568
  signerSeeds?: Seed[] | SignerSeeds,
462
569
  ): void;
570
+ createAsset(
571
+ asset: AccountMeta,
572
+ collection: AccountMeta | null,
573
+ authority: AccountMeta | null,
574
+ payer: Mut<Signer>,
575
+ owner: AccountMeta | null,
576
+ systemProgram: SystemProgram,
577
+ name: string,
578
+ uri: string,
579
+ plugins: CorePlugin[],
580
+ signerSeeds?: Seed[] | SignerSeeds,
581
+ ): void;
463
582
  createCollection(
464
583
  collection: AccountMeta,
465
584
  updateAuthority: AccountMeta | null,
@@ -469,6 +588,70 @@ export interface MplCoreProgram extends AccountMeta {
469
588
  uri: string,
470
589
  signerSeeds?: Seed[] | SignerSeeds,
471
590
  ): void;
591
+ /** With plugins every asset of the collection inherits (Royalties, PermanentFreezeDelegate, …). */
592
+ createCollection(
593
+ collection: AccountMeta,
594
+ updateAuthority: AccountMeta | null,
595
+ payer: Mut<Signer>,
596
+ systemProgram: SystemProgram,
597
+ name: string,
598
+ uri: string,
599
+ plugins: CorePlugin[],
600
+ signerSeeds?: Seed[] | SignerSeeds,
601
+ ): void;
602
+ /** AddPluginV1, signed by `authority` (`null`: the payer): the owner for owner-managed plugins, else the update authority. */
603
+ addPlugin(
604
+ asset: AccountMeta,
605
+ collection: AccountMeta | null,
606
+ payer: Mut<Signer>,
607
+ authority: AccountMeta | null,
608
+ systemProgram: SystemProgram,
609
+ plugin: CorePlugin,
610
+ signerSeeds?: Seed[] | SignerSeeds,
611
+ ): void;
612
+ /** UpdatePluginV1: new values for a plugin the asset has (no `authority`), e.g. `{ kind: "FreezeDelegate", frozen: true }`. */
613
+ updatePlugin(
614
+ asset: AccountMeta,
615
+ collection: AccountMeta | null,
616
+ payer: Mut<Signer>,
617
+ authority: AccountMeta | null,
618
+ systemProgram: SystemProgram,
619
+ plugin: CorePlugin,
620
+ signerSeeds?: Seed[] | SignerSeeds,
621
+ ): void;
622
+ removePlugin(
623
+ asset: AccountMeta,
624
+ collection: AccountMeta | null,
625
+ payer: Mut<Signer>,
626
+ authority: AccountMeta | null,
627
+ systemProgram: SystemProgram,
628
+ kind: CorePluginKind,
629
+ signerSeeds?: Seed[] | SignerSeeds,
630
+ ): void;
631
+ addCollectionPlugin(
632
+ collection: AccountMeta,
633
+ payer: Mut<Signer>,
634
+ authority: AccountMeta | null,
635
+ systemProgram: SystemProgram,
636
+ plugin: CorePlugin,
637
+ signerSeeds?: Seed[] | SignerSeeds,
638
+ ): void;
639
+ updateCollectionPlugin(
640
+ collection: AccountMeta,
641
+ payer: Mut<Signer>,
642
+ authority: AccountMeta | null,
643
+ systemProgram: SystemProgram,
644
+ plugin: CorePlugin,
645
+ signerSeeds?: Seed[] | SignerSeeds,
646
+ ): void;
647
+ removeCollectionPlugin(
648
+ collection: AccountMeta,
649
+ payer: Mut<Signer>,
650
+ authority: AccountMeta | null,
651
+ systemProgram: SystemProgram,
652
+ kind: CorePluginKind,
653
+ signerSeeds?: Seed[] | SignerSeeds,
654
+ ): void;
472
655
  /** TransferV1, signed by the owner (or a delegate) as `authority` (`null`: the payer). */
473
656
  transferAsset(
474
657
  asset: AccountMeta,
@@ -521,6 +704,106 @@ export interface BubblegumProgram extends AccountMeta {
521
704
  sellerFeeBasisPoints: u16,
522
705
  signerSeeds?: Seed[] | SignerSeeds,
523
706
  ): void;
707
+ /**
708
+ * CreateTreeConfigV2: a tree on mpl-account-compression (mcmt6…, allocated by the client and owned by it) with the
709
+ * mpl-noop log wrapper (mnoop…). `treeCreator` `null`: the payer.
710
+ */
711
+ createTreeV2(
712
+ treeConfig: AccountMeta,
713
+ merkleTree: AccountMeta,
714
+ payer: Mut<Signer>,
715
+ treeCreator: AccountMeta | null,
716
+ logWrapper: AccountMeta,
717
+ compressionProgram: AccountMeta,
718
+ systemProgram: SystemProgram,
719
+ maxDepth: u32,
720
+ maxBufferSize: u32,
721
+ isPublic: boolean,
722
+ signerSeeds?: Seed[] | SignerSeeds,
723
+ ): void;
724
+ /** MintV2 of a leaf, optionally into a Metaplex Core collection (whose update authority signs as `authority`). */
725
+ mintV2(
726
+ treeConfig: AccountMeta,
727
+ payer: Mut<Signer>,
728
+ treeDelegate: AccountMeta | null,
729
+ leafOwner: AccountMeta,
730
+ leafDelegate: AccountMeta | null,
731
+ merkleTree: AccountMeta,
732
+ logWrapper: AccountMeta,
733
+ compressionProgram: AccountMeta,
734
+ mplCoreProgram: AccountMeta,
735
+ systemProgram: SystemProgram,
736
+ metadata: CompressedMetadata,
737
+ signerSeeds?: Seed[] | SignerSeeds,
738
+ ): void;
739
+ mintV2(
740
+ treeConfig: AccountMeta,
741
+ payer: Mut<Signer>,
742
+ treeDelegate: AccountMeta | null,
743
+ leafOwner: AccountMeta,
744
+ leafDelegate: AccountMeta | null,
745
+ merkleTree: AccountMeta,
746
+ logWrapper: AccountMeta,
747
+ compressionProgram: AccountMeta,
748
+ mplCoreProgram: AccountMeta,
749
+ systemProgram: SystemProgram,
750
+ metadata: CompressedMetadata,
751
+ collection: { collection: AccountMeta; authority: AccountMeta; cpiSigner: AccountMeta },
752
+ signerSeeds?: Seed[] | SignerSeeds,
753
+ ): void;
754
+ /** TransferV2, signed by the owner or delegate as `authority` (`null`: the payer); `proof` is the leaf's Merkle proof. */
755
+ transferV2(
756
+ treeConfig: AccountMeta,
757
+ payer: Mut<Signer>,
758
+ authority: AccountMeta | null,
759
+ leafOwner: AccountMeta,
760
+ leafDelegate: AccountMeta | null,
761
+ newLeafOwner: AccountMeta,
762
+ merkleTree: AccountMeta,
763
+ coreCollection: AccountMeta | null,
764
+ logWrapper: AccountMeta,
765
+ compressionProgram: AccountMeta,
766
+ systemProgram: SystemProgram,
767
+ leaf: CompressedLeaf,
768
+ proof: RemainingAccounts | RemainingSlice,
769
+ signerSeeds?: Seed[] | SignerSeeds,
770
+ ): void;
771
+ burnV2(
772
+ treeConfig: AccountMeta,
773
+ payer: Mut<Signer>,
774
+ authority: AccountMeta | null,
775
+ leafOwner: AccountMeta,
776
+ leafDelegate: AccountMeta | null,
777
+ merkleTree: AccountMeta,
778
+ coreCollection: AccountMeta | null,
779
+ mplCoreCpiSigner: AccountMeta | null,
780
+ logWrapper: AccountMeta,
781
+ compressionProgram: AccountMeta,
782
+ mplCoreProgram: AccountMeta,
783
+ systemProgram: SystemProgram,
784
+ leaf: CompressedLeaf,
785
+ proof: RemainingAccounts | RemainingSlice,
786
+ signerSeeds?: Seed[] | SignerSeeds,
787
+ ): void;
788
+ }
789
+ /** Bubblegum V2 leaf metadata (MetadataArgsV2): non-fungible, primary sale not happened, mutable unless `isMutable: false`. */
790
+ export interface CompressedMetadata {
791
+ name: string;
792
+ symbol: string;
793
+ uri: string;
794
+ sellerFeeBasisPoints: u16;
795
+ creators?: { address: Pubkey; verified: boolean; share: u8 }[];
796
+ isMutable?: boolean;
797
+ }
798
+ /** The leaf a transfer or burn replaces, as an indexer (DAS getAssetProof / getAsset) reports it. */
799
+ export interface CompressedLeaf {
800
+ root: Hash;
801
+ dataHash: Hash;
802
+ creatorHash: Hash;
803
+ assetDataHash?: Hash | null;
804
+ flags?: u8 | null;
805
+ nonce: u64;
806
+ index: u32;
524
807
  }
525
808
 
526
809
  /** The current slot and epoch (Clock sysvar). */
@@ -602,16 +885,6 @@ interface TokenOps {
602
885
  mintTo(mint: Mint, to: TokenAccount, authority: AccountMeta, amount: u64, signerSeeds?: Seed[] | SignerSeeds): void;
603
886
  burn(from: TokenAccount, mint: Mint, authority: AccountMeta, amount: u64, signerSeeds?: Seed[] | SignerSeeds): void;
604
887
  closeAccount(account: TokenAccount, destination: AccountMeta, authority: AccountMeta, signerSeeds?: Seed[] | SignerSeeds): void;
605
- /** Token-2022 only: write name/symbol/uri into a mint whose MetadataPointer points at itself (the mint needs lamports for the larger size). */
606
- initializeMetadata(
607
- mint: Mint,
608
- updateAuthority: AccountMeta,
609
- mintAuthority: AccountMeta,
610
- name: string,
611
- symbol: string,
612
- uri: string,
613
- signerSeeds?: Seed[] | SignerSeeds,
614
- ): void;
615
888
  /** Let `delegate` move up to `amount` from `source`. */
616
889
  approve(source: TokenAccount, delegate: AccountMeta, owner: AccountMeta, amount: u64, signerSeeds?: Seed[] | SignerSeeds): void;
617
890
  revoke(source: TokenAccount, owner: AccountMeta, signerSeeds?: Seed[] | SignerSeeds): void;
@@ -627,18 +900,57 @@ interface TokenOps {
627
900
  initializeAccount3(account: AccountMeta, mint: AccountMeta, owner: Pubkey): void;
628
901
  }
629
902
 
903
+ /** Operations only Token-2022 has: on Token2022Program and TokenInterface, not on TokenProgram. */
904
+ interface Token2022Ops {
905
+ /** Token-2022 only: write name/symbol/uri into a mint whose MetadataPointer points at itself (the mint needs lamports for the larger size). */
906
+ initializeMetadata(
907
+ mint: Mint,
908
+ updateAuthority: AccountMeta,
909
+ mintAuthority: AccountMeta,
910
+ name: string,
911
+ symbol: string,
912
+ uri: string,
913
+ signerSeeds?: Seed[] | SignerSeeds,
914
+ ): void;
915
+
916
+ // Token-2022 extensions, set up on a mint created with room for them, before initializeMint2 (or, for the token
917
+ // account ones, before initializeAccount3). Token-2022 only: refused at compile time on TokenProgram.
918
+ /** TransferFeeConfig: `basisPoints` (at most 10000) of each transfer, capped at `maximumFee`. */
919
+ initializeTransferFeeConfig(mint: AccountMeta, configAuthority: Pubkey | null, withdrawAuthority: Pubkey | null, basisPoints: u16, maximumFee: u64): void;
920
+ /** MintCloseAuthority: who may close the mint once its supply is 0. */
921
+ initializeMintCloseAuthority(mint: AccountMeta, closeAuthority: Pubkey | null): void;
922
+ /** NonTransferable: tokens of this mint cannot be transferred (soul-bound). */
923
+ initializeNonTransferableMint(mint: AccountMeta): void;
924
+ /** PermanentDelegate: `delegate` may transfer or burn from any account of this mint. */
925
+ initializePermanentDelegate(mint: AccountMeta, delegate: Pubkey): void;
926
+ /** DefaultAccountState: new token accounts start frozen (true) or initialized (false). */
927
+ initializeDefaultAccountState(mint: AccountMeta, frozen: boolean): void;
928
+ /** MetadataPointer: where the mint's metadata lives (the mint itself for initializeMetadata). */
929
+ initializeMetadataPointer(mint: AccountMeta, authority: Pubkey | null, metadataAddress: Pubkey | null): void;
930
+ /** GroupPointer: where the mint's token-group data lives. */
931
+ initializeGroupPointer(mint: AccountMeta, authority: Pubkey | null, groupAddress: Pubkey | null): void;
932
+ /** GroupMemberPointer: where the mint's group-member data lives. */
933
+ initializeGroupMemberPointer(mint: AccountMeta, authority: Pubkey | null, memberAddress: Pubkey | null): void;
934
+ /** TransferHook: `programId` is invoked on every transfer of this mint. */
935
+ initializeTransferHook(mint: AccountMeta, authority: Pubkey | null, programId: Pubkey | null): void;
936
+ /** InterestBearingConfig: `rate` in basis points per year (UI amounts only; balances do not change). */
937
+ initializeInterestBearingMint(mint: AccountMeta, rateAuthority: Pubkey | null, rate: i16): void;
938
+ /** ImmutableOwner on a token account (before initializeAccount3): its owner can never change. */
939
+ initializeImmutableOwner(account: AccountMeta): void;
940
+ }
941
+
630
942
  /** The SPL Token program (Tokenkeg). Its address is checked. */
631
943
  export interface TokenProgram extends AccountMeta, TokenOps {
632
944
  readonly __token: void;
633
945
  }
634
946
 
635
947
  /** The Token-2022 program (TokenzQdB...). Its address is checked. */
636
- export interface Token2022Program extends AccountMeta, TokenOps {
948
+ export interface Token2022Program extends AccountMeta, TokenOps, Token2022Ops {
637
949
  readonly __token2022: void;
638
950
  }
639
951
 
640
952
  /** Either token program. Token accounts and mints passed alongside must belong to the program given. */
641
- export interface TokenInterface extends AccountMeta, TokenOps {
953
+ export interface TokenInterface extends AccountMeta, TokenOps, Token2022Ops {
642
954
  readonly __token_interface: void;
643
955
  }
644
956
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typesol/cli",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Compile a TypeScript-syntax language directly to Solana SBF programs.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,6 +33,11 @@
33
33
  "types": "./dist/profile.d.ts",
34
34
  "default": "./dist/profile.js"
35
35
  },
36
+ "./testing": {
37
+ "types": "./dist/testing.d.ts",
38
+ "default": "./dist/testing.js"
39
+ },
40
+ "./ts-plugin": "./ts-plugin/index.js",
36
41
  "./idl": {
37
42
  "types": "./dist/idlpublish.d.ts",
38
43
  "default": "./dist/idlpublish.js"
@@ -43,6 +48,7 @@
43
48
  "dist",
44
49
  "lib",
45
50
  "runtime",
51
+ "ts-plugin",
46
52
  "assets",
47
53
  "!assets/readme",
48
54
  "LICENSE",
@@ -73,6 +79,18 @@
73
79
  "@solana/kit": "^8.5.0",
74
80
  "typescript": "6.0.3"
75
81
  },
82
+ "peerDependencies": {
83
+ "@anchor-lang/core": "^1.2.1",
84
+ "litesvm": "^1.6.1"
85
+ },
86
+ "peerDependenciesMeta": {
87
+ "@anchor-lang/core": {
88
+ "optional": true
89
+ },
90
+ "litesvm": {
91
+ "optional": true
92
+ }
93
+ },
76
94
  "devDependencies": {
77
95
  "@anchor-lang/core": "1.2.1",
78
96
  "@noble/curves": "^2.4.0",