@typesol/cli 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/README.md +11 -7
- package/dist/cli.js +147 -20
- package/dist/clientgen.js +37 -0
- package/dist/compiler/analyze.d.ts +11 -1
- package/dist/compiler/analyze.js +307 -37
- package/dist/compiler/emit/base.d.ts +7 -0
- package/dist/compiler/emit/base.js +62 -2
- package/dist/compiler/emit/borsh.d.ts +13 -0
- package/dist/compiler/emit/borsh.js +232 -0
- package/dist/compiler/emit/calls.js +50 -15
- package/dist/compiler/emit/cpi.d.ts +32 -8
- package/dist/compiler/emit/cpi.js +706 -25
- package/dist/compiler/emit/expressions.d.ts +11 -0
- package/dist/compiler/emit/expressions.js +126 -4
- package/dist/compiler/emit/function.js +45 -12
- package/dist/compiler/emit/program.js +24 -6
- package/dist/compiler/emit/shared.d.ts +12 -0
- package/dist/compiler/emit/shared.js +22 -4
- package/dist/compiler/emit/statements.d.ts +13 -0
- package/dist/compiler/emit/statements.js +141 -7
- package/dist/compiler/emit/storage.d.ts +6 -1
- package/dist/compiler/emit/storage.js +82 -17
- package/dist/compiler/idl.js +61 -18
- package/dist/compiler/model.d.ts +56 -4
- package/dist/compiler/model.js +8 -2
- package/dist/compiler/toolchain.d.ts +2 -0
- package/dist/compiler/toolchain.js +27 -1
- package/dist/onchain.d.ts +30 -0
- package/dist/onchain.js +53 -0
- package/dist/portgen.d.ts +87 -0
- package/dist/portgen.js +367 -0
- package/dist/templates.js +49 -154
- package/dist/testing.d.ts +66 -0
- package/dist/testing.js +337 -0
- package/dist/workspace.js +11 -1
- package/docs/architecture.md +3 -0
- package/docs/cli.md +16 -2
- package/docs/errors.md +3 -2
- package/docs/language.md +19 -14
- package/docs/mainnet.md +5 -4
- package/docs/migrate-from-anchor.md +113 -0
- package/docs/test-your-program.md +52 -0
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +7 -0
- package/lib/typesol.d.ts +325 -13
- package/package.json +19 -1
- package/runtime/typesol_rt.h +54 -9
- package/ts-plugin/index.js +112 -0
- package/ts-plugin/package.json +4 -0
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { discriminator, snakeCase } from "../analyze.js";
|
|
3
|
+
import { borshMinSize } from "./borsh.js";
|
|
3
4
|
import { INT_INFO, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
|
|
4
5
|
import { MAX_ACCOUNTS, MAX_SEEDS, MAX_CPI_DATA, MAX_EVENT, U64, U8, BOOL, PUBKEY, STRING, cBytes, isStable, cString, scalarStorageC, optC, valueC, } from "./shared.js";
|
|
5
6
|
import { CallEmitter } from "./calls.js";
|
|
7
|
+
/** Metaplex Core's Plugin / PluginType enums, in order (MasterEdition, VerifiedCreators and Autograph values are refused). */
|
|
8
|
+
const CORE_PLUGINS = [
|
|
9
|
+
"Royalties",
|
|
10
|
+
"FreezeDelegate",
|
|
11
|
+
"BurnDelegate",
|
|
12
|
+
"TransferDelegate",
|
|
13
|
+
"UpdateDelegate",
|
|
14
|
+
"PermanentFreezeDelegate",
|
|
15
|
+
"Attributes",
|
|
16
|
+
"PermanentTransferDelegate",
|
|
17
|
+
"PermanentBurnDelegate",
|
|
18
|
+
"Edition",
|
|
19
|
+
"MasterEdition",
|
|
20
|
+
"AddBlocker",
|
|
21
|
+
"ImmutableMetadata",
|
|
22
|
+
"VerifiedCreators",
|
|
23
|
+
"Autograph",
|
|
24
|
+
"BubblegumV2",
|
|
25
|
+
];
|
|
26
|
+
const CORE_PLUGIN_FIELDS = {
|
|
27
|
+
Royalties: ["basisPoints", "creators"],
|
|
28
|
+
FreezeDelegate: ["frozen"],
|
|
29
|
+
PermanentFreezeDelegate: ["frozen"],
|
|
30
|
+
UpdateDelegate: ["additionalDelegates"],
|
|
31
|
+
Attributes: ["attributes"],
|
|
32
|
+
Edition: ["number"],
|
|
33
|
+
};
|
|
6
34
|
/**
|
|
7
35
|
* Other programs: PDAs and seeds, account creation, System/Token/ATA/Metaplex CPIs, generic invoke, Borsh
|
|
8
36
|
* serialization and events.
|
|
@@ -139,7 +167,10 @@ export class CpiEmitter extends CallEmitter {
|
|
|
139
167
|
// Returns whether it was created now; an existing account must be this PDA, ours, and a ${s.name}.
|
|
140
168
|
const created = this.tmp("created");
|
|
141
169
|
this.line(`bool ${created};`);
|
|
142
|
-
this.line(`TS_TRY(ts_create_pda_if_needed(params, a_${payer.name}, a_${target.name}, ${seeds.name}, ${seeds.count}, TS_DISC_${s.name}, 8 + sizeof(TSS_${s.name}), &${b}, &${created}));`);
|
|
170
|
+
this.line(`TS_TRY(ts_create_pda_if_needed(params, a_${payer.name}, a_${target.name}, ${seeds.name}, ${seeds.count}, TS_DISC_${s.name}, 8 + sizeof(TSS_${s.name}), 8 + ${s.layout === "borsh" ? borshMinSize(s, this.model) : `sizeof(TSS_${s.name})`}, &${b}, &${created}));`);
|
|
171
|
+
// An existing @layout borsh account is unpacked now (a new one starts from the zeroed struct).
|
|
172
|
+
if (s.layout === "borsh")
|
|
173
|
+
this.line(`if (!${created}) TS_TRY(ts_unpack_${s.name}(a_${target.name}->data + 8, a_${target.name}->data_len - 8, d_${target.name}));`);
|
|
143
174
|
this.created.add(target.name);
|
|
144
175
|
this.pendingInit.delete(target.name);
|
|
145
176
|
this.bumps.set(target.name, b);
|
|
@@ -248,11 +279,26 @@ export class CpiEmitter extends CallEmitter {
|
|
|
248
279
|
], buf, len, seeds);
|
|
249
280
|
return undefined;
|
|
250
281
|
}
|
|
251
|
-
/**
|
|
252
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Emit a CPI to the program account `program` with these metas (account, writable, signer), and then (`tail`) a range
|
|
284
|
+
* of the remaining accounts, read-only (a Merkle proof).
|
|
285
|
+
*/
|
|
286
|
+
cpi(program, metas, buf, len, seeds, tail) {
|
|
253
287
|
const m = this.tmp("metas");
|
|
254
|
-
|
|
255
|
-
|
|
288
|
+
const fixed = metas.map(([acc, w, sg]) => `{&${acc}->key, ${w}, ${sg}}`).join(", ");
|
|
289
|
+
if (!tail) {
|
|
290
|
+
this.line(`SolAccountMeta ${m}[${metas.length}] = {${fixed}};`);
|
|
291
|
+
this.line(`TS_TRY(ts_cpi(params, ${program}, ${buf}, ${len}, ${m}, ${metas.length}, ${seeds.name}, ${seeds.count}));`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const n = this.ix.accounts.length;
|
|
295
|
+
const cnt = this.tmp("nmetas");
|
|
296
|
+
const i = this.tmp("ri");
|
|
297
|
+
this.line(`if (${metas.length} + (${tail.to} - ${tail.from}) > TS_MAX_ACCOUNTS) TS_FAIL(TS_ERR_TOO_MANY_ACCOUNTS, "TypeSOL: the CPI lists more than ${MAX_ACCOUNTS} accounts");`);
|
|
298
|
+
this.line(`SolAccountMeta ${m}[TS_MAX_ACCOUNTS] = {${fixed}};`);
|
|
299
|
+
this.line(`uint64_t ${cnt} = ${metas.length};`);
|
|
300
|
+
this.line(`for (uint64_t ${i} = ${n} + ${tail.from}; ${i} < ${n} + ${tail.to}; ${i}++) ${m}[${cnt}++] = (SolAccountMeta){¶ms->ka[${i}]->key, 0, 0};`);
|
|
301
|
+
this.line(`TS_TRY(ts_cpi(params, ${program}, ${buf}, ${len}, ${m}, ${cnt}, ${seeds.name}, ${seeds.count}));`);
|
|
256
302
|
}
|
|
257
303
|
signerSeeds(node, authority, at) {
|
|
258
304
|
if (!node) {
|
|
@@ -371,7 +417,98 @@ export class CpiEmitter extends CallEmitter {
|
|
|
371
417
|
};
|
|
372
418
|
const value = (n, t) => (put) => this.serialize(this.expect(this.expr(n, t), t, n), n, put);
|
|
373
419
|
const optKey = { kind: "option", inner: PUBKEY };
|
|
420
|
+
const U16 = { kind: "int", name: "u16" };
|
|
421
|
+
const I16 = { kind: "int", name: "i16" };
|
|
422
|
+
const byte = (k) => (put) => {
|
|
423
|
+
const t = this.tmp("b");
|
|
424
|
+
this.line(`uint8_t ${t} = ${k};`);
|
|
425
|
+
put(`&${t}`, "1", 1);
|
|
426
|
+
};
|
|
427
|
+
/** Token-2022's OptionalNonZeroPubkey: 32 bytes, all zero for none. */
|
|
428
|
+
const nonZeroKey = (n) => (put) => {
|
|
429
|
+
this.model.options.set("pubkey", PUBKEY);
|
|
430
|
+
const v = this.expect(this.expr(n, optKey), optKey, n);
|
|
431
|
+
const t = this.tmp("nzk");
|
|
432
|
+
this.line(`SolPubkey ${t};`);
|
|
433
|
+
this.line(`{ ${optC(PUBKEY)} o = ${v.c}; if (o.some) ${t} = o.v; else ts_memzero(&${t}, 32); }`);
|
|
434
|
+
put(`&${t}`, "32", 32);
|
|
435
|
+
};
|
|
436
|
+
const token2022Only = () => {
|
|
437
|
+
if (program.kind.kind === "tokenProgram" && program.kind.flavor === "token")
|
|
438
|
+
this.fail(node, `${method} is a Token-2022 extension; use Token2022Program or TokenInterface`);
|
|
439
|
+
};
|
|
440
|
+
const mintOnly = (n, usage) => {
|
|
441
|
+
token2022Only();
|
|
442
|
+
if (a.length !== n)
|
|
443
|
+
this.fail(node, usage);
|
|
444
|
+
return anyAcct(a[0], "the mint", true);
|
|
445
|
+
};
|
|
446
|
+
const none = { name: "NULL", count: 0 };
|
|
374
447
|
switch (method) {
|
|
448
|
+
case "initializeTransferFeeConfig": {
|
|
449
|
+
const mint = mintOnly(5, "initializeTransferFeeConfig(mint, configAuthority | null, withdrawAuthority | null, basisPoints, maximumFee)");
|
|
450
|
+
this.model.options.set("pubkey", PUBKEY);
|
|
451
|
+
// Arguments in source order, as JS evaluates them: the basis points are checked when they are read.
|
|
452
|
+
const bps = (put) => {
|
|
453
|
+
const v = this.expect(this.expr(a[3], U16), U16, a[3]);
|
|
454
|
+
this.line(`if (${v.c} > 10000) TS_FAIL(TS_ERR_INSTRUCTION_DATA, "TypeSOL: transfer fee above 10000 basis points");`);
|
|
455
|
+
this.serialize(v, a[3], put);
|
|
456
|
+
};
|
|
457
|
+
simple(26, [byte(0), value(a[1], optKey), value(a[2], optKey), bps, value(a[4], U64)], [[mint, true, false]], none);
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
case "initializeMintCloseAuthority": {
|
|
461
|
+
const mint = mintOnly(2, "initializeMintCloseAuthority(mint, closeAuthority | null)");
|
|
462
|
+
this.model.options.set("pubkey", PUBKEY);
|
|
463
|
+
simple(25, [value(a[1], optKey)], [[mint, true, false]], none);
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
case "initializeNonTransferableMint": {
|
|
467
|
+
const mint = mintOnly(1, "initializeNonTransferableMint(mint)");
|
|
468
|
+
simple(32, [], [[mint, true, false]], none);
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
case "initializePermanentDelegate": {
|
|
472
|
+
const mint = mintOnly(2, "initializePermanentDelegate(mint, delegate)");
|
|
473
|
+
simple(35, [value(a[1], PUBKEY)], [[mint, true, false]], none);
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
case "initializeDefaultAccountState": {
|
|
477
|
+
const mint = mintOnly(2, "initializeDefaultAccountState(mint, frozen)");
|
|
478
|
+
const frozen = this.expect(this.expr(a[1], BOOL), BOOL, a[1]);
|
|
479
|
+
const st = this.tmp("st");
|
|
480
|
+
this.line(`uint8_t ${st} = (${frozen.c}) ? 2 : 1;`); // AccountState::Frozen / Initialized
|
|
481
|
+
simple(28, [byte(0), (put) => put(`&${st}`, "1", 1)], [[mint, true, false]], none);
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
case "initializeMetadataPointer":
|
|
485
|
+
case "initializeGroupPointer":
|
|
486
|
+
case "initializeGroupMemberPointer":
|
|
487
|
+
case "initializeTransferHook": {
|
|
488
|
+
const tag = { initializeMetadataPointer: 39, initializeGroupPointer: 40, initializeGroupMemberPointer: 41, initializeTransferHook: 36 }[method];
|
|
489
|
+
const second = {
|
|
490
|
+
initializeMetadataPointer: "metadataAddress",
|
|
491
|
+
initializeGroupPointer: "groupAddress",
|
|
492
|
+
initializeGroupMemberPointer: "memberAddress",
|
|
493
|
+
initializeTransferHook: "programId",
|
|
494
|
+
}[method];
|
|
495
|
+
const mint = mintOnly(3, `${method}(mint, authority | null, ${second} | null)`);
|
|
496
|
+
simple(tag, [byte(0), nonZeroKey(a[1]), nonZeroKey(a[2])], [[mint, true, false]], none);
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
case "initializeInterestBearingMint": {
|
|
500
|
+
const mint = mintOnly(3, "initializeInterestBearingMint(mint, rateAuthority | null, rate)");
|
|
501
|
+
simple(33, [byte(0), nonZeroKey(a[1]), value(a[2], I16)], [[mint, true, false]], none);
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
case "initializeImmutableOwner": {
|
|
505
|
+
token2022Only();
|
|
506
|
+
if (a.length !== 1)
|
|
507
|
+
this.fail(node, "initializeImmutableOwner(account)");
|
|
508
|
+
const acct = anyAcct(a[0], "the token account", true);
|
|
509
|
+
simple(22, [], [[acct, true, false]], none);
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
375
512
|
case "approve": {
|
|
376
513
|
arity(4);
|
|
377
514
|
const src = want(a[0], "token", true, "the source");
|
|
@@ -448,7 +585,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
448
585
|
return undefined;
|
|
449
586
|
}
|
|
450
587
|
}
|
|
451
|
-
this.fail(node.expression, `token programs support transfer, transferChecked, mintTo, burn, closeAccount, approve, revoke, setAuthority, freezeAccount, thawAccount, syncNative, initializeMint2, initializeAccount3 and
|
|
588
|
+
this.fail(node.expression, `token programs support transfer, transferChecked, mintTo, burn, closeAccount, approve, revoke, setAuthority, freezeAccount, thawAccount, syncNative, initializeMint2, initializeAccount3, initializeMetadata and the Token-2022 extension initializers (initializeTransferFeeConfig, initializeMintCloseAuthority, initializeNonTransferableMint, initializePermanentDelegate, initializeDefaultAccountState, initializeMetadataPointer, initializeGroupPointer, initializeGroupMemberPointer, initializeTransferHook, initializeInterestBearingMint, initializeImmutableOwner), not ${method}`);
|
|
452
589
|
}
|
|
453
590
|
ataOp(node, program, method) {
|
|
454
591
|
if (method !== "create" && method !== "createIdempotent")
|
|
@@ -471,11 +608,17 @@ export class CpiEmitter extends CallEmitter {
|
|
|
471
608
|
return undefined;
|
|
472
609
|
}
|
|
473
610
|
metadataOp(node, program, method) {
|
|
611
|
+
if (method === "createMasterEditionV3" || method === "verifyCollection")
|
|
612
|
+
return this.metadataCollectionOp(node, program, method);
|
|
474
613
|
if (method !== "createV3")
|
|
475
|
-
this.fail(node.expression,
|
|
614
|
+
this.fail(node.expression, `the Token Metadata program supports createV3, createMasterEditionV3 and verifyCollection, not ${method}`);
|
|
476
615
|
const a = node.arguments;
|
|
477
|
-
|
|
478
|
-
|
|
616
|
+
const usage = "createV3(metadata, mint, mintAuthority, payer, updateAuthority, systemProgram, name, symbol, uri, sellerFeeBasisPoints, isMutable, { creators?, collection?, isCollection? }?, signerSeeds?)";
|
|
617
|
+
// An object literal after isMutable carries the optional creators / collection; signerSeeds stays last.
|
|
618
|
+
const extras = a[11] && ts.isObjectLiteralExpression(a[11]) ? a[11] : undefined;
|
|
619
|
+
const seedsArg = extras ? a[12] : a[11];
|
|
620
|
+
if (a.length < 11 || a.length > (extras ? 13 : 12))
|
|
621
|
+
this.fail(node, usage);
|
|
479
622
|
const [metadata, mint, mintAuth, payer, updateAuth, sys] = a.slice(0, 6).map((n, i) => this.accountParam(n, `account #${i + 1}`));
|
|
480
623
|
if (!isWritable(metadata.kind))
|
|
481
624
|
this.fail(a[0], "the metadata account must be Mut<AccountInfo>");
|
|
@@ -483,11 +626,135 @@ export class CpiEmitter extends CallEmitter {
|
|
|
483
626
|
this.fail(a[3], "the payer must be a Mut<Signer>");
|
|
484
627
|
if (sys.kind.kind !== "system")
|
|
485
628
|
this.fail(a[5], "pass the SystemProgram parameter");
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
const
|
|
490
|
-
|
|
629
|
+
// Arguments in source order, as JS evaluates them: a later one that runs statements (a helper changing account
|
|
630
|
+
// text) must not change what an earlier one read.
|
|
631
|
+
const vals = [];
|
|
632
|
+
const inOrder = (n, t) => {
|
|
633
|
+
const mark = this.lines.length;
|
|
634
|
+
const v = this.expect(this.expr(n, t), t, n);
|
|
635
|
+
for (let j = 0; j < vals.length; j++)
|
|
636
|
+
vals[j] = this.settle(vals[j], mark);
|
|
637
|
+
vals.push(v);
|
|
638
|
+
return vals.length - 1;
|
|
639
|
+
};
|
|
640
|
+
const slots = [...a.slice(6, 9).map((n) => inOrder(n, STRING)), inOrder(a[9], { kind: "int", name: "u16" }), inOrder(a[10], BOOL)];
|
|
641
|
+
const settleAll = (mark) => {
|
|
642
|
+
for (let j = 0; j < vals.length; j++)
|
|
643
|
+
vals[j] = this.settle(vals[j], mark);
|
|
644
|
+
};
|
|
645
|
+
const extrasMark = this.lines.length;
|
|
646
|
+
let creators = "NULL";
|
|
647
|
+
let ncreators = "-1";
|
|
648
|
+
let collection = "NULL";
|
|
649
|
+
let sized = "false";
|
|
650
|
+
if (extras) {
|
|
651
|
+
for (const p of extras.properties) {
|
|
652
|
+
if (!ts.isPropertyAssignment(p) || !ts.isIdentifier(p.name))
|
|
653
|
+
this.fail(p, "use creators: [...], collection: key and isCollection: boolean");
|
|
654
|
+
if (p.name.text === "creators") {
|
|
655
|
+
if (!ts.isArrayLiteralExpression(p.initializer))
|
|
656
|
+
this.fail(p.initializer, "creators: [{ address, verified, share }, …] (an array literal)");
|
|
657
|
+
const list = p.initializer.elements;
|
|
658
|
+
if (list.length < 1 || list.length > 5)
|
|
659
|
+
this.fail(p.initializer, "Metaplex takes 1 to 5 creators");
|
|
660
|
+
const buf = this.tmp("creators");
|
|
661
|
+
this.line(`uint8_t ${buf}[${34 * list.length}];`);
|
|
662
|
+
let shares = 0n;
|
|
663
|
+
let constant = true;
|
|
664
|
+
list.forEach((el, i) => {
|
|
665
|
+
if (!ts.isObjectLiteralExpression(el))
|
|
666
|
+
this.fail(el, "a creator is { address, verified, share }");
|
|
667
|
+
const field = (k) => {
|
|
668
|
+
const q = el.properties.find((x) => ts.isPropertyAssignment(x) && ts.isIdentifier(x.name) && x.name.text === k);
|
|
669
|
+
const sh = el.properties.find((x) => ts.isShorthandPropertyAssignment(x) && x.name.text === k);
|
|
670
|
+
if (!q && !sh)
|
|
671
|
+
this.fail(el, `a creator needs ${k}`);
|
|
672
|
+
return q ? q.initializer : sh.name;
|
|
673
|
+
};
|
|
674
|
+
const at = 34 * i;
|
|
675
|
+
const key = this.expect(this.expr(field("address"), PUBKEY), PUBKEY, el);
|
|
676
|
+
this.line(`ts_memcpy(${buf} + ${at}, ${key.c}, 32);`);
|
|
677
|
+
const verified = this.expect(this.expr(field("verified"), BOOL), BOOL, el);
|
|
678
|
+
this.line(`${buf}[${at + 32}] = (${verified.c}) ? 1 : 0;`);
|
|
679
|
+
const shareNode = field("share");
|
|
680
|
+
if (ts.isBigIntLiteral(shareNode))
|
|
681
|
+
shares += BigInt(shareNode.text.slice(0, -1));
|
|
682
|
+
else
|
|
683
|
+
constant = false;
|
|
684
|
+
const share = this.expect(this.expr(shareNode, U8), U8, el);
|
|
685
|
+
this.line(`${buf}[${at + 33}] = (uint8_t)(${share.c});`);
|
|
686
|
+
});
|
|
687
|
+
if (constant && shares !== 100n)
|
|
688
|
+
this.fail(p.initializer, `creator shares add up to ${shares}; Metaplex requires 100`);
|
|
689
|
+
creators = buf;
|
|
690
|
+
ncreators = String(list.length);
|
|
691
|
+
}
|
|
692
|
+
else if (p.name.text === "collection") {
|
|
693
|
+
const key = this.expect(this.expr(p.initializer, PUBKEY), PUBKEY, p.initializer);
|
|
694
|
+
const t = this.tmp("coll");
|
|
695
|
+
this.line(`SolPubkey ${t} = *(${key.c});`);
|
|
696
|
+
collection = `&${t}`;
|
|
697
|
+
}
|
|
698
|
+
else if (p.name.text === "isCollection") {
|
|
699
|
+
const v = this.expect(this.expr(p.initializer, BOOL), BOOL, p.initializer);
|
|
700
|
+
const t = this.tmp("sized");
|
|
701
|
+
this.line(`bool ${t} = ${v.c};`);
|
|
702
|
+
sized = t;
|
|
703
|
+
}
|
|
704
|
+
else
|
|
705
|
+
this.fail(p.name, `createV3 extras are creators, collection and isCollection, not ${p.name.text}`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const seeds = this.signerSeeds(seedsArg, mintAuth, a[2]);
|
|
709
|
+
settleAll(extrasMark);
|
|
710
|
+
const [name, symbol, uri, fee, mutable] = slots.map((i) => vals[i]);
|
|
711
|
+
this.line(`TS_TRY(ts_mpl_create_v3(params, a_${program.name}, a_${metadata.name}, a_${mint.name}, a_${mintAuth.name}, a_${payer.name}, a_${updateAuth.name}, a_${sys.name}, ${name.c}, ${symbol.c}, ${uri.c}, ${fee.c}, ${mutable.c}, ${creators}, ${ncreators}, ${collection}, ${sized}, ${seeds.name}, ${seeds.count}));`);
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
/** CreateMasterEditionV3 (17) and Verify (52) with VerificationArgs::CollectionV1. */
|
|
715
|
+
metadataCollectionOp(node, program, method) {
|
|
716
|
+
const a = node.arguments;
|
|
717
|
+
const p = `a_${program.name}`;
|
|
718
|
+
const seedsAt = (i) => (a[i] ? this.signingSeeds(a[i]) : { name: "NULL", count: 0 });
|
|
719
|
+
const acct = (i, what, w, s) => {
|
|
720
|
+
if (a[i].kind === ts.SyntaxKind.NullKeyword)
|
|
721
|
+
this.fail(a[i], `pass ${what}`);
|
|
722
|
+
return this.mplMeta(a[i], program, what, w, s);
|
|
723
|
+
};
|
|
724
|
+
if (method === "createMasterEditionV3") {
|
|
725
|
+
if (a.length !== 9 && a.length !== 10)
|
|
726
|
+
this.fail(node, "createMasterEditionV3(edition, mint, updateAuthority, mintAuthority, payer, metadata, tokenProgram, systemProgram, maxSupply | null, signerSeeds?)");
|
|
727
|
+
const metas = [
|
|
728
|
+
acct(0, "the edition", true, false),
|
|
729
|
+
acct(1, "the mint", true, false),
|
|
730
|
+
acct(2, "the update authority", false, true),
|
|
731
|
+
acct(3, "the mint authority", false, true),
|
|
732
|
+
acct(4, "the payer", true, true),
|
|
733
|
+
acct(5, "the metadata", true, false),
|
|
734
|
+
acct(6, "the token program", false, false),
|
|
735
|
+
acct(7, "the system program", false, false),
|
|
736
|
+
];
|
|
737
|
+
// max_supply: Option<u64>; Some(0) makes a one of one, None allows unlimited prints.
|
|
738
|
+
const data = this.mplData(node, 16, a[8].kind === ts.SyntaxKind.NullKeyword ? [{ bytes: [17, 0] }] : [{ bytes: [17, 1] }, { value: a[8], type: { kind: "int", name: "u64" } }]);
|
|
739
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(9));
|
|
740
|
+
return undefined;
|
|
741
|
+
}
|
|
742
|
+
// Verify: authority, delegate record (absent: the program id), metadata, collection mint, collection metadata,
|
|
743
|
+
// collection master edition, system program, instructions sysvar.
|
|
744
|
+
if (a.length !== 7 && a.length !== 8)
|
|
745
|
+
this.fail(node, "verifyCollection(authority, metadata, collectionMint, collectionMetadata, collectionMasterEdition, systemProgram, instructionsSysvar, signerSeeds?)");
|
|
746
|
+
const metas = [
|
|
747
|
+
acct(0, "the collection authority", false, true),
|
|
748
|
+
[p, false, false],
|
|
749
|
+
acct(1, "the metadata", true, false),
|
|
750
|
+
acct(2, "the collection mint", false, false),
|
|
751
|
+
acct(3, "the collection metadata", true, false),
|
|
752
|
+
acct(4, "the collection master edition", false, false),
|
|
753
|
+
acct(5, "the system program", false, false),
|
|
754
|
+
acct(6, "the instructions sysvar", false, false),
|
|
755
|
+
];
|
|
756
|
+
const data = this.mplData(node, 8, [{ bytes: [52, 1] }]);
|
|
757
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(7));
|
|
491
758
|
return undefined;
|
|
492
759
|
}
|
|
493
760
|
/**
|
|
@@ -517,17 +784,156 @@ export class CpiEmitter extends CallEmitter {
|
|
|
517
784
|
finish(at);
|
|
518
785
|
return { buf, len };
|
|
519
786
|
}
|
|
520
|
-
/**
|
|
787
|
+
/**
|
|
788
|
+
* Borsh of one Metaplex Core plugin literal (`{ kind: "FreezeDelegate", frozen: true, authority?: … }`): the Plugin
|
|
789
|
+
* enum, then (`withAuthority`) the Option<PluginAuthority> that CreateV1's PluginAuthorityPair and AddPluginV1 take.
|
|
790
|
+
*/
|
|
791
|
+
corePlugin(n, withAuthority) {
|
|
792
|
+
if (!ts.isObjectLiteralExpression(n))
|
|
793
|
+
this.fail(n, 'a Core plugin is an object literal: { kind: "FreezeDelegate", frozen: false }');
|
|
794
|
+
const props = new Map();
|
|
795
|
+
for (const q of n.properties) {
|
|
796
|
+
if (ts.isPropertyAssignment(q) && ts.isIdentifier(q.name))
|
|
797
|
+
props.set(q.name.text, q.initializer);
|
|
798
|
+
else if (ts.isShorthandPropertyAssignment(q))
|
|
799
|
+
props.set(q.name.text, q.name);
|
|
800
|
+
else
|
|
801
|
+
this.fail(q, "write plugin fields as name: value");
|
|
802
|
+
}
|
|
803
|
+
const kindNode = props.get("kind");
|
|
804
|
+
if (!kindNode || !ts.isStringLiteral(kindNode))
|
|
805
|
+
this.fail(n, 'a Core plugin needs kind: "Royalties" | "FreezeDelegate" | …');
|
|
806
|
+
const kind = kindNode.text;
|
|
807
|
+
const index = CORE_PLUGINS.indexOf(kind);
|
|
808
|
+
if (index < 0)
|
|
809
|
+
this.fail(kindNode, `Core plugins are ${CORE_PLUGINS.join(", ")}`);
|
|
810
|
+
if (kind === "MasterEdition" || kind === "VerifiedCreators" || kind === "Autograph")
|
|
811
|
+
this.fail(kindNode, `the ${kind} plugin is not supported yet (removePlugin can remove one)`);
|
|
812
|
+
const known = new Set(["kind", "authority", ...(CORE_PLUGIN_FIELDS[kind] ?? [])]);
|
|
813
|
+
for (const [k, v] of props)
|
|
814
|
+
if (!known.has(k))
|
|
815
|
+
this.fail(v, `${kind} has no field ${k}`);
|
|
816
|
+
const need = (k) => props.get(k) ?? this.fail(n, `${kind} needs ${k}`);
|
|
817
|
+
const list = (k, max) => {
|
|
818
|
+
const e = need(k);
|
|
819
|
+
if (!ts.isArrayLiteralExpression(e))
|
|
820
|
+
this.fail(e, `${k} is an array literal`);
|
|
821
|
+
if (e.elements.length > max)
|
|
822
|
+
this.fail(e, `at most ${max} entries in ${k}`);
|
|
823
|
+
return e.elements;
|
|
824
|
+
};
|
|
825
|
+
const u32 = (x) => [x & 0xff, (x >> 8) & 0xff, (x >> 16) & 0xff, x >>> 24];
|
|
826
|
+
const fields = [{ bytes: [index] }];
|
|
827
|
+
switch (kind) {
|
|
828
|
+
case "Royalties": {
|
|
829
|
+
const bps = need("basisPoints");
|
|
830
|
+
if (ts.isBigIntLiteral(bps) && BigInt(bps.text.slice(0, -1)) > 10000n)
|
|
831
|
+
this.fail(bps, "basisPoints is at most 10000 (100%)");
|
|
832
|
+
fields.push({ value: bps, type: { kind: "int", name: "u16" } });
|
|
833
|
+
const creators = list("creators", 5);
|
|
834
|
+
fields.push({ bytes: u32(creators.length) });
|
|
835
|
+
let sum = 0n;
|
|
836
|
+
let constant = true;
|
|
837
|
+
for (const c of creators) {
|
|
838
|
+
if (!ts.isObjectLiteralExpression(c))
|
|
839
|
+
this.fail(c, "a creator is { address, percentage }");
|
|
840
|
+
const get = (k) => {
|
|
841
|
+
const q = c.properties.find((x) => (ts.isPropertyAssignment(x) || ts.isShorthandPropertyAssignment(x)) && ts.isIdentifier(x.name) && x.name.text === k);
|
|
842
|
+
if (!q)
|
|
843
|
+
this.fail(c, `a creator needs ${k}`);
|
|
844
|
+
return ts.isPropertyAssignment(q) ? q.initializer : q.name;
|
|
845
|
+
};
|
|
846
|
+
const pct = get("percentage");
|
|
847
|
+
if (ts.isBigIntLiteral(pct))
|
|
848
|
+
sum += BigInt(pct.text.slice(0, -1));
|
|
849
|
+
else
|
|
850
|
+
constant = false;
|
|
851
|
+
fields.push({ value: get("address"), type: PUBKEY }, { value: pct, type: U8 });
|
|
852
|
+
}
|
|
853
|
+
if (constant && creators.length && sum !== 100n)
|
|
854
|
+
this.fail(need("creators"), `creator percentages add up to ${sum}; Core requires 100`);
|
|
855
|
+
fields.push({ bytes: [0] }); // rule_set: RuleSet::None
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
case "FreezeDelegate":
|
|
859
|
+
case "PermanentFreezeDelegate":
|
|
860
|
+
fields.push({ value: need("frozen"), type: BOOL });
|
|
861
|
+
break;
|
|
862
|
+
case "UpdateDelegate": {
|
|
863
|
+
const keys = props.has("additionalDelegates") ? list("additionalDelegates", 8) : [];
|
|
864
|
+
fields.push({ bytes: u32(keys.length) }, ...keys.map((k) => ({ value: k, type: PUBKEY })));
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
case "Attributes": {
|
|
868
|
+
const attrs = list("attributes", 32);
|
|
869
|
+
fields.push({ bytes: u32(attrs.length) });
|
|
870
|
+
for (const at of attrs) {
|
|
871
|
+
if (!ts.isObjectLiteralExpression(at))
|
|
872
|
+
this.fail(at, "an attribute is { key, value }");
|
|
873
|
+
for (const k of ["key", "value"]) {
|
|
874
|
+
const q = at.properties.find((x) => (ts.isPropertyAssignment(x) || ts.isShorthandPropertyAssignment(x)) && ts.isIdentifier(x.name) && x.name.text === k);
|
|
875
|
+
if (!q)
|
|
876
|
+
this.fail(at, `an attribute needs ${k}`);
|
|
877
|
+
fields.push({ value: ts.isPropertyAssignment(q) ? q.initializer : q.name, type: STRING });
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
case "Edition":
|
|
883
|
+
fields.push({ value: need("number"), type: { kind: "int", name: "u32" } });
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
const auth = props.get("authority");
|
|
887
|
+
if (!withAuthority) {
|
|
888
|
+
if (auth)
|
|
889
|
+
this.fail(auth, "a plugin's authority is set when it is added; change it with Core's approve / revoke");
|
|
890
|
+
return fields;
|
|
891
|
+
}
|
|
892
|
+
if (!auth)
|
|
893
|
+
fields.push({ bytes: [0] }); // the plugin type's default authority
|
|
894
|
+
else if (ts.isStringLiteral(auth)) {
|
|
895
|
+
const i = ["none", "owner", "updateAuthority"].indexOf(auth.text);
|
|
896
|
+
if (i < 0)
|
|
897
|
+
this.fail(auth, 'authority is "none", "owner", "updateAuthority" or a Pubkey');
|
|
898
|
+
fields.push({ bytes: [1, i] });
|
|
899
|
+
}
|
|
900
|
+
else
|
|
901
|
+
fields.push({ bytes: [1, 3] }, { value: auth, type: PUBKEY });
|
|
902
|
+
return fields;
|
|
903
|
+
}
|
|
904
|
+
/** CreateV1 / CreateCollectionV1 plugins: Option<Vec<PluginAuthorityPair>>. */
|
|
905
|
+
corePluginList(n) {
|
|
906
|
+
if (!n)
|
|
907
|
+
return [{ bytes: [0] }];
|
|
908
|
+
const els = n.elements;
|
|
909
|
+
if (els.length > 16)
|
|
910
|
+
this.fail(n, "at most 16 plugins");
|
|
911
|
+
const kinds = els.map((e) => ts.isObjectLiteralExpression(e) ? e.properties.find((q) => q.name && ts.isIdentifier(q.name) && q.name.text === "kind") : undefined);
|
|
912
|
+
const seen = new Set();
|
|
913
|
+
kinds.forEach((k, i) => {
|
|
914
|
+
const lit = k && ts.isPropertyAssignment(k) && ts.isStringLiteral(k.initializer) ? k.initializer.text : undefined;
|
|
915
|
+
if (lit && seen.has(lit))
|
|
916
|
+
this.fail(els[i], `${lit} is listed twice; an asset has one plugin of each kind`);
|
|
917
|
+
if (lit)
|
|
918
|
+
seen.add(lit);
|
|
919
|
+
});
|
|
920
|
+
return [{ bytes: [1, els.length & 0xff, 0, 0, 0] }, ...els.flatMap((e) => this.corePlugin(e, true))];
|
|
921
|
+
}
|
|
922
|
+
/** Metaplex Core: assets and collections (with plugins), plugins added, updated and removed, transfer and burn. */
|
|
521
923
|
coreOp(node, core, method) {
|
|
522
924
|
const a = node.arguments;
|
|
523
925
|
const p = `a_${core.name}`;
|
|
524
926
|
const seedsAt = (i) => (a[i] ? this.signingSeeds(a[i]) : { name: "NULL", count: 0 });
|
|
525
927
|
const str = (n) => ({ value: n, type: STRING });
|
|
928
|
+
// An array literal of object literals after the uri is the plugin list; the signer seeds come after it.
|
|
929
|
+
const isPlugins = (n) => !!n && ts.isArrayLiteralExpression(n) && (n.elements.length === 0 || n.elements.every((e) => ts.isObjectLiteralExpression(e)));
|
|
930
|
+
const self = [p, false, false];
|
|
526
931
|
switch (method) {
|
|
527
932
|
case "createAsset": {
|
|
528
933
|
// CreateV1: asset, collection, authority, payer, owner, updateAuthority, systemProgram, logWrapper.
|
|
529
|
-
|
|
530
|
-
|
|
934
|
+
const plugins = isPlugins(a[8]) ? a[8] : undefined;
|
|
935
|
+
if (a.length < 8 || a.length > (plugins ? 10 : 9))
|
|
936
|
+
this.fail(node, "createAsset(asset, collection | null, authority | null, payer, owner | null, systemProgram, name, uri, plugins?, signerSeeds?)");
|
|
531
937
|
const metas = [
|
|
532
938
|
this.mplMeta(a[0], core, "the asset", true, true),
|
|
533
939
|
this.mplMeta(a[1], core, "the collection", true, false),
|
|
@@ -538,24 +944,64 @@ export class CpiEmitter extends CallEmitter {
|
|
|
538
944
|
this.mplMeta(a[5], core, "the system program", false, false),
|
|
539
945
|
[p, false, false],
|
|
540
946
|
];
|
|
541
|
-
const data = this.mplData(node,
|
|
542
|
-
this.cpi(p, metas, data.buf, data.len, seedsAt(8));
|
|
947
|
+
const data = this.mplData(node, 2048, [{ bytes: [0, 0] }, str(a[6]), str(a[7]), ...this.corePluginList(plugins)]);
|
|
948
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(plugins ? 9 : 8));
|
|
543
949
|
return undefined;
|
|
544
950
|
}
|
|
545
951
|
case "createCollection": {
|
|
546
952
|
// CreateCollectionV1: collection, updateAuthority, payer, systemProgram.
|
|
547
|
-
|
|
548
|
-
|
|
953
|
+
const plugins = isPlugins(a[6]) ? a[6] : undefined;
|
|
954
|
+
if (a.length < 6 || a.length > (plugins ? 8 : 7))
|
|
955
|
+
this.fail(node, "createCollection(collection, updateAuthority | null, payer, systemProgram, name, uri, plugins?, signerSeeds?)");
|
|
549
956
|
const metas = [
|
|
550
957
|
this.mplMeta(a[0], core, "the collection", true, true),
|
|
551
958
|
this.mplMeta(a[1], core, "the update authority", false, false),
|
|
552
959
|
this.mplMeta(a[2], core, "the payer", true, true),
|
|
553
960
|
this.mplMeta(a[3], core, "the system program", false, false),
|
|
554
961
|
];
|
|
555
|
-
const data = this.mplData(node,
|
|
962
|
+
const data = this.mplData(node, 2048, [{ bytes: [1] }, str(a[4]), str(a[5]), ...this.corePluginList(plugins)]);
|
|
963
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(plugins ? 7 : 6));
|
|
964
|
+
return undefined;
|
|
965
|
+
}
|
|
966
|
+
case "addPlugin":
|
|
967
|
+
case "updatePlugin":
|
|
968
|
+
case "removePlugin": {
|
|
969
|
+
// Add/Remove/UpdatePluginV1: asset, collection, payer, authority, systemProgram, logWrapper.
|
|
970
|
+
const last = method === "removePlugin" ? "kind" : "plugin";
|
|
971
|
+
if (a.length !== 6 && a.length !== 7)
|
|
972
|
+
this.fail(node, `${method}(asset, collection | null, payer, authority | null, systemProgram, ${last}, signerSeeds?)`);
|
|
973
|
+
const metas = [
|
|
974
|
+
this.mplMeta(a[0], core, "the asset", true, false),
|
|
975
|
+
this.mplMeta(a[1], core, "the collection", true, false),
|
|
976
|
+
this.mplMeta(a[2], core, "the payer", true, true),
|
|
977
|
+
this.mplMeta(a[3], core, "the authority", false, true),
|
|
978
|
+
this.mplMeta(a[4], core, "the system program", false, false),
|
|
979
|
+
self,
|
|
980
|
+
];
|
|
981
|
+
const body = method === "removePlugin" ? [this.corePluginType(a[5])] : this.corePlugin(a[5], method === "addPlugin");
|
|
982
|
+
const data = this.mplData(node, 2048, [{ bytes: [{ addPlugin: 2, removePlugin: 4, updatePlugin: 6 }[method]] }, ...body]);
|
|
556
983
|
this.cpi(p, metas, data.buf, data.len, seedsAt(6));
|
|
557
984
|
return undefined;
|
|
558
985
|
}
|
|
986
|
+
case "addCollectionPlugin":
|
|
987
|
+
case "updateCollectionPlugin":
|
|
988
|
+
case "removeCollectionPlugin": {
|
|
989
|
+
// Add/Remove/UpdateCollectionPluginV1: collection, payer, authority, systemProgram, logWrapper.
|
|
990
|
+
const last = method === "removeCollectionPlugin" ? "kind" : "plugin";
|
|
991
|
+
if (a.length !== 5 && a.length !== 6)
|
|
992
|
+
this.fail(node, `${method}(collection, payer, authority | null, systemProgram, ${last}, signerSeeds?)`);
|
|
993
|
+
const metas = [
|
|
994
|
+
this.mplMeta(a[0], core, "the collection", true, false),
|
|
995
|
+
this.mplMeta(a[1], core, "the payer", true, true),
|
|
996
|
+
this.mplMeta(a[2], core, "the authority", false, true),
|
|
997
|
+
this.mplMeta(a[3], core, "the system program", false, false),
|
|
998
|
+
self,
|
|
999
|
+
];
|
|
1000
|
+
const body = method === "removeCollectionPlugin" ? [this.corePluginType(a[4])] : this.corePlugin(a[4], method === "addCollectionPlugin");
|
|
1001
|
+
const data = this.mplData(node, 2048, [{ bytes: [{ addCollectionPlugin: 3, removeCollectionPlugin: 5, updateCollectionPlugin: 7 }[method]] }, ...body]);
|
|
1002
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(5));
|
|
1003
|
+
return undefined;
|
|
1004
|
+
}
|
|
559
1005
|
case "transferAsset": {
|
|
560
1006
|
// TransferV1: asset, collection, payer, authority, newOwner, systemProgram, logWrapper.
|
|
561
1007
|
if (a.length !== 5 && a.length !== 6)
|
|
@@ -590,7 +1036,16 @@ export class CpiEmitter extends CallEmitter {
|
|
|
590
1036
|
return undefined;
|
|
591
1037
|
}
|
|
592
1038
|
}
|
|
593
|
-
this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, transferAsset and burnAsset, not ${method}`);
|
|
1039
|
+
this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, addPlugin, updatePlugin, removePlugin (and the Collection forms), transferAsset and burnAsset, not ${method}`);
|
|
1040
|
+
}
|
|
1041
|
+
/** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
|
|
1042
|
+
corePluginType(n) {
|
|
1043
|
+
if (!ts.isStringLiteral(n))
|
|
1044
|
+
this.fail(n, 'name the plugin kind as a string literal: "FreezeDelegate"');
|
|
1045
|
+
const i = CORE_PLUGINS.indexOf(n.text);
|
|
1046
|
+
if (i < 0)
|
|
1047
|
+
this.fail(n, `Core plugins are ${CORE_PLUGINS.join(", ")}`);
|
|
1048
|
+
return { bytes: [i] };
|
|
594
1049
|
}
|
|
595
1050
|
/** Bubblegum (compressed NFTs on SPL account compression): createTree, mint (MintV1, no collection, no creators). */
|
|
596
1051
|
bubblegumOp(node, bg, method) {
|
|
@@ -653,7 +1108,218 @@ export class CpiEmitter extends CallEmitter {
|
|
|
653
1108
|
return undefined;
|
|
654
1109
|
}
|
|
655
1110
|
}
|
|
656
|
-
this.
|
|
1111
|
+
return this.bubblegumV2(node, bg, method);
|
|
1112
|
+
}
|
|
1113
|
+
/** Bubblegum V2 (trees on mpl-account-compression with mpl-noop; optional Metaplex Core collections). */
|
|
1114
|
+
bubblegumV2(node, bg, method) {
|
|
1115
|
+
const a = node.arguments;
|
|
1116
|
+
const p = `a_${bg.name}`;
|
|
1117
|
+
const acct = (i, what, w, s) => this.mplMeta(a[i], bg, what, w, s);
|
|
1118
|
+
const seedsAt = (i) => (a[i] ? this.signingSeeds(a[i]) : { name: "NULL", count: 0 });
|
|
1119
|
+
const U32 = { kind: "int", name: "u32" };
|
|
1120
|
+
const U64v = { kind: "int", name: "u64" };
|
|
1121
|
+
const HASH = { kind: "pubkey", hash: true };
|
|
1122
|
+
// The leaf a transfer or burn replaces: { root, dataHash, creatorHash, assetDataHash?, flags?, nonce, index }.
|
|
1123
|
+
const leafFields = (n) => {
|
|
1124
|
+
if (!ts.isObjectLiteralExpression(n))
|
|
1125
|
+
this.fail(n, "the leaf is { root, dataHash, creatorHash, assetDataHash?, flags?, nonce, index }");
|
|
1126
|
+
const props = new Map();
|
|
1127
|
+
for (const q of n.properties) {
|
|
1128
|
+
if (ts.isPropertyAssignment(q) && ts.isIdentifier(q.name))
|
|
1129
|
+
props.set(q.name.text, q.initializer);
|
|
1130
|
+
else if (ts.isShorthandPropertyAssignment(q))
|
|
1131
|
+
props.set(q.name.text, q.name);
|
|
1132
|
+
else
|
|
1133
|
+
this.fail(q, "write leaf fields as name: value");
|
|
1134
|
+
}
|
|
1135
|
+
const known = ["root", "dataHash", "creatorHash", "assetDataHash", "flags", "nonce", "index"];
|
|
1136
|
+
for (const [k, v] of props)
|
|
1137
|
+
if (!known.includes(k))
|
|
1138
|
+
this.fail(v, `the leaf has no field ${k}`);
|
|
1139
|
+
const need = (k) => props.get(k) ?? this.fail(n, `the leaf needs ${k}`);
|
|
1140
|
+
const opt = (k, t) => {
|
|
1141
|
+
const v = props.get(k);
|
|
1142
|
+
if (!v || v.kind === ts.SyntaxKind.NullKeyword)
|
|
1143
|
+
return [{ bytes: [0] }];
|
|
1144
|
+
return [{ bytes: [1] }, { value: v, type: t }];
|
|
1145
|
+
};
|
|
1146
|
+
return [
|
|
1147
|
+
{ value: need("root"), type: HASH },
|
|
1148
|
+
{ value: need("dataHash"), type: HASH },
|
|
1149
|
+
{ value: need("creatorHash"), type: HASH },
|
|
1150
|
+
...opt("assetDataHash", HASH),
|
|
1151
|
+
...opt("flags", U8),
|
|
1152
|
+
{ value: need("nonce"), type: U64v },
|
|
1153
|
+
{ value: need("index"), type: U32 },
|
|
1154
|
+
];
|
|
1155
|
+
};
|
|
1156
|
+
const proofRange = (n) => {
|
|
1157
|
+
if (!this.ix)
|
|
1158
|
+
this.fail(n, "a Merkle proof comes from remainingAccounts(), so only instructions can pass one");
|
|
1159
|
+
return this.remainingRange(n) ?? this.fail(n, "pass the proof as remainingAccounts() or rest.slice(a, b)");
|
|
1160
|
+
};
|
|
1161
|
+
switch (method) {
|
|
1162
|
+
case "createTreeV2": {
|
|
1163
|
+
// CreateTreeConfigV2: treeConfig, merkleTree, payer, treeCreator, logWrapper, compressionProgram, systemProgram.
|
|
1164
|
+
if (a.length !== 10 && a.length !== 11)
|
|
1165
|
+
this.fail(node, "createTreeV2(treeConfig, merkleTree, payer, treeCreator | null, logWrapper, compressionProgram, systemProgram, maxDepth, maxBufferSize, isPublic, signerSeeds?)");
|
|
1166
|
+
const metas = [
|
|
1167
|
+
acct(0, "the tree config", true, false),
|
|
1168
|
+
acct(1, "the merkle tree", true, false),
|
|
1169
|
+
acct(2, "the payer", true, true),
|
|
1170
|
+
acct(3, "the tree creator", false, true),
|
|
1171
|
+
acct(4, "the log wrapper", false, false),
|
|
1172
|
+
acct(5, "the compression program", false, false),
|
|
1173
|
+
acct(6, "the system program", false, false),
|
|
1174
|
+
];
|
|
1175
|
+
const data = this.mplData(node, 32, [
|
|
1176
|
+
{ bytes: [55, 99, 95, 215, 142, 203, 227, 205] },
|
|
1177
|
+
{ value: a[7], type: U32 },
|
|
1178
|
+
{ value: a[8], type: U32 },
|
|
1179
|
+
{ bytes: [1] },
|
|
1180
|
+
{ value: a[9], type: BOOL },
|
|
1181
|
+
]);
|
|
1182
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(10));
|
|
1183
|
+
return undefined;
|
|
1184
|
+
}
|
|
1185
|
+
case "mintV2": {
|
|
1186
|
+
// MintV2: treeConfig, payer, treeCreatorOrDelegate, collectionAuthority, leafOwner, leafDelegate, merkleTree,
|
|
1187
|
+
// coreCollection, mplCoreCpiSigner, logWrapper, compressionProgram, mplCoreProgram, systemProgram.
|
|
1188
|
+
const usage = "mintV2(treeConfig, payer, treeDelegate | null, leafOwner, leafDelegate | null, merkleTree, logWrapper, compressionProgram, mplCoreProgram, systemProgram, { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable? }, { collection, authority, cpiSigner }?, signerSeeds?)";
|
|
1189
|
+
const collection = a[11] && ts.isObjectLiteralExpression(a[11]) ? a[11] : undefined;
|
|
1190
|
+
if (a.length < 11 || a.length > (collection ? 13 : 12))
|
|
1191
|
+
this.fail(node, usage);
|
|
1192
|
+
const md = a[10];
|
|
1193
|
+
if (!ts.isObjectLiteralExpression(md))
|
|
1194
|
+
this.fail(md, "the metadata is { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable? }");
|
|
1195
|
+
const field = (o, k) => {
|
|
1196
|
+
const q = o.properties.find((x) => (ts.isPropertyAssignment(x) || ts.isShorthandPropertyAssignment(x)) && ts.isIdentifier(x.name) && x.name.text === k);
|
|
1197
|
+
return q ? (ts.isPropertyAssignment(q) ? q.initializer : q.name) : undefined;
|
|
1198
|
+
};
|
|
1199
|
+
for (const q of md.properties)
|
|
1200
|
+
if (!q.name || !ts.isIdentifier(q.name) || !["name", "symbol", "uri", "sellerFeeBasisPoints", "creators", "isMutable"].includes(q.name.text))
|
|
1201
|
+
this.fail(q, "the metadata takes name, symbol, uri, sellerFeeBasisPoints, creators and isMutable");
|
|
1202
|
+
const coll = collection
|
|
1203
|
+
? ["collection", "authority", "cpiSigner"].map((k) => field(collection, k) ?? this.fail(collection, `the collection needs ${k}`))
|
|
1204
|
+
: undefined;
|
|
1205
|
+
if (collection)
|
|
1206
|
+
for (const q of collection.properties)
|
|
1207
|
+
if (!q.name || !ts.isIdentifier(q.name) || !["collection", "authority", "cpiSigner"].includes(q.name.text))
|
|
1208
|
+
this.fail(q, "the collection is { collection, authority, cpiSigner }");
|
|
1209
|
+
const metas = [
|
|
1210
|
+
acct(0, "the tree config", true, false),
|
|
1211
|
+
acct(1, "the payer", true, true),
|
|
1212
|
+
acct(2, "the tree delegate", false, true),
|
|
1213
|
+
coll ? this.mplMeta(coll[1], bg, "the collection authority", false, true) : [p, false, false],
|
|
1214
|
+
acct(3, "the leaf owner", false, false),
|
|
1215
|
+
acct(4, "the leaf delegate", false, false),
|
|
1216
|
+
acct(5, "the merkle tree", true, false),
|
|
1217
|
+
coll ? this.mplMeta(coll[0], bg, "the core collection", true, false) : [p, false, false],
|
|
1218
|
+
coll ? this.mplMeta(coll[2], bg, "the mpl-core CPI signer", false, false) : [p, false, false],
|
|
1219
|
+
acct(6, "the log wrapper", false, false),
|
|
1220
|
+
acct(7, "the compression program", false, false),
|
|
1221
|
+
acct(8, "the mpl-core program", false, false),
|
|
1222
|
+
acct(9, "the system program", false, false),
|
|
1223
|
+
];
|
|
1224
|
+
const need = (k) => field(md, k) ?? this.fail(md, `the metadata needs ${k}`);
|
|
1225
|
+
const fields = [
|
|
1226
|
+
{ bytes: [120, 121, 23, 146, 173, 110, 199, 205] },
|
|
1227
|
+
{ value: need("name"), type: STRING },
|
|
1228
|
+
{ value: need("symbol"), type: STRING },
|
|
1229
|
+
{ value: need("uri"), type: STRING },
|
|
1230
|
+
{ value: need("sellerFeeBasisPoints"), type: { kind: "int", name: "u16" } },
|
|
1231
|
+
{ bytes: [0] }, // primary_sale_happened
|
|
1232
|
+
];
|
|
1233
|
+
const mutable = field(md, "isMutable");
|
|
1234
|
+
fields.push(mutable ? { value: mutable, type: BOOL } : { bytes: [1] });
|
|
1235
|
+
fields.push({ bytes: [1, 0] }); // token_standard: Some(NonFungible)
|
|
1236
|
+
const creators = field(md, "creators");
|
|
1237
|
+
if (creators && !ts.isArrayLiteralExpression(creators))
|
|
1238
|
+
this.fail(creators, "creators: [{ address, verified, share }, …] (an array literal)");
|
|
1239
|
+
const list = creators ? creators.elements : [];
|
|
1240
|
+
if (list.length > 5)
|
|
1241
|
+
this.fail(creators, "Bubblegum takes at most 5 creators");
|
|
1242
|
+
fields.push({ bytes: [list.length, 0, 0, 0] });
|
|
1243
|
+
let shares = 0n;
|
|
1244
|
+
let constant = true;
|
|
1245
|
+
for (const el of list) {
|
|
1246
|
+
if (!ts.isObjectLiteralExpression(el))
|
|
1247
|
+
this.fail(el, "a creator is { address, verified, share }");
|
|
1248
|
+
const f = (k) => field(el, k) ?? this.fail(el, `a creator needs ${k}`);
|
|
1249
|
+
const share = f("share");
|
|
1250
|
+
if (ts.isBigIntLiteral(share))
|
|
1251
|
+
shares += BigInt(share.text.slice(0, -1));
|
|
1252
|
+
else
|
|
1253
|
+
constant = false;
|
|
1254
|
+
fields.push({ value: f("address"), type: PUBKEY }, { value: f("verified"), type: BOOL }, { value: share, type: U8 });
|
|
1255
|
+
}
|
|
1256
|
+
if (list.length && constant && shares !== 100n)
|
|
1257
|
+
this.fail(creators, `creator shares add up to ${shares}; Bubblegum requires 100`);
|
|
1258
|
+
// collection: Some(the core collection's key) when minting into one.
|
|
1259
|
+
if (coll)
|
|
1260
|
+
fields.push({ bytes: [1] }, { value: this.keyOf(coll[0]), type: PUBKEY });
|
|
1261
|
+
else
|
|
1262
|
+
fields.push({ bytes: [0] });
|
|
1263
|
+
fields.push({ bytes: [0, 0] }); // asset_data, asset_data_schema: None
|
|
1264
|
+
const data = this.mplData(node, 1024, fields);
|
|
1265
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(collection ? 12 : 11));
|
|
1266
|
+
return undefined;
|
|
1267
|
+
}
|
|
1268
|
+
case "transferV2": {
|
|
1269
|
+
// TransferV2: treeConfig, payer, authority, leafOwner, leafDelegate, newLeafOwner, merkleTree, coreCollection,
|
|
1270
|
+
// logWrapper, compressionProgram, systemProgram; the proof follows as remaining accounts.
|
|
1271
|
+
if (a.length !== 13 && a.length !== 14)
|
|
1272
|
+
this.fail(node, "transferV2(treeConfig, payer, authority | null, leafOwner, leafDelegate | null, newLeafOwner, merkleTree, coreCollection | null, logWrapper, compressionProgram, systemProgram, leaf, proof, signerSeeds?)");
|
|
1273
|
+
const metas = [
|
|
1274
|
+
acct(0, "the tree config", true, false),
|
|
1275
|
+
acct(1, "the payer", true, true),
|
|
1276
|
+
acct(2, "the authority", false, true),
|
|
1277
|
+
acct(3, "the leaf owner", false, false),
|
|
1278
|
+
acct(4, "the leaf delegate", false, false),
|
|
1279
|
+
acct(5, "the new leaf owner", false, false),
|
|
1280
|
+
acct(6, "the merkle tree", true, false),
|
|
1281
|
+
acct(7, "the core collection", false, false),
|
|
1282
|
+
acct(8, "the log wrapper", false, false),
|
|
1283
|
+
acct(9, "the compression program", false, false),
|
|
1284
|
+
acct(10, "the system program", false, false),
|
|
1285
|
+
];
|
|
1286
|
+
const data = this.mplData(node, 160, [{ bytes: [119, 40, 6, 235, 234, 221, 248, 49] }, ...leafFields(a[11])]);
|
|
1287
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(13), proofRange(a[12]));
|
|
1288
|
+
return undefined;
|
|
1289
|
+
}
|
|
1290
|
+
case "burnV2": {
|
|
1291
|
+
// BurnV2: treeConfig, payer, authority, leafOwner, leafDelegate, merkleTree, coreCollection, mplCoreCpiSigner,
|
|
1292
|
+
// logWrapper, compressionProgram, mplCoreProgram, systemProgram; the proof follows as remaining accounts.
|
|
1293
|
+
if (a.length !== 14 && a.length !== 15)
|
|
1294
|
+
this.fail(node, "burnV2(treeConfig, payer, authority | null, leafOwner, leafDelegate | null, merkleTree, coreCollection | null, mplCoreCpiSigner | null, logWrapper, compressionProgram, mplCoreProgram, systemProgram, leaf, proof, signerSeeds?)");
|
|
1295
|
+
const metas = [
|
|
1296
|
+
acct(0, "the tree config", true, false),
|
|
1297
|
+
acct(1, "the payer", true, true),
|
|
1298
|
+
acct(2, "the authority", false, true),
|
|
1299
|
+
acct(3, "the leaf owner", false, false),
|
|
1300
|
+
acct(4, "the leaf delegate", false, false),
|
|
1301
|
+
acct(5, "the merkle tree", true, false),
|
|
1302
|
+
acct(6, "the core collection", true, false),
|
|
1303
|
+
acct(7, "the mpl-core CPI signer", false, false),
|
|
1304
|
+
acct(8, "the log wrapper", false, false),
|
|
1305
|
+
acct(9, "the compression program", false, false),
|
|
1306
|
+
acct(10, "the mpl-core program", false, false),
|
|
1307
|
+
acct(11, "the system program", false, false),
|
|
1308
|
+
];
|
|
1309
|
+
const data = this.mplData(node, 160, [{ bytes: [115, 210, 34, 240, 232, 143, 183, 16] }, ...leafFields(a[12])]);
|
|
1310
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(14), proofRange(a[13]));
|
|
1311
|
+
return undefined;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
this.fail(node.expression, `Bubblegum supports createTree, mint, createTreeV2, mintV2, transferV2 and burnV2, not ${method}`);
|
|
1315
|
+
}
|
|
1316
|
+
/** `acct.key` of an account parameter, as an expression to serialize. */
|
|
1317
|
+
keyOf(n) {
|
|
1318
|
+
const f = ts.factory;
|
|
1319
|
+
const e = f.createPropertyAccessExpression(n, "key");
|
|
1320
|
+
ts.setTextRange(e, n);
|
|
1321
|
+
e.parent = n.parent;
|
|
1322
|
+
return e;
|
|
657
1323
|
}
|
|
658
1324
|
/**
|
|
659
1325
|
* `remainingAccounts()` (or a const bound to it): every remaining account; `rest.slice(a, b?)`: indexes a..b of them
|
|
@@ -914,9 +1580,22 @@ export class CpiEmitter extends CallEmitter {
|
|
|
914
1580
|
e.variants.forEach((v, i) => {
|
|
915
1581
|
this.line(`case ${i}: {`);
|
|
916
1582
|
for (const f of v.fields) {
|
|
1583
|
+
const at = `${src.lv}.v.${v.name}.${f.name}`;
|
|
1584
|
+
if (f.type.kind === "string") {
|
|
1585
|
+
// Borsh String: u32 length, then the bytes.
|
|
1586
|
+
const n = this.tmp("sl");
|
|
1587
|
+
this.line(`uint32_t ${n} = (uint32_t)${at}.len;`);
|
|
1588
|
+
put(`&${n}`, "4");
|
|
1589
|
+
put(`${at}.ptr`, `${at}.len`);
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1592
|
+
if (f.type.kind === "dataEnum" && this.model.dataEnums.get(f.type.name)?.variable) {
|
|
1593
|
+
this.serializeDataEnumLv(at, f.type.name, put);
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
917
1596
|
const n = this.fieldBytes(f.type);
|
|
918
1597
|
// put() with no static size: the payload differs by variant.
|
|
919
|
-
put(`&${
|
|
1598
|
+
put(`&${at}`, String(n));
|
|
920
1599
|
}
|
|
921
1600
|
this.line(`break; }`);
|
|
922
1601
|
});
|
|
@@ -927,6 +1606,8 @@ export class CpiEmitter extends CallEmitter {
|
|
|
927
1606
|
const s = targ && ts.isTypeReferenceNode(targ) && ts.isIdentifier(targ.typeName) ? this.model.structs.get(targ.typeName.text) : undefined;
|
|
928
1607
|
if (!s)
|
|
929
1608
|
this.fail(node, "emit needs an explicit event type: emit<MyEvent>({ ... })");
|
|
1609
|
+
if (s.layout === "borsh")
|
|
1610
|
+
this.fail(node, `${s.name} is @layout borsh, an account layout; declare a separate event struct`);
|
|
930
1611
|
const obj = node.arguments[0];
|
|
931
1612
|
if (node.arguments.length !== (cpi ? 3 : 1) || !ts.isObjectLiteralExpression(obj))
|
|
932
1613
|
this.fail(node, "emit<T>({ field: value, ... }) takes one object literal");
|