Skip to content

feat(sdk-coin-kas): add Kaspa (KAS) unique chain SDK integration#8421

Open
manojkumar138 wants to merge 1 commit intomasterfrom
feat/kaspa-sdk
Open

feat(sdk-coin-kas): add Kaspa (KAS) unique chain SDK integration#8421
manojkumar138 wants to merge 1 commit intomasterfrom
feat/kaspa-sdk

Conversation

@manojkumar138
Copy link
Copy Markdown
Contributor

No description provided.

@manojkumar138 manojkumar138 requested review from a team as code owners April 7, 2026 12:27
@sachushaji
Copy link
Copy Markdown
Contributor

@claude

@mohd-kashif mohd-kashif marked this pull request as draft April 8, 2026 05:20
@manojkumar138 manojkumar138 force-pushed the feat/kaspa-sdk branch 2 times, most recently from 00809c6 to 4e4bc10 Compare April 8, 2026 07:32
@mohd-kashif mohd-kashif marked this pull request as ready for review April 9, 2026 07:17
@mohd-kashif mohd-kashif requested a review from Copilot April 9, 2026 07:17
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-party Kaspa (KAS) support across BitGoJS by introducing a new @bitgo/sdk-coin-kas package and wiring it into statics, BitGo coin registration, account-lib builders, and build/deploy plumbing.

Changes:

  • Added Kaspa statics (coin + networks) and registered kas / tkas in the global coin/token lists.
  • Introduced @bitgo/sdk-coin-kas with keypair, address utilities, transaction model, and builder (+ unit tests).
  • Integrated the new coin into BitGo/account-lib exports & registration, Docker build/link flow, and CODEOWNERS; renamed the existing ERC20 “kas” token to ekas to avoid symbol collision.

Reviewed changes

