@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,215 @@
1
+ # Integrating the TX Builder
2
+
3
+ This guide describes the API shipped in `@kaspacom/kcc20-tx-builder@0.2.5`.
4
+ The host application supplies wallet identity, network configuration, contract
5
+ artifacts, chain data, and an initialized Kaspa WASM runtime. The package
6
+ constructs operations and transaction JSON. The host signs, validates,
7
+ broadcasts, and tracks the result.
8
+
9
+ ## Install from the public registry
10
+
11
+ ```sh
12
+ npm install --save-exact @kaspacom/kcc20-tx-builder@0.2.5 \
13
+ --registry=https://registry.npmjs.org/ \
14
+ --@kaspacom:registry=https://registry.npmjs.org/
15
+ ```
16
+
17
+ The scoped registry option matters when a project or user `.npmrc` maps
18
+ `@kaspacom` to GitHub Packages. Public npm installation does not require a
19
+ GitHub token. Commit your lockfile and pin the same package version across
20
+ browser and server adapters.
21
+
22
+ Node consumers need Node.js 20 or newer. Browser consumers need a bundler
23
+ that supports ESM and their platform's Kaspa WASM build. ESM and CommonJS
24
+ exports are available:
25
+
26
+ ```js
27
+ import { KCC20_PSKT_BUILDER_INPUT_SCHEMA } from '@kaspacom/kcc20-tx-builder';
28
+ ```
29
+
30
+ ```js
31
+ const { KCC20_PSKT_BUILDER_INPUT_SCHEMA } = require('@kaspacom/kcc20-tx-builder');
32
+ ```
33
+
34
+ ## Start with amount calculations
35
+
36
+ This example runs without a wallet, RPC connection, or WASM runtime:
37
+
38
+ ```js
39
+ import {
40
+ parseKcc20DisplayAmountToBaseUnits,
41
+ formatKcc20BaseUnitsForDisplay,
42
+ calculateKcc20OrderTotalSompi,
43
+ } from '@kaspacom/kcc20-tx-builder';
44
+
45
+ const baseUnits = parseKcc20DisplayAmountToBaseUnits('1.25', 8, 'amount');
46
+ console.log(baseUnits); // '125000000'
47
+ console.log(formatKcc20BaseUnitsForDisplay(baseUnits, 8)); // '1.25'
48
+
49
+ const order = calculateKcc20OrderTotalSompi('1.25', '100000000', 8);
50
+ console.log(order?.totalSompi); // '125000000'
51
+ ```
52
+
53
+ Use decimal strings for token amounts and sompi values. Use `bigint` for
54
+ arithmetic. A token's display decimals are separate from the KAS unit scale;
55
+ read them from its verified metadata. The helpers accept token decimals from
56
+ 0 through 8. Check `roundedDown` and `roundedTokenAmountBaseUnits` when quoting
57
+ orders because some prices require rounding to an exact sompi amount.
58
+
59
+ ## Configure a persistent engine
60
+
61
+ The following adapter is TypeScript. Its parameters are dependencies supplied
62
+ by your application, not objects exported by the package:
63
+
64
+ ```ts
65
+ import {
66
+ createKcc20PsktBuilderEngine,
67
+ type KaspaWasmRuntime,
68
+ type Kcc20PsktBuilderInput,
69
+ } from '@kaspacom/kcc20-tx-builder';
70
+
71
+ export function createWalletBuilder(
72
+ loadedKaspa: KaspaWasmRuntime,
73
+ applicationRpcConnection: {
74
+ getUtxosByAddresses(request: { addresses: string[] }): Promise<unknown>;
75
+ },
76
+ artifactProvider: (key: string) => Promise<unknown>,
77
+ ) {
78
+ const engine = createKcc20PsktBuilderEngine({
79
+ wasm: loadedKaspa,
80
+ artifacts: {},
81
+ artifactProvider,
82
+ config: { KASPA_NETWORK: 'testnet-10' },
83
+ sourceProvider: {
84
+ getUtxosByAddresses: (request) =>
85
+ applicationRpcConnection.getUtxosByAddresses(request),
86
+ },
87
+ });
88
+
89
+ return (preparedInput: Kcc20PsktBuilderInput) => engine.build(preparedInput);
90
+ }
91
+ ```
92
+
93
+ Initialize WASM before calling this adapter. Select and connect your RPC
94
+ endpoint in the host. The builder does not connect or disconnect a supplied
95
+ `sourceProvider`. Reuse a persistent engine to retain its artifact cache.
96
+ Supply fresh indexed covenant state and wallet funding sources for each build.
97
+
98
+ For a browser, serve the package's JSON artifacts through your asset pipeline:
99
+
100
+ ```ts
101
+ export async function artifactProvider(key: string): Promise<unknown> {
102
+ const response = await fetch(`/kcc20-artifacts/${encodeURIComponent(key)}`);
103
+ if (!response.ok) throw new Error(`Cannot load artifact ${key}: ${response.status}`);
104
+ const rawBytes = new Uint8Array(await response.arrayBuffer());
105
+ return {
106
+ artifact: JSON.parse(new TextDecoder().decode(rawBytes)),
107
+ rawBytes,
108
+ };
109
+ }
110
+ ```
111
+
112
+ Copy the installed package's `artifacts/*.json` files to that asset directory
113
+ as part of the host build. A server can load those same files once using its
114
+ own filesystem adapter and pass an envelope containing both the parsed
115
+ `artifact` and its exact `rawBytes` through `artifacts`. Snapshot builders
116
+ require those bytes to verify the frozen SHA-256 artifact pins.
117
+ Filesystem access belongs in the host, outside this package.
118
+
119
+ `rpcClientFactory` is an alternative for hosts that need the builder to create
120
+ an RPC client. Prefer `sourceProvider` when the application already owns a
121
+ connection. Configure network and RPC explicitly rather than relying on the
122
+ engine's TN10 defaults.
123
+
124
+ ## Prepare the input
125
+
126
+ The engine accepts the in-process envelope:
127
+
128
+ ```ts
129
+ import {
130
+ KCC20_PSKT_BUILDER_INPUT_SCHEMA,
131
+ type Kcc20PsktBuilderInput,
132
+ } from '@kaspacom/kcc20-tx-builder';
133
+
134
+ export function toBuilderInput(
135
+ preparedRequest: Record<string, unknown>,
136
+ ): Kcc20PsktBuilderInput {
137
+ return {
138
+ schema: KCC20_PSKT_BUILDER_INPUT_SCHEMA,
139
+ builder: {},
140
+ request: preparedRequest,
141
+ };
142
+ }
143
+ ```
144
+
145
+ `request.builderKey` selects the operation. The remaining request fields are
146
+ operation-specific: wallet owner and address, network, params, and decoded
147
+ covenant sources. This envelope adapter does not prepare or validate them.
148
+ Use the operation helpers listed in [the API guide](API.md) to construct intent,
149
+ then let your host resolve the required sources and assemble the engine request.
150
+ There is no universal funded request that works for every operation.
151
+
152
+ Operation helpers such as `buildKcc20TransferTokenOperation` return a wallet
153
+ operation envelope with an initially missing funded PSKT. That object is not
154
+ directly an engine input. The host must convert its intent and sources into
155
+ `request`, build the PSKT, and attach the funded result to its signing flow.
156
+
157
+ Do not confuse `KCC20_PSKT_BUILDER_INPUT_SCHEMA` with
158
+ `KCC20_BUILDER_INPUT_SCHEMA` from `models.ts`. The latter describes the separate
159
+ shared source/context model. Passing that model directly to `engine.build`
160
+ fails its schema check.
161
+
162
+ ## Consume the result
163
+
164
+ The engine returns an operation-specific record with
165
+ `schema: 'kcc20-in-process-pskt-builder-output/v1'`, `psktTransactionJson`,
166
+ `signInputs`, and, where needed, covenant `scripts` and `metadata`.
167
+ `psktTransactionJson` is JSON text, not an already signed transaction.
168
+ The return type is `Record<string, unknown>`; narrow and validate the fields
169
+ your wallet adapter consumes.
170
+
171
+ 1. Present the intended action, amounts, recipients, and fees to the user.
172
+ 2. Build using fresh sources and check the resulting inputs and outputs.
173
+ 3. Use the wallet's PSKT signing interface with the returned signing instructions.
174
+ 4. Independently validate the signed transaction and authorization in your trusted host.
175
+ 5. Broadcast through your host and reconcile confirmation or reorg state through your indexer.
176
+
177
+ The package does not provide wallet discovery, signing UI, authentication,
178
+ broadcast orchestration, or confirmation tracking. Browser-built transaction
179
+ JSON remains untrusted input at a backend boundary.
180
+
181
+ ## Artifacts and privileged builders
182
+
183
+ The package pins script hashes for five shipped artifacts. The engine validates
184
+ artifacts against those hashes; mixing independently compiled artifacts with
185
+ this package version can fail the build. Upgrade artifacts and builder together.
186
+ JSON subpath loading depends on the host's module loader, so the asset-provider
187
+ approach avoids imposing a JSON import syntax on consumers.
188
+
189
+ `allowBackendOnly` defaults to disabled. The matcher builder
190
+ `kcc20orderbook.matcher-settle-crossed` requires an engine explicitly configured
191
+ with `allowBackendOnly: true` in a trusted server. That flag does not authorize
192
+ a caller. Keep matcher scheduling, authorization, and any private keys in the
193
+ server host. `buildKcc20Pskt` does not expose this opt-in; configure an engine
194
+ for privileged work.
195
+
196
+ ## Handle errors
197
+
198
+ Some helpers throw `Kcc20BuilderError`, exposing `code` and string-valued
199
+ `details`. Other helpers and the engine throw ordinary `Error` instances;
200
+ do not assume every failure has a structured code. Handle both:
201
+
202
+ ```ts
203
+ import { Kcc20BuilderError } from '@kaspacom/kcc20-tx-builder';
204
+
205
+ export function describeBuildFailure(error: unknown): string {
206
+ if (error instanceof Kcc20BuilderError) return `${error.code}: ${error.message}`;
207
+ if (error instanceof Error) return error.message;
208
+ return 'Transaction construction failed';
209
+ }
210
+ ```
211
+
212
+ Refresh sources after spent or stale UTXO failures. An artifact mismatch needs
213
+ a package/artifact compatibility check. A missing WASM capability needs a
214
+ compatible runtime. Do not retry signing or broadcast automatically after an
215
+ ambiguous submission; reconcile its transaction ID in your host first.
@@ -0,0 +1,106 @@
1
+ # Transfer using your own indexer and RPC
2
+
3
+ The browser and Node examples use the same
4
+ [`liveTransferOperation`](../examples/shared/live-transfer.ts) adapter. It retrieves
5
+ decoded holder state from an endpoint you configure, confirms the output through
6
+ RPC, and passes the validated holder to the existing transfer helper. No KaspaCom
7
+ application backend is involved.
8
+
9
+ ## Decoded-state endpoint
10
+
11
+ Serve an HTTP GET endpoint returning:
12
+
13
+ ```ts
14
+ import type { Kcc20IndexedCovenantUtxo } from "@kaspacom/kcc20-tx-builder/action-source-resolver";
15
+ type HolderResponse = {
16
+ network: "testnet-10";
17
+ utxos: Kcc20IndexedCovenantUtxo[];
18
+ };
19
+ ```
20
+
21
+ Map your indexer's transaction ID, output index, KAS amount, native covenant ID,
22
+ address and decoded state to this shape. An application alias must not replace
23
+ the native covenant ID. Amounts must be decimal strings. This adapter supports
24
+ unborrowed native holders owned by a Schnorr public key. It rejects duplicate
25
+ outpoints and malformed rows. Paginate in your indexer adapter if a response
26
+ would exceed 1,000 rows.
27
+
28
+ A complete deterministic response can be generated without a backend:
29
+
30
+ ```sh
31
+ cd examples
32
+ npm run --silent recipe -- transfer --arguments > transfer-arguments.json
33
+ node --input-type=module -e 'import fs from "node:fs"; const a=JSON.parse(fs.readFileSync("transfer-arguments.json")); console.log(JSON.stringify({network:"testnet-10",utxos:a[1].activeUtxos},null,2))' > holder-response.json
34
+ ```
35
+
36
+ This includes actual address strings, outpoints, base-unit amounts, native IDs,
37
+ owner identifiers, extension commitments and borrow fields. All outpoints are
38
+ synthetic. They demonstrate the schema and are rejected by a live node.
39
+
40
+ For real use, fill these fields from your indexer's decoded current outputs.
41
+ RPC alone does not provide token amount, owner scheme or extension commitment.
42
+ The adapter rebuilds the holder script from decoded state and the packaged
43
+ artifact, verifies its address, then checks the RPC outpoint, script, KAS amount
44
+ and native covenant ID. A fabricated or stale snapshot cannot bypass those
45
+ comparisons. The selected RPC remains a trusted source of chain data.
46
+
47
+ ## Transfer configuration
48
+
49
+ Save `transfer.json` in `examples`. Replace the marked values with your own
50
+ TN10 account and verified token metadata:
51
+
52
+ ```json
53
+ {
54
+ "network": "testnet-10",
55
+ "stateUrl": "https://your-indexer.example/holders",
56
+ "wallet": {
57
+ "walletAddress": "kaspatest:YOUR_ADDRESS",
58
+ "kcc20Owner": "YOUR_64_HEX_OWNER"
59
+ },
60
+ "covenantId": "YOUR_64_HEX_NATIVE_COVENANT_ID",
61
+ "decimals": 2,
62
+ "recipientOwner": "RECIPIENT_64_HEX_OWNER",
63
+ "tokenAmount": "12.50"
64
+ }
65
+ ```
66
+
67
+ TypeScript applications can import `LiveTransferConfig` from the shared adapter.
68
+ `decimals` comes from verified token metadata. It determines the requested base
69
+ units; it cannot be inferred from a holder amount. The recipient is an x-only
70
+ public key. The adapter checks that the sender address belongs to the supplied
71
+ owner key. This example requires one holder large enough for the transfer and
72
+ separate KAS funding. Use the consolidation recipe for fragmented balances.
73
+
74
+ ## Backend
75
+
76
+ ```sh
77
+ cd examples
78
+ KASPA_WRPC_URL=wss://your-tn10-node npm run --silent backend -- transfer transfer.json > unsigned.json
79
+ ```
80
+
81
+ The CLI checks the connected node's network, retrieves and validates the holder,
82
+ builds locally, writes the unsigned transaction and signing instructions, and
83
+ closes RPC. A server handler can call the same adapter with a process-owned
84
+ connection. Apply authorization before any server signing. Do not accept arbitrary
85
+ state URLs from unauthenticated callers: configure or allowlist indexer endpoints
86
+ in your own server to avoid turning it into an HTTP proxy.
87
+
88
+ ## Frontend
89
+
90
+ Start the Vite example, enter the RPC URL, paste the same configuration JSON into
91
+ the text area, and click **Build transfer from indexer**. The state endpoint must
92
+ allow your application's origin through CORS. HTTPS applications need HTTPS state
93
+ URLs and WSS RPC. Review the complete unsigned transaction before the separate
94
+ **Sign** and **Broadcast** actions.
95
+
96
+ The same function can be imported directly:
97
+
98
+ ```ts
99
+ const operation = await liveTransferOperation(wasm, rpc, loadArtifact, config);
100
+ const unsigned = await createBuilder(wasm, rpc, loadArtifact)(operation);
101
+ ```
102
+
103
+ The network check belongs to the host connection setup, as shown in both runnable
104
+ apps. An input can be spent after validation: on a node rejection, refresh state
105
+ and rebuild. If submission times out, check the transaction ID and spent inputs
106
+ before retrying. These examples do not claim live wallet or node acceptance.
@@ -0,0 +1,175 @@
1
+ # Operation recipes
2
+
3
+ The [recipe registry](../examples/shared/recipes.ts) imports the package's existing
4
+ helpers with their original TypeScript signatures. These helpers describe an
5
+ operation; the shared `createBuilder` adapter constructs its transaction locally.
6
+ All examples target TN10. Supply current decoded state from your own indexer or
7
+ transaction decoder, plus current UTXOs from your own RPC connection.
8
+
9
+ ## Run a helper and build its transaction
10
+
11
+ Save a JSON array containing the helper's arguments, in signature order, to
12
+ `arguments.json`. The CLI calls the selected helper and emits its operation:
13
+
14
+ ```sh
15
+ cd examples
16
+ npm run --silent recipe -- deploy arguments.json > operation.json
17
+ KASPA_WRPC_URL=wss://your-tn10-node npm run --silent backend -- build operation.json > unsigned.json
18
+ ```
19
+
20
+ For frontend applications, use the same typed registry directly:
21
+
22
+ ```ts
23
+ import { recipes } from "./shared/recipes";
24
+ const operation = recipes.deploy(
25
+ wallet,
26
+ {
27
+ ticker: "DEMO",
28
+ tokenName: "Example token",
29
+ decimals: 2,
30
+ maxSupply: "1000",
31
+ premintSupply: "1000",
32
+ mintPolicy: "fixed",
33
+ mintPricePerTokenSompi: "0",
34
+ },
35
+ { network: "testnet-10" },
36
+ );
37
+ const unsigned = await build(operation);
38
+ ```
39
+
40
+ ## Complete offline recipes
41
+
42
+ Every registry entry has a complete typed argument tuple in
43
+ [`recipe-fixtures.ts`](../examples/shared/recipe-fixtures.ts), including decoded
44
+ state and matching RPC entries derived from the packaged contract scripts.
45
+ Run any recipe without a wallet, node or private repository:
46
+
47
+ ```sh
48
+ cd examples
49
+ npm run --silent recipe -- wrap --offline > unsigned-wrap.json
50
+ npm run --silent recipe -- wrap --arguments > wrap-arguments.json
51
+ npm run --silent recipe -- wrap wrap-arguments.json > wrap-operation.json
52
+ ```
53
+
54
+ Replace `wrap` with `deploy`, `transfer`, `mint`, `publicMint`, `mintAvailability`,
55
+ `consolidate`, `reveal`, `deployMarket`, `unwrap`, `order`, `buyOrder`, `fill`,
56
+ `fillBid`, `sweep`, `sweepBids`, `cancel`, `cancelBid`, `consolidateWrapped`,
57
+ `deployFeeTicketRoot`, `updateFeeTicketRoot`, `createFeeTicket`, `burnFeeTicket`,
58
+ or `transferFeeTicket`. The frontend's **Offline recipe** selector runs the same
59
+ fixtures and always keeps signing and broadcasting disabled.
60
+
61
+ `order`, `fill`, `sweep`, and `cancel` demonstrate the ask side. The bid variants
62
+ exercise the opposite side. `mint` uses the authority owner; `publicMint` uses a
63
+ different owner against an active public minter. The availability recipe pauses
64
+ that minter. Sweep fee totals come from the package's per-leg fee helper, including
65
+ the minimum protocol fee.
66
+
67
+ All 24 recipes build real unsigned transactions with the bundled WASM. Tests
68
+ assemble signatures with public test-vector keys and compare browser/Node unsigned
69
+ transactions and signing instructions. Fixtures are independent snapshots, not a
70
+ chain simulator. They reuse synthetic funding and cannot be submitted. Replace
71
+ them with current decoded state and RPC entries for live use; see the
72
+ [live transfer adapter](LIVE_TRANSFER.md) for a complete retrieval example.
73
+
74
+ ## Native tokens
75
+
76
+ | Recipe | Existing helper | Inputs beyond the wallet |
77
+ | ------------------ | ---------------------------------------- | ------------------------------------------------------------------------------- |
78
+ | `deploy` | `buildKcc20DeployTokenOperation` | Draft supplies, decimals, mint policy, treasury and protocol fee configuration. |
79
+ | `transfer` | `buildKcc20TransferTokenOperation` | Token identity/decimals, display amount, recipient owner, current holder UTXOs. |
80
+ | `mint` | `buildKcc20MintTokenOperation` | Token mint policy, amount, active authority UTXOs, optional recipient. |
81
+ | `mintAvailability` | `buildKcc20MintAvailabilityOperation` | Token, active mint-authority state, requested enabled state. |
82
+ | `consolidate` | `buildKcc20NativeConsolidationOperation` | Token, owner holder UTXOs, optional target amount. |
83
+ | `reveal` | `buildKcc20VerifyTokenOperation` | Token identity and a spendable holder/authority snapshot. |
84
+
85
+ ```ts
86
+ const mint = recipes.mint(
87
+ wallet,
88
+ {
89
+ token,
90
+ tokenAmount: "10.00",
91
+ activeUtxos: minterUtxos,
92
+ },
93
+ { network: "testnet-10" },
94
+ );
95
+ const unsignedMint = await build(mint);
96
+ ```
97
+
98
+ `token` must include its actual decimals and mint policy. Controlled minting
99
+ requires the authority owner. A public mint can be paused. Mint-authority state
100
+ includes extension data that a holder snapshot does not contain. Public mint
101
+ amount limits and paid-mint minimums are validated by the helper.
102
+
103
+ ## Wrappers and orderbook
104
+
105
+ | Recipe | Existing helper | Required state |
106
+ | -------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------- |
107
+ | `deployMarket` | `buildKcc20WrapperMarketDeployOperation` | Token identity, market configuration and fee-ticket identity if used. |
108
+ | `wrap` | `buildKcc20WrapTokenOperationFromSnapshot` | Native holder, wrapper candidates, native covenant IDs, token decimals and amount. |
109
+ | `unwrap` | `buildKcc20UnwrapTokenOperationFromSnapshot` | Wrapped holder, wrapper reserve, market identity and amount. |
110
+ | `order` | `buildKcc20OrderOperation` | Trading context and buy/sell draft; buy uses a market root, sell uses a wrapped holder. |
111
+ | `fill` | `buildKcc20FillOperation` | Trading context, selected bid/ask, fill amount, funding/holder sources. |
112
+ | `sweep` | `buildKcc20SweepOperation` | Trading context, ordered bid/ask legs and sufficient buyer/seller sources. |
113
+ | `cancel` | `buildKcc20CancelOperation` | Owner's bid/ask and matching market context. |
114
+ | `consolidateWrapped` | `buildKcc20WrappedConsolidationOperation` | Matching wrapped-holder sources and market context. |
115
+
116
+ ```ts
117
+ const order = recipes.order(
118
+ wallet,
119
+ tradingContext,
120
+ {
121
+ wrappedMarketId: tradingContext.wrapper.marketId,
122
+ side: "buy",
123
+ tokenAmount: "10.00",
124
+ unitPriceSompi: "100000000",
125
+ },
126
+ { network: "testnet-10" },
127
+ );
128
+ const unsignedOrder = await build(order);
129
+ ```
130
+
131
+ The context contains token decimals, the wrapper identity and its buildability,
132
+ current wrapped UTXOs, and optional fee-ticket UTXOs. Preserve the distinction
133
+ between native token identity, wrapper identity, and market identity. Use helper
134
+ parameter types rather than constructing a request from UI labels. The bid/ask
135
+ side controls which builder runs and which sources it consumes. Fees, locked
136
+ KAS deposits, price scale, partial-fill change, and token change must be shown
137
+ before signing. Never reuse a quote after its input order has changed.
138
+
139
+ ## Fee tickets
140
+
141
+ | Recipe | Existing helper | Required state |
142
+ | --------------------- | ----------------------------------- | ------------------------------------------------------------------------ |
143
+ | `deployFeeTicketRoot` | `buildFeeTicketRootDeployOperation` | Utility token ID, denomination, discount and optional supported markets. |
144
+ | `updateFeeTicketRoot` | `buildFeeTicketRootUpdateOperation` | Root configuration, current root UTXOs and new denomination. |
145
+ | `createFeeTicket` | `buildFeeTicketCreateOperation` | Root configuration, root UTXOs, utility holders and quantity. |
146
+ | `burnFeeTicket` | `buildFeeTicketBurnOperation` | Owner tickets and root configuration. |
147
+ | `transferFeeTicket` | `buildFeeTicketTransferOperation` | Owner tickets, recipient and root configuration. |
148
+
149
+ ```ts
150
+ const root = recipes.deployFeeTicketRoot(
151
+ wallet,
152
+ {
153
+ utilityTokenId,
154
+ utilityTokenAmount: "100",
155
+ discountBps: 2500,
156
+ },
157
+ { network: "testnet-10" },
158
+ );
159
+ const unsignedRoot = await build(root);
160
+ ```
161
+
162
+ `utilityTokenAmount` is an integer denomination in base units. Batch creation
163
+ requires a matching artifact and root capability; use the engine's existing
164
+ `resolveFeeTicketBatchCreateCapability` rather than assuming every root supports
165
+ multiple tickets. Creating tickets burns the selected utility amount.
166
+
167
+ ## Backend-only matcher
168
+
169
+ `kcc20orderbook.matcher-settle-crossed` is intentionally excluded from the public
170
+ example registry. The engine accepts it only with `allowBackendOnly: true`.
171
+ Use a separate server instance, validated crossed-order snapshots, and explicit
172
+ operator configuration. Never enable it in the frontend, expose operator
173
+ credentials through a client configuration object, or forward all of `process.env`
174
+ to the engine. Public minting, transfer, and user order actions do not require
175
+ this matcher or KaspaCom's backend.
@@ -0,0 +1,163 @@
1
+ # Build transactions in your application
2
+
3
+ TX Builder constructs transactions locally in a browser or Node process. Your
4
+ application supplies WASM, the packaged contract artifacts, and chain data.
5
+ You do not need KaspaCom's backend to plan or build a transaction.
6
+
7
+ Start with the [runnable examples](../examples/README.md). Both applications use
8
+ [the same construction code](../examples/shared/build.ts) and build a deploy
9
+ followed by a transfer. Their offline transactions are identical across runtimes.
10
+
11
+ ## Responsibilities
12
+
13
+ | Part | Responsibility |
14
+ | --- | --- |
15
+ | Operation helpers | Validate the requested action, select supplied covenant sources, convert display amounts, and describe intent. |
16
+ | Builder engine | Construct and serialize the funded unsigned transaction, attach covenant bindings, estimate fees, and return signing instructions. |
17
+ | Application | Initialize WASM, load artifacts, retrieve funding UTXOs and decoded covenant state, show transaction intent, and manage RPC lifetime. |
18
+ | Wallet or server signer | Confirm authorization and sign the required inputs, including covenant inputs. |
19
+ | Application and node | Submit the signed transaction, handle rejection, and track acceptance. |
20
+
21
+ A build is neither a signature nor proof of chain acceptance. Source snapshots
22
+ can become stale between construction and submission. On a spent-input failure,
23
+ refresh sources and rebuild; do not keep retrying the same transaction.
24
+
25
+ ## Install and initialize
26
+
27
+ Use Node 22 for the examples. The package also supports its declared Node 20+
28
+ range. Install `@kaspacom/kcc20-tx-builder` from the public npm registry; no GitHub
29
+ Packages credentials are needed. Use the release version matching the examples.
30
+
31
+ The host initializes WASM once. See [Node initialization](../examples/backend/runtime.ts)
32
+ and [browser initialization](../examples/frontend/runtime.ts). Both use the
33
+ included ISC-licensed `kaspa-wasm` bundle, version `1.1.1-toc.1`, and call
34
+ `initWASM32Bindings({ validateClassNames: false })`. This allows bindings to work
35
+ when bundlers rename WASM classes. WASM remains host-owned; importing the package
36
+ does not initialize it or open a connection.
37
+
38
+ [COMPATIBILITY.json](COMPATIBILITY.json) pins binary/file checksums, contract
39
+ revision, compiler release, and artifact hashes. The runtime is included under
40
+ `examples/vendor/kaspa` in this repository and package, so neither example
41
+ fetches it from a private repository. The original runtime build's source commit
42
+ is not recorded in the supplied bundle. File integrity is verified; a reproducible
43
+ WASM source build is not claimed. The manifest also supplies an immutable public
44
+ `downloadBaseUrl`. Download `kaspa.js`, `kaspa_bg.wasm`, `kaspa.d.ts`, and
45
+ `LICENSE` from that base and verify each SHA-256 against `wasm.files` if you
46
+ host the runtime separately. Keep the license with redistributed files.
47
+
48
+ ## Deploy a fixed-supply token
49
+
50
+ ```ts
51
+ import { buildKcc20DeployTokenOperation } from '@kaspacom/kcc20-tx-builder/deploy-operation';
52
+
53
+ const operation = buildKcc20DeployTokenOperation(
54
+ wallet, // { walletAddress: 'kaspatest:...', kcc20Owner: '<32-byte x-only public key>' }
55
+ {
56
+ ticker: 'DEMO', tokenName: 'Example token', decimals: 2,
57
+ maxSupply: '1000', premintSupply: '1000',
58
+ mintPolicy: 'fixed', mintPricePerTokenSompi: '0',
59
+ },
60
+ { network: 'testnet-10' },
61
+ );
62
+ const unsigned = await build(operation);
63
+ ```
64
+
65
+ `build` comes from `createBuilder` in the shared example. That function creates
66
+ one existing `createKcc20PsktBuilderEngine`, verifies packaged artifact hashes,
67
+ and calls its `build` method. The complete adapter is executable code, not an
68
+ unspecified server request.
69
+
70
+ `engineInput(operation)` takes `operation.payload.signing.builderKey`, `params`,
71
+ `owner`, `network`, and source/signer requirements and assembles
72
+ `kcc20-in-process-pskt-builder-input/v1`. The separate shared-model input schema
73
+ is not interchangeable with this engine schema. The helper's
74
+ `missing-funded-pskt` status means construction is still required; historical
75
+ `backend-funded-pskt-builder` labels do not require a KaspaCom backend.
76
+ The local engine performs that construction.
77
+
78
+ The fixed-supply example creates 1,000 tokens with two decimals: 100,000 base
79
+ units. It locks KAS into the token output and returns KAS change to the funding
80
+ wallet. Use decimal strings, not floating-point multiplication, for token
81
+ amounts. KAS amounts and fees are integer sompi; 100,000,000 sompi is one KAS.
82
+ The library's protocol fee defaults still apply to relevant operations; inspect
83
+ the operation and resulting outputs before asking a wallet to sign.
84
+
85
+ ## Transfer from a decoded holder snapshot
86
+
87
+ ```ts
88
+ import { buildKcc20TransferTokenOperation } from '@kaspacom/kcc20-tx-builder/token-operation';
89
+
90
+ const operation = buildKcc20TransferTokenOperation(wallet, {
91
+ token: { covenantId: holder.covenantId, decimals: 2 },
92
+ activeUtxos: [holder],
93
+ recipientOwner, // 32-byte x-only public key, not an address string
94
+ tokenAmount: '12.50',
95
+ }, { network: 'testnet-10' });
96
+ const unsigned = await build(operation);
97
+ ```
98
+
99
+ See [fixture.ts](../examples/shared/fixture.ts) for a complete holder snapshot:
100
+ transaction ID, output index, address, KAS amount, native covenant ID, owner,
101
+ token amount, extension commitment, and borrow state. The offline fixture derives
102
+ that snapshot from its deploy result. Its outpoints are synthetic and must never
103
+ be used as live funding.
104
+
105
+ Use the [live transfer adapter and walkthrough](LIVE_TRANSFER.md) to retrieve and validate a complete holder snapshot.
106
+ For live use, retrieve decoded state from your own indexer or decode the prior
107
+ transaction and reveal script. RPC UTXOs alone do not contain every contract
108
+ state field. Supply that state to the helper, then let the engine's source
109
+ provider retrieve current RPC entries by address. Preserve the native covenant
110
+ ID from RPC; an application's token identifier or script hash is not always the
111
+ native covenant ID. The engine rebuilds the holder script and checks its address.
112
+
113
+ The example transfers 1,250 base units, retains 98,750 as token change, and
114
+ uses a separate KAS funding input. A fragmented token balance may require
115
+ consolidation first. Funding selection can require one sufficiently large KAS
116
+ UTXO even when the wallet's total balance is sufficient.
117
+
118
+ ## Inspect, sign, submit
119
+
120
+ The engine returns `psktTransactionJson`, `signInputs`, optional `scripts`,
121
+ `metadata`, and `submitTransactionSupported`. Inspect the full transaction,
122
+ recipients, token change, covenant IDs, KAS change, and fee before signing.
123
+ `inspect` computes the fee from input amounts minus output values.
124
+
125
+ [The signing adapter](../examples/shared/signing.ts) includes the covenant input
126
+ indexes from `scripts` as well as ordinary `signInputs`. After KasWare signs,
127
+ it inserts the signature into the ordered ABI arguments and wraps the redeem
128
+ script. Omitting this step produces a signed funding input but an invalid
129
+ covenant spend. Preserve every builder-provided sighash and script template. The adapter preserves
130
+ prebuilt witnesses on inputs the wallet does not sign, checks returned signature
131
+ encoding and sighash bytes, and rejects changes to transaction intent. These
132
+ checks do not replace cryptographic verification or node consensus validation.
133
+ When using the WASM signer directly, map wire sighash `1` to `wasm.SighashType.All`;
134
+ the WASM enum's numeric value is `0`, not the wire byte.
135
+
136
+ The frontend checks the wallet account and TN10 network, then offers separate
137
+ Sign and Broadcast actions. It submits through the selected RPC node. The backend
138
+ example returns an unsigned transaction for an application or wallet to sign;
139
+ it does not load private keys. The offline suite verifies construction and
140
+ script assembly, not a wallet extension's current behavior or live acceptance.
141
+
142
+ For server integrations, call the same shared `createBuilder` function from your
143
+ handler with a process-owned RPC connection. Validate the caller's request and
144
+ apply your own authorization before signing. Return the builder result as JSON;
145
+ never forward your environment or signing credentials to a browser.
146
+
147
+ ## Failure handling
148
+
149
+ | Failure | Application action |
150
+ | --- | --- |
151
+ | Insufficient KAS or no single funding UTXO | Refresh funding and obtain an adequate input; inspect the required output deposits and fee. |
152
+ | No holder large enough | Check ownership and decimals; consolidate if the balance is fragmented. |
153
+ | Active holder not found | Refresh decoded state and RPC UTXOs; the input may have been spent. |
154
+ | Rebuilt script address mismatch | Check decoded state and artifact version; do not substitute another address. |
155
+ | Artifact hash mismatch | Use the artifacts shipped with this package version. |
156
+ | Wrong network or missing WASM capability | Correct host configuration before building. |
157
+ | Wallet rejection | Keep the transaction unsigned and report cancellation. |
158
+ | Node rejection or uncertain submission | Query acceptance and spent inputs before rebuilding or retrying. |
159
+
160
+ Errors include both plain `Error` messages and structured builder errors. Do not
161
+ assume every error has a `code`. Add transport timeouts appropriate to your host,
162
+ and close failed connections. Browser applications should reuse one RPC
163
+ connection rather than reconnecting for each build.