@typesol/cli 0.18.0 → 0.20.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 +37 -0
- package/README.md +1 -1
- package/dist/clientgen.d.ts +4 -1
- package/dist/clientgen.js +201 -23
- package/dist/compiler/analyze.js +36 -4
- package/dist/compiler/anchor-errors.d.ts +1 -0
- package/dist/compiler/anchor-errors.js +80 -0
- package/dist/compiler/emit/calls.js +8 -0
- package/dist/compiler/emit/cpi.d.ts +2 -1
- package/dist/compiler/emit/cpi.js +112 -6
- package/dist/compiler/emit/expressions.d.ts +19 -0
- package/dist/compiler/emit/expressions.js +71 -16
- package/dist/compiler/emit/function.js +6 -4
- package/dist/compiler/emit/statements.js +17 -0
- package/docs/client.md +31 -8
- package/docs/language.md +5 -6
- package/docs/migrate-from-anchor.md +3 -1
- package/lib/typesol.d.ts +132 -1
- package/package.json +1 -1
- package/runtime/typesol_rt.h +98 -22
|
@@ -396,8 +396,6 @@ export class CpiEmitter extends CallEmitter {
|
|
|
396
396
|
const fixedArgs = isMint ? (extArg ? 6 : 5) : 4;
|
|
397
397
|
if (a.length !== fixedArgs && a.length !== fixedArgs + 1)
|
|
398
398
|
this.fail(node, usage);
|
|
399
|
-
if (extArg && ifNeeded)
|
|
400
|
-
this.fail(extArg, "createIfNeeded does not take extensions yet: create the mint with create(…)");
|
|
401
399
|
const payer = this.accountParam(a[0], "the payer");
|
|
402
400
|
if (payer.kind.kind !== "signer" || !payer.kind.mut)
|
|
403
401
|
this.fail(a[0], "the payer must be a Mut<Signer>");
|
|
@@ -438,6 +436,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
438
436
|
};
|
|
439
437
|
let mint;
|
|
440
438
|
const extLines = [];
|
|
439
|
+
const extChecks = [];
|
|
441
440
|
let mintSpace = "TS_MINT_LEN";
|
|
442
441
|
if (isMint) {
|
|
443
442
|
take(a[2], U8);
|
|
@@ -447,9 +446,10 @@ export class CpiEmitter extends CallEmitter {
|
|
|
447
446
|
if (extArg) {
|
|
448
447
|
if (programs[0].kind.kind === "tokenProgram" && programs[0].kind.flavor === "token")
|
|
449
448
|
this.fail(extArg, "mint extensions are Token-2022's; use Token2022Program or TokenInterface");
|
|
450
|
-
const ext = this.mintExtensions(extArg, tp, acct);
|
|
449
|
+
const ext = this.mintExtensions(extArg, tp, acct, ifNeeded);
|
|
451
450
|
mintSpace = String(ext.space);
|
|
452
451
|
extLines.push(...ext.lines);
|
|
452
|
+
extChecks.push(...ext.checks);
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
455
|
else {
|
|
@@ -474,6 +474,8 @@ export class CpiEmitter extends CallEmitter {
|
|
|
474
474
|
this.line(` TS_TRY(ts_token_existing(params, ${acct}, ${tp}, ${seeds.name}, ${seeds.count}, &${b}));`);
|
|
475
475
|
this.line(` TS_TRY(ts_check_mint(${acct}));`);
|
|
476
476
|
this.line(` TS_TRY(ts_check_mint_is(${acct}, (uint8_t)(${decimals.c}), ${authority.c}, ${freeze ? freeze.c : "NULL"}));`);
|
|
477
|
+
for (const l of extChecks)
|
|
478
|
+
this.line(` ${l}`);
|
|
477
479
|
this.line(`}`);
|
|
478
480
|
}
|
|
479
481
|
else
|
|
@@ -510,7 +512,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
510
512
|
* in source order; the returned lines (each extension's Token-2022 initializer, mint writable, before
|
|
511
513
|
* InitializeMint2) run after the account is created, sized for exactly these extensions.
|
|
512
514
|
*/
|
|
513
|
-
mintExtensions(lit, tp, acct) {
|
|
515
|
+
mintExtensions(lit, tp, acct, ifNeeded = false) {
|
|
514
516
|
const optKey = { kind: "option", inner: PUBKEY };
|
|
515
517
|
this.model.options.set("pubkey", PUBKEY);
|
|
516
518
|
// name: [extension type length, initializer tag, the fields an object value takes ([] for a plain value)]
|
|
@@ -676,8 +678,60 @@ export class CpiEmitter extends CallEmitter {
|
|
|
676
678
|
}
|
|
677
679
|
}
|
|
678
680
|
}
|
|
681
|
+
// An existing mint (createIfNeeded) is checked as Anchor's `extensions::…` constraints check it: the extension must
|
|
682
|
+
// be there (its Anchor code) and each key must match (the next codes). Anchor has constraints for these six only.
|
|
683
|
+
const CHECKS = {
|
|
684
|
+
groupPointer: {
|
|
685
|
+
type: 20,
|
|
686
|
+
code: 2024,
|
|
687
|
+
fields: [
|
|
688
|
+
["authority", 0],
|
|
689
|
+
["groupAddress", 32],
|
|
690
|
+
],
|
|
691
|
+
},
|
|
692
|
+
groupMemberPointer: {
|
|
693
|
+
type: 21,
|
|
694
|
+
code: 2027,
|
|
695
|
+
fields: [
|
|
696
|
+
["authority", 0],
|
|
697
|
+
["memberAddress", 32],
|
|
698
|
+
],
|
|
699
|
+
},
|
|
700
|
+
metadataPointer: {
|
|
701
|
+
type: 18,
|
|
702
|
+
code: 2030,
|
|
703
|
+
fields: [
|
|
704
|
+
["authority", 0],
|
|
705
|
+
["metadataAddress", 32],
|
|
706
|
+
],
|
|
707
|
+
},
|
|
708
|
+
closeAuthority: { type: 3, code: 2033, fields: [["v", 0]] },
|
|
709
|
+
permanentDelegate: { type: 12, code: 2035, fields: [["v", 0]] },
|
|
710
|
+
transferHook: {
|
|
711
|
+
type: 14,
|
|
712
|
+
code: 2037,
|
|
713
|
+
fields: [
|
|
714
|
+
["authority", 0],
|
|
715
|
+
["programId", 32],
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
const checks = [];
|
|
720
|
+
if (ifNeeded)
|
|
721
|
+
for (const [name, v] of held) {
|
|
722
|
+
const c = CHECKS[name];
|
|
723
|
+
if (!c)
|
|
724
|
+
this.fail(lit, `${name} cannot be checked on an existing mint (Anchor has no constraint for it): create the mint with create(…)`);
|
|
725
|
+
checks.push(`{ uint64_t eo, el; if (!ts_t22_ext(${acct}, ${c.type}, &eo, &el)) TS_FAIL(${c.code}, "TypeSOL: the mint has no ${name} extension"); }`);
|
|
726
|
+
c.fields.forEach(([f, at], i) => {
|
|
727
|
+
const held = v.get(f);
|
|
728
|
+
// The value given: a nonzero-or-zero key, a COption (close authority) or a plain key (permanent delegate).
|
|
729
|
+
const want = name === "closeAuthority" ? `(${held}.some ? ${held}.v : (SolPubkey){0})` : held;
|
|
730
|
+
checks.push(`{ SolPubkey k, w = ${want}; if (!ts_t22_key(${acct}, ${c.type}, ${at}, &k)) ts_memzero(&k, 32); if (!ts_key_eq(&k, &w)) TS_FAIL(${c.code + 1 + i}, "TypeSOL: the mint's ${name} ${f === "v" ? "key" : f} differs"); }`);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
679
733
|
// Base account (165, the mint padded to an account's size), the account type byte, then each TLV entry.
|
|
680
|
-
return { space: 165 + 1 + space, lines };
|
|
734
|
+
return { space: 165 + 1 + space, lines, checks };
|
|
681
735
|
}
|
|
682
736
|
/**
|
|
683
737
|
* `ata.create(payer)` / `ata.createIfNeeded(payer)` on `Ata<Init|InitIfNeeded<TokenAccount>, "owner", "mint">`
|
|
@@ -1847,6 +1901,58 @@ export class CpiEmitter extends CallEmitter {
|
|
|
1847
1901
|
this.cpi(p, metas, data.buf, data.len, seedsAt(13));
|
|
1848
1902
|
return undefined;
|
|
1849
1903
|
}
|
|
1904
|
+
case "mintToCollection": {
|
|
1905
|
+
// MintToCollectionV1: MintV1's accounts plus the collection authority (signing), its record PDA (or null),
|
|
1906
|
+
// the collection mint, metadata (writable) and master edition, Bubblegum's signer PDA and Token Metadata;
|
|
1907
|
+
// MetadataArgs with Some({ verified: false, key: collection mint }) (Bubblegum verifies it).
|
|
1908
|
+
if (a.length !== 17 && a.length !== 18)
|
|
1909
|
+
this.fail(node, "mintToCollection(treeConfig, leafOwner, leafDelegate, merkleTree, payer, treeDelegate, collectionAuthority, collectionAuthorityRecordPda | null, collectionMint, collectionMetadata, collectionEdition, bubblegumSigner, logWrapper, compressionProgram, tokenMetadataProgram, systemProgram, { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable?, editionNonce? }, signerSeeds?)");
|
|
1910
|
+
const metas = [
|
|
1911
|
+
acct(0, "the tree config", true, false),
|
|
1912
|
+
acct(1, "the leaf owner", false, false),
|
|
1913
|
+
acct(2, "the leaf delegate", false, false),
|
|
1914
|
+
acct(3, "the merkle tree", true, false),
|
|
1915
|
+
acct(4, "the payer", false, true),
|
|
1916
|
+
acct(5, "the tree delegate", false, true),
|
|
1917
|
+
acct(6, "the collection authority", false, true),
|
|
1918
|
+
acct(7, "the collection authority record", false, false),
|
|
1919
|
+
acct(8, "the collection mint", false, false),
|
|
1920
|
+
acct(9, "the collection metadata", true, false),
|
|
1921
|
+
acct(10, "the collection edition", false, false),
|
|
1922
|
+
acct(11, "Bubblegum's signer", false, false),
|
|
1923
|
+
acct(12, "the log wrapper", false, false),
|
|
1924
|
+
acct(13, "the compression program", false, false),
|
|
1925
|
+
acct(14, "Token Metadata", false, false),
|
|
1926
|
+
acct(15, "the system program", false, false),
|
|
1927
|
+
];
|
|
1928
|
+
const md = a[16];
|
|
1929
|
+
if (!ts.isObjectLiteralExpression(md))
|
|
1930
|
+
this.fail(md, "the metadata is { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable?, editionNonce? }");
|
|
1931
|
+
const props = this.literalProps(md, ["name", "symbol", "uri", "sellerFeeBasisPoints", "creators", "isMutable", "editionNonce"], "the metadata");
|
|
1932
|
+
const need = (k) => props.get(k) ?? this.fail(md, `the metadata needs ${k}`);
|
|
1933
|
+
// `collectionMint.key`, read where the call is.
|
|
1934
|
+
const key = ts.factory.createPropertyAccessExpression(a[8], "key");
|
|
1935
|
+
ts.setTextRange(key, a[8]);
|
|
1936
|
+
key.parent = node;
|
|
1937
|
+
const nonce = props.get("editionNonce");
|
|
1938
|
+
const data = this.mplData(node, 1024, [
|
|
1939
|
+
{ bytes: [153, 18, 178, 47, 197, 158, 86, 15] },
|
|
1940
|
+
{ value: need("name"), type: STRING },
|
|
1941
|
+
{ value: need("symbol"), type: STRING },
|
|
1942
|
+
{ value: need("uri"), type: STRING },
|
|
1943
|
+
{ value: need("sellerFeeBasisPoints"), type: { kind: "int", name: "u16" } },
|
|
1944
|
+
{ bytes: [0] }, // primarySaleHappened
|
|
1945
|
+
props.get("isMutable") ? { value: props.get("isMutable"), type: BOOL } : { bytes: [1] },
|
|
1946
|
+
...(nonce ? [{ bytes: [1] }, { value: nonce, type: U8 }] : [{ bytes: [0] }]),
|
|
1947
|
+
{ bytes: [1, 0] }, // tokenStandard Some(NonFungible)
|
|
1948
|
+
{ bytes: [1, 0] }, // collection Some({ verified: false, … })
|
|
1949
|
+
{ value: key, type: PUBKEY },
|
|
1950
|
+
{ bytes: [0, 0] }, // uses None, tokenProgramVersion Original
|
|
1951
|
+
...this.bubblegumCreators(props.get("creators")),
|
|
1952
|
+
]);
|
|
1953
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(17));
|
|
1954
|
+
return undefined;
|
|
1955
|
+
}
|
|
1850
1956
|
}
|
|
1851
1957
|
return this.bubblegumV2(node, bg, method);
|
|
1852
1958
|
}
|
|
@@ -2128,7 +2234,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
2128
2234
|
this.cpi(p, metas, data.buf, data.len, seedsAt(proofAt + 1), proofRange(a[proofAt]));
|
|
2129
2235
|
return undefined;
|
|
2130
2236
|
}
|
|
2131
|
-
this.fail(node.expression, `Bubblegum supports createTree, mint, createTreeV2, mintV2, transferV2, burnV2, ${Object.keys(BUBBLEGUM_OPS).join(", ")}, not ${method}`);
|
|
2237
|
+
this.fail(node.expression, `Bubblegum supports createTree, mint, mintToCollection, createTreeV2, mintV2, transferV2, burnV2, ${Object.keys(BUBBLEGUM_OPS).join(", ")}, not ${method}`);
|
|
2132
2238
|
}
|
|
2133
2239
|
/** Object literal properties by name, refusing others. */
|
|
2134
2240
|
literalProps(o, allowed, what) {
|
|
@@ -23,6 +23,25 @@ export declare abstract class ExpressionEmitter extends StorageEmitter {
|
|
|
23
23
|
/** Whether TypeScript sees this expression as a string (decides `+` between concatenation and arithmetic). */
|
|
24
24
|
protected looksLikeString(node: ts.Expression): boolean;
|
|
25
25
|
/** A string view of a string, integer or boolean (for concatenation and templates). */
|
|
26
|
+
/**
|
|
27
|
+
* A template literal's pieces in order, text known at compile time folded in: all text (`text`), or a C array of
|
|
28
|
+
* TS_Str (`arr`, `n` entries). When a later piece runs statements, earlier pieces that view account text are copied
|
|
29
|
+
* first, as JS reads them then.
|
|
30
|
+
*/
|
|
31
|
+
protected templatePieces(node: ts.TemplateExpression): {
|
|
32
|
+
text: string;
|
|
33
|
+
arr?: undefined;
|
|
34
|
+
n?: undefined;
|
|
35
|
+
} | {
|
|
36
|
+
text?: undefined;
|
|
37
|
+
arr: string;
|
|
38
|
+
n: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
|
|
42
|
+
* literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
|
|
43
|
+
*/
|
|
44
|
+
protected constantText(e: ts.Expression): string | undefined;
|
|
26
45
|
protected toText(node: ts.Expression): string;
|
|
27
46
|
/** C statement writing the decimal text of an integer into the TS_Str `out`. */
|
|
28
47
|
protected textOfInt(name: IntName, c: string, out: string): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { base58 } from "../../keys.js";
|
|
2
3
|
import { calleeName, resolveValueType } from "../analyze.js";
|
|
3
4
|
import { INT_INFO, unsignedC, optionKey, sameType, typeName } from "../model.js";
|
|
4
5
|
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cString, intLiteral, scalarStorageC, optC, valueC, vecCount } from "./shared.js";
|
|
@@ -151,22 +152,14 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
151
152
|
return { c: `ts_str((const uint8_t *)${cString(node.text)}, ${len})`, type: STRING, stringLiteral: node.text };
|
|
152
153
|
}
|
|
153
154
|
if (ts.isTemplateExpression(node)) {
|
|
154
|
-
// `a${x}b${y}c
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
]) {
|
|
163
|
-
const next = this.tmp("tpl");
|
|
164
|
-
this.line(`TS_Str ${next};`);
|
|
165
|
-
this.line(`TS_TRY(ts_str_concat(${acc}, ${piece}, &${next}));`);
|
|
166
|
-
acc = next;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return { c: acc, type: STRING };
|
|
155
|
+
// `a${x}b${y}c`: one join on the heap (or a literal when every piece is known at compile time).
|
|
156
|
+
const pieces = this.templatePieces(node);
|
|
157
|
+
if (pieces.text !== undefined)
|
|
158
|
+
return { c: `ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})`, type: STRING, stringLiteral: pieces.text };
|
|
159
|
+
const t = this.tmp("tpl");
|
|
160
|
+
this.line(`TS_Str ${t};`);
|
|
161
|
+
this.line(`TS_TRY(ts_str_join(${pieces.arr}, ${pieces.n}, &${t}));`);
|
|
162
|
+
return { c: t, type: STRING };
|
|
170
163
|
}
|
|
171
164
|
if (node.kind === ts.SyntaxKind.NullKeyword)
|
|
172
165
|
return { c: "", type: BOOL, isNull: true };
|
|
@@ -430,6 +423,68 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
430
423
|
return isText(this.checker.getTypeAtLocation(node));
|
|
431
424
|
}
|
|
432
425
|
/** A string view of a string, integer or boolean (for concatenation and templates). */
|
|
426
|
+
/**
|
|
427
|
+
* A template literal's pieces in order, text known at compile time folded in: all text (`text`), or a C array of
|
|
428
|
+
* TS_Str (`arr`, `n` entries). When a later piece runs statements, earlier pieces that view account text are copied
|
|
429
|
+
* first, as JS reads them then.
|
|
430
|
+
*/
|
|
431
|
+
templatePieces(node) {
|
|
432
|
+
const parts = [];
|
|
433
|
+
const lit = (text) => {
|
|
434
|
+
if (!text)
|
|
435
|
+
return;
|
|
436
|
+
const last = parts.at(-1);
|
|
437
|
+
if (last?.text !== undefined)
|
|
438
|
+
last.text += text;
|
|
439
|
+
else
|
|
440
|
+
parts.push({ text });
|
|
441
|
+
};
|
|
442
|
+
lit(node.head.text);
|
|
443
|
+
for (const span of node.templateSpans) {
|
|
444
|
+
const known = this.constantText(span.expression);
|
|
445
|
+
if (known !== undefined)
|
|
446
|
+
lit(known);
|
|
447
|
+
else {
|
|
448
|
+
const mark = this.lines.length;
|
|
449
|
+
const c = this.toText(span.expression);
|
|
450
|
+
for (const p of parts)
|
|
451
|
+
if (p.c !== undefined)
|
|
452
|
+
p.c = this.settle({ c: p.c, type: STRING }, mark).c;
|
|
453
|
+
parts.push({ c });
|
|
454
|
+
}
|
|
455
|
+
lit(span.literal.text);
|
|
456
|
+
}
|
|
457
|
+
if (parts.every((p) => p.text !== undefined))
|
|
458
|
+
return { text: parts.map((p) => p.text).join("") };
|
|
459
|
+
const strOf = (text) => `ts_str((const uint8_t *)${cString(text)}, ${Buffer.byteLength(text, "utf8")})`;
|
|
460
|
+
const arr = this.tmp("parts");
|
|
461
|
+
this.line(`TS_Str ${arr}[${parts.length}] = {${parts.map((p) => (p.text !== undefined ? strOf(p.text) : p.c)).join(", ")}};`);
|
|
462
|
+
return { arr, n: parts.length };
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
|
|
466
|
+
* literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
|
|
467
|
+
*/
|
|
468
|
+
constantText(e) {
|
|
469
|
+
if (ts.isParenthesizedExpression(e))
|
|
470
|
+
return this.constantText(e.expression);
|
|
471
|
+
if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e))
|
|
472
|
+
return e.text;
|
|
473
|
+
if (e.kind === ts.SyntaxKind.TrueKeyword)
|
|
474
|
+
return "true";
|
|
475
|
+
if (e.kind === ts.SyntaxKind.FalseKeyword)
|
|
476
|
+
return "false";
|
|
477
|
+
if (ts.isBigIntLiteral(e))
|
|
478
|
+
return BigInt(e.text.slice(0, -1).replace(/_/g, "")).toString();
|
|
479
|
+
if (ts.isIdentifier(e) && !this.lookup(e.text)) {
|
|
480
|
+
const k = this.model.constants.get(e.text);
|
|
481
|
+
if (k && typeof k.value === "string")
|
|
482
|
+
return k.value;
|
|
483
|
+
if (k && typeof k.value === "object")
|
|
484
|
+
return base58(Uint8Array.from(k.value.pubkey));
|
|
485
|
+
}
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
433
488
|
toText(node) {
|
|
434
489
|
const v = this.expr(node);
|
|
435
490
|
if (v.type.kind === "string")
|
|
@@ -371,15 +371,17 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
371
371
|
const k = a.kind.kind;
|
|
372
372
|
if (c.hasOne.length && k !== "account" && k !== "token" && k !== "mint")
|
|
373
373
|
this.reporter.fail(a.node, "HasOne applies to existing accounts (Account<T> / Mut<T>), TokenAccount and Mint");
|
|
374
|
-
for (const
|
|
374
|
+
for (const entry of c.hasOne) {
|
|
375
|
+
// "field=param" (TokenOf / MintOf) or a field checked against the parameter of the same name (HasOne).
|
|
376
|
+
const [f, p = f] = entry.split("=");
|
|
375
377
|
if (k === "token")
|
|
376
378
|
out.push(f === "mint"
|
|
377
|
-
? ` if (!ts_key_eq(ts_token_mint(a_${a.name}), &a_${
|
|
378
|
-
: ` if (!ts_key_eq(ts_token_owner(a_${a.name}), &a_${
|
|
379
|
+
? ` if (!ts_key_eq(ts_token_mint(a_${a.name}), &a_${p}->key)) TS_FAIL(TS_ERR_TOKEN_MINT, "TypeSOL: ${a.name} is not a token account of ${p}");`
|
|
380
|
+
: ` if (!ts_key_eq(ts_token_owner(a_${a.name}), &a_${p}->key)) TS_FAIL(TS_ERR_TOKEN_OWNER, "TypeSOL: ${a.name} is not owned by ${p}");`);
|
|
379
381
|
else if (k === "mint") {
|
|
380
382
|
const at = f === "mintAuthority" ? 0 : 46;
|
|
381
383
|
const code = f === "mintAuthority" ? "TS_ERR_MINT_AUTHORITY" : "TS_ERR_FREEZE_AUTHORITY";
|
|
382
|
-
out.push(` if (ts_read_le(a_${a.name}->data + ${at}, 4) != 1 || !ts_key_eq((const SolPubkey *)(a_${a.name}->data + ${at + 4}), &a_${
|
|
384
|
+
out.push(` if (ts_read_le(a_${a.name}->data + ${at}, 4) != 1 || !ts_key_eq((const SolPubkey *)(a_${a.name}->data + ${at + 4}), &a_${p}->key)) TS_FAIL(${code}, "TypeSOL: ${p} is not the ${f} of ${a.name}");`);
|
|
383
385
|
}
|
|
384
386
|
else
|
|
385
387
|
out.push(` if (!ts_key_eq(&d_${a.name}->${f}, &a_${f}->key)) TS_FAIL(TS_ERR_CONSTRAINT, "TypeSOL: ${a.name}.${f} must be the ${f} account");`);
|
|
@@ -842,7 +842,24 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
842
842
|
this.line(`TS_Str ${cur};`);
|
|
843
843
|
this.line(`TS_TRY(ts_str_copy(${current.c}, &${cur}));`);
|
|
844
844
|
}
|
|
845
|
+
// A local grows in place when it is the newest heap string (a loop of `s += …` copies each piece once); a
|
|
846
|
+
// template on the right is appended piece by piece.
|
|
847
|
+
const local = cur === current.c && place.write("X") === `${cur} = X`;
|
|
848
|
+
const bare = (e) => (ts.isParenthesizedExpression(e) ? bare(e.expression) : e);
|
|
849
|
+
const rhsNode = bare(valueNode);
|
|
850
|
+
if (local && ts.isTemplateExpression(rhsNode)) {
|
|
851
|
+
const pieces = this.templatePieces(rhsNode);
|
|
852
|
+
if (pieces.text !== undefined)
|
|
853
|
+
this.line(`TS_TRY(ts_str_append(&${cur}, ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})));`);
|
|
854
|
+
else
|
|
855
|
+
this.line(`TS_TRY(ts_str_append_parts(&${cur}, ${pieces.arr}, ${pieces.n}));`);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
845
858
|
const rhs = this.toText(valueNode);
|
|
859
|
+
if (local) {
|
|
860
|
+
this.line(`TS_TRY(ts_str_append(&${cur}, ${rhs}));`);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
846
863
|
const t = this.tmp("cat");
|
|
847
864
|
this.line(`TS_Str ${t};`);
|
|
848
865
|
this.line(`TS_TRY(ts_str_concat(${cur}, ${rhs}, &${t}));`);
|
package/docs/client.md
CHANGED
|
@@ -25,23 +25,45 @@ ledgerbook.errorCode(e); // 6002
|
|
|
25
25
|
// Events written by `emit`, from a transaction's log messages
|
|
26
26
|
for (const ev of ledgerbook.parseEvents(logs)) if (ev.name === "Filed") ev.data.amount;
|
|
27
27
|
|
|
28
|
-
// Return data of an instruction that returns a value
|
|
28
|
+
// Return data of an instruction that returns a value, or the value itself by simulation (Anchor's `.view()`)
|
|
29
29
|
ledgerbook.returns.total(returnDataBytes); // bigint
|
|
30
|
+
await ledgerbook.views.total(rpc, signer, { book }, { status: Status.Approved }); // bigint, nothing sent
|
|
31
|
+
|
|
32
|
+
// Send and confirm (one transaction, signers in the accounts sign too), or simulate
|
|
33
|
+
const signature = await ledgerbook.sendInstructions({ rpc, rpcSubscriptions }, signer, [create, file]);
|
|
34
|
+
const { err, logs, unitsConsumed, returnData } = await ledgerbook.simulate(rpc, signer, [file]);
|
|
35
|
+
|
|
36
|
+
// Every Book of the program, or those with given bytes at an offset (after the 8-byte discriminator)
|
|
37
|
+
const books = await ledgerbook.accounts.Book.fetchAll(rpc); // [{ address, data }]
|
|
38
|
+
const several = await ledgerbook.accounts.Book.fetchMultiple(rpc, [a, b]); // (Book | null)[]
|
|
39
|
+
|
|
40
|
+
// Live updates, until the AbortSignal aborts
|
|
41
|
+
for await (const book of ledgerbook.accounts.Book.watch(rpcSubscriptions, address, { abortSignal })) render(book);
|
|
42
|
+
for await (const { signature, event } of ledgerbook.watchEvents(rpcSubscriptions, { abortSignal })) show(event);
|
|
43
|
+
|
|
44
|
+
// Remaining accounts go after the instruction's own (Anchor's `.remainingAccounts([...])`)
|
|
45
|
+
await ledgerbook.approveMany({ approver: signer, book }, { indices: [0n, 1n] }, [{ address: extra, role: AccountRole.WRITABLE }]);
|
|
46
|
+
|
|
47
|
+
// Events of a fetched transaction (getTransaction, json): log events and emitCpi events from inner instructions
|
|
48
|
+
ledgerbook.eventsOf(await rpc.getTransaction(signature, { maxSupportedTransactionVersion: 0 }).send());
|
|
30
49
|
```
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
Signer accounts take a `TransactionSigner`, which kit uses to sign; an `Address` works too when you sign separately.
|
|
52
|
+
Kit's own transaction functions work with the instructions as well; `sendInstructions` is that pipeline in one call.
|
|
34
53
|
|
|
35
54
|
## What the client contains
|
|
36
55
|
|
|
37
56
|
| | |
|
|
38
57
|
| --- | --- |
|
|
39
58
|
| `<program>.<instruction>(accounts, args)` | Builds the `Instruction`. Accounts with a fixed address and PDAs whose seeds are constants, arguments or other accounts' keys are optional and filled in; pass one to override. An optional account passed as `null` is left out (sent as the program id, the convention TypeSOL and Anchor share); left undefined, it is filled in like any other. |
|
|
40
|
-
| `<program
|
|
59
|
+
| `<program>.<instruction>(accounts, args, remaining?)` | The same, with remaining accounts appended in order (kit `AccountMeta`s). |
|
|
60
|
+
| `<program>.accounts.<Name>` | `decode(bytes)`, `encode(value)`, `fetch(rpc, address)`, `fetchMaybe(rpc, address)`, `fetchMultiple(rpc, addresses)`, `fetchAll(rpc, filters?)` (getProgramAccounts by discriminator, plus `{ offset, bytes }` filters), `watch(rpcSubscriptions, address, { abortSignal })`, `discriminator`. `decode` checks the discriminator. |
|
|
41
61
|
| `<program>.pdas.<account>(seeds)` | The PDA (address and bump) for each account an instruction derives. |
|
|
42
62
|
| `<program>.returns.<instruction>(bytes)` | Decodes return data. |
|
|
43
|
-
| `<program>.
|
|
44
|
-
| `<program>.
|
|
63
|
+
| `<program>.views.<instruction>(rpc, feePayer, accounts, args)` | Simulates an instruction that returns a value and decodes it (throws with the logs if it fails). |
|
|
64
|
+
| `<program>.sendInstructions(connection, feePayer, instructions)`, `simulate(rpc, feePayer, instructions)` | One transaction sent and confirmed (kit's sendAndConfirm), or simulated without signatures (error, logs, units, this program's return data). |
|
|
65
|
+
| `<program>.errors`, `errorCode(e)`, `isError(e, name)` | The program's errors (6000+), and for TypeSOL programs also every Anchor framework error (`AnchorError` and the constraints raise them) and TypeSOL's runtime errors. |
|
|
66
|
+
| `<program>.parseEvents(logs)`, `parseCpiEvents(data)`, `eventsOf(tx)`, `watchEvents(rpcSubscriptions, { abortSignal })` | Events from `Program data:` log lines, from `emitCpi` inner instructions (bytes or base58), from a fetched transaction (both), or live from log subscriptions. |
|
|
45
67
|
| Types and codecs | Every defined type as a TypeScript type (`Book`, the decoded value) and an input type (`BookArgs`, which also accepts `number` for 64- and 128-bit integers), with a kit codec (`getBookCodec()`). |
|
|
46
68
|
|
|
47
69
|
Types follow the program's TypeScript: `u64`/`i64`/`u128`/`i128` are `bigint` (inputs accept `number` too), smaller
|
|
@@ -65,5 +87,6 @@ Anchor IDLs are refused with a hint to convert them (`anchor idl convert`).
|
|
|
65
87
|
for each.
|
|
66
88
|
- **Left out of a client generated from an Anchor IDL**, with a note: generic types and anything using them, and
|
|
67
89
|
zero-copy (`bytemuck`) accounts.
|
|
68
|
-
- **Not done yet**:
|
|
69
|
-
|
|
90
|
+
- **Not done yet**: a LiteSVM loader for tests (use `svm.addProgramFromFile(ledgerbook.address, soPath)`, or
|
|
91
|
+
`typesol/testing`). `watchEvents` reads log events; `emitCpi` events are in inner instructions, which log
|
|
92
|
+
subscriptions do not carry (use `eventsOf` on the fetched transaction).
|
package/docs/language.md
CHANGED
|
@@ -11,7 +11,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
11
11
|
| Integers | `u8` `u16` `u32` `u64` `u128` `i8` `i16` `i32` `i64` `i128` — bigint literals (`1n`), every `+ - * / % **` (and `+=` … `**=`) checked (`**` aborts on a negative exponent, as JS throws); `& \| ^` on equal types, `~` within the width, `<<` / `>>` checked (amount below the width, `<<` may not lose bits) |
|
|
12
12
|
| Integer helpers | `mulDiv(a, b, c)` / `mulDivCeil` (u64 through a 128-bit product, u128 through a 256-bit one), `isqrt(x)` (up to u128), `min(a, b)`, `max(a, b)` |
|
|
13
13
|
| Other scalars | `boolean`, `Pubkey` (compare with `===` or `.equals`) |
|
|
14
|
-
| Errors | `export enum MyError { Name = "message" }` + `require(cond, MyError.Name)` → codes 6000
|
|
14
|
+
| Errors | `export enum MyError { Name = "message" }` + `require(cond, MyError.Name)` → codes 6000+; `require(cond, AnchorError.RequireKeysEqViolated)` fails with one of Anchor's own codes (2502 here; every anchor-lang `ErrorCode`) |
|
|
15
15
|
| Casts | `u8(x)`, `u64(x)`, … abort if the value does not fit |
|
|
16
16
|
| Logging | `log("text")`, `log(value)`, `log(pubkey)` |
|
|
17
17
|
| Arrays | `scores: FixedArray<u64, 8>` in account data — `.get(i)`, `.set(i, v)`, `.length`, bounds-checked; elements can be structs or strings |
|
|
@@ -36,7 +36,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
36
36
|
| Constants | top-level `const SEED = "vault"`, `const FEE: u16 = 30n`, `const ON = true` — inlined, usable as seeds (and described in the IDL) |
|
|
37
37
|
| Data enums | `export type Action = { kind: "Deposit"; amount: u64 } \| { kind: "Pause" }` — `a.kind === "Deposit"`, `switch (a.kind)`, TypeScript narrowing picks the variant (checked again at runtime, 5112); Borsh enums in arguments/events, fixed `{ tag, payload }` in account data. A variant may hold a `string` (`{ kind: "Say"; text: string }`): such an enum works in arguments, events and locals (Borsh, as Anchor encodes it) but not in account data |
|
|
38
38
|
| Option of structs | `home: Option<Point>` in account data — `=== null`, `p.home.x` after narrowing, `p.home = null` / `{ x, y }` |
|
|
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) |
|
|
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
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
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">`) |
|
|
@@ -67,7 +67,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
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
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 |
|
|
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 |
|
|
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) |
|
|
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,7 +77,7 @@ 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 (exactly one token program parameter, 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. `createIfNeeded(…)` returns whether it created it; an existing one must be that PDA and have those decimals and authorities (2018 / 2016 / 2017) |
|
|
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 (exactly one token program parameter, 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
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) |
|
|
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` |
|
|
@@ -159,8 +159,7 @@ Growable `Vec`s anywhere but the last account field (use `FixedVec<T, N>`), Fixe
|
|
|
159
159
|
the next instruction), `?.` on anything but optional accounts and Option<struct> fields, `!` on anything but an Option
|
|
160
160
|
or an optional account, Core's external plugin adapters (oracles, app data, lifecycle hooks: use `invoke`), closures
|
|
161
161
|
stored or returned (they are called or passed to list methods), arrow literals and closures as helper arguments (pass a
|
|
162
|
-
named function), `map` / `filter` (they would build a new list),
|
|
163
|
-
mint with `create(…, extensions)`), floating point (by design).
|
|
162
|
+
named function), `map` / `filter` (they would build a new list), floating point (by design).
|
|
164
163
|
They are rejected at compile time rather than half-supported.
|
|
165
164
|
|
|
166
165
|
`Init.create` handles a PDA address someone pre-funded: it tops up rent, then allocates and assigns instead of
|
|
@@ -49,7 +49,7 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
|
|
|
49
49
|
| `Program<'info, System>` | `SystemProgram` |
|
|
50
50
|
| `Program<'info, Token>` / `Token2022` / `Interface<'info, TokenInterface>` | `TokenProgram` / `Token2022Program` / `TokenInterface` |
|
|
51
51
|
| `Account<'info, TokenAccount>` / `Account<'info, Mint>` (`InterfaceAccount`) | `TokenAccount` / `Mint` (`Mut<…>` when written) |
|
|
52
|
-
| `token::mint = m, token::authority = a` | `HasOne<TokenAccount, "mint" \| "owner">`
|
|
52
|
+
| `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) |
|
|
53
53
|
| `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
54
|
| `#[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
55
|
| `#[account(init, payer = p, seeds = […], bump, token::mint = m, token::authority = a)]` | `Init<TokenAccount>`, then `vault.create(p, […], m, a.key)` (`InitIfNeeded<TokenAccount>` + `createIfNeeded`) |
|
|
@@ -91,6 +91,8 @@ Anchor instruction that takes or returns a struct with `String` / `Vec` / `Optio
|
|
|
91
91
|
| `-> Result<u64>` / a struct | `: u64` / a struct: sent as return data, listed in the IDL |
|
|
92
92
|
| `#[error_code] enum MyError { #[msg("…")] X }` | `export enum MyError { X = "…" }` (codes from 6000, in order) |
|
|
93
93
|
| `require!(c, MyError::X)`, `err!(MyError::X)` | `require(c, MyError.X)`, `require(false, MyError.X)` |
|
|
94
|
+
| `require_keys_eq!(a, b)`, `err!(ErrorCode::AccountDidNotDeserialize)` (Anchor's own codes) | `require(a === b, AnchorError.RequireKeysEqViolated)`, `require(false, AnchorError.AccountDidNotDeserialize)` |
|
|
95
|
+
| `init_if_needed` + `extensions::…` on a mint | `mint.createIfNeeded(…, { metadataPointer: {…}, … })`: an existing mint is checked as Anchor checks it (2024–2039) |
|
|
94
96
|
| `require_keys_eq!(a, b)` | `require(a === b, …)` |
|
|
95
97
|
| `a.checked_add(b).ok_or(…)?` | `a + b` (every operation is checked; overflow aborts with 5100) |
|
|
96
98
|
| `emit!(Ev { … })` / `emit_cpi!` | `emit<Ev>({ … })` / `emitCpi<Ev>({ … }, eventAuthority, self)` |
|