The XChain SDK

The component you actually build against: balances, encoding, signing, broadcasting, streaming, and cross-chain coordination, as function calls. It talks to the encoder and explorer for you, so you do not.

Install

Zero config is the default

A network string is the entire configuration. Mainnet and testnet resolve to the public *.xchain.io hosts (the hub discovers the explorer and encoder, falling back to the public defaults); any *-regtest network resolves to localhost. Point it somewhere else by passing full URLs.

Getting it

The package is not published to npm yet. Until it is, install from source:

git clone https://github.com/XChain-Platform/xchain-sdk.git
                    cd xchain-sdk && npm install

Once published, this becomes a one-line npm install. We would rather say so than print an install line that fails.

Try it in thirty seconds

The REPL drops you into a live session with an SDK instance already built, which is the fastest way to find out whether this fits your problem.

npm run repl

Pointing it at your own services

const sdk = new XChainSDK({
                        network:     bitcoin-testnet,
                        explorerUrl: https://explorer.example.com,
                        encoderUrl:  https://encoder.example.com,
                    });

Include the scheme. A bare host is treated as http://host:<dev-port>.

In practice

How little code this takes

Every example below runs against regtest and uses a placeholder key. On mainnet you change the network string and supply a real key, and nothing else about the shape of the code changes.

The whole pipeline, in one call

Encode, sign, broadcast, and wait for the indexer to confirm. One call, not four services.

const { XChainSDK } = require(xchain-sdk);
                    
                    // A network string is the whole config. Regtest resolves to localhost.
                    const sdk = new XChainSDK({ network: bitcoin-regtest });
                    
                    const tx = await sdk.submitAction(
                        { action: SEND, params: { tick: MYTOKEN, amount: 100, destination: bcrt1q... } },
                        { pubkey: 02abc123... },
                        { wif: cREGTEST_PLACEHOLDER_KEY },
                    );
                    console.log(tx.txid);      // broadcast
                    console.log(tx.indexed);   // and confirmed by the indexer

Bind a key once, then just act

A session carries the address, key, and UTXO state, so repeated actions from one address stop repeating themselves.

const session = sdk.session(cREGTEST_PLACEHOLDER_KEY);
                    
                    await session.send({ tick: MYTOKEN, amount: 50, destination: bcrt1qaddr1... });
                    await session.send({ tick: MYTOKEN, amount: 50, destination: bcrt1qaddr2... });
                    
                    // Back-to-back sends do not double-spend: the session chains UTXOs
                    // in memory rather than re-querying a stale set.
                    const balances = await session.getBalances();

Many actions, one transaction

The fluent batch builder packs several actions into a single on-chain transaction, so you pay one fee instead of several.

// build() is async: it resolves tickers and picks formats before encoding.
                    const batch = await sdk.batch()
                        .send({ tick: MYTOKEN, amount: 10, destination: bcrt1qaddr1... })
                        .mint({ tick: MYTOKEN, amount: 500 })
                        .build();

Wait for confirmation without polling

Event-driven, so you are not writing a retry loop around an explorer endpoint.

const indexed = await sdk.waitForAction(txid);
                    console.log(indexed.status);

Subscribe to the chain

Blocks, actions, and per-address activity over one WebSocket. No polling loop to write or tune.

sdk.onBlock((block) => console.log(new block, block.height));
                    sdk.onAction((action) => console.log(action.action, action.tick));
                    sdk.onAddress(bcrt1q..., (evt) => console.log(activity, evt));

Coordinate across two chains

One helper over two SDK instances. No bridge, no wrapped coin: the swap settles natively on both chains.

const { CrossChainHelper } = require(xchain-sdk);
                    
                    const cross = new CrossChainHelper({
                        BTC: new XChainSDK({ network: bitcoin-regtest }),
                        LTC: new XChainSDK({ network: litecoin-regtest }),
                    });
                    
                    await cross.createSwap({
                        giveCoin: BTC, giveTick: MYTOKEN, giveAmount: 100,
                        getCoin: LTC, getTick: THEIRTOKEN, getAmount: 250,
                        wif: cREGTEST_PLACEHOLDER_KEY,
                    });

Give an agent a bounded wallet

A policy block an AI agent cannot argue its way past: out-of-policy actions are refused before anything is signed.

const agent = sdk.agentSession(cREGTEST_PLACEHOLDER_KEY, {
                        allowedActions: [SEND, EXECUTE],
                        maxPerAction:   { SEND: { MYTOKEN: 100 } },
                        maxPerWindow:   { hours: 24, perTick: { MYTOKEN: 500 }, maxActions: 50 },
                    });
                    await agent.send({ tick: MYTOKEN, amount: 5, destination: bcrt1q... });
                    
                    // Honest about what this is: a client-side guardrail, not a security
                    // boundary. Whoever holds the key can bypass it with raw SDK calls.
                    // Hard enforcement is the MuSig2 co-signer, which withholds its
                    // partial signature on an out-of-policy transaction.

Recipes for the multi-step things

Issue a token and distribute it in one call, instead of orchestrating ISSUE then a fan of SENDs yourself.

await sdk.issueAndDistribute(cREGTEST_PLACEHOLDER_KEY,
                        { tick: NEWTOKEN, maxSupply: 1000000, decimals: 8 },
                        [
                            { destination: bcrt1qaddr1..., amount: 500000 },
                            { destination: bcrt1qaddr2..., amount: 300000 },
                        ]);

The parts you notice later

What it handles so you don't

None of these sell a first demo. All of them decide whether the thing survives contact with production.

Automatic format selection

Every ACTION has versioned wire formats. The SDK picks the smallest one your parameters permit, so your transactions stay cheap without you tracking format tables.

Ticker compaction

Token tickers resolve to their compact ^id wire form before encoding, shrinking the on-chain payload. On by default; opt out with { compactTickers: false }.

UTXO chaining

An in-memory UTXO cache keeps rapid sequential sends from double-spending against a stale set, which is the first thing that breaks when you script the SDK in a loop.

Retry with backoff

429, 502, 503 and 504 are retried with backoff, respecting Retry-After. Your integration does not fall over because a service was briefly busy.

Request hooks

onRequest, onResponse, onError and onRetry callbacks, so the SDK's traffic lands in your logging and metrics rather than beside them.

Types and a browser build

Full TypeScript definitions for IDE autocomplete, plus a browser bundle when the code needs to run client-side.

Boundaries

What the SDK deliberately cannot do

Better to read it here than to discover it by failing.

  • It cannot encode an ATTEST or an XCALL action. Both are emitted by the VM, not broadcast by a user: a contract calls xchain.attestation.request(...) or xchain.emit.crossExecute(...), and the validators emit the on-chain action. The SDK builds the envelope a contract passes in, and reads the results back through getAttestations(). See ATTEST and XCALL.
  • An agent policy is a guardrail, not a boundary. agentSession() refuses out-of-policy actions client-side, and whoever holds the key can bypass it with raw SDK calls. Hard enforcement is the MuSig2 co-signer, which runs the same policy server-side and withholds its signature.
  • It is a client, not a node. It talks to an encoder and an explorer. Running the platform yourself is a separate and deliberate step: see run a node.

Go deeper

Where the detail lives