This guide walks you through creating your first XChain token using the SDK. You'll go from zero to an issued token on-chain in about 5 minutes.
- Node.js 22 (22.x LTS); Node 18 fails on the
mariadbESM package (ERR_REQUIRE_ESM); Node 24 cannot buildisolated-vm. Node 22 is required. - A running XChain platform (either local via regtest or a public node)
- A Bitcoin/Litecoin/Dogecoin wallet with a funded address (for mainnet/testnet), or use regtest for free development
npm install @dankest-llc/xchain-sdkThe SDK connects to an XChain hub to discover all running services automatically.
const XChainSDK = require('@dankest-llc/xchain-sdk');
const sdk = new XChainSDK({
hubUrl: 'http://localhost:10000', // xchain-hub address
});
// Initialize: fetches service endpoints from the hub
await sdk.init();If you're not using a hub, you can provide service URLs directly:
const sdk = new XChainSDK({
explorerUrl: 'http://localhost:8080',
encoderUrl: 'http://localhost:3003',
});Use sdk.issue() to define a new token. This builds the ACTION string; it doesn't broadcast anything yet.
const result = await sdk.issue({
tick: 'MYTOKEN', // Token ticker (up to 250 chars, no | ; . / chars)
maxSupply: '1000000', // Maximum total supply
decimals: 8, // Decimal places (0–18)
mintSupply: '1000', // Supply credited to you immediately at ISSUE (not a per-MINT amount)
description: 'My first XChain token',
});
console.log(result.actionString);
// => "ISSUE|0|MYTOKEN|1000000|8|1000|My first XChain token"The returned object contains:
| Field | Description |
|---|---|
action |
ACTION name (ISSUE) |
version |
Format version selected |
actionString |
The pipe-delimited ACTION string |
fields |
Parsed field map |
encoding |
Recommended encoding type (OP_RETURN, P2SH, etc.) |
The ACTION string needs to be embedded in a blockchain transaction. The encoder service does this, and the SDK talks to it for you.
First, gather your UTXOs (the encoder client can fetch them from the UTXO tracker):
const { utxos } = await sdk.encoder.getUTXOs('your-bitcoin-address');Then create a PSBT (Partially Signed Bitcoin Transaction):
const { psbt } = await sdk.encoder.createTx({
data: result.actionString,
pubkey: 'your-compressed-public-key-hex',
utxos: utxos,
});
console.log(psbt); // Hex-encoded PSBT ready for signingThe PSBT comes back unsigned. Sign it with your wallet software (any PSBT-compatible Bitcoin wallet works), then broadcast the signed transaction to the network.
// Sign with your wallet (wallet-specific; this is a placeholder)
const signedPsbt = yourWallet.signPsbt(psbt);
// Broadcast the signed transaction
const txid = await yourWallet.broadcast(signedPsbt);
console.log('Transaction broadcast:', txid);Once the transaction is confirmed in a block, the XChain decoder picks it up, the indexer processes it, and your token is live.
After confirmation (or instantly in regtest), query the explorer:
// Get token details
const token = await sdk.explorer.getToken('MYTOKEN');
console.log(token);
// => { tick: 'MYTOKEN', maxSupply: '1000000', decimals: 8, ... }
// Get balances for an address
const balances = await sdk.explorer.getBalances('your-address');
console.log(balances);
// => [{ tick: 'MYTOKEN', amount: '0', ... }, ...]The ISSUE action defines the token. mintSupply already credited you its amount at issuance; MINT is how any further supply enters circulation. Each MINT carries its own amount, which the minter chooses:
const mintResult = await sdk.mint({
tick: 'MYTOKEN',
amount: '500', // Required. MINT never reuses mintSupply
});
// Encode, sign, broadcast the PSBT the same way as above
const { utxos: mintUtxos } = await sdk.encoder.getUTXOs('your-address');
const { psbt } = await sdk.encoder.createTx({
data: mintResult.actionString,
pubkey: 'your-compressed-public-key-hex',
utxos: mintUtxos,
});Each MINT transaction adds the amount you passed to your address. That amount is capped per transaction by maxMint (unset means no per-transaction cap), cumulatively per address by mintAddressMax, and overall by maxSupply.
Transfer tokens to another address:
const sendResult = await sdk.send({
tick: 'MYTOKEN',
amount: '100',
destination: 'recipient-bitcoin-address',
memo: 'Optional memo', // optional
});
// Encode, sign, broadcast as before
const { utxos: sendUtxos } = await sdk.encoder.getUTXOs('your-address');
const { psbt } = await sdk.encoder.createTx({
data: sendResult.actionString,
pubkey: 'your-compressed-public-key-hex',
utxos: sendUtxos,
});The batch builder lets you combine multiple actions into a single transaction, saving on-chain fees:
const batchResult = await sdk.batch()
.mint({ tick: 'MYTOKEN' })
.send({ tick: 'MYTOKEN', amount: '50', destination: 'recipient-address' })
.build();
console.log(batchResult.actionString);
// => "BATCH|0|MINT|0|MYTOKEN;SEND|0|MYTOKEN|50|recipient-address"The SDK provides 100+ explorer query methods:
// Token information
await sdk.explorer.getToken('MYTOKEN');
await sdk.explorer.getTokens('your-address', 'address', { limit: 20, page: 1 });
// Balances
await sdk.explorer.getBalances('your-address');
await sdk.explorer.getBalances('your-address', { tick: 'MYTOKEN' });
// Transaction history
await sdk.explorer.getSends('MYTOKEN', 'token', { limit: 10 });
await sdk.explorer.getHistory('your-address', 'address');
// DEX
await sdk.explorer.getOrders('MYTOKEN', 'token');
await sdk.explorer.getDispensers('MYTOKEN', 'token');The SDK throws typed errors you can catch by class:
const { SDKValidationError, SDKEncoderError } = require('@dankest-llc/xchain-sdk');
try {
const result = await sdk.issue({ tick: 'MYTOKEN', maxSupply: '1000000' });
} catch (err) {
if (err instanceof SDKValidationError) {
console.error('Bad input:', err.message);
} else if (err instanceof SDKEncoderError) {
console.error('Encoder problem:', err.message);
} else {
throw err;
}
}Regtest is a local blockchain mode where blocks are mined on demand, coins have no real value, and you can test the full stack without spending anything. It's the fastest way to iterate.
See Regtest Development for setup instructions.
| Mode | How | When to use |
|---|---|---|
| Node.js library | require('@dankest-llc/xchain-sdk') |
Application code, scripts |
| JSON-RPC microservice | npm run api in xchain-sdk |
Any language via HTTP |
| Browser bundle | dist/xchain_sdk.min.js |
Client-side web apps |
The SDK covers all 31 user-submittable actions. Thirty of them have convenience methods: sdk.issue(), sdk.mint(), sdk.send(), sdk.sweep(), sdk.airdrop(), sdk.dividend(), sdk.order(), sdk.coinpay(), sdk.dispenser(), sdk.swap(), sdk.broadcast(), sdk.message(), sdk.file(), sdk.address(), sdk.link(), sdk.list(), sdk.sleep(), sdk.callback(), sdk.destroy(), sdk.price(), sdk.bet(), sdk.stake(), sdk.unstake(), sdk.delegate(), sdk.collect(), sdk.deploy(), sdk.execute(), sdk.deposit(), sdk.withdraw(), sdk.vote(). sdk.transfer() is an alias for sdk.send(). The thirty-first, BATCH, has no convenience method: sdk.batch() returns a builder for composing BATCH actions. The 6 remaining actions (ANCHOR, ATTEST, NODEPROOF, ROLLCALL, SLASH, and XCALL) are validator-broadcast, VM-emitted, or permissionless-proof actions and are not user-submittable; see concepts/ACTIONS.md for the full taxonomy.
- Full SDK Documentation: all methods, configuration options, error types, and examples
- ACTION Concepts: conceptual overview of the ACTION set and the ACTION format
- ACTION Protocol Specs: per-action field-level formats and validation rules for all 38 actions
- Regtest Development: run a full local stack for free
- Explorer API: all 200+ REST and JSON-RPC endpoints
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.