XChain Platform SDK — Wallet & Auth Reference
This document covers the SDK’s wallet and authentication modules: key management, address validation, message signing, challenge-response wallet verification, PSBT signing, transaction broadcasting, and UTXO queries.
Overview
The SDK provides two modules for wallet integration:
sdk.wallet(WalletUtils) — Key management, address derivation and validation, PSBT signing, transaction broadcasting, and UTXO queries.sdk.auth(AuthUtils) — Challenge-response wallet ownership verification and message signing.
Both modules are available on the SDK instance and as top-level convenience methods:
const sdk = new XChainSDK({ network: 'bitcoin-mainnet', encoderUrl: 'localhost' });
// Namespaced access
sdk.wallet.generateKeyPair();
sdk.auth.generateChallenge('bc1q...');
// Convenience access (same thing)
sdk.generateKeyPair();
sdk.generateChallenge('bc1q...');
All methods that involve private keys are synchronous (pure crypto, no network calls). Methods that query external services (broadcastTx, getUTXOs) are async and require the encoder to be configured.
Supported Networks
| Network string | Chain | Segwit | WIF prefix |
|---|---|---|---|
bitcoin-mainnet |
Bitcoin | Yes | 0x80 |
bitcoin-testnet |
Bitcoin testnet | Yes | 0xef |
bitcoin-regtest |
Bitcoin regtest | Yes | 0xef |
litecoin-mainnet |
Litecoin | Yes | 0xb0 |
litecoin-testnet |
Litecoin testnet | Yes | 0xef |
litecoin-regtest |
Litecoin regtest | Yes | 0xef |
dogecoin-mainnet |
Dogecoin | No | 0x9e |
dogecoin-testnet |
Dogecoin testnet | No | 0xf1 |
dogecoin-regtest |
Dogecoin regtest | No | 0xf1 |
Dogecoin does not support SegWit. Requesting a p2wpkh or p2sh-p2wpkh address on a Dogecoin network throws SEGWIT_NOT_SUPPORTED.
Key Management
Generate a Key Pair
const kp = sdk.generateKeyPair();
// kp.wif — WIF-encoded private key (string)
// kp.privateKey — raw private key (Buffer)
// kp.publicKey — compressed public key (Buffer)
// kp.publicKeyHex — compressed public key (hex string)
// kp.compressed — true
Pass { compressed: false } to generate an uncompressed key pair (65-byte public key).
Import a WIF Key
const kp = sdk.importWIF('cNjGh3...');
// Returns the same shape as generateKeyPair()
Throws NETWORK_MISMATCH if the WIF’s network byte doesn’t match the SDK’s configured network. Throws INVALID_WIF for malformed input.
Address Derivation
Derive an address from a public key (Buffer or hex string):
// P2PKH (legacy) — default
const legacy = sdk.deriveAddress(kp.publicKey);
// P2WPKH (bech32 / native segwit)
const segwit = sdk.deriveAddress(kp.publicKey, { type: 'p2wpkh' });
// P2SH-P2WPKH (wrapped segwit)
const wrapped = sdk.deriveAddress(kp.publicKey, { type: 'p2sh-p2wpkh' });
| Type | Format | Example prefix (BTC mainnet) |
|---|---|---|
p2pkh |
Base58Check | 1... |
p2wpkh |
Bech32 | bc1q... |
p2sh-p2wpkh |
Base58Check (P2SH) | 3... |
Address Validation
Validate any coin address against the configured network (or a specific override):
const result = sdk.validateAddress('bc1qexample...');
// result.valid — boolean
// result.type — 'p2pkh', 'p2wpkh', 'p2sh', 'p2wsh', or null
// result.network — 'bitcoin-mainnet', etc., or null
// result.error — error message string, or null
// Check against a specific network
const ltcResult = sdk.validateAddress('ltc1q...', 'litecoin-mainnet');
If no network is configured and no override is given, the address is checked against all 9 supported networks. Returns { valid: false } (does not throw) for invalid addresses.
Challenge-Response Wallet Verification
The auth module provides a three-step flow for proving wallet ownership:
Step 1: Generate a Challenge (Server)
// Default structured message (includes address, nonce, timestamp)
const challenge = sdk.generateChallenge('bc1quseraddress...');
// challenge.challenge — the message string to sign
// challenge.nonce — hex nonce (store this server-side)
// challenge.timestamp — ISO 8601 timestamp
// challenge.expiresAt — ISO 8601 expiry (default 5 minutes)
// Custom application ID
const challenge = sdk.generateChallenge('bc1q...', { appId: 'MyMusicApp' });
// Custom message (your site generates its own format)
const challenge = sdk.generateChallenge('bc1q...', {
message: 'Sign this to access premium content on MyApp. Nonce: abc123'
});
// challenge.challenge === the custom message, used as-is
When opts.message is provided, the SDK uses it verbatim. The nonce and timestamp metadata are still returned for the caller’s bookkeeping but are not embedded in the message. This means sites can generate their own messages and verify independently of the SDK.
Step 2: Sign the Challenge (Wallet/Client)
const signed = sdk.signMessage(challenge.challenge, userWIF);
// signed.signature — base64 encoded signature
// signed.address — the address derived from the signing key
For SegWit addresses, pass the appropriate option:
// Native segwit (bech32)
sdk.signMessage(message, wif, { segwitNative: true });
// Wrapped segwit (P2SH-P2WPKH)
sdk.signMessage(message, wif, { segwitRedeemScript: true });
Step 3: Verify Ownership (Server)
const result = sdk.verifyOwnership('bc1quseraddress...', challenge.challenge, signed.signature);
// result.valid — boolean
// result.address — the address that was verified
// result.error — null on success, error message on failure
verifyOwnership never throws — it returns { valid: false, error: '...' } on failure. This makes it safe to use in request handlers without try/catch:
app.post('/auth/verify', (req, res) => {
const { address, message, signature } = req.body;
const result = sdk.verifyOwnership(address, message, signature);
if (!result.valid) return res.status(401).json({ error: result.error });
// Grant access...
});
Verify Any Message
verifyMessage is a simpler alias that doesn’t return the address:
const result = sdk.verifyMessage(address, message, signature);
// result.valid — boolean
// result.error — null or error string
SDK-Independent Verification
Sites that don’t want to depend on the SDK for server-side verification can use bitcoinjs-message directly:
const bitcoinMessage = require('bitcoinjs-message');
const valid = bitcoinMessage.verify(message, address, signature);
Or use the coin node’s verifymessage JSON-RPC method. The signatures produced by sdk.signMessage are standard Bitcoin message signatures compatible with all of these.
PSBT Signing
Sign an unsigned PSBT hex string (from the encoder) with a WIF private key:
const result = sdk.signPsbt(unsignedPsbtHex, wif);
// result.txHex — signed raw transaction hex (ready to broadcast)
// result.txid — transaction ID
// result.psbtHex — signed PSBT hex
This method finalizes all inputs after signing and extracts the raw transaction. The txHex is what you pass to broadcastTx.
Full Workflow: Create → Sign → Broadcast
// 1. Create an action and encode it as a PSBT
const action = await sdk.send(
{ tick: 'MYTOKEN', amount: '100', destination: 'bc1qrecipient...' },
{ pubkey: publicKeyHex }
);
// 2. Sign the PSBT
const signed = sdk.signPsbt(action.psbt, wif);
// 3. Broadcast the signed transaction
const broadcast = await sdk.broadcastTx(signed.txHex);
console.log('Transaction ID:', broadcast.txid);
Transaction Broadcasting
Broadcast a signed raw transaction to the coin node via the encoder:
const result = await sdk.broadcastTx(signedTxHex);
// result.txid — the transaction ID returned by the coin node
Requires the encoder to be configured. The encoder proxies the sendrawtransaction call to the coin node.
UTXO Queries
Fetch UTXOs for an address:
const utxos = await sdk.getUTXOs('bc1qmyaddress...');
// [ { txid: 'abc...', vout: 0, value: 50000 }, ... ]
Requires the encoder to be configured. The encoder proxies the request to the xchain-utxo-tracker service.
Error Handling
The wallet and auth modules throw two error classes:
| Class | When |
|---|---|
SDKWalletError |
Key management, address derivation, PSBT signing, broadcasting, UTXO queries |
SDKAuthError |
Challenge generation, message signing, signature verification failures |
See Errors for the complete list of error codes.
Exception: verifyOwnership and verifyMessage do not throw — they return { valid: false, error: '...' } on failure. This is intentional so that verification can be used in request handlers without try/catch boilerplate.
Copyright © 2025–2026 Dankest, LLC
Based on XChain Platform by Dankest, LLC – https://dankest.llc
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-or-later) with a commercial license available for proprietary use.
You may use, modify, and distribute this material under the terms of the License. See LICENSE and NOTICE for full terms. See the licensing overview.