@radiant-core/sdk is the clean TypeScript SDK most web and app developers reach for first. It lets you build Radiant (RXD) dapps — wallets, marketplaces, games, payment flows — without reading internal wallet or library source. It wraps radiantjs (the low-level tx/script library) and adds the ecosystem-specific pieces app devs actually need.
transferFungible.getRecentTokens, getTokensByType) over the public indexer, cursor-paginated.parseNftScript / parseFtScript / tokenScriptKind validate what a prevout actually holds before you trust it.alice.rxd → address.m/44'/512'/0'/0/k (SLIP-0044 coin type 512).BigInt throughout.@radiant-core/sdk is the application layer built on top of it (networking, token-safe funding, Glyph, WAVE, units). You install only the SDK — radiantjs comes along as a dependency and you never touch its API directly.
npm install @radiant-core/sdk
# Node < 22 has no global WebSocket — also install the optional peer:
npm install ws
Requires Node 20.19+ or 22+ (radiantjs pulls in an ESM-only dependency that needs require(ESM) support). Ships dual ESM + CJS with bundled types.
BigInt — 1 RXD = 100,000,000 photons. Convert only at your UI edge with rxdToPhotons() / photonsToRxd(). Never do balance math in floating point.
The ElectrumClient speaks JSON-RPC over a single reconnecting WebSocket. Scripthash methods accept a plain address (the scripthash is computed for you).
import { ElectrumClient, photonsToRxd } from '@radiant-core/sdk';
const client = new ElectrumClient({ network: 'mainnet' });
await client.connect();
const address = '1Yourradiantaddress...';
const { confirmed, unconfirmed } = await client.getBalance(address);
console.log(`Confirmed: ${photonsToRxd(confirmed)} RXD`);
const utxos = await client.listUnspent(address); // Utxo[] (photons as BigInt)
// React to incoming payments:
await client.subscribe(address, (status) => {
console.log('address changed:', status); // re-fetch balance/UTXOs here
});
The default mainnet endpoint is wss://electrumx.radiantcore.org:443; pass endpoint to point at your own ElectrumX/RXinDexer.
Create or restore a BIP39 HD wallet. Derivation defaults to the Radiant path m/44'/512'/account'/0/index.
import { HDWallet } from '@radiant-core/sdk';
// New wallet
const mnemonic = HDWallet.generateMnemonic();
const wallet = HDWallet.fromMnemonic(mnemonic, { network: 'mainnet' });
const me = wallet.deriveKey(0); // m/44'/512'/0'/0/0
console.log(me.path, me.address, me.scriptHash);
// Batch derive a gap of receive addresses
const receive = wallet.deriveRange(0, 20); // DerivedKey[]
The single most important safety property of the SDK: funding is never gathered by a value heuristic. A UTXO that carries a token ref (FT/NFT/dMint) looks like plain value to a naive selector, but spending it as funding burns the token. selectRxdFunding screens every candidate two ways — indexer-reported refs and a local script scan — and silently excludes token UTXOs.
import { ElectrumClient, HDWallet, buildRxdTransfer, rxdToPhotons } from '@radiant-core/sdk';
const client = new ElectrumClient({ network: 'mainnet' });
const wallet = HDWallet.fromMnemonic(mnemonic);
const me = wallet.deriveKey(0);
const utxos = await client.listUnspent(me.address);
const { hex, txid } = buildRxdTransfer({
address: me.address,
wif: me.wif,
to: '1Recipient...',
amount: rxdToPhotons('1.25'), // 1.25 RXD -> photons
utxos, // token-bearing UTXOs are auto-excluded
feeRate: 10_000n, // photons/byte (mainnet min-relay)
});
await client.broadcastTx(hex);
console.log('sent', txid);
filterFundingCandidates(utxos) or guard with assertFundingSafe(utxos) first.
Mint and move Glyph tokens with the commit/reveal pattern handled for you. Fund from token-free UTXOs.
import { mintFT, mintNFT, transferToken, transferFungible, filterFundingCandidates } from '@radiant-core/sdk';
const funding = filterFundingCandidates(await client.listUnspent(me.address));
// Fungible token (FT amount == output photons)
const ft = await mintFT({
client, address: me.address, wif: me.wif,
ticker: 'DEMO',
supply: 1_000_000n,
metadata: { name: 'Demo Token', desc: 'Minted with @radiant-core/sdk' },
fundingUtxos: funding,
});
console.log('FT ref:', ft.refDisplay, ft.commitTxid, ft.revealTxid);
// Non-fungible token (singleton)
const nft = await mintNFT({
client, address: me.address, wif: me.wif,
metadata: { name: 'My NFT', attrs: { rarity: 'rare' } },
fundingUtxos: funding,
});
// Transfer a token (FT or NFT) to a new owner — moves one UTXO whole
await transferToken({
client, address: me.address, wif: me.wif,
tokenUtxo, // the FT/NFT UTXO (must include its on-chain script)
toAddress: '1Recipient...',
fundingUtxos: funding, // covers the fee; token value is conserved
});
// Send PART of a fungible balance (new in 0.2.0). Accumulates across your
// FT UTXOs and returns the remainder to you as an FT change output.
await transferFungible({
client, address: me.address, wif: me.wif,
tokenUtxos, // your FT UTXOs for this token (with scripts)
amount: 50n, // send 50 of e.g. a 500 balance
toAddress: '1Recipient...',
fundingUtxos: funding,
});
transferToken now rebuilds outputs canonically instead of editing the on-chain script in place. Previously a WAVE-name or mutable-NFT transfer carried the mutable covenant's auth preamble (OP_REQUIREINPUTREF) into the output and the node rejected it (invalid-transaction-reference-operations). The fix is proven on-chain against a regtest node.
Validate before you trust (new in 0.2.0). When a counterparty hands you a prevout — a swap maker "advertising" a token, a listing, anything you didn't build yourself — parse it with the shape-exact parsers instead of eyeballing refs:
import { parseNftScript, parseFtScript, tokenScriptKind } from '@radiant-core/sdk';
const kind = tokenScriptKind(prevout.script); // 'nft' | 'ft' | null
const { ref, addressHash } = parseNftScript(prevout.script);
if (ref) console.log(ref, addressHash); // set only if the script is
// EXACTLY an NFT shape
(parseP2pkhScript does the same for plain pay-to-pubkey-hash outputs.)
parseNftScript returns the singleton ref that actually identifies the token. If you match a maker's advertised token by a loose scan, you can be paid in the wrong asset.
scriptHash(zeroRefs(tokenScript)), not the owner's plain address. To list a held token, query client.listUnspent(scriptHash(zeroRefs(ftScript(owner, ref)))) — the entries come back with refs:[{ ref, type }] (type is "normal" for FT, "single" for an NFT singleton).
mintFT / mintNFT / transferToken broadcast real, irreversible transactions. They reproduce the proven Photonic-Wallet on-chain templates and are regtest-validated, but you should smoke-test your own flow on regtest/testnet before mainnet.
New in 0.2.0: query global, newest-first token lists — every Glyph asset on the chain, or one type at a time — without running your own indexer. Backed by the public RXinDexer v4 discovery indexes (live on https://radiantcore.org/api since 2026-07-18; the raw REST endpoints are documented in the Developer Guide).
import { getRecentTokens, getTokensByType, GLYPH_TOKEN_TYPE } from '@radiant-core/sdk';
// Newest tokens across ALL types (one page)
const { tokens, nextCursor } = await getRecentTokens({ limit: 100 });
for (const t of tokens) {
console.log(t.type_name, t.name ?? t.ticker, t.ref, t.deploy_height);
}
// Newest NFTs only — either form works:
await getRecentTokens({ typeId: GLYPH_TOKEN_TYPE.NFT });
await getTokensByType(GLYPH_TOKEN_TYPE.NFT, { order: 'recent' });
Pages are cursor-paginated: pass the previous page's nextCursor back as cursor to fetch the next page (null = no more pages). Cursors are opaque, URL-safe strings and are order-specific — never reuse a cursor across a change of order or type. getTokensByType defaults to order: 'ref' (the stable pre-v4 hash order); pass order: 'recent' for newest-first.
The watermark-sync pattern. Because the lists are newest-first, an indexer, gallery, or bot never has to re-walk the chain: do one full walk, save the newest deploy_height you saw, then on later runs page newest-first and stop as soon as a token's height drops below your watermark. You only ever pay for tokens minted since the last run.
import { getRecentTokens } from '@radiant-core/sdk';
async function syncNewTokens(watermark /* newest deploy_height already seen */) {
let cursor, newest = watermark;
paging: while (true) {
const { tokens, nextCursor } = await getRecentTokens({ limit: 200, cursor });
for (const t of tokens) {
if (t.deploy_height < watermark) break paging; // everything older is known
newest = Math.max(newest, t.deploy_height);
await upsertToken(t); // idempotent — heights at the boundary can repeat
}
if (!nextCursor) break;
cursor = nextCursor;
}
return newest; // save as the next run's watermark
}
GLYPH_TOKEN_TYPE = FT: 1, NFT: 2, DAT: 3, DMINT: 4, WAVE: 5, CONTAINER: 6, AUTHORITY: 7 (UNKNOWN: 0). Each row is a GlyphTokenSummary — ref, ref_hex, type/type_name, protocols, name, ticker, deploy_height, deploy_txid, is_spent, plus whatever else the indexer knows (supply, icon, attrs, …).Resolve WAVE names to addresses through the public indexer.
import { waveResolve, waveResolveAddress } from '@radiant-core/sdk';
const rec = await waveResolve('alice'); // "alice.rxd" works too
if (rec.registered) console.log(rec.address, rec.owner, rec.expires);
const addr = await waveResolveAddress('alice'); // string | null
| Area | Exports |
|---|---|
| Client | ElectrumClient |
| Wallet | HDWallet, Keys |
| Funding | selectRxdFunding, filterFundingCandidates, isFundingSafe, assertFundingSafe, estimateFee, sumValue |
| Transactions | buildRxdTransfer, buildTx |
| Tokens | mintFT, mintNFT, transferToken, transferFungible, encodeGlyph, ftScript, nftScript, parseTokenRef |
| Discovery | getRecentTokens, getTokensByType, GLYPH_TOKEN_TYPE |
| WAVE | waveResolve, waveResolveAddress, waveLabel |
| Script | scriptHash, addressToScriptHash, p2pkhScript, parseNftScript, parseFtScript, parseP2pkhScript, tokenScriptKind, isTokenBearing, zeroRefs, packRef, unpackRef |
| Units | rxdToPhotons, photonsToRxd |
| Errors | RadiantSdkError, InsufficientFundsError, TokenBurnGuardError, ElectrumError, ValidationError |
Full reference (every export, options, and return types) is in docs/API.md.
Validated end-to-end. The SDK ships with unit tests (CI on Node 20/22/24), on-chain consensus validation against a radiantd regtest node (FT/NFT mint + transfer accepted), and indexer validation against RXinDexer/ElectrumX (tokens surfaced via listUnspent with their refs).
Fee policy. Mainnet min-relay is 10,000 photons/byte; testnet/regtest is 1,000. The SDK sets the fee from the measured signed transaction size, so token transactions reliably clear the floor.
Fee sanity guard (new in 0.2.0). buildTx now refuses to build a transaction whose fee lands above ~2× the network floor (plus size headroom), throwing a ValidationError instead of broadcasting. This catches the three silent overpay paths — photons/byte ↔ sats/kB unit slips, over-funding with addChange: false, and radiantjs rolling sub-dust change into the fee — all of which are irrecoverable once mined. The guard is automatic; there is nothing to configure.
| Resource | Link |
|---|---|
| npm package | @radiant-core/sdk |
| Source & issues | github.com/Radiant-Core/radiant-sdk |
| API reference | docs/API.md |
| Low-level library | radiantjs |
| Related guides | Developer Guide · Token Creation · WAVE Names |