@kaspacom/kcc20-tx-builder 0.2.5 → 0.4.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +54 -8
  3. package/RELEASING.md +93 -0
  4. package/SECURITY.md +16 -0
  5. package/artifacts/KCC20SnapshotBootstrap.placeholder.json +101677 -0
  6. package/artifacts/KCC20SnapshotController.placeholder.json +46547 -0
  7. package/artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json +101677 -0
  8. package/artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json +46547 -0
  9. package/artifacts/snapshot-standards.lock.json +56 -0
  10. package/dist/artifacts.d.ts +31 -0
  11. package/dist/artifacts.d.ts.map +1 -1
  12. package/dist/artifacts.js +31 -0
  13. package/dist/artifacts.js.map +1 -1
  14. package/dist/cjs/artifacts.d.ts +31 -0
  15. package/dist/cjs/artifacts.js +32 -1
  16. package/dist/cjs/artifacts.js.map +1 -1
  17. package/dist/cjs/engine.js +58 -44
  18. package/dist/cjs/engine.js.map +1 -1
  19. package/dist/cjs/index.d.ts +7 -0
  20. package/dist/cjs/index.js +8 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/operations.d.ts +1 -1
  23. package/dist/cjs/operations.js +4 -0
  24. package/dist/cjs/operations.js.map +1 -1
  25. package/dist/cjs/sale-common.d.ts +1 -1
  26. package/dist/cjs/sale-common.js +39 -4
  27. package/dist/cjs/sale-common.js.map +1 -1
  28. package/dist/cjs/snapshot-deployment.d.ts +82 -0
  29. package/dist/cjs/snapshot-deployment.js +82 -0
  30. package/dist/cjs/snapshot-deployment.js.map +1 -0
  31. package/dist/cjs/snapshot-operation.d.ts +27 -0
  32. package/dist/cjs/snapshot-operation.js +2433 -0
  33. package/dist/cjs/snapshot-operation.js.map +1 -0
  34. package/dist/cjs/snapshot-signerless-transactions.d.ts +132 -0
  35. package/dist/cjs/snapshot-signerless-transactions.js +746 -0
  36. package/dist/cjs/snapshot-signerless-transactions.js.map +1 -0
  37. package/dist/cjs/snapshot-signerless.d.ts +226 -0
  38. package/dist/cjs/snapshot-signerless.js +1022 -0
  39. package/dist/cjs/snapshot-signerless.js.map +1 -0
  40. package/dist/cjs/snapshot-verification.d.ts +57 -0
  41. package/dist/cjs/snapshot-verification.js +96 -0
  42. package/dist/cjs/snapshot-verification.js.map +1 -0
  43. package/dist/cjs/token-operation.js +3 -3
  44. package/dist/cjs/token-operation.js.map +1 -1
  45. package/dist/engine.d.ts.map +1 -1
  46. package/dist/engine.js +58 -44
  47. package/dist/engine.js.map +1 -1
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +7 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/operations.d.ts +1 -1
  53. package/dist/operations.d.ts.map +1 -1
  54. package/dist/operations.js +4 -0
  55. package/dist/operations.js.map +1 -1
  56. package/dist/sale-common.d.ts +1 -1
  57. package/dist/sale-common.d.ts.map +1 -1
  58. package/dist/sale-common.js +39 -4
  59. package/dist/sale-common.js.map +1 -1
  60. package/dist/snapshot-deployment.d.ts +83 -0
  61. package/dist/snapshot-deployment.d.ts.map +1 -0
  62. package/dist/snapshot-deployment.js +78 -0
  63. package/dist/snapshot-deployment.js.map +1 -0
  64. package/dist/snapshot-operation.d.ts +28 -0
  65. package/dist/snapshot-operation.d.ts.map +1 -0
  66. package/dist/snapshot-operation.js +2428 -0
  67. package/dist/snapshot-operation.js.map +1 -0
  68. package/dist/snapshot-signerless-transactions.d.ts +133 -0
  69. package/dist/snapshot-signerless-transactions.d.ts.map +1 -0
  70. package/dist/snapshot-signerless-transactions.js +735 -0
  71. package/dist/snapshot-signerless-transactions.js.map +1 -0
  72. package/dist/snapshot-signerless.d.ts +227 -0
  73. package/dist/snapshot-signerless.d.ts.map +1 -0
  74. package/dist/snapshot-signerless.js +993 -0
  75. package/dist/snapshot-signerless.js.map +1 -0
  76. package/dist/snapshot-verification.d.ts +58 -0
  77. package/dist/snapshot-verification.d.ts.map +1 -0
  78. package/dist/snapshot-verification.js +92 -0
  79. package/dist/snapshot-verification.js.map +1 -0
  80. package/dist/token-operation.js +3 -3
  81. package/dist/token-operation.js.map +1 -1
  82. package/docs/API.md +67 -0
  83. package/docs/COMPATIBILITY.json +84 -0
  84. package/docs/INTEGRATION.md +215 -0
  85. package/docs/LIVE_TRANSFER.md +106 -0
  86. package/docs/RECIPES.md +175 -0
  87. package/docs/TRANSACTION_GUIDE.md +163 -0
  88. package/docs/snapshot-start.md +27 -0
  89. package/examples/README.md +81 -0
  90. package/examples/backend/cli.ts +55 -0
  91. package/examples/backend/recipe.ts +39 -0
  92. package/examples/backend/runtime.ts +13 -0
  93. package/examples/frontend/index.html +48 -0
  94. package/examples/frontend/main.ts +213 -0
  95. package/examples/frontend/runtime.ts +14 -0
  96. package/examples/package-lock.json +1287 -0
  97. package/examples/package.json +23 -0
  98. package/examples/shared/build.ts +179 -0
  99. package/examples/shared/fixture.ts +24 -0
  100. package/examples/shared/live-transfer.ts +155 -0
  101. package/examples/shared/recipe-fixtures.ts +543 -0
  102. package/examples/shared/recipes.ts +55 -0
  103. package/examples/shared/signing.ts +110 -0
  104. package/examples/shared/timeout.ts +8 -0
  105. package/examples/test/browser.ts +116 -0
  106. package/examples/test/build.test.ts +197 -0
  107. package/examples/test/recipes.test.ts +59 -0
  108. package/examples/test/sources.test.ts +73 -0
  109. package/examples/tsconfig.json +26 -0
  110. package/examples/vendor/NOTICE.md +15 -0
  111. package/examples/vendor/kaspa/LICENSE +15 -0
  112. package/examples/vendor/kaspa/kaspa.d.ts +8908 -0
  113. package/examples/vendor/kaspa/kaspa.js +15202 -0
  114. package/examples/vendor/kaspa/kaspa_bg.wasm +0 -0
  115. package/examples/vendor/kaspa/package.json +24 -0
  116. package/package.json +21 -4
  117. package/src/artifacts.ts +35 -0
  118. package/src/engine.ts +91 -97
  119. package/src/index.ts +8 -0
  120. package/src/operations.ts +4 -0
  121. package/src/sale-common.ts +44 -3
  122. package/src/snapshot-deployment.ts +108 -0
  123. package/src/snapshot-operation.ts +3592 -0
  124. package/src/snapshot-signerless-transactions.ts +1272 -0
  125. package/src/snapshot-signerless.ts +1525 -0
  126. package/src/snapshot-verification.ts +86 -0
  127. package/src/token-operation.ts +3 -3
