XChain Platform - Software Developer Kit (SDK)
Developer-facing SDK for the XChain Platform: generate XChain transactions and query blockchain data.
Features
- 31 ACTION types:
sdk.send(),sdk.issue(),sdk.mint(),sdk.stake(), and 27 more convenience methods - Transaction lifecycle:
sdk.submitAction()handles the full encode -> sign -> broadcast -> wait pipeline in one call - Wallet sessions:
sdk.session(wif)bundles address/key/UTXO state for repeated actions from one address - Fee estimation:
sdk.estimateFees()returns fee info without signing or broadcasting - UTXO chaining: in-memory UTXO cache prevents double-spend on rapid sequential transactions
- Workflow recipes:
sdk.issueAndDistribute(),sdk.deployAndFund(),sdk.stakeAndDelegate(), and more - Cross-chain helpers: coordinate swaps and parallel actions across BTC, LTC, and DOGE SDK instances
- Event-driven confirmation:
sdk.waitForAction(txid)resolves when the indexer processes a transaction - Contract settle gate:
sdk.waitForContractState(index, { key: 'status', equals: 'FUNDED' })andsdk.waitForContractBalance(index, tick, { minQuantity })wait on the contract's own state, which is the only signal that cannot race the indexer;submitAction({ awaitContract: {...} })runs the same gate inline, so a deposit does not hand control back before the contract has been credited - Interactive REPL:
npm run repldrops into a live session with a pre-configured SDK instance - Automatic format selection: picks the smallest encoding format for every action
- PSBT generation: integrates with xchain-encoder to produce unsigned transactions
- 115+ explorer query methods: balances, tokens, transactions, markets, history, contracts
- Batch builder: fluent API:
await sdk.batch().send({...}).mint({...}).build()(build()is async) - Real-time events: WebSocket streaming with automatic reconnection and catch-up via
onBlock(),onAction(),onAddress(),onToken(),onMarket(),onDispenser(),onBetFeed(),onXcall(),onAttestation(), and more;sdk.wsexposes the low-level client (subscribe(),on(),listSubscriptions(), ...) directly - Encrypted messaging: ECIES, ECDH, and AES encryption for MESSAGE actions;
messaging.send()accepts aBufferpayload andgetMessages()exposesmsg.bytesfor binary ECIES - Token-gated file publishing:
sdk.gatedFile.encryptFileBytes()andsdk.gatedFile.encryptPack()produce AES-256-GCM ciphertext + key for FILE v1 gated content; key handoff as a compact 33-byte binary payload viaserializeKeyPayload()/parseKeyPayload()(sent through ECIES in binary mode). See Token-Gated Content - Attestation envelope helpers:
AttestationHelpers.llm({...})builds the JSON envelope a VM contract passes toxchain.attestation.request(...)with provider_id'llm';AttestationHelpers.httpGet({url})validates the URL and returns the payload string for'http_get';AttestationHelpers.requestOptions({redundancy, deadlineBlocks})builds the gateway options object. By design these are envelope builders only: there is no user-submittable ATTEST action. ATTEST v0 (request) and v1 (response) are VM-emitted: a contract callsxchain.attestation.request(...)and validators emit the on-chain attestation. So the SDK helps you shape the request a contract makes, and you read the results viagetAttestations(). It does not (and cannot) encode an ATTEST action directly, the same way XCALL is VM-emission-only. - Token-ownership trading helpers:
ORDER/SWAP/DISPENSERv0 carryGIVE_OWNERSHIP/GET_OWNERSHIPflags;SWEEPcarries independentORDERS/SWAPS/DISPENSERSflags (was a singleESCROWSflag) - HTTP 402 payments:
X402ClientandX402Gatewayimplement an XChain-native, x402-shaped pay-per-call flow over on-chain SEND actions, withxchain-send(pay-per-call),xchain-dispenser(hold-to-access), andxchain-deposit(metered spend ledger) schemes; the client pays only ticks given a per-tickmaxAmountceiling and refuses every offer when none is set. See x402 Payments - Contract-targeted staking:
session.stakeToContract({ amount, signingPubkey, targetContractIndex, tick }),session.unstakeFromContract({...}), andsession.delegateForContract({...})emit STAKE v3 / UNSTAKE v1 / DELEGATE v1 against a smart contract deployed via DEPLOY v1 (withCOOLDOWN_BLOCKS+SLASH_DESTINATION). High-level recipes:sdk.deployStakeableContract()andsdk.stakeToContractAndDelegate() - NFT helpers:
sdk.nft.unique(),sdk.nft.edition(),sdk.nft.collectionItem(),sdk.nft.attachContentParams(), andsdk.nft.isNft()for building the NFT pattern (ISSUE with DECIMALS=0 + LOCK_MAX_SUPPLY=1) plus high-levelsdk.issueNft(),sdk.issueNftEdition(),sdk.issueCollectionItem(), andsdk.attachContent()submit recipes - Project registry helpers:
sdk.project.rosterParams()andsdk.project.rosterEditParams()build LIST actions for owner-attested token rosters;sdk.setRoster()runs LIST then LINK and waits for the indexer - Ticker compaction: on by default; resolves token tickers to their compact
^idwire form via the explorer before encoding to shrink on-chain payload size; opt out with{ compactTickers: false } - MCP server:
npx xchain-mcpexposes all explorer query tools as Model Context Protocol tools for AI agent use - Wallet & auth: key management, PSBT signing, challenge-response verification
- Smart contracts: deploy, execute, deposit, withdraw via xchain-vm integration
- Contract identity pre-flight: a deployed contract must export
meta: { name, description, version }(CONTRACT_META_REQUIRED).sdk.deploy(),session.deploy(),session.deployChunk(),sdk.deployAndFund()andsdk.deployStakeableContract()read it statically before the action is composed and refuse with the chain's own verdict string, so a contract the indexer will reject never costs a fee.sdk.contracts.getExportedMeta(source)exposes the same read - Hub discovery: auto-resolves service endpoints from xchain-hub
- Retry with backoff: handles HTTP 429/502/503/504, respects
Retry-Afterheaders - Request hooks:
onRequest,onResponse,onError,onRetrycallbacks - TypeScript definitions: full
.d.tsfor IDE autocomplete - Browser bundle: Browserify build for client-side use
Documentation
Full SDK developer guide is published at docs.xchain.io/components/sdk:
Install
npm install @dankest-llc/xchain-sdk
Node 22 or newer. For development against the source, clone this repository and npm install inside it; the companion MCP server for AI agents is published separately as xchain-mcp.
Quick Start
const { XChainSDK } = require('@dankest-llc/xchain-sdk');
// Zero-config: a network alone targets the public XChain Platform.
// Mainnet/testnet default to the public hosts (hub.xchain.io discovers
// explorer/encoder, falling back to explorer.xchain.io / encoder.xchain.io);
// on any *-regtest network only the explorer defaults to localhost:8080. The
// encoder (needed for writes) needs ENCODER_URL or ENCODER_PORT, and the
// WebSocket needs a WEBSOCKET_* or EXPLORER_* URL or port (or the matching
// constructor options).
const sdk = new XChainSDK({ network: 'bitcoin-mainnet' });
// To point at your own services, pass full URLs (include the scheme; a
// bare host is treated as http://host:<dev-port>):
// const sdk = new XChainSDK({
// network: 'bitcoin-mainnet',
// explorerUrl: 'https://explorer.example.com',
// encoderUrl: 'https://encoder.example.com'
// });
// Generate an action string
const result = await sdk.send({
tick: 'MYTOKEN',
amount: '100',
destination: 'bc1q...',
memo: 'Payment'
});
console.log(result.actionString); // 'SEND|0|MYTOKEN|100|bc1q...|Payment'
// Multi-destination SEND: one entry per recipient. The wire format version
// is chosen from the legs (v1 shared tick, v2 per-leg tick, v3 per-leg memo).
// A flat {tick, amount, destination} map can only ever express ONE leg, so
// the SDK refuses one against a multi-leg format instead of repeating leg 1.
const multi = await sdk.send({
tick: 'MYTOKEN',
legs: [
{ amount: '100', destination: 'bc1qaddr1...' },
{ amount: '250', destination: 'bc1qaddr2...' }
]
});
console.log(multi.actionString); // 'SEND|1|MYTOKEN|100|bc1qaddr1...|250|bc1qaddr2...'
// Full lifecycle: create, encode, sign, broadcast, wait for indexer
const tx = await sdk.submitAction(
{ action: 'SEND', params: { tick: 'MYTOKEN', amount: '100', destination: 'bc1q...' } },
{ pubkey: '02abc123...' },
{ wif: 'your-wif-key' }
);
console.log(tx.txid); // transaction hash
console.log(tx.indexed); // action data from the indexer
// Wallet session: bind to a key and send multiple actions
const session = sdk.session('your-wif-key');
await session.send({ tick: 'MYTOKEN', amount: '50', destination: 'bc1q...' });
await session.send({ tick: 'MYTOKEN', amount: '50', destination: 'bc1q...' });
const balances = await session.getBalances();
// Workflow recipes: multi-step operations in one call
await sdk.issueAndDistribute('your-wif-key',
{ tick: 'NEWTOKEN', maxSupply: '1000000', decimals: 8 },
[
{ destination: 'bc1qaddr1...', amount: '500000' },
{ destination: 'bc1qaddr2...', amount: '300000' }
]
);
// Deploy a smart contract. Every contract must export its identity: `meta.name`
// and `meta.description` are consensus-required (CONTRACT_META_REQUIRED), and
// `meta.version` is optional but indexed. Use STRING LITERALS: the chain evaluates
// `meta` at deploy, so a computed name is not what you read in the source, and the
// pre-flight cannot check it for you.
const code = `module.exports = {
meta: {
name: 'Escrow', // 1..64 bytes
description: 'Two-party escrow with an arbiter', // 1..512 bytes
version: '1.0.0' // optional, 1..32 bytes
},
permissions: ['SEND'],
initialize(xchain) { xchain.state.set('status', 'OPEN'); }
};`;
await session.deploy({ code, gasLimit: 200000 });
// A contract with no conforming meta is refused BEFORE anything is composed,
// signed or broadcast, with the exact verdict the indexer would have written:
// SDKContractError: invalid: CONTRACT_MANIFEST (meta required)
// Pass { preflight: 'warn' | 'off' } (session/workflow seams) or
// { lint: 'warn' | 'off' } (sdk.deploy) to downgrade or skip the check; the
// deploy is still rejected on-chain. A computed or unreadable `meta` only warns.
sdk.contracts.getExportedMeta(code); // { status: 'present', name: 'Escrow', ... }
// Query blockchain data. The token record arrives NESTED under `info`:
const token = await sdk.getToken('MYTOKEN');
token.info.tick_id; // '42' <- the fields live here
token.tick_id; // undefined
// A tick that does not exist answers HTTP 404, so getToken() THROWS
// SDKExplorerError (code EXPLORER_HTTP_404) instead of answering an empty
// body. Use these for an existence check rather than a try/catch:
await sdk.tokenExists('MYTOKEN'); // true / false, never throws on absence
await sdk.findToken('MYTOKEN'); // the unwrapped info record, or null
// Both answer "absent" only for the 404. A timeout, network failure, 429 or
// 5xx still throws, because an explorer that could not answer is not proof
// that the ticker is free.
Configuration
Scripts
API server module
npm run api (node ./src/api/index.js) runs the JSON-RPC server as a standalone process, opening a listener on SDK_API_PORT at load. A consumer that wants to own the server instead, mounting it under an existing express app, choosing its own listen path, or starting and stopping it from a test, requires the module directly rather than shelling out to the script:
const { createApp, startApi } = require('@dankest-llc/xchain-sdk/src/api');
// createApp(sdk) is synchronous and listener-free: it wires the same guard
// stack (rate limit, auth gate, batch cap, /openrpc.json, JSON-RPC router)
// as the CLI entry and returns the express app to mount yourself.
const app = createApp(sdk);
// startApi({ port }) builds the SDK from the environment, runs hub discovery,
// and resolves to the listening http.Server (port 0 for an ephemeral port).
const server = await startApi({ port: 0 });
await new Promise((resolve) => server.close(resolve));
Requiring src/api never opens a socket; only the CLI entry (npm run api / node ./src/api/index.js) starts listening automatically.
Test Suite
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.