@kaspacom/kcc20-tx-builder 0.2.0 → 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.
- package/CHANGELOG.md +21 -0
- package/README.md +54 -8
- package/RELEASING.md +93 -0
- package/SECURITY.md +16 -0
- package/artifacts/KCC20SnapshotBootstrap.placeholder.json +101677 -0
- package/artifacts/KCC20SnapshotController.placeholder.json +46547 -0
- package/artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json +101677 -0
- package/artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json +46547 -0
- package/artifacts/snapshot-standards.lock.json +56 -0
- package/dist/artifacts.d.ts +31 -0
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +31 -0
- package/dist/artifacts.js.map +1 -1
- package/dist/cjs/artifacts.d.ts +31 -0
- package/dist/cjs/artifacts.js +32 -1
- package/dist/cjs/artifacts.js.map +1 -1
- package/dist/cjs/engine.js +58 -44
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/operations.d.ts +1 -1
- package/dist/cjs/operations.js +4 -0
- package/dist/cjs/operations.js.map +1 -1
- package/dist/cjs/sale-common.d.ts +1 -1
- package/dist/cjs/sale-common.js +39 -4
- package/dist/cjs/sale-common.js.map +1 -1
- package/dist/cjs/snapshot-deployment.d.ts +82 -0
- package/dist/cjs/snapshot-deployment.js +82 -0
- package/dist/cjs/snapshot-deployment.js.map +1 -0
- package/dist/cjs/snapshot-operation.d.ts +27 -0
- package/dist/cjs/snapshot-operation.js +2433 -0
- package/dist/cjs/snapshot-operation.js.map +1 -0
- package/dist/cjs/snapshot-signerless-transactions.d.ts +132 -0
- package/dist/cjs/snapshot-signerless-transactions.js +746 -0
- package/dist/cjs/snapshot-signerless-transactions.js.map +1 -0
- package/dist/cjs/snapshot-signerless.d.ts +226 -0
- package/dist/cjs/snapshot-signerless.js +1022 -0
- package/dist/cjs/snapshot-signerless.js.map +1 -0
- package/dist/cjs/snapshot-verification.d.ts +57 -0
- package/dist/cjs/snapshot-verification.js +96 -0
- package/dist/cjs/snapshot-verification.js.map +1 -0
- package/dist/cjs/token-operation.js +3 -3
- package/dist/cjs/token-operation.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +58 -44
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +4 -0
- package/dist/operations.js.map +1 -1
- package/dist/sale-common.d.ts +1 -1
- package/dist/sale-common.d.ts.map +1 -1
- package/dist/sale-common.js +39 -4
- package/dist/sale-common.js.map +1 -1
- package/dist/snapshot-deployment.d.ts +83 -0
- package/dist/snapshot-deployment.d.ts.map +1 -0
- package/dist/snapshot-deployment.js +78 -0
- package/dist/snapshot-deployment.js.map +1 -0
- package/dist/snapshot-operation.d.ts +28 -0
- package/dist/snapshot-operation.d.ts.map +1 -0
- package/dist/snapshot-operation.js +2428 -0
- package/dist/snapshot-operation.js.map +1 -0
- package/dist/snapshot-signerless-transactions.d.ts +133 -0
- package/dist/snapshot-signerless-transactions.d.ts.map +1 -0
- package/dist/snapshot-signerless-transactions.js +735 -0
- package/dist/snapshot-signerless-transactions.js.map +1 -0
- package/dist/snapshot-signerless.d.ts +227 -0
- package/dist/snapshot-signerless.d.ts.map +1 -0
- package/dist/snapshot-signerless.js +993 -0
- package/dist/snapshot-signerless.js.map +1 -0
- package/dist/snapshot-verification.d.ts +58 -0
- package/dist/snapshot-verification.d.ts.map +1 -0
- package/dist/snapshot-verification.js +92 -0
- package/dist/snapshot-verification.js.map +1 -0
- package/dist/token-operation.js +3 -3
- package/dist/token-operation.js.map +1 -1
- package/docs/API.md +67 -0
- package/docs/COMPATIBILITY.json +84 -0
- package/docs/INTEGRATION.md +215 -0
- package/docs/LIVE_TRANSFER.md +106 -0
- package/docs/RECIPES.md +175 -0
- package/docs/TRANSACTION_GUIDE.md +163 -0
- package/docs/snapshot-start.md +27 -0
- package/examples/README.md +81 -0
- package/examples/backend/cli.ts +55 -0
- package/examples/backend/recipe.ts +39 -0
- package/examples/backend/runtime.ts +13 -0
- package/examples/frontend/index.html +48 -0
- package/examples/frontend/main.ts +213 -0
- package/examples/frontend/runtime.ts +14 -0
- package/examples/package-lock.json +1287 -0
- package/examples/package.json +23 -0
- package/examples/shared/build.ts +179 -0
- package/examples/shared/fixture.ts +24 -0
- package/examples/shared/live-transfer.ts +155 -0
- package/examples/shared/recipe-fixtures.ts +543 -0
- package/examples/shared/recipes.ts +55 -0
- package/examples/shared/signing.ts +110 -0
- package/examples/shared/timeout.ts +8 -0
- package/examples/test/browser.ts +116 -0
- package/examples/test/build.test.ts +197 -0
- package/examples/test/recipes.test.ts +59 -0
- package/examples/test/sources.test.ts +73 -0
- package/examples/tsconfig.json +26 -0
- package/examples/vendor/NOTICE.md +15 -0
- package/examples/vendor/kaspa/LICENSE +15 -0
- package/examples/vendor/kaspa/kaspa.d.ts +8908 -0
- package/examples/vendor/kaspa/kaspa.js +15202 -0
- package/examples/vendor/kaspa/kaspa_bg.wasm +0 -0
- package/examples/vendor/kaspa/package.json +24 -0
- package/package.json +21 -4
- package/src/artifacts.ts +35 -0
- package/src/engine.ts +91 -97
- package/src/index.ts +8 -0
- package/src/operations.ts +4 -0
- package/src/sale-common.ts +44 -3
- package/src/snapshot-deployment.ts +108 -0
- package/src/snapshot-operation.ts +3592 -0
- package/src/snapshot-signerless-transactions.ts +1272 -0
- package/src/snapshot-signerless.ts +1525 -0
- package/src/snapshot-verification.ts +86 -0
- package/src/token-operation.ts +3 -3
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { preview } from "vite";
|
|
3
|
+
import { chromium } from "playwright";
|
|
4
|
+
import { runtime, loadArtifact } from "../backend/runtime.ts";
|
|
5
|
+
import {
|
|
6
|
+
createBuilder,
|
|
7
|
+
deployOperation,
|
|
8
|
+
transferOperation,
|
|
9
|
+
} from "../shared/build.ts";
|
|
10
|
+
import { recipeFixtures } from "../shared/recipe-fixtures.ts";
|
|
11
|
+
import { recipes } from "../shared/recipes.ts";
|
|
12
|
+
import { fixture, recipient, owner } from "../shared/fixture.ts";
|
|
13
|
+
const server = await preview({
|
|
14
|
+
root: "frontend",
|
|
15
|
+
preview: { host: "127.0.0.1", port: 4173, strictPort: true },
|
|
16
|
+
});
|
|
17
|
+
const browser = await chromium.launch({ headless: true });
|
|
18
|
+
try {
|
|
19
|
+
const page = await browser.newPage();
|
|
20
|
+
const external: string[] = [];
|
|
21
|
+
await page.route("**/*", (route) => {
|
|
22
|
+
if (new URL(route.request().url()).origin !== "http://127.0.0.1:4173") {
|
|
23
|
+
external.push(route.request().url());
|
|
24
|
+
return route.abort();
|
|
25
|
+
}
|
|
26
|
+
return route.continue();
|
|
27
|
+
});
|
|
28
|
+
await page.routeWebSocket(/.*/, (socket) => {
|
|
29
|
+
external.push(socket.url());
|
|
30
|
+
socket.close();
|
|
31
|
+
});
|
|
32
|
+
await page.goto("http://127.0.0.1:4173");
|
|
33
|
+
await page.waitForFunction(
|
|
34
|
+
() => typeof (window as any).runOfflineExample === "function",
|
|
35
|
+
);
|
|
36
|
+
const browserResult = await page.evaluate(() =>
|
|
37
|
+
(window as any).runOfflineExample(),
|
|
38
|
+
);
|
|
39
|
+
const wasm = await runtime();
|
|
40
|
+
const data = fixture(wasm);
|
|
41
|
+
const build = createBuilder(wasm, data.sources, loadArtifact);
|
|
42
|
+
const deploy = await build(deployOperation(data.wallet));
|
|
43
|
+
const transfer = await build(
|
|
44
|
+
transferOperation(data.wallet, data.addDeploy(deploy), recipient),
|
|
45
|
+
);
|
|
46
|
+
for (const [key, node] of Object.entries({ deploy, transfer })) {
|
|
47
|
+
assert.deepEqual(
|
|
48
|
+
JSON.parse(browserResult[key].psktTransactionJson),
|
|
49
|
+
JSON.parse(node.psktTransactionJson),
|
|
50
|
+
);
|
|
51
|
+
assert.deepEqual(browserResult[key].signInputs, node.signInputs);
|
|
52
|
+
assert.deepEqual(browserResult[key].scripts, node.scripts);
|
|
53
|
+
}
|
|
54
|
+
const all = await recipeFixtures(wasm, loadArtifact);
|
|
55
|
+
for (const name of Object.keys(recipes) as (keyof typeof recipes)[]) {
|
|
56
|
+
const nodeResult = await all.build(all.operation(name));
|
|
57
|
+
const browserRecipe = await page.evaluate(
|
|
58
|
+
(name) => (window as any).runOfflineRecipe(name),
|
|
59
|
+
name,
|
|
60
|
+
);
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
JSON.parse(browserRecipe.psktTransactionJson),
|
|
63
|
+
JSON.parse(nodeResult.psktTransactionJson),
|
|
64
|
+
name,
|
|
65
|
+
);
|
|
66
|
+
assert.deepEqual(browserRecipe.scripts, nodeResult.scripts, name);
|
|
67
|
+
assert.deepEqual(browserRecipe.signInputs, nodeResult.signInputs, name);
|
|
68
|
+
}
|
|
69
|
+
assert.deepEqual(external, []);
|
|
70
|
+
await page
|
|
71
|
+
.getByRole("button", { name: "Build offline deploy and transfer" })
|
|
72
|
+
.click();
|
|
73
|
+
await page.waitForFunction(
|
|
74
|
+
() => document.querySelector("#status")?.textContent === "Complete",
|
|
75
|
+
);
|
|
76
|
+
assert.equal(await page.locator("#sign").isDisabled(), true);
|
|
77
|
+
assert.equal(await page.locator("#broadcast").isDisabled(), true);
|
|
78
|
+
await page.evaluate(`window.kasware = {
|
|
79
|
+
getNetwork: async () => 'kaspa_testnet_10',
|
|
80
|
+
requestAccounts: async () => [${JSON.stringify(data.wallet.walletAddress)}],
|
|
81
|
+
getPublicKey: async () => ${JSON.stringify("02" + owner)},
|
|
82
|
+
signPskt: async () => { throw new Error('Preparing an operation must not sign'); }
|
|
83
|
+
}`);
|
|
84
|
+
await page
|
|
85
|
+
.getByRole("button", { name: "Prepare deploy from KasWare account" })
|
|
86
|
+
.click();
|
|
87
|
+
await page.waitForFunction(
|
|
88
|
+
() =>
|
|
89
|
+
(document.querySelector("#operation") as HTMLTextAreaElement).value
|
|
90
|
+
.length > 0,
|
|
91
|
+
);
|
|
92
|
+
const prepared = JSON.parse(await page.locator("#operation").inputValue());
|
|
93
|
+
assert.equal(prepared.payload.owner.kcc20Owner, owner);
|
|
94
|
+
assert.equal(prepared.payload.signing.builderKey, "kcc20.deploy-token");
|
|
95
|
+
assert.equal(await page.locator("#sign").isDisabled(), true);
|
|
96
|
+
await page
|
|
97
|
+
.getByRole("button", { name: "Build offline recipe", exact: true })
|
|
98
|
+
.click();
|
|
99
|
+
await page.waitForFunction(
|
|
100
|
+
() => document.querySelector("#status")?.textContent === "Complete",
|
|
101
|
+
);
|
|
102
|
+
assert.equal(await page.locator("#sign").isDisabled(), true);
|
|
103
|
+
assert.equal(await page.locator("#broadcast").isDisabled(), true);
|
|
104
|
+
await page.screenshot({
|
|
105
|
+
path: "/tmp/kcc20-public-examples.png",
|
|
106
|
+
fullPage: false,
|
|
107
|
+
});
|
|
108
|
+
console.log(
|
|
109
|
+
"Browser/Node all 24 recipes are identical; no external requests; offline signing disabled.",
|
|
110
|
+
);
|
|
111
|
+
} finally {
|
|
112
|
+
await browser.close();
|
|
113
|
+
await new Promise<void>((resolve, reject) =>
|
|
114
|
+
server.httpServer.close((error) => (error ? reject(error) : resolve())),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { recipes } from "../shared/recipes.ts";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { runtime, loadArtifact } from "../backend/runtime.ts";
|
|
5
|
+
import {
|
|
6
|
+
createBuilder,
|
|
7
|
+
deployOperation,
|
|
8
|
+
transferOperation,
|
|
9
|
+
engineInput,
|
|
10
|
+
inspect,
|
|
11
|
+
type Runtime,
|
|
12
|
+
} from "../shared/build.ts";
|
|
13
|
+
import { fixture, recipient } from "../shared/fixture.ts";
|
|
14
|
+
import { attachScripts, signingInputs } from "../shared/signing.ts";
|
|
15
|
+
const wasm = await runtime();
|
|
16
|
+
// Offline construction must not silently fall back to any backend or RPC.
|
|
17
|
+
globalThis.fetch = async () => {
|
|
18
|
+
throw new Error("Network access forbidden in offline examples");
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
test("build deploy and transfer with actual WASM, covenant bindings, amounts and change", async () => {
|
|
22
|
+
const data = fixture(wasm);
|
|
23
|
+
const build = createBuilder(
|
|
24
|
+
Object.freeze({ ...wasm }),
|
|
25
|
+
data.sources,
|
|
26
|
+
loadArtifact,
|
|
27
|
+
);
|
|
28
|
+
const deploy = await build(deployOperation(data.wallet));
|
|
29
|
+
const deployTx = JSON.parse(deploy.psktTransactionJson);
|
|
30
|
+
assert.equal(deployTx.version, 1);
|
|
31
|
+
assert.equal(
|
|
32
|
+
deployTx.outputs[0].covenant.covenantId,
|
|
33
|
+
deploy.metadata.covenantId,
|
|
34
|
+
);
|
|
35
|
+
const holder = data.addDeploy(deploy);
|
|
36
|
+
const transfer = await build(
|
|
37
|
+
transferOperation(data.wallet, holder, recipient),
|
|
38
|
+
);
|
|
39
|
+
const tx = JSON.parse(transfer.psktTransactionJson);
|
|
40
|
+
assert.equal(tx.inputs.length, 2);
|
|
41
|
+
assert.equal(tx.outputs.length, 3);
|
|
42
|
+
assert.equal(tx.outputs[0].covenant.covenantId, deploy.metadata.covenantId);
|
|
43
|
+
assert.equal(
|
|
44
|
+
transfer.metadata.tokenAmount ?? transfer.metadata.transferAmount,
|
|
45
|
+
"1250",
|
|
46
|
+
);
|
|
47
|
+
assert(BigInt(inspect(transfer).feeSompi) > 0n);
|
|
48
|
+
assert.equal(tx.inputs[0].transactionId, holder.txidHex);
|
|
49
|
+
assert.equal(signingInputs(transfer).length, 2);
|
|
50
|
+
// Exercise script assembly with a synthetic signature only, never submit.
|
|
51
|
+
for (const input of tx.inputs)
|
|
52
|
+
input.signatureScript = "41" + "11".repeat(64) + "01";
|
|
53
|
+
const signed = JSON.parse(attachScripts(wasm, JSON.stringify(tx), transfer));
|
|
54
|
+
assert(signed.inputs[0].signatureScript.length > 132);
|
|
55
|
+
assert.equal(signed.inputs[1].signatureScript, tx.inputs[1].signatureScript);
|
|
56
|
+
tx.outputs[0].value = (BigInt(tx.outputs[0].value) + 1n).toString();
|
|
57
|
+
assert.throws(
|
|
58
|
+
() => attachScripts(wasm, JSON.stringify(tx), transfer),
|
|
59
|
+
/changed the transaction intent/,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
test("reject missing funding, stale source, wrong owner, network and incompatible artifact/runtime", async () => {
|
|
63
|
+
const data = fixture(wasm);
|
|
64
|
+
const build = createBuilder(wasm, data.sources, loadArtifact);
|
|
65
|
+
const operation = deployOperation(data.wallet);
|
|
66
|
+
const deploy = await build(operation);
|
|
67
|
+
const holder = data.addDeploy(deploy);
|
|
68
|
+
const transfer = transferOperation(data.wallet, holder, recipient);
|
|
69
|
+
data.rows.delete(holder.address);
|
|
70
|
+
await assert.rejects(build(transfer), /not found/);
|
|
71
|
+
data.rows.clear();
|
|
72
|
+
await assert.rejects(build(operation), /funding|UTXO|insufficient/i);
|
|
73
|
+
assert.throws(
|
|
74
|
+
() =>
|
|
75
|
+
engineInput({
|
|
76
|
+
...operation,
|
|
77
|
+
payload: { ...operation.payload, network: "mainnet" },
|
|
78
|
+
}),
|
|
79
|
+
/testnet-10/,
|
|
80
|
+
);
|
|
81
|
+
assert.throws(
|
|
82
|
+
() =>
|
|
83
|
+
transferOperation(
|
|
84
|
+
data.wallet,
|
|
85
|
+
{ ...holder, state: { ...holder.state, ownerIdentifier: recipient } },
|
|
86
|
+
recipient,
|
|
87
|
+
),
|
|
88
|
+
/holder UTXO/,
|
|
89
|
+
);
|
|
90
|
+
await assert.rejects(
|
|
91
|
+
createBuilder(wasm, fixture(wasm).sources, async () => ({
|
|
92
|
+
script: [1, 2],
|
|
93
|
+
}))(operation),
|
|
94
|
+
/hash mismatch/,
|
|
95
|
+
);
|
|
96
|
+
assert.throws(
|
|
97
|
+
() => createBuilder({} as Runtime, data.sources, loadArtifact),
|
|
98
|
+
/Incompatible WASM/,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("reject conflicting explicit native identity, but retain legacy alias resolution", async () => {
|
|
103
|
+
const data = fixture(wasm);
|
|
104
|
+
const build = createBuilder(wasm, data.sources, loadArtifact);
|
|
105
|
+
const holder = data.addDeploy(await build(deployOperation(data.wallet)));
|
|
106
|
+
const operation = transferOperation(data.wallet, holder, recipient);
|
|
107
|
+
const entry = data.rows.get(holder.address)![0];
|
|
108
|
+
entry.covenantId = "ff".repeat(32);
|
|
109
|
+
await assert.rejects(build(operation), /covenant id does not match/);
|
|
110
|
+
delete entry.covenantId;
|
|
111
|
+
await assert.rejects(build(operation), /missing covenant id/);
|
|
112
|
+
entry.covenantId = holder.covenantId;
|
|
113
|
+
const aliasedOperation = recipes.transfer(
|
|
114
|
+
data.wallet,
|
|
115
|
+
{
|
|
116
|
+
token: { covenantId: "ff".repeat(32), decimals: 2 },
|
|
117
|
+
activeUtxos: [{ ...holder, covenantId: undefined }],
|
|
118
|
+
recipientOwner: recipient,
|
|
119
|
+
tokenAmount: "1.00",
|
|
120
|
+
},
|
|
121
|
+
{ network: "testnet-10" },
|
|
122
|
+
);
|
|
123
|
+
assert.equal(
|
|
124
|
+
aliasedOperation.payload.params.activeHolderNativeCovenantId,
|
|
125
|
+
undefined,
|
|
126
|
+
);
|
|
127
|
+
const aliased = await build(aliasedOperation);
|
|
128
|
+
assert.equal(aliased.metadata.covenantId, holder.covenantId);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("preserve witnesses outside requested signing inputs and reject malformed signatures", async () => {
|
|
132
|
+
const data = fixture(wasm);
|
|
133
|
+
const build = createBuilder(wasm, data.sources, loadArtifact);
|
|
134
|
+
const deploy = await build(deployOperation(data.wallet));
|
|
135
|
+
const holder = data.addDeploy(deploy);
|
|
136
|
+
const transfer = await build(
|
|
137
|
+
transferOperation(data.wallet, holder, recipient),
|
|
138
|
+
);
|
|
139
|
+
// Model a pre-authorized covenant input: the wallet must sign funding only.
|
|
140
|
+
const original = JSON.parse(transfer.psktTransactionJson);
|
|
141
|
+
original.inputs[0].signatureScript = "0101";
|
|
142
|
+
const preauthorized = {
|
|
143
|
+
...transfer,
|
|
144
|
+
scripts: [],
|
|
145
|
+
psktTransactionJson: JSON.stringify(original),
|
|
146
|
+
};
|
|
147
|
+
original.inputs[1].signatureScript = "41" + "11".repeat(64) + "01";
|
|
148
|
+
assert.equal(
|
|
149
|
+
JSON.parse(attachScripts(wasm, JSON.stringify(original), preauthorized))
|
|
150
|
+
.inputs[0].signatureScript,
|
|
151
|
+
"0101",
|
|
152
|
+
);
|
|
153
|
+
original.inputs[0].signatureScript = "";
|
|
154
|
+
assert.throws(
|
|
155
|
+
() => attachScripts(wasm, JSON.stringify(original), preauthorized),
|
|
156
|
+
/changed the transaction intent/,
|
|
157
|
+
);
|
|
158
|
+
for (const signatureScript of [
|
|
159
|
+
"00",
|
|
160
|
+
"41" + "11".repeat(64) + "02",
|
|
161
|
+
"11".repeat(65),
|
|
162
|
+
]) {
|
|
163
|
+
const tx = JSON.parse(deploy.psktTransactionJson);
|
|
164
|
+
tx.inputs[0].signatureScript = signatureScript;
|
|
165
|
+
assert.throws(
|
|
166
|
+
() => attachScripts(wasm, JSON.stringify(tx), deploy),
|
|
167
|
+
/signature encoding or sighash/,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("generic transfer rejects conflicting RPC scripts and KAS amounts", async () => {
|
|
173
|
+
const data = fixture(wasm),
|
|
174
|
+
build = createBuilder(wasm, data.sources, loadArtifact);
|
|
175
|
+
const holder = data.addDeploy(await build(deployOperation(data.wallet)));
|
|
176
|
+
const operation = transferOperation(data.wallet, holder, recipient);
|
|
177
|
+
const row = data.rows.get(holder.address)![0];
|
|
178
|
+
const originalScript = row.scriptPublicKey;
|
|
179
|
+
row.scriptPublicKey = wasm.payToAddressScript(data.wallet.walletAddress);
|
|
180
|
+
await assert.rejects(build(operation), /RPC script does not match/);
|
|
181
|
+
row.scriptPublicKey = originalScript;
|
|
182
|
+
row.amount += 1n;
|
|
183
|
+
await assert.rejects(build(operation), /KAS amount does not match/);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("reject a present funding input that cannot cover the required KAS", async () => {
|
|
187
|
+
const data = fixture(wasm);
|
|
188
|
+
data.rows.get(data.wallet.walletAddress)![0].amount = 1n;
|
|
189
|
+
await assert.rejects(
|
|
190
|
+
createBuilder(
|
|
191
|
+
wasm,
|
|
192
|
+
data.sources,
|
|
193
|
+
loadArtifact,
|
|
194
|
+
)(deployOperation(data.wallet)),
|
|
195
|
+
/no single funding UTXO can cover/,
|
|
196
|
+
);
|
|
197
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { runtime, loadArtifact } from "../backend/runtime.ts";
|
|
4
|
+
import { recipeFixtures } from "../shared/recipe-fixtures.ts";
|
|
5
|
+
import { inspect } from "../shared/build.ts";
|
|
6
|
+
import { recipes } from "../shared/recipes.ts";
|
|
7
|
+
import { attachScripts, signingInputs } from "../shared/signing.ts";
|
|
8
|
+
import { owner } from "../shared/fixture.ts";
|
|
9
|
+
const wasm = await runtime();
|
|
10
|
+
const fixture = await recipeFixtures(wasm, loadArtifact);
|
|
11
|
+
for (const name of Object.keys(recipes) as (keyof typeof recipes)[]) {
|
|
12
|
+
test(`${name}: helper to unsigned transaction to offline signing`, async () => {
|
|
13
|
+
const operation = fixture.operation(name);
|
|
14
|
+
const result = await fixture.build(operation);
|
|
15
|
+
assert.equal(
|
|
16
|
+
result.metadata.builderKey,
|
|
17
|
+
operation.payload.signing.builderKey,
|
|
18
|
+
);
|
|
19
|
+
assert(BigInt(inspect(result).feeSompi) > 0n);
|
|
20
|
+
const tx = wasm.Transaction.deserializeFromSafeJSON(
|
|
21
|
+
result.psktTransactionJson,
|
|
22
|
+
);
|
|
23
|
+
// Public generator test vectors (scalars 1 and 2), never funded wallets.
|
|
24
|
+
const scalar = operation.payload.owner.kcc20Owner === owner ? 1n : 2n;
|
|
25
|
+
const key = new wasm.PrivateKey(scalar.toString(16).padStart(64, "0"));
|
|
26
|
+
for (const input of signingInputs(result)) {
|
|
27
|
+
assert.equal(input.sighashType, 1); // Wire SIGHASH_ALL byte; WASM enum All is 0.
|
|
28
|
+
tx.inputs[input.index].signatureScript = wasm.createInputSignature(
|
|
29
|
+
tx,
|
|
30
|
+
input.index,
|
|
31
|
+
key,
|
|
32
|
+
wasm.SighashType.All,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const signed = JSON.parse(
|
|
36
|
+
attachScripts(wasm, tx.serializeToSafeJSON(), result),
|
|
37
|
+
);
|
|
38
|
+
assert(
|
|
39
|
+
signed.inputs.every(
|
|
40
|
+
(input: { signatureScript: string }) =>
|
|
41
|
+
input.signatureScript.length > 0,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
if (name === "publicMint") {
|
|
45
|
+
assert.equal(result.scripts?.length ?? 0, 0);
|
|
46
|
+
assert(
|
|
47
|
+
JSON.parse(result.psktTransactionJson).inputs[0].signatureScript
|
|
48
|
+
.length > 100,
|
|
49
|
+
);
|
|
50
|
+
signed.inputs[0].signatureScript = "";
|
|
51
|
+
assert.throws(
|
|
52
|
+
() => attachScripts(wasm, JSON.stringify(signed), result),
|
|
53
|
+
/changed the transaction intent/,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
key.free();
|
|
57
|
+
tx.free();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { runtime, loadArtifact } from "../backend/runtime.ts";
|
|
4
|
+
import { createBuilder, deployOperation, network } from "../shared/build.ts";
|
|
5
|
+
import { fixture, recipient } from "../shared/fixture.ts";
|
|
6
|
+
import { liveTransferOperation } from "../shared/live-transfer.ts";
|
|
7
|
+
const wasm = await runtime();
|
|
8
|
+
async function setup() {
|
|
9
|
+
const data = fixture(wasm);
|
|
10
|
+
const build = createBuilder(wasm, data.sources, loadArtifact);
|
|
11
|
+
const holder = data.addDeploy(await build(deployOperation(data.wallet)));
|
|
12
|
+
const config = {
|
|
13
|
+
network,
|
|
14
|
+
stateUrl: "https://indexer.example/holders",
|
|
15
|
+
wallet: data.wallet,
|
|
16
|
+
covenantId: holder.covenantId!,
|
|
17
|
+
decimals: 2,
|
|
18
|
+
recipientOwner: recipient,
|
|
19
|
+
tokenAmount: "12.50",
|
|
20
|
+
} as const;
|
|
21
|
+
const document = { network, utxos: [holder] };
|
|
22
|
+
const fetcher: typeof fetch = async () =>
|
|
23
|
+
new Response(JSON.stringify(document), { status: 200 });
|
|
24
|
+
return { data, build, config, document, fetcher, holder };
|
|
25
|
+
}
|
|
26
|
+
test("decoded endpoint data and RPC produce a real unsigned transfer", async () => {
|
|
27
|
+
const { data, build, config, fetcher } = await setup();
|
|
28
|
+
const operation = await liveTransferOperation(
|
|
29
|
+
wasm,
|
|
30
|
+
data.sources,
|
|
31
|
+
loadArtifact,
|
|
32
|
+
config,
|
|
33
|
+
fetcher,
|
|
34
|
+
);
|
|
35
|
+
const result = await build(operation);
|
|
36
|
+
assert.equal(result.metadata.tokenAmount, "1250");
|
|
37
|
+
assert.equal(result.metadata.covenantId, config.covenantId);
|
|
38
|
+
});
|
|
39
|
+
for (const failure of [
|
|
40
|
+
"network",
|
|
41
|
+
"spent",
|
|
42
|
+
"amount",
|
|
43
|
+
"identity",
|
|
44
|
+
"script",
|
|
45
|
+
"state",
|
|
46
|
+
"duplicate",
|
|
47
|
+
"decimals",
|
|
48
|
+
"owner",
|
|
49
|
+
] as const) {
|
|
50
|
+
test(`reject invalid live transfer source: ${failure}`, async () => {
|
|
51
|
+
const { data, config, document, fetcher, holder } = await setup();
|
|
52
|
+
const row = data.rows.get(holder.address)![0];
|
|
53
|
+
if (failure === "network") document.network = "mainnet";
|
|
54
|
+
if (failure === "spent") data.rows.delete(holder.address);
|
|
55
|
+
if (failure === "amount") row.amount += 1n;
|
|
56
|
+
if (failure === "identity") row.covenantId = "ff".repeat(32);
|
|
57
|
+
if (failure === "script")
|
|
58
|
+
row.scriptPublicKey = wasm.payToAddressScript(data.wallet.walletAddress);
|
|
59
|
+
if (failure === "state")
|
|
60
|
+
holder.state!.extensionCommitment = "ff".repeat(32);
|
|
61
|
+
if (failure === "duplicate") document.utxos.push(holder);
|
|
62
|
+
const input = {
|
|
63
|
+
...config,
|
|
64
|
+
...(failure === "decimals" ? { decimals: 9 } : {}),
|
|
65
|
+
...(failure === "owner"
|
|
66
|
+
? { wallet: { ...config.wallet, kcc20Owner: recipient } }
|
|
67
|
+
: {}),
|
|
68
|
+
};
|
|
69
|
+
await assert.rejects(
|
|
70
|
+
liveTransferOperation(wasm, data.sources, loadArtifact, input, fetcher),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"lib": [
|
|
12
|
+
"ES2022",
|
|
13
|
+
"DOM"
|
|
14
|
+
],
|
|
15
|
+
"types": [
|
|
16
|
+
"node",
|
|
17
|
+
"vite/client"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"include": [
|
|
21
|
+
"shared/**/*.ts",
|
|
22
|
+
"backend/**/*.ts",
|
|
23
|
+
"frontend/**/*.ts",
|
|
24
|
+
"test/**/*.ts"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Bundled WASM runtime
|
|
2
|
+
|
|
3
|
+
`kaspa/` contains the unmodified `kaspa-wasm` 1.1.1-toc.1 JavaScript, declarations,
|
|
4
|
+
and WASM bundle from `KASPACOM/kaspa-covenants` commit
|
|
5
|
+
`acd14ce0862d7dc87f5c15c93de07df844120303`, path
|
|
6
|
+
`wallet-lab/vendor/kaspa-wasm-toc`. Copyright Kaspa developers; ISC license
|
|
7
|
+
retained in `kaspa/LICENSE`.
|
|
8
|
+
|
|
9
|
+
Upstream project: https://github.com/kaspanet/rusty-kaspa.
|
|
10
|
+
The exact upstream source/build commit is not recorded in the supplied bundle.
|
|
11
|
+
See `docs/COMPATIBILITY.json` for file hashes; these pin the tested distribution,
|
|
12
|
+
not a claim that an upstream release builds the identical binary.
|
|
13
|
+
|
|
14
|
+
The TX Builder and example application code use the package's Apache-2.0 license.
|
|
15
|
+
Vendored runtime code retains its own license.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2024 Kaspa developers
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|