@typesol/cli 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ until 1.0, a minor version may change the language, the IDL layout or error code
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.14.0 — 2026-10-27
9
+
10
+ PDAs are derived without the PDA syscalls, so instructions that create, check or search for PDAs use up to a few
11
+ thousand fewer compute units. No language, IDL or error-code changes. Not externally audited (see SECURITY.md).
12
+
13
+ ### Performance
14
+ - PDAs are derived with the `sol_sha256` and `sol_curve_validate_point` syscalls instead of
15
+ `sol_try_find_program_address` / `sol_create_program_address` (1,500 CU per bump tried): the
16
+ same addresses, bumps and limits (16 seeds to check, 15 to search, 32 bytes each). `init` with seeds, `Seeds<…>`,
17
+ `create`, `signer` and `findProgramAddress` all use it. Benchmarks: counter.initialize 3,030 → 1,872 CU,
18
+ escrow.make 5,974 → 3,638; in the official examples escrow take_offer 14,519 → 7,117 and close_user 1,728 → 586.
19
+ Binaries grow 0.4–1.2 KB. New tests compare it with @solana/kit's derivation over random seeds, canonical bumps
20
+ below 255 and on-curve bumps. Needs the curve25519 syscalls (active on mainnet-beta since epoch 637).
21
+
8
22
  ## 0.13.0 — 2026-10-27
9
23
 
10
24
  Every program in solana-developers/program-examples now ports to TypeSOL: PDA seeds from account fields, remaining
