@typesol/cli 0.24.0 → 0.25.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.
@@ -0,0 +1,224 @@
1
+ # Case study: porting Anchor's escrow
2
+
3
+ This page follows one complete Anchor program into TypeSOL: the token escrow from
4
+ [solana-developers/program-examples](https://github.com/solana-developers/program-examples/tree/de090195e4de698e150572c4f886c333323a64a5/tokens/escrow/anchor)
5
+ (`tokens/escrow`, commit `de09019`). It shows the original and the port side by side, what changed, how the two were
6
+ checked against each other, where they still differ, and what the port costs to run. The port is
7
+ [`test/ports/tokens/escrow.ts`](../test/ports/tokens/escrow.ts); [Migrate from Anchor](migrate-from-anchor.md) is the
8
+ general reference.
9
+
10
+ ## The program
11
+
12
+ A maker offers an amount of token A for an amount of token B.
13
+
14
+ - `make_offer(id, token_a_offered_amount, token_b_wanted_amount)` creates the offer account, a PDA at
15
+ `["offer", maker, id]`, creates the vault (the offer's associated token account for mint A) and moves the offered
16
+ tokens into it.
17
+ - `take_offer()` sends token B from the taker to the maker, releases the vault to the taker, closes the vault and the
18
+ offer, and returns their rent to the maker. It creates the taker's mint A account and the maker's mint B account if
19
+ they do not exist.
20
+ - `refund_offer()` returns the vault to the maker and closes both accounts.
21
+
22
+ Upstream, this is 398 lines of Rust in ten files. The port is one 95-line TypeScript file.
23
+
24
+ ## Side by side
25
+
26
+ ### State
27
+
28
+ ```rust
29
+ #[account]
30
+ #[derive(InitSpace)]
31
+ pub struct Offer {
32
+ pub id: u64,
33
+ pub maker: Pubkey,
34
+ pub token_mint_a: Pubkey,
35
+ pub token_mint_b: Pubkey,
36
+ pub token_b_wanted_amount: u64,
37
+ pub bump: u8,
38
+ }
39
+ ```
40
+
41
+ ```ts
42
+ export interface Offer {
43
+ id: u64;
44
+ maker: Pubkey;
45
+ tokenMintA: Pubkey;
46
+ tokenMintB: Pubkey;
47
+ tokenBWantedAmount: u64;
48
+ bump: u8;
49
+ }
50
+ ```
51
+
52
+ The account has the same discriminator and the same Borsh bytes: an Anchor client decodes a TypeSOL offer unchanged.
53
+ Field names are camelCase in TypeScript and snake_case in the IDL, as Anchor writes them.
54
+
55
+ ### make_offer
56
+
57
+ Anchor declares the accounts in a struct with constraint attributes, then splits the work into helper functions:
58
+
59
+ ```rust
60
+ #[derive(Accounts)]
61
+ #[instruction(id: u64)]
62
+ pub struct MakeOffer<'info> {
63
+ #[account(mut)]
64
+ pub maker: Signer<'info>,
65
+ #[account(mint::token_program = token_program)]
66
+ pub token_mint_a: InterfaceAccount<'info, Mint>,
67
+ #[account(mint::token_program = token_program)]
68
+ pub token_mint_b: InterfaceAccount<'info, Mint>,
69
+ #[account(mut, associated_token::mint = token_mint_a, associated_token::authority = maker,
70
+ associated_token::token_program = token_program)]
71
+ pub maker_token_account_a: InterfaceAccount<'info, TokenAccount>,
72
+ #[account(init, payer = maker, space = ANCHOR_DISCRIMINATOR + Offer::INIT_SPACE,
73
+ seeds = [b"offer", maker.key().as_ref(), id.to_le_bytes().as_ref()], bump)]
74
+ pub offer: Account<'info, Offer>,
75
+ #[account(init, payer = maker, associated_token::mint = token_mint_a, associated_token::authority = offer,
76
+ associated_token::token_program = token_program)]
77
+ pub vault: InterfaceAccount<'info, TokenAccount>,
78
+ pub associated_token_program: Program<'info, AssociatedToken>,
79
+ pub token_program: Interface<'info, TokenInterface>,
80
+ pub system_program: Program<'info, System>,
81
+ }
82
+ // + send_offered_tokens_to_vault (a TransferChecked CPI through a shared transfer_tokens helper)
83
+ // + save_offer (offer.set_inner(Offer { … bump: context.bumps.offer }))
84
+ ```
85
+
86
+ In TypeSOL the accounts are the function's parameters, in the same order, and each constraint is part of the
87
+ parameter's type:
88
+
89
+ ```ts
90
+ export function makeOffer(
91
+ maker: Mut<Signer>,
92
+ tokenMintA: MintOf<Mint, never, never, "tokenProgram">,
93
+ tokenMintB: MintOf<Mint, never, never, "tokenProgram">,
94
+ makerTokenAccountA: Ata<Mut<TokenAccount>, "maker", "tokenMintA">,
95
+ offer: Init<Offer>,
96
+ vault: Ata<Init<TokenAccount>, "offer", "tokenMintA">,
97
+ associatedTokenProgram: AssociatedTokenProgram,
98
+ tokenProgram: TokenInterface,
99
+ systemProgram: SystemProgram,
100
+ id: u64,
101
+ tokenAOfferedAmount: u64,
102
+ tokenBWantedAmount: u64,
103
+ ): void {
104
+ offer.bump = offer.create(maker, ["offer", maker.key, id]);
105
+ vault.create(maker);
106
+ tokenProgram.transferChecked(makerTokenAccountA, tokenMintA, vault, maker, tokenAOfferedAmount);
107
+ offer.id = id;
108
+ offer.maker = maker.key;
109
+ offer.tokenMintA = tokenMintA.key;
110
+ offer.tokenMintB = tokenMintB.key;
111
+ offer.tokenBWantedAmount = tokenBWantedAmount;
112
+ }
113
+ ```
114
+
115
+ `MintOf<Mint, never, never, "tokenProgram">` is `mint::token_program = token_program`: the mint must be owned by
116
+ the token program passed (2022 otherwise). `offer.create` finds the canonical bump, creates the PDA with the computed space and returns the bump, which Anchor
117
+ exposes as `context.bumps.offer`. `transferChecked` reads the mint's decimals itself.
118
+
119
+ ### take_offer and refund_offer
120
+
121
+ The offer is checked declaratively, as in Anchor: the seeds come from the maker account and the offer's own `id`
122
+ field, and the stored bump is used.
123
+
124
+ ```rust
125
+ #[account(mut, close = maker, has_one = maker, has_one = token_mint_a, has_one = token_mint_b,
126
+ seeds = [b"offer", maker.key().as_ref(), offer.id.to_le_bytes().as_ref()], bump = offer.bump)]
127
+ offer: Account<'info, Offer>,
128
+ ```
129
+
130
+ ```ts
131
+ offer: Seeds<CloseTo<HasOne<Mut<Offer>, "maker" | "tokenMintA" | "tokenMintB">, "maker">, ["offer", "@maker", "@offer.id"], "bump">,
132
+ ```
133
+
134
+ The body signs for the vault with the offer's seeds, where Anchor builds a `signer_seeds` array and a
135
+ `CpiContext::new_with_signer` for each call:
136
+
137
+ ```ts
138
+ takerTokenAccountA.createIfNeeded(taker);
139
+ makerTokenAccountB.createIfNeeded(taker);
140
+ tokenProgram.transferChecked(takerTokenAccountB, tokenMintB, makerTokenAccountB, taker, offer.tokenBWantedAmount);
141
+ tokenProgram.transferChecked(vault, tokenMintA, takerTokenAccountA, offer, vault.amount, ["offer", maker.key, offer.id, offer.bump]);
142
+ tokenProgram.closeAccount(vault, maker, offer, ["offer", maker.key, offer.id, offer.bump]);
143
+ ```
144
+
145
+ `CloseTo<…, "maker">` closes the offer after the body returns, as Anchor's `close = maker` does.
146
+
147
+ ## What changed
148
+
149
+ | Anchor | Port |
150
+ | --- | --- |
151
+ | `Signer`, `SystemAccount` (`#[account(mut)]`) | `Mut<Signer>`, `Mut<SystemAccount>` |
152
+ | `InterfaceAccount<Mint>`, `InterfaceAccount<TokenAccount>` | `Mint`, `TokenAccount` |
153
+ | `mint::token_program = token_program` | `MintOf<Mint, never, never, "tokenProgram">` |
154
+ | `associated_token::mint = m, associated_token::authority = a` | `Ata<TokenAccount, "a", "m">` |
155
+ | `init` / `init_if_needed` on an associated token account | `Ata<Init<…>>` + `vault.create(payer)` / `Ata<InitIfNeeded<…>>` + `createIfNeeded(payer)` |
156
+ | `init, seeds, bump` + `context.bumps.offer` | `Init<Offer>` + `offer.bump = offer.create(maker, seeds)` |
157
+ | `seeds = […], bump = offer.bump` | `Seeds<…, ["offer", "@maker", "@offer.id"], "bump">` |
158
+ | `has_one`, `close` | `HasOne<…>`, `CloseTo<…>` |
159
+ | `transfer_checked(CpiContext::new_with_signer(…), amount, decimals)` | `tokenProgram.transferChecked(from, mint, to, authority, amount, seeds)` |
160
+ | `ErrorCode::CustomError` (unused upstream) | `enum ErrorCode`, the same code 6000 |
161
+
162
+ Nothing in the program needed a construct TypeSOL lacks, and every account constraint has its TypeSOL form; the port
163
+ is marked PORTED in [the ports table](../test/ports/README.md).
164
+
165
+ ## Same inputs, same results
166
+
167
+ Two independent checks run the original and the port against each other.
168
+
169
+ - **The port's own test** ([`test/ports-tokens.test.ts`](../test/ports-tokens.test.ts)) runs the upstream flow with SPL
170
+ Token mints and again with Token-2022 mints: make, take (creating the taker's and the maker's missing token accounts),
171
+ make again, a refund attempt by someone other than the maker, and the refund. It checks balances, the decoded offer
172
+ and that offer and vault are closed.
173
+ - **The four-way comparison** in [typesol/benchmarks](https://github.com/typesol/benchmarks) builds the upstream Anchor,
174
+ native and Pinocchio versions unmodified and sends all four the same 21 offers (ten taken, ten refunded, one left
175
+ open). The decoded open offer, every token balance, the vault's authority and which accounts were closed must be
176
+ identical before any number is reported. They are.
177
+
178
+ Both implementations refuse the hostile calls the comparison makes:
179
+
180
+ | Hostile call | Anchor | TypeSOL |
181
+ | --- | --- | --- |
182
+ | refund_offer signed by someone other than the maker | 2006 ConstraintSeeds | 2006 ConstraintSeeds |
183
+ | take_offer naming the taker as the maker | 2015 ConstraintTokenOwner | 2040 ConstraintDuplicateMutableAccount |
184
+
185
+ TypeSOL checks each account's constraints in Anchor's order, so a refused transaction usually gets the same error
186
+ code (see [which error a transaction gets](errors.md#which-error-a-transaction-gets)). The second call differs on
187
+ purpose: taker and maker are then the same writable account, which TypeSOL refuses for any two writable parameters,
188
+ where Anchor only compares program and token accounts and fails later, on the maker's token account. A client that
189
+ matches on this code needs to accept either.
190
+
191
+ ## Cost
192
+
193
+ Mean compute units per instruction in LiteSVM (21 make_offer, 10 take_offer and 10 refund_offer calls), and the
194
+ program size, from typesol/benchmarks (TypeSOL at this version; Anchor 1.0.2). Lower is better.
195
+
196
+ | | TypeSOL | Anchor | Native Rust | Pinocchio |
197
+ | --- | ---: | ---: | ---: | ---: |
198
+ | make_offer | **19,697** | 35,726 | 33,206 | 20,581 |
199
+ | take_offer | **7,053** | 29,235 | 24,995 | 21,606 |
200
+ | refund_offer | **4,271** | 15,326 | 12,477 | 8,137 |
201
+ | Binary | 32.0 KB | 244.5 KB | 120.4 KB | **25.1 KB** |
202
+
203
+ Most of `make_offer` in every version is the Associated Token Account program creating the vault. The native and
204
+ Pinocchio versions are teaching code with fewer checks (no owner or type check on the offer, `Transfer` instead of
205
+ `TransferChecked`; Pinocchio takes the offer's bump from the client).
206
+
207
+ The same program, built with an earlier TypeSOL, runs on mainnet next to the other three; the transactions are listed
208
+ with their Solscan links in the [performance report](https://typesol.com/report/) and the
209
+ [README](../README.md#on-mainnet).
210
+
211
+ ## Porting your own program
212
+
213
+ The steps this port followed, in order:
214
+
215
+ 1. Generate the skeleton from the Anchor IDL: `npx typesol port target/idl/escrow.json --out programs/escrow.ts`. It
216
+ keeps instruction names, discriminators, account order and error codes.
217
+ 2. Turn each `#[account(...)]` into the parameter's type, using the table above and
218
+ [Accounts and constraints](migrate-from-anchor.md#accounts-and-constraints). Keep the account order: it is the
219
+ wire format.
220
+ 3. Port the bodies. Helpers like `transfer_tokens` usually disappear into one call; `signer_seeds` arrays become the
221
+ last argument of the CPI.
222
+ 4. Run the upstream test flow against the port with `typesol/testing`, including the failure paths, and compare the
223
+ decoded accounts.
224
+ 5. Compare the IDLs, and check the error codes your clients match on (the table above shows how they can differ).
package/docs/errors.md CHANGED
@@ -5,6 +5,7 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
5
5
  | Code | Meaning |
6
6
  | --- | --- |
7
7
  | 100 / 101 / 102 | instruction data missing / unknown instruction / malformed arguments (InstructionMissing / FallbackNotFound / DidNotDeserialize) |
8
+ | 2000 | a `Mut<…>` / `Init<…>` account passed read-only (ConstraintMut) |
8
9
  | 2001 | HasOne constraint failed (ConstraintHasOne) |
9
10
  | 2002 | event authority did not sign (ConstraintSigner) |
10
11
  | 2004 | Owner constraint failed (ConstraintOwner) |
@@ -15,6 +16,7 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
15
16
  | 2012 | not the expected address (ConstraintAddress) |
16
17
  | 2014 / 2015 | token account mint / owner mismatch (ConstraintTokenMint / ConstraintTokenOwner) |
17
18
  | 2016 / 2017 / 2018 | mint authority / freeze authority / decimals mismatch (2018: an existing mint `createIfNeeded` found) |
19
+ | 2021 / 2022 | token account / mint not owned by the token program `TokenOf` / `MintOf` names (ConstraintTokenTokenProgram / ConstraintMintTokenProgram) |
18
20
  | 2024–2039 | an existing mint `createIfNeeded` found lacks a Token-2022 extension it was given, or holds other keys in it: group pointer 2024–2026, group member pointer 2027–2029, metadata pointer 2030–2032, close authority 2033–2034, permanent delegate 2035–2036, transfer hook 2037–2039 (the first code of each: the extension is missing; the next: the authority / address differs) (ConstraintMint…Extension…) |
19
21
  | 2040 | the same account passed in two writable slots (ConstraintDuplicateMutableAccount) |
20
22
  | 3000 | account already exists / initialized (AccountDiscriminatorAlreadySet) |
@@ -22,7 +24,7 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
22
24
  | 3003 | data too small, not an initialized token account / mint, a read past the end, or a `@layout borsh` account whose bytes are not a valid Borsh value (AccountDidNotDeserialize) |
23
25
  | 3004 | a `@layout borsh` account too small for the value it would now hold (AccountDidNotSerialize) |
24
26
  | 3005 | not enough accounts (AccountNotEnoughKeys) |
25
- | 3006 | account must be writable (AccountNotMutable) |
27
+ | 3006 | a remaining account or CPI account the program needs writable was passed read-only (AccountNotMutable) |
26
28
  | 3007 | wrong owner program (AccountOwnedByWrongProgram) |
27
29
  | 3008 / 3009 | wrong program address / not executable (InvalidProgramId / InvalidProgramExecutable) |
28
30
  | 3010 | account must sign (AccountNotSigner; also a new keypair account a `create(payer, null, …)` makes) |
@@ -47,3 +49,26 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
47
49
 
48
50
  Any of Anchor's own codes can be raised directly: `require(cond, AnchorError.RequireKeysEqViolated)` fails with 2502,
49
51
  as `require_keys_eq!` does.
52
+
53
+ ## Which error a transaction gets
54
+
55
+ A transaction can break several checks; it fails with the first. Within each account's constraints the checks run in
56
+ the order Anchor's generated code runs them, so a transaction gets the same code under both:
57
+
58
+ 1. The account count (3005), then each account in parameter order: its type, signer (3010), `Mut` (2000), owner,
59
+ discriminator and size of a program account (3007 / 3012 / 3002 / 3003), token account or mint, program address.
60
+ 2. Two writable parameters naming one account (2040).
61
+ 3. The instruction's arguments (102).
62
+ 4. Each account in parameter order, its constraints in Anchor's order: seeds (2006), associated token account (owner
63
+ 2015, then address 2009), `has_one` (2001), owner (2004), executable (2007), address (2012), then `TokenOf` /
64
+ `MintOf` (2015, 2014, 2021 / 2016, 2017, 2022).
65
+
66
+ Where TypeSOL's order differs, only a transaction that breaks two of these checks at once sees it:
67
+
68
+ - Anchor decodes the arguments first, and checks `mut` with the constraints (after seeds and the associated token
69
+ account). TypeSOL keeps both earlier and later as above, which keeps binaries smaller.
70
+ - Too few accounts fails with 3005 before any account is checked; Anchor fails on the first account it cannot find.
71
+ - Accounts the instruction creates (`Init<…>`, `create(…)`) are created in the body, after every other check, where
72
+ Anchor creates them before checking the other accounts.
73
+ - TypeSOL refuses any two writable parameters naming one account (2040), where Anchor only compares program and token
74
+ accounts: a signer passed as two writable parameters fails with 2040 here and later, or not at all, under Anchor.
package/docs/language.md CHANGED
@@ -38,8 +38,8 @@ The full reference. For a tour, start with the [README](../README.md).
38
38
  | Option of structs | `home: Option<Point>` in account data — `=== null`, `p.home.x` after narrowing, `p.home = null` / `{ x, y }` |
39
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): a template is joined in one allocation, text known at compile time (literals, string and Pubkey constants) is folded in, and `s += …` grows the newest heap string in place |
40
40
  | `switch` | on integers (128-bit ones too), enums and data-enum `.kind`, with JS `break` / fallthrough; case labels are literals, constants or enum members |
41
- | Hashing & signatures | `sha256(...)`, `keccak256(...)` → `Hash`; `findProgramAddress(seeds, program)` → the canonical PDA of any program (Rust `Pubkey::find_program_address(..).0`, e.g. a Bubblegum asset id); `merkleVerify(leaf, proof: Vec<Hash>, root)` (sorted-pair keccak, OpenZeppelin convention); `verifyEd25519(instructionsSysvar, signer, message)` checks an Ed25519 precompile instruction in the same transaction; `verifySecp256k1(keccak256(...message) | ethMessageHash(message), recoveryId, r, s, ethAddress)` (the hash must be computed in the program; a passed-in hash could be forged) |
42
- | 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
+ | 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; inner nodes and leaves are hashed alike, so hash leaves as OpenZeppelin does, `keccak256(keccak256(data))`, and never take the leaf itself from the arguments: an inner node would pass as a leaf); `verifyEd25519(instructionsSysvar, signer, message)` checks an Ed25519 precompile instruction in the same transaction; `verifySecp256k1(keccak256(...message) | ethMessageHash(message), recoveryId, r, s, ethAddress)` (the hash must be computed in the program; a passed-in hash could be forged) |
42
+ | Oracles | `pythPrice(update: PythPriceUpdate, feedId, maxAgeSeconds, exponent?)` (fully verified, feed-matched, fresh; with `exponent`, a feed at another scale is refused); raw `update.price / conf / exponent / publishTime` are unchecked (any feed, partially verified or stale: check `feedId`, `fullyVerified` and the age yourself, or use `pythPrice`); `switchboardValue(feed: SwitchboardFeed, expectedFeed, maxStaleSlots, decimals, minSamples?)` (the feed must be the expected one: anyone can create feeds; `minSamples`, default 1, the fewest oracle samples accepted) (mainnet program unless `SwitchboardFeed<"devnet">`) |
43
43
  | Multiple files | `import { Side, min } from "./math"` — structs, enums, constants and helpers can live in other files; only the entry file's exported functions are instructions; names are program-wide, so imports from TypeSOL files cannot be renamed; error codes number imported files first |
44
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
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, a `string`, a function (see below) 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) |
@@ -65,9 +65,9 @@ The full reference. For a tour, start with the [README](../README.md).
65
65
  | `Signer` / `Mut<Signer>` | signed (and writable) |
66
66
  | `Account<T>` / `Mut<T>` | owned by this program, 8-byte discriminator matches, data large enough (and writable) |
67
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) |
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 |
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. An existing account is used as it is: check the fields you rely on |
69
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 |
70
- | `HasOne<TokenAccount, "mint" \| "owner">`, `HasOne<Mint, "mintAuthority" \| "freezeAuthority">` | token constraints (2014 / 2015 / 2016 / 2017) against the parameter with that name; `TokenOf<TokenAccount, "mintParam", "authorityParam">` / `MintOf<Mint, "authorityParam", "freezeParam">` name the parameters, as Anchor's `token::mint = …` / `mint::authority = …` do (`never` leaves one out) |
70
+ | `HasOne<TokenAccount, "mint" \| "owner">`, `HasOne<Mint, "mintAuthority" \| "freezeAuthority">` | token constraints (2014 / 2015 / 2016 / 2017) against the parameter with that name; `TokenOf<TokenAccount, "mintParam", "authorityParam", "tokenProgramParam">` / `MintOf<Mint, "authorityParam", "freezeParam", "tokenProgramParam">` name the parameters, as Anchor's `token::mint = …` / `mint::authority = …` / `…::token_program = …` do (`never` leaves one out; the token program one: owned by that token program, 2021 / 2022) |
71
71
  | `Ata<TokenAccount, "owner", "mint">` | the account is the associated token account of those parameters (2009; ATA `pda` in the IDL) |
72
72
  | `Ata<Init<TokenAccount>, "owner", "mint">`, `Ata<InitIfNeeded<TokenAccount>, "owner", "mint">` | Anchor `init` / `init_if_needed` with `associated_token::…`: `acct.create(payer)` creates the ATA (fails if it exists); `const created = acct.createIfNeeded(payer)` creates it only when missing, otherwise checks it is that ATA (2009). Then it is a `TokenAccount` of that mint and owner (2014 / 2015). Needs `AssociatedTokenProgram`, `SystemProgram` and a token program parameter; call it at the top level before using the account |
73
73
  | `Address<A, "4Nd1…">` | exact address (2012; `address` in the IDL) |
@@ -77,8 +77,8 @@ The full reference. For a tour, start with the [README](../README.md).
77
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` |
78
78
  | `EventAuthority`, `SelfProgram` | Anchor's `["__event_authority"]` PDA, and this program's own address, for `emitCpi` |
79
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 (its only token program parameter, or one passed as the last argument of `create` / `createIfNeeded`; 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. With `createIfNeeded` an existing mint must carry groupPointer / groupMemberPointer / metadataPointer / closeAuthority / permanentDelegate / transferHook with those keys (Anchor's 2024–2039). `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) |
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 (its only token program parameter, or one passed as the last argument of `create` / `createIfNeeded`; 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. With `createIfNeeded` an existing mint must carry groupPointer / groupMemberPointer / metadataPointer / closeAuthority / permanentDelegate / transferHook with those keys (Anchor's 2024–2039); extensions not listed are not looked at, so an existing mint may carry others (a permanent delegate, say). `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), not its delegate or close authority (as Anchor) |
82
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` |
83
83
  | `Mint` / `Mut<Mint>` | owned by SPL Token or Token-2022, initialized; `decimals`, `supply`, `mintAuthority`, `freezeAuthority`, `tokenProgram` |
84
84
  | `SystemProgram`, `TokenProgram`, `Token2022Program` | exact program address |
@@ -94,7 +94,9 @@ The full reference. For a tour, start with the [README](../README.md).
94
94
  Every account exposes `key`, `lamports`, `dataLen`, `isSigner`, `isWritable`. At most 32 accounts per instruction (more is refused, 5113).
95
95
 
96
96
  Two writable parameters can never be the same account: aliasing is refused with error 2040 (also when a remaining
97
- account written through `.lamports` or `loadAccountMut` is another account of the instruction).
97
+ account written through `.lamports` or `loadAccountMut` is another account of the instruction). A remaining account
98
+ can be loaded with `loadAccountMut` once per instruction: loading the same one again (`rest.get(i)` and `rest.get(j)`
99
+ with `i == j`) is refused with 2040 too, since the two views would alias.
98
100
 
99
101
  ## Built-ins
100
102
 
@@ -106,7 +108,9 @@ account written through `.lamports` or `loadAccountMut` is another account of th
106
108
  `freezeAccount` / `thawAccount(account, mint, freezeAuthority)`, `syncNative(account)`, `initializeMint2(mint, decimals, mintAuthority, freezeAuthority | null)`,
107
109
  `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
108
110
  - `account.lamports` read/write on `Mut` accounts, `close(account, destination)` for program accounts
109
- - `requirePda(account, seeds, bump)`
111
+ - `requirePda(account, seeds, bump)`: any bump that gives a valid PDA passes, as with Anchor's `bump = expr`. Use a bump
112
+ stored when the account was created (or `Seeds<…>`); one taken from the instruction's arguments lets the caller pick
113
+ among up to 256 addresses for the same seeds
110
114
 
111
115
  ## Implementing another program's interface (transfer hooks)
112
116
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  TypeSOL programs speak Anchor's wire format: the same instruction and account discriminators, Borsh arguments, an
4
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.
5
+ Anchor's constructs to TypeSOL's and shows the porting workflow. For one complete program ported this way, with the
6
+ original side by side, the checks run against it and its cost, see [Case study: porting Anchor's escrow](case-study-escrow.md).
6
7
 
7
8
  ## Start from the IDL
8
9
 
@@ -50,6 +51,7 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
50
51
  | `Program<'info, Token>` / `Token2022` / `Interface<'info, TokenInterface>` | `TokenProgram` / `Token2022Program` / `TokenInterface` |
51
52
  | `Account<'info, TokenAccount>` / `Account<'info, Mint>` (`InterfaceAccount`) | `TokenAccount` / `Mint` (`Mut<…>` when written) |
52
53
  | `token::mint = m, token::authority = a` | `TokenOf<TokenAccount, "m", "a">` (2014 / 2015), or `HasOne<TokenAccount, "mint" \| "owner">` when the parameters have those names; `mint::authority = a, mint::freeze_authority = f` on an existing mint: `MintOf<Mint, "a", "f">` (2016 / 2017) |
54
+ | `mint::token_program = tp` / `token::token_program = tp` | `MintOf<Mint, never, never, "tp">` / `TokenOf<TokenAccount, never, never, "tp">` (2022 / 2021) |
53
55
  | `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
56
  | `#[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
57
  | `#[account(init, payer = p, seeds = […], bump, token::mint = m, token::authority = a)]` | `Init<TokenAccount>`, then `vault.create(p, […], m, a.key)` (`InitIfNeeded<TokenAccount>` + `createIfNeeded`) |
@@ -0,0 +1,59 @@
1
+ # The C runtime
2
+
3
+ `runtime/typesol_rt.h` is included by every generated program. It holds what the emitted C calls: error codes, the
4
+ entry point's input parser, account checks, checked arithmetic, Borsh helpers, CPIs, sysvars, strings and the heap.
5
+ This page is for people changing it; programs never call it directly.
6
+
7
+ ## Conventions
8
+
9
+ - **Header-only, `static inline`.** Each program compiles the header with its own code, and `--gc-sections` drops
10
+ what it does not use. `always_inline` marks the few helpers that must disappear into their caller (word loads and
11
+ stores, `ts_memcpy`, checked add/subtract); `noinline` marks the ones that should exist once per program because
12
+ many call sites would otherwise each grow the binary (`ts_fail`, the rent fallback `ts_rent_slow`, PDA search).
13
+ - **Errors are return values.** A function that can fail returns `uint64_t`: `SUCCESS`, or an error code. Fail with
14
+ `TS_FAIL(code, "TypeSOL: …")` (logs the message, then returns the code) and propagate with `TS_TRY(call)`.
15
+ Nothing aborts: the instruction returns the code, and the runtime reports it as `Custom(code)`.
16
+ - **Error codes follow Anchor.** Where Anchor has a framework error with the same meaning, its number is used (2006
17
+ ConstraintSeeds, 3003 AccountDidNotDeserialize, …), so Anchor and Codama clients name failures correctly.
18
+ TypeSOL-only conditions are 5100–5199; user errors start at 6000. Every code is listed in [errors.md](errors.md).
19
+ - **No byte loops from the SDK.** The SDK's `sol_memcpy`, `sol_memcmp` and `SolPubkey_same` move one byte per
20
+ iteration. The runtime uses `ts_ld64` / `ts_st64` (unaligned 8-byte loads and stores, which SBF allows) in
21
+ `ts_memcpy`, `ts_memzero`, `ts_mem_eq`, `ts_key_eq` and `ts_memmove`.
22
+ - **Same results as the reference.** Fast paths must give bit-identical results to what they replace: `ts_add_overflow_u64`
23
+ matches `__builtin_add_overflow`, the rent fast path and `ts_rent_slow` match the double computation, PDA search
24
+ matches `sol_try_find_program_address`. A change here needs a test against the reference, not only against itself.
25
+
26
+ ## Sections
27
+
28
+ In file order:
29
+
30
+ | Section | What it provides |
31
+ | --- | --- |
32
+ | Error codes, `TS_FAIL`, `TS_TRY` | The codes above and the two macros |
33
+ | Word-wise memory helpers | `ts_ld64`, `ts_st64`, `ts_memcpy`, `ts_memmove`, `ts_memzero`, `ts_mem_eq`; checked u64 add/subtract/multiply |
34
+ | Well-known program ids | System, Token, Token-2022, ATA, Metaplex and the other programs the language names |
35
+ | Strings | `TS_Str` views (pointer, length), UTF-8 validation, comparison, `FixedString<N>` storage |
36
+ | Entry point input | `TsAccount` (the loader's serialized account, read in place), `TsParams`, `ts_deserialize` |
37
+ | Account checks | owner, discriminator, signer, writable, executable, address, size, uniqueness (`ts_check_*`), growable Vec lengths |
38
+ | Little-endian IO | `ts_read_le` and friends for argument decoding and Borsh |
39
+ | Sysvars | clock, rent (`ts_rent`, `ts_minimum_balance_of`, `ts_minimum_balance`), program data |
40
+ | PDA | `ts_pda_hash`, `ts_create_pda_address`, `ts_find_pda` (canonical bump), `ts_require_pda` |
41
+ | System program CPIs | create, assign, allocate, transfer, `ts_create_pda`, `ts_resize` / `ts_realloc`, close |
42
+ | Token-2022 extensions | TLV walking for the extensions the language reads |
43
+ | Metaplex Core and Bubblegum | account readers (`coreAssetOwner`, …) |
44
+ | SPL Token / Token-2022 CPIs | transfer, mint, burn, approve, close, freeze and their checked forms |
45
+ | Return data | `sol_set_return_data` wrappers |
46
+ | Crypto | sha256, keccak256, ed25519 / secp256k1 verification helpers, Merkle proofs |
47
+ | Heap | `ts_alloc`, a bump allocator (32 KiB by default, `HEAP_SIZE_KIB` up to 256), string building (`ts_str_join`, `ts_str_append`, number and base58 text) |
48
+ | Oracles | Pyth price updates, Switchboard |
49
+ | Associated token accounts, token metadata, transfer hooks | ATA derivation and creation, Token Metadata CPIs, transfer-hook extra accounts |
50
+
51
+ ## Changing it
52
+
53
+ - Every runtime change can move compute units or binary size. Measure it before and after with
54
+ [typesol/benchmarks](https://github.com/typesol/benchmarks) pointed at your checkout, and put the difference in
55
+ the pull request (see CONTRIBUTING).
56
+ - A change that alters behavior (a result, an error code, a log line, account bytes) is a language change and goes
57
+ in the CHANGELOG as one. One that only changes cost says so: "same results".
58
+ - New error codes: add the `#define` here, the row in [errors.md](errors.md) and the name in the compiler's runtime
59
+ error list (`TYPESOL_ERRORS` in `src/clientgen.ts`), so clients name it (a test checks every code has a name).
package/lib/typesol.d.ts CHANGED
@@ -162,7 +162,8 @@ export declare function remainingAccounts(): RemainingAccounts;
162
162
 
163
163
  /**
164
164
  * Read a struct of this program from a remaining account: owner, discriminator and size are checked.
165
- * `loadAccountMut` also requires it to be writable and not aliased by another writable account.
165
+ * `loadAccountMut` also requires it to be writable, not aliased by another writable account, and not already loaded
166
+ * with `loadAccountMut` in this instruction (2040).
166
167
  */
167
168
  export declare function loadAccount<T>(account: RemainingAccount): Readonly<T>;
168
169
  export declare function loadAccountMut<T>(account: RemainingAccount): T;
@@ -180,7 +181,8 @@ export declare function keccak256(...parts: (string | Pubkey | Hash | bigint | b
180
181
 
181
182
  /**
182
183
  * Verify a Merkle proof (keccak256, sorted pairs: the OpenZeppelin convention most airdrop tools use).
183
- * `proof` must be a `Vec<Hash>` instruction argument.
184
+ * `proof` must be a `Vec<Hash>` instruction argument. Leaves and inner nodes hash alike: build the leaf in the program
185
+ * as OpenZeppelin does, `keccak256(keccak256(data))`, never take it from the arguments (an inner node would pass).
184
186
  */
185
187
  export declare function merkleVerify(leaf: Hash, proof: Vec<Hash>, root: Hash): boolean;
186
188
 
@@ -211,7 +213,8 @@ export declare function ethMessageHash(message: string): Hash;
211
213
 
212
214
  /**
213
215
  * A Pyth `PriceUpdateV2` account (Pyth Solana Receiver, rec5EKMG…). Owner and discriminator are checked.
214
- * The fields are the raw update; use pythPrice() for a verified, fresh, feed-matched price.
216
+ * The fields are the raw update and are NOT checked: it may be only partially verified, for another feed, or stale.
217
+ * Use pythPrice() for a verified, fresh, feed-matched price (with `exponent` to pin the scale).
215
218
  */
216
219
  export interface PythPriceUpdate extends AccountMeta {
217
220
  readonly __pyth: void;
@@ -223,8 +226,12 @@ export interface PythPriceUpdate extends AccountMeta {
223
226
  readonly publishTime: i64;
224
227
  readonly fullyVerified: boolean;
225
228
  }
226
- /** Like Pyth's get_price_no_older_than: fully verified, `feedId` matches, published within `maxAgeSeconds`. */
227
- export declare function pythPrice(update: PythPriceUpdate, feedId: Hash, maxAgeSeconds: u64): i64;
229
+ /**
230
+ * Like Pyth's get_price_no_older_than: fully verified, `feedId` matches, published within `maxAgeSeconds` (5111
231
+ * otherwise). The price is in units of 10^exponent: pass the `exponent` your arithmetic assumes and a feed whose
232
+ * exponent differs is refused, instead of read at the wrong scale.
233
+ */
234
+ export declare function pythPrice(update: PythPriceUpdate, feedId: Hash, maxAgeSeconds: u64, exponent?: i32): i64;
228
235
 
229
236
  /**
230
237
  * A Switchboard On-Demand pull feed (PullFeedAccountData). Mainnet program unless `SwitchboardFeed<"devnet">`
@@ -237,7 +244,14 @@ export interface SwitchboardFeed<Cluster extends "mainnet" | "devnet" = "mainnet
237
244
  * The feed's current result scaled to `decimals` (from 18), if `feed` is `expectedFeed` (anyone can create feeds),
238
245
  * it has samples and it is at most `maxStaleSlots` old.
239
246
  */
240
- export declare function switchboardValue(feed: SwitchboardFeed<"mainnet" | "devnet">, expectedFeed: Pubkey, maxStaleSlots: u64, decimals: u8): i64;
247
+ export declare function switchboardValue(
248
+ feed: SwitchboardFeed<"mainnet" | "devnet">,
249
+ expectedFeed: Pubkey,
250
+ maxStaleSlots: u64,
251
+ decimals: u8,
252
+ /** The fewest oracle samples the result may rest on (default 1). */
253
+ minSamples?: u8,
254
+ ): i64;
241
255
 
242
256
  /** The Associated Token Account program (ATokenGP…). Its address is checked. */
243
257
  export interface AssociatedTokenProgram extends AccountMeta {
@@ -570,13 +584,18 @@ export type InitIfNeeded<T> = T extends TokenAccount
570
584
  */
571
585
  export type HasOne<A, Field extends string> = A & { readonly __hasOne?: Field };
572
586
  /**
573
- * Anchor's `token::mint = mintParam, token::authority = authorityParam` on an existing token account, naming the
574
- * parameters (2014 / 2015); `never` leaves one out.
587
+ * Anchor's `token::mint = mintParam, token::authority = authorityParam, token::token_program = tokenProgramParam` on an
588
+ * existing token account, naming the parameters (2014 / 2015 / 2021: owned by that token program); `never` leaves one out.
575
589
  */
576
- export type TokenOf<A, MintParam extends string, AuthorityParam extends string = never> = A & { readonly __tokenOf?: [MintParam, AuthorityParam] };
577
- /** Anchor's `mint::authority = a, mint::freeze_authority = f` on an existing mint, naming the parameters (2016 / 2017). */
578
- export type MintOf<A, AuthorityParam extends string, FreezeAuthorityParam extends string = never> = A & {
579
- readonly __mintOf?: [AuthorityParam, FreezeAuthorityParam];
590
+ export type TokenOf<A, MintParam extends string, AuthorityParam extends string = never, TokenProgramParam extends string = never> = A & {
591
+ readonly __tokenOf?: [MintParam, AuthorityParam, TokenProgramParam];
592
+ };
593
+ /**
594
+ * Anchor's `mint::authority = a, mint::freeze_authority = f, mint::token_program = tp` on an existing mint, naming the
595
+ * parameters (2016 / 2017 / 2022: owned by that token program); `never` leaves one out.
596
+ */
597
+ export type MintOf<A, AuthorityParam extends string, FreezeAuthorityParam extends string = never, TokenProgramParam extends string = never> = A & {
598
+ readonly __mintOf?: [AuthorityParam, FreezeAuthorityParam, TokenProgramParam];
580
599
  };
581
600
  export type Seeds<A, Names extends readonly string[], Bump extends string = never, ProgramId extends string = never> = A & {
582
601
  readonly __seeds?: [Names, Bump, ProgramId];
@@ -1642,7 +1661,10 @@ export declare function emit<T>(event: T): void;
1642
1661
  */
1643
1662
  export declare function returnData<T extends bigint | boolean | Pubkey | Hash>(program: AccountMeta): T;
1644
1663
 
1645
- /** Verify `account.key` is the PDA for `seeds` + `bump` under this program. */
1664
+ /**
1665
+ * Verify `account.key` is the PDA for `seeds` + `bump` under this program. Any bump that gives a PDA passes (Anchor's
1666
+ * `bump = expr`): use a stored bump, not one from the arguments, or the caller picks among up to 256 addresses.
1667
+ */
1646
1668
  export declare function requirePda(account: AccountMeta, seeds: Seed[], bump: u8): void;
1647
1669
 
1648
1670
  /** Close a program-owned account: move all lamports to `destination` and wipe it. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typesol/cli",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "Compile a TypeScript-syntax language directly to Solana SBF programs.",
5
5
  "type": "module",
6
6
  "license": "MIT",