@typesol/cli 0.23.0 → 0.24.1

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +77 -2
  2. package/README.md +18 -15
  3. package/SECURITY.md +1 -1
  4. package/dist/clientgen.js +222 -61
  5. package/dist/compiler/analyze.js +132 -20
  6. package/dist/compiler/emit/base.d.ts +27 -4
  7. package/dist/compiler/emit/base.js +68 -15
  8. package/dist/compiler/emit/calls.d.ts +7 -0
  9. package/dist/compiler/emit/calls.js +99 -9
  10. package/dist/compiler/emit/cpi.d.ts +33 -1
  11. package/dist/compiler/emit/cpi.js +353 -110
  12. package/dist/compiler/emit/expressions.d.ts +9 -1
  13. package/dist/compiler/emit/expressions.js +16 -12
  14. package/dist/compiler/emit/function.js +3 -1
  15. package/dist/compiler/emit/shared.d.ts +11 -1
  16. package/dist/compiler/emit/statements.d.ts +4 -2
  17. package/dist/compiler/emit/statements.js +128 -27
  18. package/dist/compiler/emit/storage.d.ts +6 -2
  19. package/dist/compiler/emit/storage.js +36 -32
  20. package/dist/compiler/model.d.ts +8 -0
  21. package/dist/idlpublish.d.ts +6 -1
  22. package/dist/idlpublish.js +47 -5
  23. package/dist/portgen.js +1 -1
  24. package/dist/svmrpc.d.ts +7 -1
  25. package/dist/svmrpc.js +133 -19
  26. package/dist/testing.js +23 -3
  27. package/docs/architecture.md +26 -2
  28. package/docs/cli.md +2 -2
  29. package/docs/client.md +13 -10
  30. package/docs/errors.md +9 -3
  31. package/docs/language.md +18 -17
  32. package/docs/mainnet.md +1 -1
  33. package/docs/migrate-from-anchor.md +8 -9
  34. package/docs/runtime.md +59 -0
  35. package/docs/test-your-program.md +6 -2
  36. package/docs/testing.md +1 -1
  37. package/docs/troubleshooting.md +3 -2
  38. package/lib/typesol.d.ts +120 -21
  39. package/package.json +1 -1
  40. package/runtime/typesol_rt.h +106 -28
@@ -207,6 +207,7 @@ export class CpiEmitter extends CallEmitter {
207
207
  ts.isPostfixUnaryExpression(n) ||
208
208
  ts.forEachChild(n, mutates) === true;
209
209
  const inPlace = !node.elements.some(mutates);
210
+ const mark = this.lines.length;
210
211
  for (const el of node.elements) {
211
212
  const desc = this.describeSeed(el);
212
213
  if (!desc)
@@ -252,9 +253,19 @@ export class CpiEmitter extends CallEmitter {
252
253
  else
253
254
  this.fail(el, "seeds can be strings, Pubkeys, integers or unit enum values");
254
255
  }
256
+ // The same list again (escrow: the vault transfer, then its close) reuses the array declared for the first, when
257
+ // nothing was copied into temporaries (every entry points at a literal or at the place itself, so the array reads
258
+ // what a new one would), it has no spare slot (callers never write into those arrays) and it is still in scope.
259
+ const key = parts.join(", ");
260
+ const reusable = extra === 0 && parts.length > 0 && this.lines.length === mark;
261
+ const known = reusable ? this.seedArrays.get(key) : undefined;
262
+ if (known && this.scopes.includes(known.scope) && this.lines[known.at]?.trim() === known.text)
263
+ return { name: known.name, count: parts.length, idl };
255
264
  const name = this.tmp("seeds");
256
265
  const slots = parts.length + extra;
257
266
  this.line(`SolSignerSeed ${name}[${Math.max(slots, 1)}] = {${parts.join(", ")}};`);
267
+ if (reusable)
268
+ this.seedArrays.set(key, { name, scope: this.scopes[this.scopes.length - 1], at: this.lines.length - 1, text: this.lines.at(-1).trim() });
258
269
  return { name, count: parts.length, idl };
259
270
  }
260
271
  describeSeed(el) {
@@ -1526,6 +1537,8 @@ export class CpiEmitter extends CallEmitter {
1526
1537
  this.line(`static const uint8_t ${name}[${f.bytes.length}] = ${cBytes(f.bytes)};`);
1527
1538
  put(name, String(f.bytes.length), f.bytes.length);
1528
1539
  }
1540
+ else if ("c" in f)
1541
+ put(f.c, f.n);
1529
1542
  else
1530
1543
  this.serialize(this.expect(this.expr(f.value, f.type), f.type, f.value), f.value, put);
1531
1544
  }
@@ -1713,124 +1726,354 @@ export class CpiEmitter extends CallEmitter {
1713
1726
  });
1714
1727
  return [{ bytes: [1, els.length & 0xff, 0, 0, 0] }, ...els.flatMap((e) => this.corePlugin(e, true))];
1715
1728
  }