package/README.md CHANGED
@@ -227,19 +227,20 @@ shows mean compute units per call (generated by `scripts/readme-bench.ts` from t
227
227
  <!-- bench:table -->
228
228
  | Instruction | TypeSOL | Pinocchio 0.10 | Native Rust | Anchor 1.0.2 |
229
229
  | --- | ---: | ---: | ---: | ---: |
230
- | counter.initialize | **3,030** | 3,186 | 4,601 | 6,573 |
230
+ | counter.initialize | **1,872** | 3,186 | 4,601 | 6,573 |
231
231
  | counter.increment | **110** | 113 | 857 | 2,323 |
232
- | vault.open | **3,044** | 3,185 | 4,613 | 6,627 |
232
+ | vault.open | **1,891** | 3,185 | 4,613 | 6,627 |
233
233
  | vault.deposit | **1,314** | 1,377 | 2,832 | 4,355 |
234
234
  | vault.withdraw | **315** | 370 | 1,176 | 3,628 |
235
235
  | vault.shut | **161** | 167 | 950 | 2,024 |
236
- | escrow.make | **5,974** | 6,179 | 9,240 | 13,304 |
236
+ | escrow.make | **3,638** | 6,179 | 9,240 | 13,304 |
237
237
  | escrow.take | **4,035** | 4,051 | 8,500 | 14,365 |
238
238
  | escrow.refund | **2,716** | 2,769 | 5,695 | 8,449 |
239
- | binary, counter / vault / escrow | 12.8 / 16.1 / 23.5 KB | 19.6 / 22.9 / 29.4 KB | 27.9 / 35.0 / 46.5 KB | 145.2 / 158.9 / 190.7 KB |
239
+ | binary, counter / vault / escrow | 13.2 / 16.5 / 24.0 KB | 19.6 / 22.9 / 29.4 KB | 27.9 / 35.0 / 46.5 KB | 145.2 / 158.9 / 190.7 KB |
240
240
  <!-- /bench:table -->
241
241
 
242
- `initialize`, `open` and `make` include the PDA bump search, which costs the same in every implementation. The
242
+ `initialize`, `open` and `make` include the PDA bump search. TypeSOL hashes PDA candidates with the sha256 and
243
+ curve-check syscalls instead of the 1,500-CU PDA syscalls the others call, with the same results. The
243
244
  escrow moves SPL tokens through CPIs (`take` makes three). Like Pinocchio, TypeSOL reads accounts in place and builds
244
245
  CPI account records only for the accounts a call lists. It uses fewer compute units than Pinocchio on every
245
246
  instruction here, while also refusing duplicate writable accounts as Anchor does. Fewer compute
@@ -275,7 +276,7 @@ Details are in [docs/testing.md](docs/testing.md).
275
276
 
276
277
  ## Status
277
278
 
278
- **TypeSOL is pre-1.0 (0.13).** Releases follow the organization's
279
+ **TypeSOL is pre-1.0 (0.14).** Releases follow the organization's
279
280
  [versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md); [CHANGELOG.md](CHANGELOG.md) lists every
280
281
  change.
281
282
  - The compiler, runtime and tooling have been through internal security reviews and are covered by the tests above.
@@ -361,7 +361,7 @@ export class CallEmitter extends ExpressionEmitter {
361
361
  const t = this.tmp("pda");
362
362
  const b = this.tmp("bump");
363
363
  this.line(`SolPubkey ${t}; uint8_t ${b};`);
364
- this.line(`if (sol_try_find_program_address(${seeds.name}, ${seeds.count}, ${program.c}, &${t}, &${b}) != SUCCESS) TS_FAIL(TS_ERR_SEEDS, "TypeSOL: no program address for these seeds");`);
364
+ this.line(`if (ts_find_pda(${seeds.name}, ${seeds.count}, ${program.c}, &${t}, &${b}) != SUCCESS) TS_FAIL(TS_ERR_SEEDS, "TypeSOL: no program address for these seeds");`);
365
365
  return { c: `(&${t})`, type: PUBKEY };
366
366
  }
367
367
  case "metadataAddress": {
@@ -444,7 +444,7 @@ export class FunctionEmitter extends StatementEmitter {
444
444
  else {
445
445
  bumpDecls.push(` uint8_t sb_${a.name}; (void)sb_${a.name};`);
446
446
  out.push(` { SolPubkey pda_${a.name};`);
447
- out.push(` if (sol_try_find_program_address(${seeds}, ${parts.length}, ${program}, &pda_${a.name}, &sb_${a.name}) != SUCCESS || !ts_key_eq(&pda_${a.name}, &a_${a.name}->key)) TS_FAIL(TS_ERR_SEEDS, "TypeSOL: ${a.name} is not the expected PDA"); }`);
447
+ out.push(` if (ts_find_pda(${seeds}, ${parts.length}, ${program}, &pda_${a.name}, &sb_${a.name}) != SUCCESS || !ts_key_eq(&pda_${a.name}, &a_${a.name}->key)) TS_FAIL(TS_ERR_SEEDS, "TypeSOL: ${a.name} is not the expected PDA"); }`);
448
448
  this.bumps.set(a.name, `sb_${a.name}`);
449
449
  }
450
450
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typesol/cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Compile a TypeScript-syntax language directly to Solana SBF programs.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -305,6 +305,55 @@ static inline __attribute__((always_inline)) uint64_t ts_invoke(TsParams *params
305
305
  return sol_invoke_signed(ix, infos, ix->account_len, signers, nsigners);
306
306
  }
307
307
 
308
+ /*
309
+ * ---- Program derived addresses ------------------------------------------------
310
+ * The same addresses sol_create_program_address / sol_try_find_program_address compute — sha256 of the seeds, the
311
+ * program id and "ProgramDerivedAddress", valid only off the ed25519 curve — from the sha256 and curve-validation
312
+ * syscalls, which cost a fraction of the 1500 compute units the PDA syscalls charge per attempt. The seed limits are the
313
+ * syscalls' (at most 16 seeds of at most 32 bytes, the bump included), and so is the result for every input.
314
+ */
315
+ uint64_t sol_curve_validate_point(uint64_t curve_id, const uint8_t *point, uint8_t *result);
316
+ #define TS_MAX_PDA_SEEDS 16
317
+
318
+ /* The address of `seeds` (and `bump`, when given) under `program`; false when it lies on the curve (no PDA). */
319
+ static inline bool ts_pda_hash(const SolSignerSeed *seeds, int n, const uint8_t *bump, const SolPubkey *program, SolPubkey *out) {
320
+ SolBytes parts[TS_MAX_PDA_SEEDS + 2];
321
+ int k = 0;
322
+ for (int i = 0; i < n; i++) parts[k++] = (SolBytes){seeds[i].addr, seeds[i].len};
323
+ if (bump) parts[k++] = (SolBytes){bump, 1};
324
+ parts[k++] = (SolBytes){program->x, 32};
325
+ parts[k++] = (SolBytes){(const uint8_t *)"ProgramDerivedAddress", 21};
326
+ sol_sha256(parts, k, out->x);
327
+ uint8_t ignored;
328
+ return sol_curve_validate_point(0 /* CURVE25519_EDWARDS */, out->x, &ignored) != 0; /* 0: a valid curve point */
329
+ }
330
+
331
+ static inline bool ts_pda_seeds_ok(const SolSignerSeed *seeds, int n, int max) {
332
+ if (n > max) return false;
333
+ for (int i = 0; i < n; i++)
334
+ if (seeds[i].len > 32) return false;
335
+ return true;
336
+ }
337
+
338
+ /* sol_create_program_address: the bump is the last of `seeds`. */
339
+ static inline uint64_t ts_create_pda_address(const SolSignerSeed *seeds, int n, const SolPubkey *program, SolPubkey *out) {
340
+ if (!ts_pda_seeds_ok(seeds, n, TS_MAX_PDA_SEEDS) || !ts_pda_hash(seeds, n, NULL, program, out)) return 1; /* not SUCCESS: the PDA syscalls fail the same inputs */
341
+ return SUCCESS;
342
+ }
343
+
344
+ /* sol_try_find_program_address: the canonical (highest) bump. */
345
+ static inline uint64_t ts_find_pda(const SolSignerSeed *seeds, int n, const SolPubkey *program, SolPubkey *out, uint8_t *bump_out) {
346
+ if (!ts_pda_seeds_ok(seeds, n, TS_MAX_PDA_SEEDS - 1)) return 1; /* not SUCCESS: the PDA syscalls fail the same inputs */
347
+ for (int b = 255; b >= 0; b--) {
348
+ uint8_t bump = (uint8_t)b;
349
+ if (ts_pda_hash(seeds, n, &bump, program, out)) {
350
+ *bump_out = bump;
351
+ return SUCCESS;
352
+ }
353
+ }
354
+ return 1; /* not SUCCESS: the PDA syscalls fail the same inputs */
355
+ }
356
+
308
357
  /* ---- Account checks ------------------------------------------------------ */
309
358
  /* Copy between two places of the same type: distinct, or the very same place (then nothing to do). */
310
359
  static inline void ts_copy(void *dst, const void *src, uint64_t n) {
@@ -461,7 +510,7 @@ static inline uint64_t ts_check_program_data(const TsAccount *a, const SolPubkey
461
510
  SolSignerSeed seed = {(const uint8_t *)program_id, 32};
462
511
  SolPubkey expected;
463
512
  uint8_t bump;
464
- if (sol_try_find_program_address(&seed, 1, &TS_BPF_UPGRADEABLE_LOADER_ID, &expected, &bump) != SUCCESS || !ts_key_eq(&expected, &a->key))
513
+ if (ts_find_pda(&seed, 1, &TS_BPF_UPGRADEABLE_LOADER_ID, &expected, &bump) != SUCCESS || !ts_key_eq(&expected, &a->key))
465
514
  TS_FAIL(TS_ERR_ADDRESS, "TypeSOL: not this program's ProgramData account");
466
515
  return SUCCESS;
467
516
  }
@@ -471,7 +520,7 @@ static const uint8_t TS_EVENT_IX_TAG[8] = {0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0
471
520
  static inline uint64_t ts_check_event_authority(const TsAccount *a, const SolPubkey *program_id, uint8_t *bump) {
472
521
  SolSignerSeed seed = {(const uint8_t *)"__event_authority", 17};
473
522
  SolPubkey expected;
474
- if (sol_try_find_program_address(&seed, 1, program_id, &expected, bump) != SUCCESS || !ts_key_eq(&expected, &a->key))
523
+ if (ts_find_pda(&seed, 1, program_id, &expected, bump) != SUCCESS || !ts_key_eq(&expected, &a->key))
475
524
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: not this program's event authority");
476
525
  return SUCCESS;
477
526
  }
@@ -510,7 +559,7 @@ static inline uint64_t ts_init_zeroed(TsAccount *a, const SolPubkey *program_id,
510
559
  static inline uint64_t ts_require_pda(const TsAccount *a, const SolSignerSeed *seeds, int n,
511
560
  const SolPubkey *program_id) {
512
561
  SolPubkey derived;
513
- if (sol_create_program_address(seeds, n, program_id, &derived) != SUCCESS || !ts_key_eq(&derived, &a->key))
562
+ if (ts_create_pda_address(seeds, n, program_id, &derived) != SUCCESS || !ts_key_eq(&derived, &a->key))
514
563
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: PDA seeds do not match account");
515
564
  return SUCCESS;
516
565
  }
@@ -536,7 +585,7 @@ static inline uint64_t ts_create_pda(TsParams *params, TsAccount *payer, TsAccou
536
585
  uint8_t *bump_out) {
537
586
  SolPubkey derived;
538
587
  uint8_t bump;
539
- if (sol_try_find_program_address(seeds, n, params->program_id, &derived, &bump) != SUCCESS ||
588
+ if (ts_find_pda(seeds, n, params->program_id, &derived, &bump) != SUCCESS ||
540
589
  !ts_key_eq(&derived, &acct->key))
541
590
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: PDA seeds do not match account");
542
591
  if (acct->data_len != 0 || !ts_key_eq(&acct->owner, &TS_SYSTEM_PROGRAM_ID))
@@ -627,7 +676,7 @@ static inline uint64_t ts_create_pda_if_needed(TsParams *params, TsAccount *paye
627
676
  if (ts_key_eq(&acct->owner, params->program_id)) {
628
677
  SolPubkey derived;
629
678
  uint8_t bump;
630
- if (sol_try_find_program_address(seeds, n, params->program_id, &derived, &bump) != SUCCESS ||
679
+ if (ts_find_pda(seeds, n, params->program_id, &derived, &bump) != SUCCESS ||
631
680
  !ts_key_eq(&derived, &acct->key))
632
681
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: PDA seeds do not match account");
633
682
  if (acct->data_len < space) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account data too small");
@@ -1105,7 +1154,7 @@ static const SolPubkey TS_MPL_METADATA_ID = {{11,112,101,177,227,209,124,69,56,1
1105
1154
  static inline uint64_t ts_ata_address(const SolPubkey *owner, const SolPubkey *mint, const SolPubkey *token_program, SolPubkey *out) {
1106
1155
  SolSignerSeed seeds[3] = {{(const uint8_t *)owner, 32}, {(const uint8_t *)token_program, 32}, {(const uint8_t *)mint, 32}};
1107
1156
  uint8_t bump;
1108
- if (sol_try_find_program_address(seeds, 3, &TS_ATA_PROGRAM_ID, out, &bump) != SUCCESS)
1157
+ if (ts_find_pda(seeds, 3, &TS_ATA_PROGRAM_ID, out, &bump) != SUCCESS)
1109
1158
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: cannot derive the associated token address");
1110
1159
  return SUCCESS;
1111
1160
  }
@@ -1155,7 +1204,7 @@ static inline uint64_t ts_t22_init_metadata(TsParams *params, const TsAccount *p
1155
1204
  static inline uint64_t ts_metadata_address(const SolPubkey *mint, SolPubkey *out) {
1156
1205
  SolSignerSeed seeds[3] = {{(const uint8_t *)"metadata", 8}, {(const uint8_t *)&TS_MPL_METADATA_ID, 32}, {(const uint8_t *)mint, 32}};
1157
1206
  uint8_t bump;
1158
- if (sol_try_find_program_address(seeds, 3, &TS_MPL_METADATA_ID, out, &bump) != SUCCESS)
1207
+ if (ts_find_pda(seeds, 3, &TS_MPL_METADATA_ID, out, &bump) != SUCCESS)
1159
1208
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: cannot derive the metadata address");
1160
1209
  return SUCCESS;
1161
1210
  }
@@ -1225,7 +1274,7 @@ static inline uint64_t ts_update_extra_metas(TsParams *params, TsAccount *payer,
1225
1274
  SolSignerSeed seeds[2] = {{(const uint8_t *)"extra-account-metas", 19}, {(const uint8_t *)mint, 32}};
1226
1275
  SolPubkey pda;
1227
1276
  uint8_t bump;
1228
- if (sol_try_find_program_address(seeds, 2, params->program_id, &pda, &bump) != SUCCESS || !ts_key_eq(&pda, &acct->key))
1277
+ if (ts_find_pda(seeds, 2, params->program_id, &pda, &bump) != SUCCESS || !ts_key_eq(&pda, &acct->key))
1229
1278
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: not the mint's extra-account-metas PDA");
1230
1279
  if (!ts_key_eq(&acct->owner, params->program_id)) TS_FAIL(TS_ERR_WRONG_OWNER, "TypeSOL: the extra-account-metas account is not this program's");
1231
1280
  if (acct->data_len < 16 || ts_ld64(acct->data) != ts_ld64(TS_TRANSFER_HOOK_EXECUTE_DISC))