Copilot reviewed 41 out of 41 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tsconfig.packages.json Adds sdk-coin-kas TS project reference.
modules/statics/test/unit/fixtures/expectedColdFeatures.ts Adds kas / tkas to expected cold features fixture.
modules/statics/src/networks.ts Defines Kaspa network types and registers main/test networks.
modules/statics/src/kas.ts Adds Kaspa statics coin factory/class.
modules/statics/src/index.ts Exports KASCoin from statics.
modules/statics/src/coins/erc20Coins.ts Renames ERC20 “kas” token to ekas.
modules/statics/src/base.ts Adds CoinFamily.KAS and BaseUnit.KAS.
modules/statics/src/allCoinsAndTokens.ts Registers kas and tkas in the global coin list.
modules/sdk-core/src/bitgo/environments.ts Adds kasNodeUrl environment config and default URLs.
modules/sdk-coin-kas/tsconfig.json New TS config for @bitgo/sdk-coin-kas.
modules/sdk-coin-kas/test/unit/utils.test.ts Unit tests for Kaspa address/key utilities.
modules/sdk-coin-kas/test/unit/transactionFlow.test.ts E2E flow test for build/sign/serialize/deserialize.
modules/sdk-coin-kas/test/unit/transactionBuilder.test.ts Unit tests for transaction builder behavior.
modules/sdk-coin-kas/test/unit/transaction.test.ts Unit tests for tx serialization, txid, sighash, explain.
modules/sdk-coin-kas/test/unit/keyPair.test.ts Unit tests for keypair generation/validation/address derivation.
modules/sdk-coin-kas/test/unit/coin.test.ts Unit tests for coin class surface behavior.
modules/sdk-coin-kas/test/fixtures/kas.fixtures.ts Test vectors/fixtures for Kaspa tests.
modules/sdk-coin-kas/src/tkas.ts Adds testnet coin class.
modules/sdk-coin-kas/src/register.ts Adds SDK registration helper for kas / tkas.
modules/sdk-coin-kas/src/lib/utils.ts Implements address encoding/decoding + serialization utils.
modules/sdk-coin-kas/src/lib/transactionBuilderFactory.ts Adds builder factory for transfers/from-raw.
modules/sdk-coin-kas/src/lib/transactionBuilder.ts Implements Kaspa UTXO transaction builder + signing.
modules/sdk-coin-kas/src/lib/transaction.ts Implements tx model, serialization, txid, sighash.
modules/sdk-coin-kas/src/lib/keyPair.ts Implements secp256k1 keypair and address derivation.
modules/sdk-coin-kas/src/lib/index.ts Re-exports coin library primitives.
modules/sdk-coin-kas/src/lib/iface.ts Adds Kaspa type definitions for tx/build/sign flows.
modules/sdk-coin-kas/src/lib/constants.ts Adds Kaspa constants (HRPs, opcodes, fees, URLs, tags).
modules/sdk-coin-kas/src/kas.ts Implements mainnet coin class (sign/explain/verify/etc).
modules/sdk-coin-kas/src/index.ts Package entrypoint exports.
modules/sdk-coin-kas/package.json New package manifest and dependencies.
modules/sdk-coin-kas/.mocharc.yml Mocha config for running TS tests.
modules/sdk-coin-kas/.eslintignore ESLint ignore list for the new module.
modules/bitgo/tsconfig.json Adds sdk-coin-kas TS project reference.
modules/bitgo/src/v2/coins/index.ts Exports Kaspa coin classes from BitGo v2 coins index.
modules/bitgo/src/v2/coinFactory.ts Registers kas / tkas constructors in coin factory.
modules/bitgo/package.json Adds @bitgo/sdk-coin-kas dependency.
modules/account-lib/tsconfig.json Adds sdk-coin-kas TS project reference.
modules/account-lib/src/index.ts Exposes Kaspa builders and adds to builder map.
modules/account-lib/package.json Adds @bitgo/sdk-coin-kas dependency.
Dockerfile Adds build/link steps for sdk-coin-kas in image.
CODEOWNERS Adds code ownership for modules/sdk-coin-kas/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to +71
const maxv = (1 << toBits) - 1;
for (const value of data) {
acc = (acc << fromBits) | value;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
result.push((acc >> bits) & maxv);
}
}
if (pad && bits > 0) {
result.push((acc << (toBits - bits)) & maxv);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kaspaConvertBits uses bitwise shifts (<<, >>, |, &) on a JS number, which truncates to 32-bit signed integers. Since Kaspa address payloads are 32 bytes, the accumulator will overflow/wrap during encoding/decoding, producing non-standard addresses/checksums. Rework kaspaConvertBits to avoid 32-bit bitwise ops (e.g., use BigInt or arithmetic + masking like the standard convertbits implementation) and validate leftover bits when pad is false.

Suggested change
const maxv = (1 << toBits) - 1;
for (const value of data) {
acc = (acc << fromBits) | value;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
result.push((acc >> bits) & maxv);
}
}
if (pad && bits > 0) {
result.push((acc << (toBits - bits)) & maxv);
}
const fromBase = 2 ** fromBits;
const toBase = 2 ** toBits;
const maxv = toBase - 1;
for (const value of data) {
if (!Number.isInteger(value) || value < 0 || value >= fromBase) {
throw new Error('Invalid value for kaspaConvertBits');
}
acc = acc * fromBase + value;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
const divisor = 2 ** bits;
result.push(Math.floor(acc / divisor) % toBase);
acc = acc % divisor;
}
}
if (pad) {
if (bits > 0) {
result.push((acc * (2 ** (toBits - bits))) % toBase);
}
} else if (bits >= fromBits || acc * (2 ** (toBits - bits)) > maxv) {
throw new Error('Invalid incomplete group in kaspaConvertBits');
}

Copilot uses AI. Check for mistakes.
* Kaspa (KAS) Utility Functions
*
* Address validation, encoding, script generation, and other utilities.
* Kaspa uses bech32m-encoded addresses with x-only secp256k1 public keys.
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File header says Kaspa uses “bech32m-encoded addresses”, but this implementation is cashaddr-style with a hrp: separator (and later comments also say “not standard bech32/bech32m”). Please update the top-level documentation to match the actual encoding to avoid misleading integrators.

Suggested change
* Kaspa uses bech32m-encoded addresses with x-only secp256k1 public keys.
* Kaspa uses cashaddr-style addresses with an HRP and ':' separator, and
* x-only secp256k1 public keys.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +51
/**
* Extract x-only public key from address (for script building).
* Kaspa P2PK address payload IS the x-only public key.
*/
function addressToXOnlyPubKey(address: string): Buffer {
const { payload } = kaspaDecodeAddress(address);
return payload;
}