@@ -0,0 +1,27 @@
1
+ # Signerless snapshot builders
2
+
3
+ Protocol: `signerless-start-v2`. Contract source and shared transaction vectors come from `KASPACOM/kaspa-covenants` commit `d968dca9d77632fff5f740adb78b2f8e3fde54f2`. The artifacts and builders are unchanged at `acd14ce0862d7dc87f5c15c93de07df844120303`.
4
+
5
+ - `snapshot-signerless`: state encoding, commitments and pure transition plans.
6
+ - `snapshot-signerless-transactions`: genesis, start, claim and recipient-transfer transactions.
7
+ - `snapshot-deployment`: deterministic peer-first deployment sequencing and token identity.
8
+ - `snapshot-verification`: metadata payload and reconstruction of the complete deployment/start transaction from approved inputs.
9
+
10
+ `prepareSnapshotDeployment` accepts an immutable definition and independently confirmed genesis identities. It returns the next controller, bootstrap or start plan. It does not fetch chain state, select a wallet, approve a candidate, sign or broadcast. Hosts own canonical acceptance, UTXO selection, persistence, governance and recovery.
11
+
12
+ Only the funding input may be signed for genesis/start. Start consumes the bootstrap and every controller, produces all reserves and initialized controllers, and leaves no bootstrap successor. Maximums are eight shards and Merkle depth twenty.
13
+
14
+ Pin raw artifact hashes in the host approval. The package also verifies compiled script hashes. Compiler and standards pins are in `artifacts/snapshot-standards.lock.json`. Preserve legacy records separately; these artifacts cannot silently replace previously approved artifacts.
15
+
16
+ ## Verification
17
+
18
+ ```sh
19
+ npm ci
20
+ npm test
21
+ SNAPSHOT_TEST_WASM=/path/to/kaspa-covenants/wallet-lab/vendor/kaspa-wasm-toc npm run test:snapshot:transactions
22
+ npm pack --dry-run
23
+ ```
24
+
25
+ The SDK transaction tests use synthetic funded UTXOs. They cover one, two and eight shards, exact reconstruction, metadata, mutated transactions, funding-signature selection, claims and recipient transfers. They do not establish network admission. A funded DEV rehearsal and independent review are required before enabling deployment in a host application.
26
+
27
+ For the bundled offline test runtime, set `SNAPSHOT_TEST_WASM="$PWD/examples/vendor/kaspa"`. CI runs the transaction and claim tests with these pinned bytes. The public-release compiler gate currently reproduces the five core families; source reproduction of the two snapshot artifacts remains a separate release check.
@@ -0,0 +1,81 @@
1
+ # Frontend and backend transaction examples
2
+
3
+ These examples build KCC20 transactions inside the application, without
4
+ KaspaCom backend build, plan, or source endpoints. They share TypeScript
5
+ construction code and include their WASM runtime and license.
6
+
7
+ ## Run from the package repository
8
+
9
+ ```sh
10
+ npm ci
11
+ npm run build
12
+ npm ci --prefix examples --registry=https://registry.npmjs.org/ --@kaspacom:registry=https://registry.npmjs.org/
13
+ npm exec --prefix examples -- playwright install --with-deps --only-shell chromium
14
+ npm run test:examples
15
+ npm --prefix examples run backend -- offline
16
+ npm --prefix examples run frontend
17
+ ```
18
+
19
+ `test:examples` packs the current candidate and installs that tarball into the
20
+ examples extracted from that tarball before testing; it also installs the same candidate into the checkout. This matters before 0.4.0 is published: the lockfile's
21
+ registry bootstrap dependency is 0.2.5, which predates the browser compatibility
22
+ fix. Do not skip the candidate installation when testing a checkout.
23
+
24
+ After publication, users can copy `examples` from the package or repository,
25
+ run `npm ci` inside it, then install the matching release explicitly:
26
+
27
+ ```sh
28
+ npm install --no-save --package-lock=false @kaspacom/kcc20-tx-builder@0.4.0 \
29
+ --registry=https://registry.npmjs.org/ --@kaspacom:registry=https://registry.npmjs.org/
30
+ npm run backend -- offline
31
+ npm run frontend
32
+ ```
33
+
34
+ Node 22 is the reference runtime. The browser demo opens on the Vite localhost
35
+ URL. Click **Build offline deploy and transfer**. No wallet, RPC, credentials,
36
+ or private repository is needed. The synthetic addresses are public test vectors:
37
+ do not fund them.
38
+
39
+ ## Build against your TN10 node
40
+
41
+ Construct an operation with the helpers in `shared/build.ts` or use the
42
+ [operation recipes](../docs/RECIPES.md). Save the result to `examples/operation.json` when running the command below
43
+ from the repository root. npm runs this CLI with `examples` as its working directory.
44
+
45
+ ```sh
46
+ KASPA_WRPC_URL=wss://your-tn10-node npm --prefix examples run backend -- build operation.json
47
+ ```
48
+
49
+ The CLI writes the full unsigned builder result. It never signs or broadcasts.
50
+ For a frontend deploy, click **Prepare deploy from KasWare account** to
51
+ construct the operation locally from your connected TN10 account, then review
52
+ the demo token parameters. For other operations, paste the helper-generated
53
+ operation JSON. Enter your RPC URL in the browser form. Inspect the output, then optionally sign with KasWare and broadcast
54
+ with the separate button. Use a TN10-compatible node and wallet. Your RPC endpoint
55
+ must permit browser WebSocket access; HTTPS pages need a secure `wss://` endpoint.
56
+
57
+ Read the [transaction guide](../docs/TRANSACTION_GUIDE.md) for source snapshots,
58
+ decimals, fees, covenant script signing, and failure handling. Offline tests do
59
+ not establish that the selected node or wallet supports the current TN10 rules.
60
+
61
+ ## Files to copy into your application
62
+
63
+ - `shared/build.ts`: helpers, operation-to-engine adapter, source injection, inspection.
64
+ - `backend/runtime.ts` or `frontend/runtime.ts`: initialize WASM and load package artifacts.
65
+ - `shared/signing.ts`: covenant script assembly after wallet signing.
66
+ - `shared/recipes.ts`: typed entry points for the other operation families.
67
+
68
+ `shared/fixture.ts` is offline test data. Replace it with your RPC provider and
69
+ own indexer/decoded snapshots for live builds. Keep one host-owned connection
70
+ and close it when the host shuts down.
71
+
72
+ ## Complete operation recipes and live transfer
73
+
74
+ The browser **Offline recipe** selector and `npm run recipe -- <name> --offline`
75
+ construct all 24 documented scenarios. `--arguments` prints their complete typed
76
+ helper arguments as JSON. See [recipes](../docs/RECIPES.md).
77
+
78
+ To retrieve real holder state independently, follow the
79
+ [live transfer guide](../docs/LIVE_TRANSFER.md). Node supports
80
+ `npm run backend -- transfer transfer.json`; the browser accepts the same config
81
+ through **Build transfer from indexer**. Both verify decoded state against RPC.
@@ -0,0 +1,55 @@
1
+ import { liveTransferOperation } from "../shared/live-transfer.ts";
2
+ import { withTimeout } from "../shared/timeout.ts";
3
+ import { readFile } from "node:fs/promises";
4
+ import { runtime, loadArtifact } from "./runtime.ts";
5
+ import {
6
+ createBuilder,
7
+ deployOperation,
8
+ transferOperation,
9
+ inspect,
10
+ network,
11
+ } from "../shared/build.ts";
12
+ import { fixture, recipient } from "../shared/fixture.ts";
13
+ const wasm = await runtime();
14
+ const mode = process.argv[2] ?? "offline";
15
+ if (mode === "offline") {
16
+ const data = fixture(wasm);
17
+ const build = createBuilder(wasm, data.sources, loadArtifact);
18
+ const deploy = await build(deployOperation(data.wallet));
19
+ const transfer = await build(
20
+ transferOperation(data.wallet, data.addDeploy(deploy), recipient),
21
+ );
22
+ console.log(
23
+ JSON.stringify(
24
+ { deploy: inspect(deploy), transfer: inspect(transfer) },
25
+ null,
26
+ 2,
27
+ ),
28
+ );
29
+ } else if (mode === "build" || mode === "transfer") {
30
+ const path = process.argv[3];
31
+ if (!path || !process.env.KASPA_WRPC_URL)
32
+ throw new Error(
33
+ "Usage: KASPA_WRPC_URL=wss://your-node npm run backend -- build operation.json",
34
+ );
35
+ const document = JSON.parse(await readFile(path, "utf8"));
36
+ const rpc = new wasm.RpcClient({
37
+ url: process.env.KASPA_WRPC_URL,
38
+ encoding: wasm.Encoding.Borsh,
39
+ networkId: network,
40
+ });
41
+ try {
42
+ await withTimeout(rpc.connect(), "RPC connection");
43
+ const info = await withTimeout(rpc.getBlockDagInfo(), "Network query");
44
+ if (info.network !== network)
45
+ throw new Error(`RPC network mismatch: ${info.network}`);
46
+ const operation =
47
+ mode === "transfer"
48
+ ? await liveTransferOperation(wasm, rpc, loadArtifact, document)
49
+ : document;
50
+ const result = await createBuilder(wasm, rpc, loadArtifact)(operation);
51
+ console.log(JSON.stringify(result, null, 2));
52
+ } finally {
53
+ await withTimeout(rpc.disconnect(), "RPC disconnect", 5_000);
54
+ }
55
+ } else throw new Error("Expected offline, build or transfer");
@@ -0,0 +1,39 @@
1
+ import { runtime, loadArtifact } from "./runtime.ts";
2
+ import { recipeFixtures } from "../shared/recipe-fixtures.ts";
3
+ import { inspect } from "../shared/build.ts";
4
+ import { readFile } from "node:fs/promises";
5
+ import { recipes } from "../shared/recipes.ts";
6
+ import { engineInput, type Operation } from "../shared/build.ts";
7
+ const name = process.argv[2];
8
+ const path = process.argv[3];
9
+ if (!Object.hasOwn(recipes, name) || !path)
10
+ throw new Error(
11
+ `Usage: npm run recipe -- <${Object.keys(recipes).join("|")}> arguments.json`,
12
+ );
13
+ if (path === "--offline" || path === "--arguments") {
14
+ const fixture = await recipeFixtures(await runtime(), loadArtifact);
15
+ const key = name as keyof typeof recipes;
16
+ const unsigned =
17
+ path === "--offline"
18
+ ? await fixture.build(fixture.operation(key))
19
+ : undefined;
20
+ console.log(
21
+ JSON.stringify(
22
+ unsigned
23
+ ? { inspection: inspect(unsigned), unsigned }
24
+ : fixture.args[key],
25
+ null,
26
+ 2,
27
+ ),
28
+ );
29
+ } else {
30
+ const args = JSON.parse(await readFile(path, "utf8"));
31
+ if (!Array.isArray(args))
32
+ throw new Error("arguments.json must contain an array of helper arguments");
33
+ const fn = recipes[name as keyof typeof recipes] as (
34
+ ...args: any[]
35
+ ) => unknown;
36
+ const operation = fn(...args) as Operation;
37
+ engineInput(operation); // Reject accidental non-TN10 configuration before RPC access.
38
+ console.log(JSON.stringify(operation, null, 2));
39
+ }
@@ -0,0 +1,13 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import * as wasm from '../vendor/kaspa/kaspa.js';
3
+ let initialized: Promise<unknown> | undefined;
4
+ export async function runtime() {
5
+ initialized ??= readFile(new URL('../vendor/kaspa/kaspa_bg.wasm', import.meta.url)).then(bytes => wasm.default({ module_or_path: bytes }));
6
+ await initialized;
7
+ wasm.initWASM32Bindings({ validateClassNames: false });
8
+ return wasm;
9
+ }
10
+ export async function loadArtifact(key: string) {
11
+ const path = import.meta.resolve(`@kaspacom/kcc20-tx-builder/artifacts/${key}`);
12
+ return JSON.parse(await readFile(new URL(path), 'utf8'));
13
+ }
@@ -0,0 +1,48 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <meta charset="utf-8" /><meta
4
+ name="viewport"
5
+ content="width=device-width, initial-scale=1"
6
+ />
7
+ <title>KCC20 transaction builder examples</title>
8
+ <body>
9
+ <main>
10
+ <h1>Build KCC20 transactions locally</h1>
11
+ <p>
12
+ TN10 development example. Offline inputs are synthetic and cannot be
13
+ broadcast.
14
+ </p>
15
+ <button id="offline">Build offline deploy and transfer</button>
16
+ <p>
17
+ <label
18
+ >Offline recipe
19
+ <select id="recipe"></select
20
+ ></label>
21
+ <button id="recipe-build">Build offline recipe</button>
22
+ </p>
23
+ <h2>Build using your TN10 node</h2>
24
+ <button id="prepare">Prepare deploy from KasWare account</button>
25
+ <label
26
+ >RPC URL <input id="rpc" placeholder="wss://your-tn10-node" size="45"
27
+ /></label>
28
+ <p>
29
+ <label
30
+ >Operation or transfer configuration JSON
31
+ <textarea
32
+ id="operation"
33
+ rows="12"
34
+ cols="80"
35
+ placeholder="Paste the output of deployOperation or transferOperation"
36
+ ></textarea>
37
+ </label>
38
+ </p>
39
+ <button id="build">Build unsigned transaction</button>
40
+ <button id="transfer">Build transfer from indexer</button>
41
+ <button id="sign" disabled>Sign with KasWare</button>
42
+ <button id="broadcast" disabled>Broadcast signed transaction</button>
43
+ <p id="status" role="status">Ready</p>
44
+ <pre id="result"></pre>
45
+ </main>
46
+ <script type="module" src="/main.ts"></script>
47
+ </body>
48
+ </html>
@@ -0,0 +1,213 @@
1
+ import { recipeFixtures } from "../shared/recipe-fixtures.ts";
2
+ import { recipes } from "../shared/recipes.ts";
3
+ import { liveTransferOperation } from "../shared/live-transfer.ts";
4
+ import { withTimeout } from "../shared/timeout.ts";
5
+ import { runtime, loadArtifact } from "./runtime.ts";
6
+ import {
7
+ createBuilder,
8
+ deployOperation,
9
+ transferOperation,
10
+ inspect,
11
+ network,
12
+ type Built,
13
+ } from "../shared/build.ts";
14
+ import { fixture, recipient } from "../shared/fixture.ts";
15
+ import { attachScripts, signingInputs } from "../shared/signing.ts";
16
+ const status = document.querySelector("#status")!;
17
+ const output = document.querySelector("#result")!;
18
+ const sign = document.querySelector<HTMLButtonElement>("#sign")!;
19
+ const broadcast = document.querySelector<HTMLButtonElement>("#broadcast")!;
20
+ let built: Built | undefined;
21
+ let signed: string | undefined;
22
+ let rpc:
23
+ | InstanceType<Awaited<ReturnType<typeof runtime>>["RpcClient"]>
24
+ | undefined;
25
+ let buildAddress = "";
26
+ let busy = false;
27
+ async function action(fn: () => Promise<void>) {
28
+ if (busy) return;
29
+ busy = true;
30
+ document
31
+ .querySelectorAll("button, input, textarea, select")
32
+ .forEach((element) => ((element as HTMLInputElement).disabled = true));
33
+ status.textContent = "Working";
34
+ try {
35
+ await fn();
36
+ status.textContent = "Complete";
37
+ } catch (error) {
38
+ await clear();
39
+ status.textContent = String(error);
40
+ } finally {
41
+ busy = false;
42
+ document
43
+ .querySelectorAll("button, input, textarea, select")
44
+ .forEach((element) => ((element as HTMLInputElement).disabled = false));
45
+ sign.disabled = !built;
46
+ broadcast.disabled = !signed;
47
+ }
48
+ }
49
+ async function clear() {
50
+ built = undefined;
51
+ signed = undefined;
52
+ sign.disabled = true;
53
+ broadcast.disabled = true;
54
+ if (rpc)
55
+ await withTimeout(rpc.disconnect(), "RPC disconnect", 5_000).catch(
56
+ () => undefined,
57
+ );
58
+ rpc = undefined;
59
+ }
60
+ export async function offline() {
61
+ const wasm = await runtime();
62
+ const data = fixture(wasm);
63
+ const build = createBuilder(wasm, data.sources, loadArtifact);
64
+ const deploy = await build(deployOperation(data.wallet));
65
+ const transfer = await build(
66
+ transferOperation(data.wallet, data.addDeploy(deploy), recipient),
67
+ );
68
+ return { deploy, transfer };
69
+ }
70
+ // Used by the browser acceptance test, which blocks every external request.
71
+ let preparedFixtures: ReturnType<typeof recipeFixtures> | undefined;
72
+ async function offlineRecipe(name: keyof typeof recipes) {
73
+ preparedFixtures ??= runtime().then((wasm) =>
74
+ recipeFixtures(wasm, loadArtifact),
75
+ );
76
+ const data = await preparedFixtures;
77
+ return await data.build(data.operation(name));
78
+ }
79
+ Object.assign(window, {
80
+ runOfflineExample: offline,
81
+ runOfflineRecipe: offlineRecipe,
82
+ });
83
+ const recipeSelect = document.querySelector<HTMLSelectElement>("#recipe")!;
84
+ for (const name of Object.keys(recipes))
85
+ recipeSelect.add(new Option(name, name));
86
+ document.querySelector("#recipe-build")!.addEventListener("click", () =>
87
+ action(async () => {
88
+ await clear();
89
+ const result = await offlineRecipe(
90
+ recipeSelect.value as keyof typeof recipes,
91
+ );
92
+ output.textContent = JSON.stringify(
93
+ { inspection: inspect(result), unsigned: result },
94
+ null,
95
+ 2,
96
+ );
97
+ }),
98
+ );
99
+ document.querySelector("#offline")!.addEventListener("click", () =>
100
+ action(async () => {
101
+ await clear();
102
+ const result = await offline();
103
+ output.textContent = JSON.stringify(
104
+ { deploy: inspect(result.deploy), transfer: inspect(result.transfer) },
105
+ null,
106
+ 2,
107
+ );
108
+ }),
109
+ );
110
+ document.querySelector("#prepare")!.addEventListener("click", () =>
111
+ action(async () => {
112
+ await clear();
113
+ const wallet = (window as any).kasware;
114
+ if (!wallet?.getPublicKey)
115
+ throw new Error("Install KasWare to prepare a live deploy");
116
+ if ((await wallet.getNetwork()) !== "kaspa_testnet_10")
117
+ throw new Error("Select TN10 in KasWare");
118
+ const [walletAddress] = await wallet.requestAccounts();
119
+ if (!walletAddress) throw new Error("Wallet did not return an account");
120
+ const wasm = await runtime();
121
+ const publicKey = await wallet.getPublicKey();
122
+ const owner =
123
+ publicKey.length === 64
124
+ ? publicKey
125
+ : new wasm.PublicKey(publicKey).toXOnlyPublicKey().toString();
126
+ if (
127
+ new wasm.XOnlyPublicKey(owner).toAddress(network).toString() !==
128
+ walletAddress
129
+ )
130
+ throw new Error("Wallet address and public key do not match");
131
+ const operation = deployOperation({ walletAddress, kcc20Owner: owner });
132
+ document.querySelector<HTMLTextAreaElement>("#operation")!.value =
133
+ JSON.stringify(operation, null, 2);
134
+ output.textContent =
135
+ "Deploy operation prepared locally. Review the token parameters, choose your RPC, then build.";
136
+ }),
137
+ );
138
+ async function buildLive(fromIndexer = false) {
139
+ await clear();
140
+ const wasm = await runtime();
141
+ const documentInput = JSON.parse(
142
+ document.querySelector<HTMLTextAreaElement>("#operation")!.value,
143
+ );
144
+ const url = document.querySelector<HTMLInputElement>("#rpc")!.value;
145
+ if (!/^wss?:\/\//.test(url))
146
+ throw new Error("Enter your TN10 WebSocket RPC URL");
147
+ rpc = new wasm.RpcClient({
148
+ url,
149
+ networkId: network,
150
+ encoding: wasm.Encoding.Borsh,
151
+ });
152
+ await withTimeout(rpc.connect(), "RPC connection");
153
+ if (
154
+ (await withTimeout(rpc.getBlockDagInfo(), "Network query")).network !==
155
+ network
156
+ )
157
+ throw new Error("RPC network mismatch");
158
+ const operation = fromIndexer
159
+ ? await liveTransferOperation(wasm, rpc, loadArtifact, documentInput)
160
+ : documentInput;
161
+ built = await createBuilder(wasm, rpc, loadArtifact)(operation);
162
+ buildAddress = operation.payload.owner.walletAddress;
163
+ output.textContent = JSON.stringify(
164
+ { inspection: inspect(built), transaction: built },
165
+ null,
166
+ 2,
167
+ );
168
+ sign.disabled = false;
169
+ }
170
+ document
171
+ .querySelector("#build")!
172
+ .addEventListener("click", () => action(() => buildLive()));
173
+ document
174
+ .querySelector("#transfer")!
175
+ .addEventListener("click", () => action(() => buildLive(true)));
176
+ sign.addEventListener("click", () =>
177
+ action(async () => {
178
+ signed = undefined;
179
+ broadcast.disabled = true;
180
+ const wallet = (window as any).kasware;
181
+ if (!built || !wallet?.signPskt)
182
+ throw new Error("Install a KasWare version with PSKT signing");
183
+ if ((await wallet.getNetwork()) !== "kaspa_testnet_10")
184
+ throw new Error("Select TN10 in KasWare");
185
+ if ((await wallet.requestAccounts())[0] !== buildAddress)
186
+ throw new Error("Wallet account differs from transaction owner");
187
+ const json = await wallet.signPskt({
188
+ txJsonString: built.psktTransactionJson,
189
+ options: { signInputs: signingInputs(built) },
190
+ });
191
+ signed = attachScripts(await runtime(), json, built);
192
+ output.textContent = signed!;
193
+ broadcast.disabled = false;
194
+ }),
195
+ );
196
+ broadcast.addEventListener("click", () =>
197
+ action(async () => {
198
+ if (!signed || !rpc) throw new Error("Build and sign first");
199
+ if (!confirm("Broadcast this signed transaction to TN10?")) return;
200
+ const wasm = await runtime();
201
+ const result = await withTimeout(
202
+ rpc.submitTransaction({
203
+ transaction: wasm.Transaction.deserializeFromSafeJSON(signed),
204
+ allowOrphan: false,
205
+ }),
206
+ "Submission; check transaction acceptance before retrying",
207
+ );
208
+ output.textContent = JSON.stringify(result);
209
+ signed = undefined;
210
+ built = undefined;
211
+ await clear();
212
+ }),
213
+ );
@@ -0,0 +1,14 @@
1
+ import * as wasm from '../vendor/kaspa/kaspa.js';
2
+ let initialized: Promise<unknown> | undefined;
3
+ export async function runtime() {
4
+ initialized ??= wasm.default({ module_or_path: new URL('../vendor/kaspa/kaspa_bg.wasm', import.meta.url) });
5
+ await initialized;
6
+ wasm.initWASM32Bindings({ validateClassNames: false });
7
+ return wasm;
8
+ }
9
+ const artifacts = import.meta.glob('../node_modules/@kaspacom/kcc20-tx-builder/artifacts/*.json', { import: 'default' });
10
+ export async function loadArtifact(key: string) {
11
+ const load = artifacts[`../node_modules/@kaspacom/kcc20-tx-builder/artifacts/${key}`];
12
+ if (!load) throw new Error(`Missing packaged artifact ${key}`);
13
+ return load();
14
+ }