1716
- /** Metaplex Core: assets and collections (with plugins), plugins added, updated and removed, transfer and burn. */
1729
+ /**
1730
+ * Metaplex Core: assets and collections (with plugins), plugins and external plugin adapters added, updated, written
1731
+ * and removed, transfer and burn. Each method is a row of CORE_OPS below: its accounts in order, its data.
1732
+ */
1717
1733
  coreOp(node, core, method) {
1718
1734
  const a = node.arguments;
1719
1735
  const p = `a_${core.name}`;
1720
- const seedsAt = (i) => (a[i] ? this.signingSeeds(a[i]) : { name: "NULL", count: 0 });
1721
- const str = (n) => ({ value: n, type: STRING });
1722
- // An array literal of object literals after the uri is the plugin list; the signer seeds come after it.
1736
+ const op = this.coreOps()[method];
1737
+ if (!op)
1738
+ this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, addPlugin, updatePlugin, removePlugin, addExternalPlugin, removeExternalPlugin, writeExternalPluginData (and the Collection forms), transferAsset and burnAsset, not ${method}`);
1739
+ // An array literal of object literals right after the fixed arguments is the plugin list; the signer seeds follow.
1723
1740
  const isPlugins = (n) => !!n && ts.isArrayLiteralExpression(n) && (n.elements.length === 0 || n.elements.every((e) => ts.isObjectLiteralExpression(e)));
1724
- const self = [p, false, false];
1725
- switch (method) {
1726
- case "createAsset": {
1727
- // CreateV1: asset, collection, authority, payer, owner, updateAuthority, systemProgram, logWrapper.
1728
- const plugins = isPlugins(a[8]) ? a[8] : undefined;
1729
- if (a.length < 8 || a.length > (plugins ? 10 : 9))
1730
- this.fail(node, "createAsset(asset, collection | null, authority | null, payer, owner | null, systemProgram, name, uri, plugins?, signerSeeds?)");
1731
- const metas = [
1732
- this.mplMeta(a[0], core, "the asset", true, true),
1733
- this.mplMeta(a[1], core, "the collection", true, false),
1734
- this.mplMeta(a[2], core, "the collection authority", false, true),
1735
- this.mplMeta(a[3], core, "the payer", true, true),
1736
- this.mplMeta(a[4], core, "the owner", false, false),
1737
- [p, false, false], // update authority: the collection's, or the payer by default
1738
- this.mplMeta(a[5], core, "the system program", false, false),
1739
- [p, false, false],
1740
- ];
1741
- const data = this.mplData(node, 2048, [{ bytes: [0, 0] }, str(a[6]), str(a[7]), ...this.corePluginList(plugins)]);
1742
- this.cpi(p, metas, data.buf, data.len, seedsAt(plugins ? 9 : 8));
1743
- return undefined;
1744
- }
1745
- case "createCollection": {
1741
+ const list = op.plugins && isPlugins(a[op.args]) ? a[op.args] : undefined;
1742
+ const seedsAt = op.args + (list ? 1 : 0);
1743
+ if (op.plugins ? a.length < op.args || a.length > seedsAt + 1 : a.length !== op.args && a.length !== op.args + 1)
1744
+ this.fail(node, op.usage(method));
1745
+ const metas = op.metas.map((m) => (m === "self" ? [p, false, false] : this.mplMeta(a[m[0]], core, m[1], m[2], m[3])));
1746
+ const data = this.mplData(node, op.max, op.data(a, list));
1747
+ this.cpi(p, metas, data.buf, data.len, a[seedsAt] ? this.signingSeeds(a[seedsAt]) : { name: "NULL", count: 0 });
1748
+ return undefined;
1749
+ }
1750
+ /**
1751
+ * Core instructions: `args` fixed arguments (then an optional plugin list when `plugins`, then optional signer
1752
+ * seeds), accounts in order (`[argument, label, writable, signer]`, or "self": the Core program, Core's "none" for an
1753
+ * optional account), at most `max` bytes of data.
1754
+ */
1755
+ coreOps() {
1756
+ const str = (n) => ({ value: n, type: STRING });
1757
+ const asset = [0, "the asset", true, false];
1758
+ const assetPlugin = {
1759
+ usage: (m) => `${m}(asset, collection | null, payer, authority | null, systemProgram, ${m === "removePlugin" ? "kind" : "plugin"}, signerSeeds?)`,
1760
+ args: 6,
1761
+ // Add/Remove/UpdatePluginV1: asset, collection, payer, authority, systemProgram, logWrapper.
1762
+ metas: [
1763
+ asset,
1764
+ [1, "the collection", true, false],
1765
+ [2, "the payer", true, true],
1766
+ [3, "the authority", false, true],
1767
+ [4, "the system program", false, false],
1768
+ "self",
1769
+ ],
1770
+ max: 2048,
1771
+ };
1772
+ const collectionPlugin = {
1773
+ usage: (m) => `${m}(collection, payer, authority | null, systemProgram, ${m === "removeCollectionPlugin" ? "kind" : "plugin"}, signerSeeds?)`,
1774
+ args: 5,
1775
+ // Add/Remove/UpdateCollectionPluginV1: collection, payer, authority, systemProgram, logWrapper.
1776
+ metas: [
1777
+ [0, "the collection", true, false],
1778
+ [1, "the payer", true, true],
1779
+ [2, "the authority", false, true],
1780
+ [3, "the system program", false, false],
1781
+ "self",
1782
+ ],
1783
+ max: 2048,
1784
+ };
1785
+ const assetPluginOp = (method, disc) => ({
1786
+ ...assetPlugin,
1787
+ data: (a) => [
1788
+ { bytes: [disc] },
1789
+ ...(method === "removePlugin" ? [this.corePluginType(a[5])] : this.corePlugin(a[5], method === "addPlugin")),
1790
+ ],
1791
+ });
1792
+ const collectionPluginOp = (method, disc) => ({
1793
+ ...collectionPlugin,
1794
+ data: (a) => [
1795
+ { bytes: [disc] },
1796
+ ...(method === "removeCollectionPlugin" ? [this.corePluginType(a[4])] : this.corePlugin(a[4], method === "addCollectionPlugin")),
1797
+ ],
1798
+ });
1799
+ // Add/RemoveExternalPluginAdapterV1: asset, collection, payer, authority, systemProgram, logWrapper; the write has a
1800
+ // buffer account (left out: the data is in the instruction) before systemProgram. The Collection forms drop the asset.
1801
+ const external = (method, disc, collection) => {
1802
+ const write = method.startsWith("write");
1803
+ const add = method.startsWith("add");
1804
+ const n = (collection ? 5 : 6) + (write ? 1 : 0);
1805
+ const at = collection ? 4 : 5;
1806
+ const head = collection ? "collection, payer, authority | null, systemProgram" : "asset, collection | null, payer, authority | null, systemProgram";
1807
+ const metas = collection
1808
+ ? [
1809
+ [0, "the collection", true, false],
1810
+ [1, "the payer", true, true],
1811
+ [2, "the authority", false, true],
1812
+ ]
1813
+ : [asset, [1, "the collection", true, false], [2, "the payer", true, true], [3, "the authority", false, true]];
1814
+ if (write)
1815
+ metas.push("self");
1816
+ metas.push([collection ? 3 : 4, "the system program", false, false], "self");
1817
+ return {
1818
+ usage: (m) => `${m}(${head}, ${add ? "plugin" : write ? "key, data" : "key"}, signerSeeds?)`,
1819
+ args: n,
1820
+ metas,
1821
+ max: 1024,
1822
+ data: (a) => [
1823
+ { bytes: [disc] },
1824
+ ...(add
1825
+ ? this.coreExternalPlugin(a[at])
1826
+ : write
1827
+ ? [...this.coreExternalKey(a[at]), ...this.coreExternalData(a[at + 1])]
1828
+ : this.coreExternalKey(a[at])),
1829
+ ],
1830
+ };
1831
+ };
1832
+ return {
1833
+ createAsset: {
1834
+ usage: () => "createAsset(asset, collection | null, authority | null, payer, owner | null, systemProgram, name, uri, plugins?, signerSeeds?)",
1835
+ args: 8,
1836
+ plugins: true,
1837
+ // CreateV1: asset, collection, authority, payer, owner, updateAuthority (the collection's, or the payer by
1838
+ // default), systemProgram, logWrapper.
1839
+ metas: [
1840
+ [0, "the asset", true, true],
1841
+ [1, "the collection", true, false],
1842
+ [2, "the collection authority", false, true],
1843
+ [3, "the payer", true, true],
1844
+ [4, "the owner", false, false],
1845
+ "self",
1846
+ [5, "the system program", false, false],
1847
+ "self",
1848
+ ],
1849
+ max: 2048,
1850
+ data: (a, plugins) => [{ bytes: [0, 0] }, str(a[6]), str(a[7]), ...this.corePluginList(plugins)],
1851
+ },
1852
+ createCollection: {
1853
+ usage: () => "createCollection(collection, updateAuthority | null, payer, systemProgram, name, uri, plugins?, signerSeeds?)",
1854
+ args: 6,
1855
+ plugins: true,
1746
1856
  // CreateCollectionV1: collection, updateAuthority, payer, systemProgram.
1747
- const plugins = isPlugins(a[6]) ? a[6] : undefined;
1748
- if (a.length < 6 || a.length > (plugins ? 8 : 7))
1749
- this.fail(node, "createCollection(collection, updateAuthority | null, payer, systemProgram, name, uri, plugins?, signerSeeds?)");
1750
- const metas = [
1751
- this.mplMeta(a[0], core, "the collection", true, true),
1752
- this.mplMeta(a[1], core, "the update authority", false, false),
1753
- this.mplMeta(a[2], core, "the payer", true, true),
1754
- this.mplMeta(a[3], core, "the system program", false, false),
1755
- ];
1756
- const data = this.mplData(node, 2048, [{ bytes: [1] }, str(a[4]), str(a[5]), ...this.corePluginList(plugins)]);
1757
- this.cpi(p, metas, data.buf, data.len, seedsAt(plugins ? 7 : 6));
1758
- return undefined;
1759
- }
1760
- case "addPlugin":
1761
- case "updatePlugin":
1762
- case "removePlugin": {
1763
- // Add/Remove/UpdatePluginV1: asset, collection, payer, authority, systemProgram, logWrapper.
1764
- const last = method === "removePlugin" ? "kind" : "plugin";
1765
- if (a.length !== 6 && a.length !== 7)
1766
- this.fail(node, `${method}(asset, collection | null, payer, authority | null, systemProgram, ${last}, signerSeeds?)`);
1767
- const metas = [
1768
- this.mplMeta(a[0], core, "the asset", true, false),
1769
- this.mplMeta(a[1], core, "the collection", true, false),
1770
- this.mplMeta(a[2], core, "the payer", true, true),
1771
- this.mplMeta(a[3], core, "the authority", false, true),
1772
- this.mplMeta(a[4], core, "the system program", false, false),
1773
- self,
1774
- ];
1775
- const body = method === "removePlugin" ? [this.corePluginType(a[5])] : this.corePlugin(a[5], method === "addPlugin");
1776
- const data = this.mplData(node, 2048, [{ bytes: [{ addPlugin: 2, removePlugin: 4, updatePlugin: 6 }[method]] }, ...body]);
1777
- this.cpi(p, metas, data.buf, data.len, seedsAt(6));
1778
- return undefined;
1779
- }
1780
- case "addCollectionPlugin":
1781
- case "updateCollectionPlugin":
1782
- case "removeCollectionPlugin": {
1783
- // Add/Remove/UpdateCollectionPluginV1: collection, payer, authority, systemProgram, logWrapper.
1784
- const last = method === "removeCollectionPlugin" ? "kind" : "plugin";
1785
- if (a.length !== 5 && a.length !== 6)
1786
- this.fail(node, `${method}(collection, payer, authority | null, systemProgram, ${last}, signerSeeds?)`);
1787
- const metas = [
1788
- this.mplMeta(a[0], core, "the collection", true, false),
1789
- this.mplMeta(a[1], core, "the payer", true, true),
1790
- this.mplMeta(a[2], core, "the authority", false, true),
1791
- this.mplMeta(a[3], core, "the system program", false, false),
1792
- self,
1793
- ];
1794
- const body = method === "removeCollectionPlugin" ? [this.corePluginType(a[4])] : this.corePlugin(a[4], method === "addCollectionPlugin");
1795
- const data = this.mplData(node, 2048, [{ bytes: [{ addCollectionPlugin: 3, removeCollectionPlugin: 5, updateCollectionPlugin: 7 }[method]] }, ...body]);
1796
- this.cpi(p, metas, data.buf, data.len, seedsAt(5));
1797
- return undefined;
1798
- }
1799
- case "transferAsset": {
1857
+ metas: [
1858
+ [0, "the collection", true, true],
1859
+ [1, "the update authority", false, false],
1860
+ [2, "the payer", true, true],
1861
+ [3, "the system program", false, false],
1862
+ ],
1863
+ max: 2048,
1864
+ data: (a, plugins) => [{ bytes: [1] }, str(a[4]), str(a[5]), ...this.corePluginList(plugins)],
1865
+ },
1866
+ addPlugin: assetPluginOp("addPlugin", 2),
1867
+ removePlugin: assetPluginOp("removePlugin", 4),
1868
+ updatePlugin: assetPluginOp("updatePlugin", 6),
1869
+ addCollectionPlugin: collectionPluginOp("addCollectionPlugin", 3),
1870
+ removeCollectionPlugin: collectionPluginOp("removeCollectionPlugin", 5),
1871
+ updateCollectionPlugin: collectionPluginOp("updateCollectionPlugin", 7),
1872
+ transferAsset: {
1873
+ usage: () => "transferAsset(asset, collection | null, payer, authority | null, newOwner, signerSeeds?)",
1874
+ args: 5,
1800
1875
  // TransferV1: asset, collection, payer, authority, newOwner, systemProgram, logWrapper.
1801
- if (a.length !== 5 && a.length !== 6)
1802
- this.fail(node, "transferAsset(asset, collection | null, payer, authority | null, newOwner, signerSeeds?)");
1803
- const metas = [
1804
- this.mplMeta(a[0], core, "the asset", true, false),
1805
- this.mplMeta(a[1], core, "the collection", false, false),
1806
- this.mplMeta(a[2], core, "the payer", true, true),
1807
- this.mplMeta(a[3], core, "the authority", false, true),
1808
- this.mplMeta(a[4], core, "the new owner", false, false),
1809
- [p, false, false],
1810
- [p, false, false],
1811
- ];
1812
- const data = this.mplData(node, 8, [{ bytes: [14, 0] }]);
1813
- this.cpi(p, metas, data.buf, data.len, seedsAt(5));
1814
- return undefined;
1815
- }
1816
- case "burnAsset": {
1876
+ metas: [
1877
+ asset,
1878
+ [1, "the collection", false, false],
1879
+ [2, "the payer", true, true],
1880
+ [3, "the authority", false, true],
1881
+ [4, "the new owner", false, false],
1882
+ "self",
1883
+ "self",
1884
+ ],
1885
+ max: 8,
1886
+ data: () => [{ bytes: [14, 0] }],
1887
+ },
1888
+ burnAsset: {
1889
+ usage: () => "burnAsset(asset, collection | null, payer, authority | null, signerSeeds?)",
1890
+ args: 4,
1817
1891
  // BurnV1: asset, collection, payer, authority, systemProgram, logWrapper.
1818
- if (a.length !== 4 && a.length !== 5)
1819
- this.fail(node, "burnAsset(asset, collection | null, payer, authority | null, signerSeeds?)");
1820
- const metas = [
1821
- this.mplMeta(a[0], core, "the asset", true, false),
1822
- this.mplMeta(a[1], core, "the collection", true, false),
1823
- this.mplMeta(a[2], core, "the payer", true, true),
1824
- this.mplMeta(a[3], core, "the authority", false, true),
1825
- [p, false, false],
1826
- [p, false, false],
1827
- ];
1828
- const data = this.mplData(node, 8, [{ bytes: [12, 0] }]);
1829
- this.cpi(p, metas, data.buf, data.len, seedsAt(4));
1830
- return undefined;
1831
- }
1892
+ metas: [asset, [1, "the collection", true, false], [2, "the payer", true, true], [3, "the authority", false, true], "self", "self"],
1893
+ max: 8,
1894
+ data: () => [{ bytes: [12, 0] }],
1895
+ },
1896
+ addExternalPlugin: external("addExternalPlugin", 22, false),
1897
+ removeExternalPlugin: external("removeExternalPlugin", 24, false),
1898
+ writeExternalPluginData: external("writeExternalPluginData", 28, false),
1899
+ addCollectionExternalPlugin: external("addCollectionExternalPlugin", 23, true),
1900
+ removeCollectionExternalPlugin: external("removeCollectionExternalPlugin", 25, true),
1901
+ writeCollectionExternalPluginData: external("writeCollectionExternalPluginData", 29, true),
1902
+ };
1903
+ }
1904
+ /** The fields of a Core object literal (`{ kind: "AppData", … }`) by name. */
1905
+ coreProps(node, what) {
1906
+ let n = node;
1907
+ while (ts.isParenthesizedExpression(n) || ts.isAsExpression(n) || ts.isSatisfiesExpression(n))
1908
+ n = n.expression;
1909
+ if (!ts.isObjectLiteralExpression(n))
1910
+ this.fail(n, `${what} is an object literal: { kind: "…", … }`);
1911
+ const props = new Map();
1912
+ for (const q of n.properties) {
1913
+ if (ts.isPropertyAssignment(q) && ts.isIdentifier(q.name))
1914
+ props.set(q.name.text, q.initializer);
1915
+ else if (ts.isShorthandPropertyAssignment(q))
1916
+ props.set(q.name.text, q.name);
1917
+ else
1918
+ this.fail(q, "write the fields as name: value");
1919
+ }
1920
+ return props;
1921
+ }
1922
+ /** PluginAuthority: "none" | "owner" | "updateAuthority" (literals), or an address. */
1923
+ coreAuthority(n) {
1924
+ if (ts.isStringLiteral(n)) {
1925
+ const i = ["none", "owner", "updateAuthority"].indexOf(n.text);
1926
+ if (i < 0)
1927
+ this.fail(n, 'an authority is "none", "owner", "updateAuthority" or a Pubkey');
1928
+ return [{ bytes: [i] }];
1929
+ }
1930
+ return [{ bytes: [3] }, { value: n, type: PUBKEY }];
1931
+ }
1932
+ coreOption(n, inner) {
1933
+ return n ? [{ bytes: [1] }, ...inner(n)] : [{ bytes: [0] }];
1934
+ }
1935
+ coreSchema(n) {
1936
+ const i = ts.isStringLiteral(n) ? ["binary", "json", "msgpack"].indexOf(n.text) : -1;
1937
+ if (i < 0)
1938
+ this.fail(n, 'schema is "binary", "json" or "msgpack"');
1939
+ return [{ bytes: [i] }];
1940
+ }
1941
+ /** Vec<(HookableLifecycleEvent, ExternalCheckResult)>: `[{ event: "transfer", canReject: true }, …]`. */
1942
+ coreChecks(n, at) {
1943
+ if (!n || !ts.isArrayLiteralExpression(n))
1944
+ this.fail(n ?? at, 'checks is a list: [{ event: "transfer", canApprove: true, canReject: true }]');
1945
+ const out = [{ bytes: [n.elements.length & 0xff, 0, 0, 0] }];
1946
+ for (const e of n.elements) {
1947
+ const p = this.coreProps(e, "a lifecycle check");
1948
+ const ev = p.get("event");
1949
+ const i = ev && ts.isStringLiteral(ev) ? ["create", "transfer", "burn", "update", "execute"].indexOf(ev.text) : -1;
1950
+ if (i < 0)
1951
+ this.fail(ev ?? e, 'event is "create", "transfer", "burn", "update" or "execute"');
1952
+ let flags = 0;
1953
+ for (const [k, bit] of [
1954
+ ["canListen", 1],
1955
+ ["canApprove", 2],
1956
+ ["canReject", 4],
1957
+ ]) {
1958
+ const v = p.get(k);
1959
+ if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
1960
+ this.fail(v, `${k} is true or false`);
1961
+ if (v?.kind === ts.SyntaxKind.TrueKeyword)
1962
+ flags |= bit;
1963
+ }
1964
+ for (const k of p.keys())
1965
+ if (!["event", "canListen", "canApprove", "canReject"].includes(k))
1966
+ this.fail(e, `a lifecycle check has event, canListen, canApprove and canReject, not ${k}`);
1967
+ out.push({ bytes: [i, flags, 0, 0, 0] });
1968
+ }
1969
+ return out;
1970
+ }
1971
+ /** ExtraAccount: `{ kind: "address", address, isSigner?, isWritable? }` or a preconfigured one (`{ kind: "owner" }`, …). */
1972
+ coreExtraAccount(n) {
1973
+ const p = this.coreProps(n, "an extra account");
1974
+ const kind = p.get("kind");
1975
+ const kinds = ["program", "collection", "owner", "recipient", "asset", "customPda", "address"];
1976
+ const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
1977
+ if (i < 0 || i === 5)
1978
+ this.fail(kind ?? n, 'an extra account is { kind: "address", address } or "program", "collection", "owner", "recipient", "asset"');
1979
+ const flag = (k) => {
1980
+ const v = p.get(k);
1981
+ if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
1982
+ this.fail(v, `${k} is true or false`);
1983
+ return v?.kind === ts.SyntaxKind.TrueKeyword ? 1 : 0;
1984
+ };
1985
+ if (i === 6) {
1986
+ const addr = p.get("address");
1987
+ if (!addr)
1988
+ this.fail(n, "an address extra account needs address");
1989
+ return [{ bytes: [6] }, { value: addr, type: PUBKEY }, { bytes: [flag("isSigner"), flag("isWritable")] }];
1990
+ }
1991
+ return [{ bytes: [i, flag("isSigner"), flag("isWritable")] }];
1992
+ }
1993
+ /**
1994
+ * ExternalPluginAdapterInitInfo from a literal: `{ kind: "AppData", dataAuthority, schema? }`, `{ kind: "Oracle",
1995
+ * baseAddress, checks, resultsOffset?, baseAddressConfig? }`, `{ kind: "LifecycleHook", hookedProgram, checks,
1996
+ * extraAccounts?, dataAuthority?, schema? }` and the Linked forms (collections); `authority?` is the plugin's own.
1997
+ */
1998
+ coreExternalPlugin(n) {
1999
+ const p = this.coreProps(n, "a Core external plugin");
2000
+ const kind = p.get("kind");
2001
+ const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
2002
+ const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
2003
+ if (i < 0)
2004
+ this.fail(kind ?? n, `a Core external plugin is ${kinds.join(", ")}`);
2005
+ const allowed = {
2006
+ 0: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
2007
+ 1: ["baseAddress", "checks", "baseAddressConfig", "resultsOffset"],
2008
+ 2: ["dataAuthority", "schema"],
2009
+ 3: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
2010
+ 4: ["dataAuthority", "schema"],
2011
+ };
2012
+ for (const k of p.keys())
2013
+ if (k !== "kind" && k !== "authority" && !allowed[i].includes(k))
2014
+ this.fail(n, `${kinds[i]} takes ${allowed[i].join(", ")} and authority, not ${k}`);
2015
+ const need = (k) => p.get(k) ?? this.fail(n, `${kinds[i]} needs ${k}`);
2016
+ const auth = this.coreOption(p.get("authority"), (x) => this.coreAuthority(x));
2017
+ const schema = this.coreOption(p.get("schema"), (x) => this.coreSchema(x));
2018
+ if (i === 2 || i === 4)
2019
+ return [{ bytes: [i] }, ...this.coreAuthority(need("dataAuthority")), ...auth, ...schema];
2020
+ if (i === 1) {
2021
+ const offset = p.get("resultsOffset");
2022
+ const off = !offset
2023
+ ? [{ bytes: [0] }]
2024
+ : ts.isStringLiteral(offset) && (offset.text === "none" || offset.text === "anchor")
2025
+ ? [{ bytes: [1, offset.text === "none" ? 0 : 1] }]
2026
+ : [{ bytes: [1, 2] }, { value: offset, type: U64 }];
2027
+ return [
2028
+ { bytes: [1] },
2029
+ { value: need("baseAddress"), type: PUBKEY },
2030
+ ...auth,
2031
+ ...this.coreChecks(need("checks"), n),
2032
+ ...this.coreOption(p.get("baseAddressConfig"), (x) => this.coreExtraAccount(x)),
2033
+ ...off,
2034
+ ];
2035
+ }
2036
+ const extras = p.get("extraAccounts");
2037
+ if (extras && !ts.isArrayLiteralExpression(extras))
2038
+ this.fail(extras, "extraAccounts is a list of extra accounts");
2039
+ return [
2040
+ { bytes: [i] },
2041
+ { value: need("hookedProgram"), type: PUBKEY },
2042
+ ...auth,
2043
+ ...this.coreChecks(need("checks"), n),
2044
+ ...(extras ? [{ bytes: [1, extras.elements.length & 0xff, 0, 0, 0] }, ...extras.elements.flatMap((e) => this.coreExtraAccount(e))] : [{ bytes: [0] }]),
2045
+ ...this.coreOption(p.get("dataAuthority"), (x) => this.coreAuthority(x)),
2046
+ ...schema,
2047
+ ];
2048
+ }
2049
+ /** ExternalPluginAdapterKey: `{ kind: "AppData", dataAuthority }`, `{ kind: "Oracle", address }`, `{ kind: "LifecycleHook", address }`, … */
2050
+ coreExternalKey(n) {
2051
+ const p = this.coreProps(n, "a Core external plugin key");
2052
+ const kind = p.get("kind");
2053
+ const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
2054
+ const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
2055
+ if (i < 0)
2056
+ this.fail(kind ?? n, `a Core external plugin key is ${kinds.join(", ")}`);
2057
+ if (i === 2 || i === 4)
2058
+ return [{ bytes: [i] }, ...this.coreAuthority(p.get("dataAuthority") ?? this.fail(n, `${kinds[i]}'s key is its dataAuthority`))];
2059
+ return [
2060
+ { bytes: [i] },
2061
+ { value: p.get("address") ?? this.fail(n, `${kinds[i]}'s key is its address (the oracle's base address or the hooked program)`), type: PUBKEY },
2062
+ ];
2063
+ }
2064
+ /** The data written: Option<Vec<u8>> from a string, a Vec<u8> argument, or null (clears it). */
2065
+ coreExternalData(n) {
2066
+ if (n.kind === ts.SyntaxKind.NullKeyword)
2067
+ return [{ bytes: [0] }];
2068
+ const l = ts.isIdentifier(n) ? this.lookup(n.text) : undefined;
2069
+ if (l?.type.kind === "vecArg") {
2070
+ if (l.type.elem.kind !== "int" || l.type.elem.name !== "u8")
2071
+ this.fail(n, "data is a string, a Vec<u8> argument or null");
2072
+ const t = this.tmp("dl");
2073
+ this.line(`uint32_t ${t} = (uint32_t)${l.c}.len;`);
2074
+ return [{ bytes: [1] }, { c: `&${t}`, n: "4" }, { c: `${l.c}.ptr`, n: `(uint64_t)${t}` }];
1832
2075
  }
1833
- this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, addPlugin, updatePlugin, removePlugin (and the Collection forms), transferAsset and burnAsset, not ${method}`);
2076
+ return [{ bytes: [1] }, { value: n, type: STRING }];
1834
2077
  }
1835
2078
  /** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
1836
2079
  corePluginType(n) {
@@ -32,17 +32,25 @@ export declare abstract class ExpressionEmitter extends StorageEmitter {
32
32
  text: string;
33
33
  arr?: undefined;
34
34
  n?: undefined;
35
+ only?: undefined;
35
36
  } | {
36
37
  text?: undefined;
37
38
  arr: string;
38
39
  n: number;
40
+ only?: undefined;
41
+ } | {
42
+ only: string;
43
+ text?: undefined;
39
44
  };
40
45
  /**
41
46
  * The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
42
47
  * literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
43
48
  */
44
49
  protected constantText(e: ts.Expression): string | undefined;
45
- protected toText(node: ts.Expression): string;
50
+ /** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
51
+ protected toText(node: ts.Expression, converted?: {
52
+ yes: boolean;
53
+ }): string;
46
54
  /** C statement writing the decimal text of an integer into the TS_Str `out`. */
47
55
  protected textOfInt(name: IntName, c: string, out: string): string;
48
56
  /** `c ? a : b` on values: only the chosen branch runs (its checks and side effects included). */
@@ -128,7 +128,7 @@ export class ExpressionEmitter extends StorageEmitter {
128
128
  if (ts.isNonNullExpression(node)) {
129
129
  const v = this.expr(node.expression, hint && hint.kind !== "option" ? { kind: "option", inner: hint } : hint);
130
130
  if (v.type.kind !== "option")
131
- this.fail(node, "! works on Option values; check optional accounts with `x !== null`");
131
+ this.fail(node, "! works on Option values and optional accounts (acct!.field); check other values with `x !== null`");
132
132
  return this.unwrap(v, node);
133
133
  }
134
134
  if (ts.isPropertyAccessExpression(node) && node.questionDotToken)
@@ -154,6 +154,8 @@ export class ExpressionEmitter extends StorageEmitter {
154
154
  if (ts.isTemplateExpression(node)) {
155
155
  // `a${x}b${y}c`: one join on the heap (or a literal when every piece is known at compile time).
156
156
  const pieces = this.templatePieces(node);
157
+ if (pieces.only !== undefined)
158
+ return { c: pieces.only, type: STRING };
157
159
  if (pieces.text !== undefined)
158
160
  return { c: `ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})`, type: STRING, stringLiteral: pieces.text };
159
161
  const t = this.tmp("tpl");
@@ -171,7 +173,7 @@ export class ExpressionEmitter extends StorageEmitter {
171
173
  const l = this.lookup(node.text);
172
174
  if (l) {
173
175
  if (l.closure)
174
- this.fail(node, `"${node.text}" is a function: TypeSOL functions are called (${node.text}(…)), not passed around or stored`);
176
+ this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
175
177
  if (l.type.kind === "array")
176
178
  this.fail(node, `"${node.text}" is an array; use .get(i)`);
177
179
  if (l.type.kind === "vec")
@@ -204,7 +206,7 @@ export class ExpressionEmitter extends StorageEmitter {
204
206
  if (this.accounts.has(node.text))
205
207
  this.fail(node, `"${node.text}" is an account; use ${node.text}.key for its address`);
206
208
  if (calleeName(node, this.model.helperDecls, this.checker) || this.model.helpers.has(node.text))
207
- this.fail(node, `"${node.text}" is a function: TypeSOL functions are called (${node.text}(…)), not passed around or stored`);
209
+ this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
208
210
  this.fail(node, `unknown name "${node.text}"`);
209
211
  }
210
212
  if (ts.isPropertyAccessExpression(node))
@@ -455,16 +457,20 @@ export class ExpressionEmitter extends StorageEmitter {
455
457
  lit(known);
456
458
  else {
457
459
  const mark = this.lines.length;
458
- const c = this.toText(span.expression);
460
+ const converted = { yes: false };
461
+ const c = this.toText(span.expression, converted);
459
462
  for (const p of parts)
460
463
  if (p.c !== undefined)
461
464
  p.c = this.settle({ c: p.c, type: STRING }, mark).c;
462
- parts.push({ c });
465
+ parts.push({ c, converted: converted.yes });
463
466
  }
464
467
  lit(span.literal.text);
465
468
  }
466
469
  if (parts.every((p) => p.text !== undefined))
467
470
  return { text: parts.map((p) => p.text).join("") };
471
+ // `${n}` alone: the text a conversion just made (or a constant one) is already a string of its own; no join.
472
+ if (parts.length === 1 && parts[0].converted)
473
+ return { only: parts[0].c };
468
474
  const strOf = (text) => `ts_str((const uint8_t *)${cString(text)}, ${Buffer.byteLength(text, "utf8")})`;
469
475
  const arr = this.tmp("parts");
470
476
  this.line(`TS_Str ${arr}[${parts.length}] = {${parts.map((p) => (p.text !== undefined ? strOf(p.text) : p.c)).join(", ")}};`);
@@ -494,10 +500,13 @@ export class ExpressionEmitter extends StorageEmitter {
494
500
  }
495
501
  return undefined;
496
502
  }
497
- toText(node) {
503
+ /** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
504
+ toText(node, converted) {
498
505
  const v = this.expr(node);
499
506
  if (v.type.kind === "string")
500
507
  return v.c;
508
+ if (converted)
509
+ converted.yes = true;
501
510
  if (v.type.kind === "option" && (v.type.inner.kind === "int" || v.type.inner.kind === "bool")) {
502
511
  // `${opt}`: the value, or "null" like JS.
503
512
  const inner = v.type.inner;
@@ -557,12 +566,7 @@ export class ExpressionEmitter extends StorageEmitter {
557
566
  (ts.isBigIntLiteral(node.whenFalse) || ts.isPrefixUnaryExpression(node.whenFalse));
558
567
  if (!type && !bothLiteral && (ts.isBigIntLiteral(node.whenTrue) || node.whenTrue.kind === ts.SyntaxKind.NullKeyword)) {
559
568
  // Type the non-literal branch first, without emitting it, to give the literal its type.
560
- const saved = { lines: this.lines.length, hoisted: this.hoisted.length, temp: this.temp, diagnostics: this.reporter.diagnostics.length };
561
- const probe = this.reporter.guard(() => this.expr(node.whenFalse));
562
- this.lines.length = saved.lines;
563
- this.hoisted.length = saved.hoisted; // a probed helper call may hoist declarations
564
- this.temp = saved.temp;
565
- this.reporter.diagnostics.length = saved.diagnostics;
569
+ const probe = this.probe(() => this.expr(node.whenFalse));
566
570
  if (probe && probe.literal === undefined && !probe.isNull)
567
571
  type = probe.type;
568
572
  }