@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.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,54 @@
1
1
  # Changelog
2
2
 
3
- TypeSOL follows [semantic versioning](https://semver.org/) with the 0.x rules described in CONTRIBUTING.md:
4
- until 1.0, a minor version may change the language, the IDL layout or error codes, and says so here.
3
+ TypeSOL follows the organization's [versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md)
4
+ (compiler specifics in CONTRIBUTING.md): until 1.0, a minor version may change the language, the IDL layout or error
5
+ codes, and says so here.
5
6
 
6
7
  ## Unreleased
7
8
 
9
+ ## 0.25.0 — 2026-10-29
10
+
11
+ Security review (October 2026) fixes, and error codes aligned with Anchor. Some refused transactions now report a
12
+ different error code (below): a minor version.
13
+
14
+ - Fixed: `loadAccountMut` on the same remaining account twice (`rest.get(i)`, `rest.get(j)` with `i == j`) gave two
15
+ writable views of one account; a program reading both before writing both could be made to create value. The second
16
+ load is now refused with 2040.
17
+ - Fixed: a Vec element written with a right side that resizes the Vec (`book.bids.get(i).qty += settle(book)` where the
18
+ helper pops, a closure, or a `push` / `pop` / `clear` in the expression) wrote through the element's old position,
19
+ into the bytes of the next Vec. Such an assignment is now a compile error ("store its result in a local first"),
20
+ as it already was for a plain `=` with a helper.
21
+ - Fixed: `Ata<Init<…>>` / `Ata<InitIfNeeded<…>>` creation and `Ata<AccountInfo, …>` with two token program parameters
22
+ used the first one; they now need exactly one (use `TokenInterface`).
23
+ - New: `TokenOf<…, …, …, "tokenProgram">` / `MintOf<…, …, …, "tokenProgram">`, Anchor's `token::token_program` /
24
+ `mint::token_program` (2021 / 2022). The escrow port uses it.
25
+ - New: `pythPrice(update, feedId, maxAge, exponent)` refuses a feed whose exponent is not the one given;
26
+ `switchboardValue(…, minSamples)` refuses a result resting on fewer oracle samples.
27
+ - Changed: each account's constraints are checked in Anchor's order (seeds, associated token account, has_one, owner,
28
+ executable, address, token::, mint::), so a transaction breaking several fails with Anchor's code: e.g. a stranger's
29
+ call on a `Seeds<HasOne<…>>` account now fails with 2006 (ConstraintSeeds) instead of 2001, and an `Ata<…>` of
30
+ another wallet with 2015 instead of 2009. A `Mut<…>` account passed read-only fails with 2000 (ConstraintMut), as
31
+ Anchor's `#[account(mut)]`, instead of 3006. docs/errors.md lists the order and the differences left.
32
+ - Docs: unchecked raw Pyth fields, `requirePda` with a bump from the arguments, Merkle leaves, and what
33
+ `createIfNeeded` leaves unchecked on an existing account.
34
+ - Docs: [Case study: porting Anchor's escrow](docs/case-study-escrow.md), one Anchor program ported end to end, with the
35
+ differences, the checks run against the original and the cost.
36
+ - Releases: the release workflow checks that CHANGELOG.md has the version, and after publishing installs the package
37
+ from npm, scaffolds a project from each template and runs its typecheck, build and test.
38
+
39
+ ## 0.24.1 — 2026-10-28
40
+
41
+ Smaller programs and fewer compute units, with the same results; no change to the language, IDLs or error codes. Also
42
+ new contributor docs (repository layout, a guide to the C runtime). Not externally audited (see SECURITY.md).
43
+
44
+ ### Performance
45
+ - Smaller programs, same results: the rent calculation no longer links soft-float (an exact integer equivalent,
46
+ checked against IEEE doubles), and every failure logs through one shared call. The benchmarked programs are 11.6%
47
+ smaller in total (counter 13.1 → 11.0 KB, escrow 23.9 → 21.3 KB). A failing instruction costs 2 more compute units;
48
+ successful ones are unchanged.
49
+ - An identical signer-seed list in the same scope reuses the array declared for the first CPI (escrow `take_offer`
50
+ 7058 → 7044, `refund_offer` 4287 → 4271).
51
+
8
52
  ## 0.24.0 — 2026-10-28
9
53
 
10
54
  Functions as values, closures for any helper, stored `map` to structs and text, Metaplex Core external plugins, and
package/README.md CHANGED
@@ -162,8 +162,9 @@ existing project: `npm install -D typesol@npm:@typesol/cli`.
162
162
  Deploying again upgrades the program in place. Keep `target/deploy/<name>-keypair.json`: that keypair is your
163
163
  program's address.
164
164
 
165
- Coming from Anchor: [docs/migrate-from-anchor.md](docs/migrate-from-anchor.md) maps every construct, and `typesol port`
166
- starts from your program's IDL. Tests use `typesol/testing` (accounts by name, plain values; [docs/test-your-program.md](docs/test-your-program.md)). To go to
165
+ Coming from Anchor: [docs/migrate-from-anchor.md](docs/migrate-from-anchor.md) maps every construct, `typesol port`
166
+ starts from your program's IDL, and [docs/case-study-escrow.md](docs/case-study-escrow.md) follows one complete Anchor
167
+ program through the port. Tests use `typesol/testing` (accounts by name, plain values; [docs/test-your-program.md](docs/test-your-program.md)). To go to
167
168
  mainnet (wallet, cost, deploy, IDL, verification), follow [docs/mainnet.md](docs/mainnet.md).
168
169
 
169
170
  ## How it works
@@ -232,7 +233,9 @@ Subpath exports: `typesol/compiler`, `typesol/workspace`, `typesol/cpi`, `typeso
232
233
 
233
234
  The same counter, vault and escrow programs, written four ways. All four use the same instruction encoding, account
234
235
  layout and checks. They get identical transactions in LiteSVM and must end in identical account state. The table
235
- shows mean compute units per call (generated by `scripts/readme-bench.ts` from the benchmark run):
236
+ shows mean compute units per call (generated by `scripts/readme-bench.ts` from the benchmark run). The programs,
237
+ scenarios and harness are in [typesol/benchmarks](https://github.com/typesol/benchmarks), which pins the compiler and
238
+ toolchain and records them with every result:
236
239
 
237
240
  <!-- bench:table -->
238
241
  | Instruction | TypeSOL | Pinocchio 0.10 | Native Rust | Anchor 1.0.2 |
@@ -244,9 +247,9 @@ shows mean compute units per call (generated by `scripts/readme-bench.ts` from t
244
247
  | vault.withdraw | **315** | 370 | 1,176 | 3,628 |
245
248
  | vault.shut | **161** | 167 | 950 | 2,024 |
246
249
  | escrow.make | **3,605** | 6,179 | 9,240 | 13,304 |
247
- | escrow.take | **4,035** | 4,051 | 8,500 | 14,365 |
248
- | escrow.refund | **2,716** | 2,769 | 5,695 | 8,449 |
249
- | binary, counter / vault / escrow | 13.1 / 16.5 / 23.9 KB | 19.6 / 22.9 / 29.4 KB | 27.9 / 35.0 / 46.5 KB | 145.2 / 158.9 / 190.7 KB |
250
+ | escrow.take | **4,019** | 4,051 | 8,500 | 14,365 |
251
+ | escrow.refund | **2,696** | 2,769 | 5,695 | 8,449 |
252
+ | binary, counter / vault / escrow | 11.0 / 14.1 / 21.3 KB | 19.6 / 22.9 / 29.4 KB | 27.9 / 35.0 / 46.5 KB | 145.2 / 158.9 / 190.7 KB |
250
253
  <!-- /bench:table -->
251
254
 
252
255
  `initialize`, `open` and `make` include the PDA bump search. TypeSOL hashes PDA candidates with the sha256 and
@@ -307,7 +310,7 @@ Details are in [docs/testing.md](docs/testing.md).
307
310
 
308
311
  ## Status
309
312
 
310
- **TypeSOL is pre-1.0 (0.24).** Releases follow the organization's
313
+ **TypeSOL is pre-1.0.** Releases follow the organization's
311
314
  [versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md); [CHANGELOG.md](CHANGELOG.md) lists every
312
315
  change.
313
316
  - The compiler, runtime and tooling have been through internal security reviews and are covered by the tests above.
package/dist/clientgen.js CHANGED
@@ -32,6 +32,7 @@ export const TYPESOL_ERRORS = {
32
32
  100: "InstructionMissing",
33
33
  101: "InstructionFallbackNotFound",
34
34
  102: "InstructionDidNotDeserialize",
35
+ 2000: "ConstraintMut",
35
36
  2001: "ConstraintHasOne",
36
37
  2002: "ConstraintSigner",
37
38
  2004: "ConstraintOwner",
@@ -45,6 +46,8 @@ export const TYPESOL_ERRORS = {
45
46
  2016: "ConstraintMintMintAuthority",
46
47
  2017: "ConstraintMintFreezeAuthority",
47
48
  2018: "ConstraintMintDecimals",
49
+ 2021: "ConstraintTokenTokenProgram",
50
+ 2022: "ConstraintMintTokenProgram",
48
51
  2040: "ConstraintDuplicateMutableAccount",
49
52
  3000: "AccountDiscriminatorAlreadySet",
50
53
  3002: "AccountDiscriminatorMismatch",
@@ -1102,7 +1105,10 @@ function generate(idl, opts) {
1102
1105
  // A seed or program path names an account: in the same group first, then at the top level.
1103
1106
  const byPath = (p, group) => flat.find((f) => `${f.group}${f.name}` === `${group}${p}`) ?? flat.find((f) => f.group === "" && f.name === p);
1104
1107
  const argType = new Map(ix.args.map((a) => [a.name, a.type]));
1105
- const seedExpr = (s, group) => {
1108
+ const seedsProp = (k) => `seeds${NAME.test(k) && !JS_RESERVED.has(k) ? `.${k}` : `[${JSON.stringify(k)}]`}`;
1109
+ const BUILDER = { arg: (n) => `args.${n}`, account: (k) => `$${k}` };
1110
+ const HELPER = { arg: (n) => seedsProp(n), account: (k) => seedsProp(k) };
1111
+ const seedExpr = (s, group, refs = BUILDER) => {
1106
1112
  if (s.kind === "const")
1107
1113
  return bytes(s.value);
1108
1114
  if (s.kind === "arg") {
@@ -1111,7 +1117,7 @@ function generate(idl, opts) {
1111
1117
  let t = argType.get(head);
1112
1118
  if (t === undefined)
1113
1119
  return { why: `the seed argument ${head} does not exist` };
1114
- let v = `args.${camel(head)}`;
1120
+ let v = refs.arg(camel(head));
1115
1121
  for (const part of rest) {
1116
1122
  const d = defs.get(definedName(t) ?? "");
1117
1123
  const f = d?.type.kind === "struct" ? (d.type.fields ?? []).find((x) => isField(x) && x.name === part) : undefined;
@@ -1134,25 +1140,30 @@ function generate(idl, opts) {
1134
1140
  if (!target)
1135
1141
  return { why: `the seed account ${s.path} is not an account of the instruction` };
1136
1142
  use("getAddressEncoder");
1137
- return `getAddressEncoder().encode(key($${target.key}))`;
1143
+ return `getAddressEncoder().encode(key(${refs.account(target.key)}))`;
1138
1144
  };
1139
- const resolvable = new Map();
1140
- for (const a of flat) {
1141
- if (!a.pda || a.address)
1142
- continue;
1143
- const seeds = a.pda.seeds.map((s) => seedExpr(s, a.group));
1144
- let program;
1145
+ /** A PDA's program as the code being written names it (undefined: this program). */
1146
+ const pdaProgram = (a, refs) => {
1145
1147
  const p = a.pda.program;
1146
1148
  if (p?.kind === "const")
1147
- program = (use("getAddressDecoder"), `programFromBytes(${bytes(p.value)})`);
1148
- else if (p?.kind === "account") {
1149
+ return (use("getAddressDecoder"), `programFromBytes(${bytes(p.value)})`);
1150
+ if (p?.kind === "account") {
1149
1151
  const target = p.path.includes(".") ? undefined : byPath(p.path, a.group);
1150
- program = target ? `key($${target.key})` : { why: `the PDA program ${p.path} is not an account of the instruction` };
1152
+ return target ? `key(${refs.account(target.key)})` : { why: `the PDA program ${p.path} is not an account of the instruction` };
1151
1153
  }
1152
- else if (p?.kind === "arg") {
1154
+ if (p?.kind === "arg") {
1153
1155
  const t = p.path.includes(".") ? undefined : argType.get(p.path);
1154
- program = t === "pubkey" || t === "publicKey" ? `args.${camel(p.path)}` : { why: "the PDA program comes from an argument that is not a Pubkey" };
1156
+ return t === "pubkey" || t === "publicKey" ? refs.arg(camel(p.path)) : { why: "the PDA program comes from an argument that is not a Pubkey" };
1155
1157
  }
1158
+ return undefined;
1159
+ };
1160
+ const resolvable = new Map();
1161
+ for (const a of flat) {
1162
+ if (!a.pda || a.address)
1163
+ continue;
1164
+ const seeds = a.pda.seeds.map((s) => seedExpr(s, a.group));
1165
+ const program = pdaProgram(a, BUILDER);
1166
+ const p = a.pda.program;
1156
1167
  const why = [...seeds, ...(program === undefined ? [] : [program])].find((x) => typeof x === "object");
1157
1168
  if (why) {
1158
1169
  notes.push(`instruction ${ix.name}: ${a.name} is not filled in (${why.why}); pass it`);
@@ -1228,11 +1239,13 @@ function generate(idl, opts) {
1228
1239
  return [k, `${k}: Address | TransactionSigner`];
1229
1240
  })).values(),
1230
1241
  ];
1231
- const rewrite = (e) => e.replace(/\bargs\.([A-Za-z0-9_$]+)/g, "seeds.$1").replace(/key\(\$([A-Za-z0-9_]+)\)/g, "key(seeds.$1)");
1242
+ // The same derivation, its inputs named as the helper's `seeds` parameter.
1243
+ const helperSeeds = a.pda.seeds.map((s) => seedExpr(s, a.group, HELPER));
1244
+ const helperProgram = pdaProgram(a, HELPER) ?? "PROGRAM_ADDRESS";
1232
1245
  use("type ProgramDerivedAddress");
1233
1246
  pdaFns.set(pn, {
1234
1247
  json,
1235
- source: ` /** The ${a.name} PDA (as ${ix.name} derives it). */\n ${prop(pn)}(seeds: { ${params.join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${rewrite(r.program ?? "PROGRAM_ADDRESS")}, seeds: [${r.seeds.map(rewrite).join(", ")}] });\n },`,
1248
+ source: ` /** The ${a.name} PDA (as ${ix.name} derives it). */\n ${prop(pn)}(seeds: { ${params.join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${helperProgram}, seeds: [${helperSeeds.join(", ")}] });\n },`,
1236
1249
  });
1237
1250
  }
1238
1251
  const metas = flat.map((a) => {
@@ -1326,6 +1339,7 @@ function generate(idl, opts) {
1326
1339
  }
1327
1340
  const bases = new Map();
1328
1341
  const seeds = [];
1342
+ const helperSeeds = []; // the same seeds, named as a `pdas` helper's `seeds` (for one seeded from its own fields)
1329
1343
  const own = [];
1330
1344
  // Inputs of a `pdas` helper for a PDA seeded from its own fields: its own field values, accounts and arguments.
1331
1345
  const params = new Map();
@@ -1334,13 +1348,14 @@ function generate(idl, opts) {
1334
1348
  let usesArgs = p?.kind === "arg";
1335
1349
  for (const sd of a.pda.seeds) {
1336
1350
  if (sd.kind === "const")
1337
- seeds.push(bytes(sd.value));
1351
+ (seeds.push(bytes(sd.value)), helperSeeds.push(bytes(sd.value)));
1338
1352
  else if (sd.kind === "arg") {
1339
1353
  const e = seedExpr(sd, a.group);
1340
1354
  if (typeof e !== "string")
1341
1355
  ok = false;
1342
1356
  else {
1343
1357
  seeds.push(e);
1358
+ helperSeeds.push(seedExpr(sd, a.group, HELPER));
1344
1359
  usesArgs = true;
1345
1360
  const head = sd.path.split(".")[0];
1346
1361
  params.set(camel(head), `${camel(head)}: ${tsType(argType.get(head), "in")}`);
@@ -1353,6 +1368,7 @@ function generate(idl, opts) {
1353
1368
  else {
1354
1369
  use("getAddressEncoder");
1355
1370
  seeds.push(`getAddressEncoder().encode(key(${acc(t)}!))`);
1371
+ helperSeeds.push(`getAddressEncoder().encode(key(${seedsProp(t.key)}))`);
1356
1372
  params.set(t.key, `${t.key}: Address | TransactionSigner`);
1357
1373
  if (auto(t))
1358
1374
  autoDep = true;
@@ -1370,6 +1386,7 @@ function generate(idl, opts) {
1370
1386
  own.push({ field: parts[1], type: field.type });
1371
1387
  params.set(camel(parts[1]), `${camel(parts[1])}: ${tsType(field.type, "in")}`);
1372
1388
  seeds.push(enc);
1389
+ helperSeeds.push(enc);
1373
1390
  }
1374
1391
  else if (!consts || auto(base))
1375
1392
  ok = false;
@@ -1399,13 +1416,11 @@ function generate(idl, opts) {
1399
1416
  continue;
1400
1417
  if (p?.kind === "arg")
1401
1418
  params.set(camel(p.path), `${camel(p.path)}: Address`);
1402
- const rewrite = (e) => e
1403
- .replace(/\bargs\.([A-Za-z0-9_$]+)/g, "seeds.$1")
1404
- .replace(/key\(ownProp\(acc, "([^"]+)"\)!\)/g, (_, k) => `key(seeds${NAME.test(k) && !JS_RESERVED.has(k) ? `.${k}` : `[${JSON.stringify(k)}]`})`);
1419
+ const helperProgram = p?.kind === "arg" ? seedsProp(camel(p.path)) : program;
1405
1420
  use("type ProgramDerivedAddress", "getProgramDerivedAddress");
1406
1421
  pdaFns.set(pn, {
1407
1422
  json,
1408
- source: ` /** The ${a.name} PDA (as ${ix.name} derives it), from the values of its own fields it is seeded with (${own.map((o) => camel(o.field)).join(", ")}). */\n ${prop(pn)}(seeds: { ${[...params.values()].join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${rewrite(program)}, seeds: [${seeds.map(rewrite).join(", ")}] });\n },`,
1423
+ source: ` /** The ${a.name} PDA (as ${ix.name} derives it), from the values of its own fields it is seeded with (${own.map((o) => camel(o.field)).join(", ")}). */\n ${prop(pn)}(seeds: { ${[...params.values()].join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${helperProgram}, seeds: [${helperSeeds.join(", ")}] });\n },`,
1409
1424
  });
1410
1425
  continue;
1411
1426
  }
@@ -680,7 +680,7 @@ export function analyze(entry) {
680
680
  dataEnums,
681
681
  options,
682
682
  constants,
683
- events: new Set(),
683
+ events: collectEvents([...functions, ...helperNodes], structs),
684
684
  accountStructs,
685
685
  argStructs,
686
686
  fstrings,
@@ -1164,6 +1164,26 @@ function fnParamType(arrow, checker) {
1164
1164
  }
1165
1165
  return undefined;
1166
1166
  }
1167
+ /**
1168
+ * The structs a program emits (`emit<T>(…)` / `emitCpi<T>(…)` anywhere in its functions and helpers), known before
1169
+ * any code is emitted, so the IDL and the event checks do not depend on the order code is emitted in.
1170
+ */
1171
+ function collectEvents(sources, structs) {
1172
+ const events = new Set();
1173
+ for (const { node, imports } of sources) {
1174
+ const visit = (n) => {
1175
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
1176
+ const name = imports.get(n.expression.text);
1177
+ const t = n.typeArguments?.[0];
1178
+ if ((name === "emit" || name === "emitCpi") && t && ts.isTypeReferenceNode(t) && ts.isIdentifier(t.typeName) && structs.has(t.typeName.text))
1179
+ events.add(t.typeName.text);
1180
+ }
1181
+ ts.forEachChild(n, visit);
1182
+ };
1183
+ visit(node);
1184
+ }
1185
+ return events;
1186
+ }
1167
1187
  /** Whether a function returns a function: typed so, or its body is (or only returns) an arrow. */
1168
1188
  function returnsFunction(fn) {
1169
1189
  if (fn.type)
@@ -1453,6 +1473,13 @@ function analyzeInstruction(fn, reporter, scope, structs) {
1453
1473
  if (entry.includes("=")) {
1454
1474
  if (!accounts.some((o) => o.name === param))
1455
1475
  reporter.fail(a.node, `add an account parameter named "${param}"`);
1476
+ if (f === "tokenProgram") {
1477
+ if (a.kind.kind !== "token" && a.kind.kind !== "mint")
1478
+ reporter.fail(a.node, "TokenOf / MintOf apply to a TokenAccount / Mint");
1479
+ if (accounts.find((o) => o.name === param).kind.kind !== "tokenProgram")
1480
+ reporter.fail(a.node, `"${param}" must be a TokenProgram, Token2022Program or TokenInterface parameter`);
1481
+ continue;
1482
+ }
1456
1483
  if (a.kind.kind === "token" ? f !== "mint" && f !== "owner" : a.kind.kind === "mint" ? false : true)
1457
1484
  reporter.fail(a.node, a.kind.kind === "token" || a.kind.kind === "mint" ? "TokenOf<…> applies to a TokenAccount" : "TokenOf / MintOf apply to a TokenAccount / Mint");
1458
1485
  if (a.kind.kind === "mint" && f !== "mintAuthority" && f !== "freezeAuthority")
@@ -1590,12 +1617,15 @@ function unwrapConstraints(node, reporter, imports, out) {
1590
1617
  const one = (t) => (t && t.kind !== ts.SyntaxKind.NeverKeyword ? lit(t)[0] : undefined);
1591
1618
  const p1 = one(args[1]);
1592
1619
  const p2 = one(args[2]);
1593
- if (!p1 && !p2)
1620
+ const p3 = one(args[3]);
1621
+ if (!p1 && !p2 && !p3)
1594
1622
  reporter.fail(node, `${name}<…> names at least one parameter`);
1595
1623
  if (p1)
1596
1624
  out.hasOne.push(`${first}=${p1}`);
1597
1625
  if (p2)
1598
1626
  out.hasOne.push(`${second}=${p2}`);
1627
+ if (p3)
1628
+ out.hasOne.push(`tokenProgram=${p3}`);
1599
1629
  }
1600
1630
  if (name === "CloseTo")
1601
1631
  out.closeTo = lit(args[1])[0];
@@ -24,6 +24,16 @@ export declare abstract class EmitterBase {
24
24
  protected readonly pendingInit: Set<string>;
25
25
  /** Bump temporaries from create()/createIfNeeded(), for `acct.pdaBump`. */
26
26
  protected readonly bumps: Map<string, string>;
27
+ /**
28
+ * Seed arrays already declared (no spare slot, every seed read in place): the C entries, and where the array is.
29
+ * An identical list in the same or an inner block reuses it (see seeds()).
30
+ */
31
+ protected readonly seedArrays: Map<string, {
32
+ name: string;
33
+ scope: Map<string, Local>;
34
+ at: number;
35
+ text: string;
36
+ }>;
27
37
  /** Closures being expanded, innermost last: a `return` inside one assigns `result` and jumps to `label`. */
28
38
  protected inlineFrames: {
29
39
  label: string;
@@ -23,6 +23,11 @@ export class EmitterBase {
23
23
  pendingInit = new Set();
24
24
  /** Bump temporaries from create()/createIfNeeded(), for `acct.pdaBump`. */
25
25
  bumps = new Map();
26
+ /**
27
+ * Seed arrays already declared (no spare slot, every seed read in place): the C entries, and where the array is.
28
+ * An identical list in the same or an inner block reuses it (see seeds()).
29
+ */
30
+ seedArrays = new Map();
26
31
  /** Closures being expanded, innermost last: a `return` inside one assigns `result` and jumps to `label`. */
27
32
  inlineFrames = [];
28
33
  /** Closures being expanded (a closure calling itself cannot be expanded). */
@@ -300,6 +305,7 @@ export class EmitterBase {
300
305
  created: new Set(this.created),
301
306
  pendingInit: new Set(this.pendingInit),
302
307
  bumps: new Map(this.bumps),
308
+ seedArrays: new Map(this.seedArrays),
303
309
  };
304
310
  try {
305
311
  return this.reporter.guard(fn);
@@ -322,6 +328,8 @@ export class EmitterBase {
322
328
  }
323
329
  this.bumps.clear();
324
330
  saved.bumps.forEach((v, k) => this.bumps.set(k, v));
331
+ this.seedArrays.clear();
332
+ saved.seedArrays.forEach((v, k) => this.seedArrays.set(k, v));
325
333
  }
326
334
  }
327
335
  fail(node, message) {
@@ -3,7 +3,7 @@ import { calleeName, resolveScalarType, resolveValueType } from "../analyze.js";
3
3
  import { ANCHOR_ERRORS } from "../anchor-errors.js";
4
4
  import { decodeAddress } from "../../keys.js";
5
5
  import { INT_INFO, INT_NAMES, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
6
- import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, emptySizeC, vecLenCheck, } from "./shared.js";
6
+ import { U64, U8, I64, I32, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, emptySizeC, vecLenCheck, } from "./shared.js";
7
7
  import { ExpressionEmitter } from "./expressions.js";
8
8
  /**
9
9
  * Calls: built-in functions, Vec methods, helpers, remaining accounts, foreign account reads and return data.
@@ -344,29 +344,29 @@ export class CallEmitter extends ExpressionEmitter {
344
344
  return { c: `ts_secp256k1_verify(${h.c}, (uint64_t)${id.c}, ${r.c}, ${sv.c}, ${eth.c})`, type: BOOL };
345
345
  }
346
346
  case "pythPrice": {
347
- if (args.length !== 3)
348
- this.fail(node, "pythPrice(update, feedId, maxAgeSeconds) takes three arguments");
347
+ if (args.length !== 3 && args.length !== 4)
348
+ this.fail(node, "pythPrice(update, feedId, maxAgeSeconds, exponent?) takes three or four arguments");
349
349
  const u = this.accountParam(args[0], "the price update");
350
350
  if (u.kind.kind !== "pyth")
351
351
  this.fail(args[0], "pass a PythPriceUpdate parameter");
352
- const [feed, age] = this.lowerInOrder(args.slice(1), [HASH, U64]);
352
+ const [feed, age, expo] = this.lowerInOrder(args.slice(1), [HASH, U64, I32]);
353
353
  const t = this.tmp("px");
354
354
  this.line(`int64_t ${t};`);
355
- this.line(`TS_TRY(ts_pyth_price(a_${u.name}, ${feed.c}, ${age.c}, &${t}));`);
355
+ this.line(`TS_TRY(ts_pyth_price(a_${u.name}, ${feed.c}, ${age.c}, ${expo ? `true, ${expo.c}` : "false, 0"}, &${t}));`);
356
356
  return { c: t, type: I64 };
357
357
  }
358
358
  case "switchboardValue": {
359
359
  // Anyone can create a Switchboard feed: like pythPrice's feed id, the expected feed address is required.
360
- if (args.length !== 4)
361
- this.fail(node, "switchboardValue(feed, expectedFeed, maxStaleSlots, decimals) takes four arguments");
360
+ if (args.length !== 4 && args.length !== 5)
361
+ this.fail(node, "switchboardValue(feed, expectedFeed, maxStaleSlots, decimals, minSamples?) takes four or five arguments");
362
362
  const f = this.accountParam(args[0], "the feed");
363
363
  if (f.kind.kind !== "switchboard")
364
364
  this.fail(args[0], "pass a SwitchboardFeed parameter");
365
- const [expected, stale, dec] = this.lowerInOrder(args.slice(1), [PUBKEY, U64, U8]);
365
+ const [expected, stale, dec, min] = this.lowerInOrder(args.slice(1), [PUBKEY, U64, U8, U8]);
366
366
  const t = this.tmp("sb");
367
367
  this.line(`int64_t ${t};`);
368
368
  this.line(`if (!ts_key_eq(&a_${f.name}->key, ${expected.c})) TS_FAIL(TS_ERR_ORACLE, "TypeSOL: not the expected Switchboard feed");`);
369
- this.line(`TS_TRY(ts_switchboard_value(a_${f.name}, ${stale.c}, ${dec.c}, &${t}));`);
369
+ this.line(`TS_TRY(ts_switchboard_value(a_${f.name}, ${stale.c}, ${dec.c}, ${min ? min.c : "1"}, &${t}));`);
370
370
  return { c: t, type: I64 };
371
371
  }
372
372
  case "associatedTokenAddress": {
@@ -980,7 +980,7 @@ export class CallEmitter extends ExpressionEmitter {
980
980
  this.line(l);
981
981
  if (mut) {
982
982
  this.line(`TS_TRY(ts_check_writable(${h}));`);
983
- this.line(`TS_TRY(ts_check_unaliased(params, ${h}));`);
983
+ this.line(`TS_TRY(ts_claim_mut(params, ${h}));`);
984
984
  }
985
985
  return { c: `((TSS_${s.name} *)(${h}->data + 8))`, type: { kind: "structRef", struct: s.name, writable: mut } };
986
986
  }
@@ -103,8 +103,17 @@ export declare abstract class CpiEmitter extends CallEmitter {
103
103
  protected corePlugin(n: ts.Expression, withAuthority: boolean): MplField[];
104
104
  /** CreateV1 / CreateCollectionV1 plugins: Option<Vec<PluginAuthorityPair>>. */
105
105
  protected corePluginList(n: ts.Expression | undefined): MplField[];
106
- /** Metaplex Core: assets and collections (with plugins), plugins added, updated and removed, transfer and burn. */
106
+ /**
107
+ * Metaplex Core: assets and collections (with plugins), plugins and external plugin adapters added, updated, written
108
+ * and removed, transfer and burn. Each method is a row of CORE_OPS below: its accounts in order, its data.
109
+ */
107
110
  protected coreOp(node: ts.CallExpression, core: AccountParam, method: string): undefined;
111
+ /**
112
+ * Core instructions: `args` fixed arguments (then an optional plugin list when `plugins`, then optional signer
113
+ * seeds), accounts in order (`[argument, label, writable, signer]`, or "self": the Core program, Core's "none" for an
114
+ * optional account), at most `max` bytes of data.
115
+ */
116
+ private coreOps;
108
117
  /** The fields of a Core object literal (`{ kind: "AppData", … }`) by name. */
109
118
  private coreProps;
110
119
  /** PluginAuthority: "none" | "owner" | "updateAuthority" (literals), or an address. */