/**
* Build a P2PK script public key from an address.
*/
function addressToScriptPublicKey(address: string): KaspaScriptPublicKey {
const xOnlyPubKey = addressToXOnlyPubKey(address);
return buildScriptPublicKey(xOnlyPubKey);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressToScriptPublicKey always builds a P2PK locking script from the decoded payload, but isValidAddress allows both pubkey (0x00) and scripthash (0x08) address versions. For a scripthash address this would generate an invalid script and could burn funds. Either restrict builder outputs to pubkey addresses or branch on the decoded version and build the correct script for each address type.

Copilot uses AI. Check for mistakes.
Comment on lines +195 to +196
const sig = secp256k1.sign(sighash, privKeyBytes, { lowS: false });
const sigBytes = Buffer.from(sig.toCompactRawBytes());
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code comments/state indicate Schnorr signatures, but the implementation uses secp256k1.sign(...) which is ECDSA in @noble/curves. Kaspa requires Schnorr signatures; use secp256k1.schnorr.sign(...) (or the correct Kaspa signing primitive) and ensure the produced signature format matches Kaspa’s script expectations.

Suggested change
const sig = secp256k1.sign(sighash, privKeyBytes, { lowS: false });
const sigBytes = Buffer.from(sig.toCompactRawBytes());
const sigBytes = Buffer.from(secp256k1.schnorr.sign(sighash, privKeyBytes));

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +178
async signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
const kasParams = params as KaspaSignTransactionOptions;
const txHex = kasParams.txPrebuild?.txHex ?? kasParams.txPrebuild?.halfSigned?.txHex;
if (!txHex) {
throw new SigningError('Missing txHex in transaction prebuild');
}
if (!kasParams.prv) {
throw new SigningError('Missing private key');
}

const txBuilder = this.getBuilder().from(txHex);
txBuilder.sign({ key: kasParams.prv });
const tx = await txBuilder.build();

const signed = tx.toBroadcastFormat();
// Return halfSigned if only one signature (multisig), fully signed if complete
return tx.signature.length >= 2 ? { txHex: signed } : { halfSigned: { txHex: signed } };
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signTransaction reconstructs the builder from txHex and immediately signs, but signing requires per-input UTXO data (utxoEntries) and that data is not serialized into txHex (and Transaction.deserialize doesn’t populate it). As written, txBuilder.sign() will throw for typical prebuilds. Update the prebuild/params to carry the input UTXOs (similar to other UTXO coins’ txPrebuild.txInfo.unspents) and inject them into the builder/transaction before computing sighashes.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +17
"scripts": {
"build": "yarn tsc --build --incremental --verbose .",
"fmt": "prettier --write .",
"check-fmt": "prettier --check '**/*.{ts,js,json}'",
"clean": "rm -r ./dist",
"lint": "eslint --quiet .",
"prepare": "npm run build",
"test": "npm run coverage",
"coverage": "nyc -- npm run unit-test",
"unit-test": "mocha"
},
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bitgo/sdk-coin-kas’s tests rely on mocha+tsx (.mocharc.yml requires tsx) and other modules typically declare @bitgo/sdk-api / @bitgo/sdk-test (and sometimes tsx) in devDependencies. This package currently has an empty devDependencies, which can break standalone installs/tests/publishing. Align package.json devDependencies with the repo’s other sdk-coin-* packages.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +52
describe('getAddress', () => {
it('should derive a valid mainnet address', () => {
const kp = new KeyPair({ prv: validPrivateKey });
const address = kp.getAddress('mainnet');
address.should.startWith('kaspa:');
isValidAddress(address).should.be.true();
});

it('should derive a valid testnet address', () => {
const kp = new KeyPair({ prv: validPrivateKey });
const address = kp.getAddress('testnet');
address.should.startWith('kaspatest:');
isValidAddress(address).should.be.true();
});
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current KeyPair/address tests only assert prefix + isValidAddress(), which can pass even if the implementation is self-consistent but not spec-compliant. Add an assertion against known-good vectors (e.g., TEST_ACCOUNT.publicKeyTEST_ACCOUNT.mainnetAddress) to catch encoding/signing regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +1977 to +1984
/**
* Kaspa network interface.
* Kaspa is a UTXO-based BlockDAG using GHOSTDAG/Proof-of-Work with bech32m addresses.
*/
export interface KaspaNetwork extends BaseNetwork {
/** bech32m Human-Readable Part ('kaspa' for mainnet, 'kaspatest' for testnet) */
readonly hrp: string;
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kaspa network docs here say “bech32m addresses”, but the SDK implementation uses cashaddr-style hrp: addresses. Please make the statics network documentation consistent with the actual address format used elsewhere in the PR.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +117
@@ -113,6 +114,7 @@ export { Etc, Tetc };
export { EvmCoin, EthLikeErc20Token, EthLikeErc721Token };
export { Flr, Tflr, FlrToken };
export { Flrp };
export { Kas, TKas };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TKas should be Tkas
this the naming convention followed for other coins

Flr,
Flrp,
Kas,
TKas,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tkas

Comment on lines +55 to +61
// RPC URLs (wRPC WebSocket)
export const MAINNET_NODE_URL = 'wss://mainnet.kaspa.green';
export const TESTNET_NODE_URL = 'wss://testnet-10.kaspa.green';

// Explorer URLs
export const MAINNET_EXPLORER_URL = 'https://explorer.kaspa.org/txs/';
export const TESTNET_EXPLORER_URL = 'https://explorer-tn10.kaspa.org/txs/';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if they can be picked from configs/statics

Comment on lines +22 to +23
/** BLAKE2B output size (32 bytes) */
const HASH_SIZE = 32;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was in constant.ts, use from there

import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
import { Kas } from './kas';

export class TKas extends Kas {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tkas

Comment on lines -9366 to -9367
'kas',
'Kaspa',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a existing token, why changing this?

import { EvmCoin, EthLikeErc20Token, EthLikeErc721Token } from '@bitgo/sdk-coin-evm';
import { Flr, Tflr, FlrToken } from '@bitgo/sdk-coin-flr';
import { Flrp } from '@bitgo/sdk-coin-flrp';
import { Kas, TKas } from '@bitgo/sdk-coin-kas';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this as well. It needs to be Tkas

coinFactory.register('tfiatusd', TfiatUsd.createInstance);
coinFactory.register('tflr', Tflr.createInstance);
coinFactory.register('tflrp', Flrp.createInstance);
coinFactory.register('tkas', TKas.createInstance);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case 'tflrp':
return Flrp.createInstance;
case 'tkas':
return TKas.createInstance;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +227 to +234
async signMessage(key: BaseKeyPair, message: string | Buffer): Promise<Buffer> {
throw new MethodNotImplementedError();
}

/** @inheritdoc */
auditDecryptedKey(params: AuditDecryptedKeyParams): void {
throw new MethodNotImplementedError();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not throw an error for non implemented methods rather just return it.
It is avoid any issues with TAT that we faced in the past.

Comment on lines +98 to +103
try {
new KeyPair({ pub });
return true;
} catch {
return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to utils

Comment on lines +110 to +115
try {
new KeyPair({ prv });
return true;
} catch {
return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manojkumar138 manojkumar138 force-pushed the feat/kaspa-sdk branch 4 times, most recently from f1da067 to f9e9eb2 Compare April 10, 2026 09:37
…HO-388]

Implements a full BitGoJS SDK module for Kaspa (KAS), a UTXO-based
BlockDAG chain using Schnorr signatures over secp256k1 and BLAKE2B
sighash with the "TransactionSigningHash" domain tag.

Changes:
- modules/sdk-coin-kas/: new SDK module with KeyPair, Transaction,
  TransactionBuilder, TransactionBuilderFactory, Kas/TKas coin classes,
  constants, interfaces, and utilities
- Kaspa cashaddr encoding (`:` separator, custom polymod checksum)
- statics: KasCoin class, CoinFamily.KAS, BaseUnit.KAS, KaspaMainnet/
  KaspaTestnet networks, kas/tkas coin entries with UUIDs
- sdk-core/environments.ts: kasNodeUrl field
- bitgo/coinFactory.ts + coins/index.ts: kas/tkas registered
- account-lib: kas/tkas added to coinBuilderMap
- tsconfig references updated (account-lib, bitgo, tsconfig.packages.json)
- CODEOWNERS, Dockerfile updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants