@@ -17,7 +17,7 @@ Supported coin identifiers:
1717- Address derivation (kaspa bech32 P2PK Schnorr)
1818- UTXO transaction building
1919- Schnorr signing and verification (Blake2b-256 sighash)
20- - TSS/MPC support (ECDSA algorithm)
20+ - TSS/MPC support (ECDSA algorithm, per-input DKLS sessions )
2121- Full serialization round-trip (hex/JSON)
2222
2323## Installation
@@ -28,82 +28,273 @@ yarn add @bitgo/sdk-coin-kaspa
2828
2929## Usage
3030
31- ### Register with BitGo SDK
31+ ### 1. Register with BitGo SDK
3232
3333``` typescript
34+ import { BitGo } from ' bitgo' ;
3435import { register } from ' @bitgo/sdk-coin-kaspa' ;
36+
37+ const bitgo = new BitGo ({ env: ' prod' });
3538register (bitgo );
39+
40+ const kaspa = bitgo .coin (' kaspa' );
41+ const tkaspa = bitgo .coin (' tkaspa' ); // testnet
42+ ```
43+
44+ Alternatively, instantiate directly (useful in tests or scripts):
45+
46+ ``` typescript
47+ import { Kaspa , Tkaspa } from ' @bitgo/sdk-coin-kaspa' ;
48+
49+ const kaspa = Kaspa .createInstance (bitgo );
50+ const tkaspa = Tkaspa .createInstance (bitgo );
51+ ```
52+
53+ ---
54+
55+ ### 2. Key Generation
56+
57+ ``` typescript
58+ // Random key pair
59+ const kp = kaspa .generateKeyPair ();
60+ console .log (kp .pub ); // 66-char hex compressed secp256k1 public key
61+ console .log (kp .prv ); // 64-char hex private key
62+
63+ // Deterministic from a 32-byte seed
64+ const seed = Buffer .from (' ...32 bytes...' , ' hex' );
65+ const kpFromSeed = kaspa .generateKeyPair (seed );
66+
67+ // Validation
68+ kaspa .isValidPub (kp .pub ); // true
69+ kaspa .isValidPrv (kp .prv ); // true
70+ ```
71+
72+ Using ` KeyPair ` directly:
73+
74+ ``` typescript
75+ import { KeyPair } from ' @bitgo/sdk-coin-kaspa' ;
76+
77+ const keyPair = new KeyPair (); // random
78+ const { pub, prv } = keyPair .getKeys ();
3679```
3780
38- ### Key Pair
81+ ---
82+
83+ ### 3. Address Generation
3984
4085``` typescript
4186import { KeyPair } from ' @bitgo/sdk-coin-kaspa' ;
4287
43- // Generate a random key pair
44- const kp = new KeyPair ();
45- const { pub, prv } = kp .getKeys ();
88+ const keyPair = new KeyPair ({ prv: ' <64-char-hex-private-key>' });
89+
90+ const mainnetAddress = keyPair .getAddress (' mainnet' ); // kaspa:qq...
91+ const testnetAddress = keyPair .getAddress (' testnet' ); // kaspatest:qq...
92+
93+ kaspa .isValidAddress (mainnetAddress ); // true
94+ ```
95+
96+ Computing the P2PK ` scriptPublicKey ` for a key (required when constructing UTXO inputs):
4697
47- // Derive address
48- const mainnetAddress = kp .getAddress (' mainnet' );
49- const testnetAddress = kp .getAddress (' testnet' );
98+ ``` typescript
99+ import { compressedToXOnly , buildP2PKScriptPublicKey } from ' @bitgo/sdk-coin-kaspa' ;
100+
101+ const xOnlyPub = compressedToXOnly (Buffer .from (pub , ' hex' ));
102+ const scriptPublicKey = buildP2PKScriptPublicKey (xOnlyPub ).toString (' hex' );
50103```
51104
52- ### Build and Sign a Transaction
105+ ---
106+
107+ ### 4. Building a Transaction
53108
54109``` typescript
55- import { TransactionBuilderFactory } from ' @bitgo/sdk-coin-kaspa' ;
110+ import { TransactionBuilderFactory , Transaction } from ' @bitgo/sdk-coin-kaspa' ;
56111import { coins } from ' @bitgo/statics' ;
112+ import type { KaspaUtxoInput } from ' @bitgo/sdk-coin-kaspa' ;
113+
114+ const utxo: KaspaUtxoInput = {
115+ transactionId: ' <64-char-hex-prev-tx-id>' ,
116+ transactionIndex: 0 ,
117+ amount: ' 100000000' , // 1 KASPA in sompi (1e8)
118+ scriptPublicKey: ' <hex>' , // P2PK script of the sender's key
119+ sequence: ' 0' ,
120+ sigOpCount: 1 ,
121+ };
57122
58123const factory = new TransactionBuilderFactory (coins .get (' kaspa' ));
59124const builder = factory .getBuilder ();
60125
61126builder
62- .addInput ({
63- transactionId: ' <prev-tx-id>' ,
64- transactionIndex: 0 ,
65- amount: ' 100000000' , // 1 KASPA in sompi
66- scriptPublicKey: ' <spk>' ,
67- sequence: ' 0' ,
68- sigOpCount: 1 ,
69- })
70- .to (' <recipient-kaspa-address>' , ' 99998000' )
127+ .addInput (utxo )
128+ .to (' kaspa:qq...recipient...' , ' 99998000' ) // amount in sompi
71129 .fee (' 2000' );
72130
73- const tx = await builder .build ();
131+ const tx = (await builder .build ()) as Transaction ;
132+ ```
133+
134+ Multiple inputs:
135+
136+ ``` typescript
137+ builder
138+ .addInput (utxo1 )
139+ .addInput (utxo2 )
140+ .to (recipientAddress , ' 299996000' )
141+ .fee (' 4000' );
142+ ```
143+
144+ ---
145+
146+ ### 5. Signing — Path A: Direct Private Key (non-TSS)
147+
148+ ``` typescript
149+ // `tx.sign` takes a 32-byte Buffer (raw private key)
74150tx .sign (Buffer .from (privateKeyHex , ' hex' ));
75151
76- const broadcastPayload = tx .toBroadcastFormat (); // JSON string for RPC
152+ // Signs every input at once. Fully signed → txHex; partial → halfSigned
153+ const signedTxHex = tx .toHex (); // SDK-internal format for round-trips
154+
155+ // Or via the coin interface:
156+ const result = await kaspa .signTransaction ({
157+ txPrebuild: { txHex: unsignedTxHex },
158+ prv: privateKeyHex ,
159+ } as any ) as { txHex: string };
77160```
78161
162+ ---
163+
164+ ### 6. Signing — Path B: TSS / MPC (per-input DKLS sessions)
165+
166+ Kaspa is UTXO-based: every input has its own sighash (Blake2b-256, BIP-143-like).
167+ Each input ** requires an independent DKLS session** — there is no way to produce N valid
168+ Schnorr signatures from a single signing operation.
169+
170+ ``` typescript
171+ const unsignedTx = (await builder .build ()) as Transaction ;
172+ const txHex = unsignedTx .toHex ();
173+
174+ // Step 1: one sighash Buffer per input — the messages for each DKLS session
175+ const sighashes: Buffer [] = unsignedTx .signablePayloads ; // Buffer[N]
176+
177+ // Step 2: run N DKLS sessions in parallel (one per sighash)
178+ // Each session produces a 64-byte raw Schnorr signature
179+
180+ // Step 3: collect results and call signTransaction
181+ const signatures = sighashes .map ((hash , inputIndex ) => ({
182+ inputIndex ,
183+ pubKey: compressedPubKeyHex , // 33-byte hex
184+ signature: dklsSession (hash ), // 64-byte hex Schnorr sig
185+ }));
186+
187+ const result = await kaspa .signTransaction ({
188+ txPrebuild: { txHex },
189+ signatures ,
190+ } as any ) as { txHex: string } | { halfSigned: { txHex: string } };
191+
192+ // result.txHex → all inputs signed
193+ // result.halfSigned → some inputs still unsigned (partial TSS round)
194+ ```
195+
196+ ---
197+
198+ ### 7. Broadcasting
199+
200+ ``` typescript
201+ // toBroadcastFormat() returns the Kaspa RPC-compatible JSON string
202+ const broadcastPayload = tx .toBroadcastFormat ();
203+
204+ // toHex() is the SDK-internal round-trip format (preserves amount + scriptPublicKey
205+ // on inputs for sighash recomputation). Do NOT send this to the Kaspa node directly.
206+ const internalHex = tx .toHex ();
207+ ```
208+
209+ ---
210+
211+ ### 8. Explaining / Parsing a Transaction
212+
213+ ``` typescript
214+ // Human-readable breakdown
215+ const explained = await kaspa .explainTransaction ({ txHex });
216+ console .log (explained .outputs ); // [{ address, amount }]
217+ console .log (explained .outputAmount ); // total sent (sompi)
218+ console .log (explained .fee ); // fee (sompi)
219+
220+ // Structured parse — inputs and outputs tagged with coin name
221+ const parsed = await kaspa .parseTransaction ({ txHex } as any );
222+ // { inputs: [{ amount, coin: 'kaspa' }], outputs: [{ address, amount, coin: 'kaspa' }] }
223+ ```
224+
225+ ---
226+
227+ ### 9. Verifying a Transaction
228+
229+ ``` typescript
230+ const valid = await kaspa .verifyTransaction ({
231+ txPrebuild: { txHex },
232+ txParams: {
233+ recipients: [{ address: ' kaspa:qq...' , amount: ' 99998000' }],
234+ },
235+ } as any );
236+
237+ console .log (valid ); // true
238+ ```
239+
240+ ---
241+
242+ ### 10. Coin Properties
243+
244+ ``` typescript
245+ kaspa .getChain (); // 'kaspa'
246+ kaspa .getFamily (); // 'kaspa'
247+ kaspa .getFullName (); // 'Kaspa'
248+ kaspa .getBaseFactor (); // 100_000_000 (sompi per KASPA)
249+ kaspa .supportsTss (); // true
250+ kaspa .getMPCAlgorithm (); // 'ecdsa'
251+
252+ tkaspa .getChain (); // 'tkaspa'
253+ tkaspa .getFullName (); // 'Testnet Kaspa'
254+ ```
255+
256+ ---
257+
258+ ## Key Constants
259+
260+ | Property | Value |
261+ | ---| ---|
262+ | 1 KASPA | ` 100_000_000 ` sompi |
263+ | ` getBaseFactor() ` | ` 1e8 ` |
264+ | Mainnet address prefix | ` kaspa: ` |
265+ | Testnet address prefix | ` kaspatest: ` |
266+ | Address type | P2PK Schnorr (x-only secp256k1) |
267+ | Signature algorithm | Schnorr (Blake2b-256 sighash) |
268+ | TSS algorithm | ` ecdsa ` (DKLS) |
269+ | Multisig type | ` onchain ` |
270+
271+ ---
272+
79273## Module Structure
80274
81275```
82276src/
83- ├── kaspa.ts # Kaspa mainnet coin class
84- ├── tkaspa.ts # Kaspa testnet coin class
85- ├── register.ts # SDK registration
277+ ├── kaspa.ts # AbstractKaspaLikeCoin, Kaspa, Tkaspa classes
278+ ├── register.ts # SDK registration helper
86279├── index.ts
87280└── lib/
88- ├── constants.ts # Chain constants (prefixes, decimals, fees )
281+ ├── constants.ts # Chain constants (prefixes, decimals, default fee )
89282 ├── iface.ts # TypeScript interfaces
90- ├── keyPair.ts # secp256k1 key pair
91- ├── sighash.ts # Blake2b-256 Schnorr sighash
92- ├── transaction.ts # Transaction class (sign/ verify/ explain)
283+ ├── keyPair.ts # secp256k1 key pair + address derivation
284+ ├── sighash.ts # Blake2b-256 Schnorr sighash + script utilities
285+ ├── transaction.ts # Transaction class (sign / verify / explain / serialize )
93286 ├── transactionBuilder.ts # UTXO transaction builder
94287 ├── transactionBuilderFactory.ts
95288 ├── utils.ts # Address validation and encoding
96289 └── index.ts
97290test/
98291├── fixtures/
99- │ ├── kaspa.fixtures.ts # Deterministic test vectors
100- │ └── kaspaFixtures.ts # Synthetic test fixtures
292+ │ └── kaspa.fixtures.ts # Deterministic test vectors
101293└── unit/
102294 ├── coin.test.ts
103295 ├── keyPair.test.ts
104296 ├── transaction.test.ts
105297 ├── transactionBuilder.test.ts
106- ├── transactionFlow.test.ts
107298 └── utils.test.ts
108299```
109300
@@ -117,7 +308,7 @@ Kaspa uses a custom cashaddr-like bech32 encoding:
117308
118309## Signing
119310
120- Kaspa uses ** Schnorr signatures over secp256k1** with a ** Blake2b-256** sighash. The sighash preimage follows the Kaspa BIP-143-like specification. Each input is signed independently, producing a 65-byte signature: 64 bytes Schnorr + 1 byte sighash type.
311+ Kaspa uses ** Schnorr signatures over secp256k1** with a ** Blake2b-256** sighash. The sighash preimage follows the Kaspa BIP-143-like specification. Each input is signed independently, producing a 65-byte signature: 64 bytes Schnorr + 1 byte sighash type ( ` 0x01 ` = SIGHASH_ALL) .
121312
122313## References
123314
0 commit comments