BTMS — Basic Token Management System — is a modular library for issuing, sending, receiving, and burning UTXO-based tokens on the BSV blockchain.
BSV Desktop and BSV Browser present a spend authorization modal whenever BSV is spent from the default basket. Without token context, this modal shows only the raw satoshi amount of the UTXO — typically 1 satoshi for a BTMS token output. That's misleading: the real value is the token embedded in the output's locking script, not the sats funding it.
BTMS solves this per-token. A token issuer (e.g. a USD stablecoin issuer) ships a BTMS module — a small piece of code that:
This modular approach means no single token specification is hardcoded. Any token implementation can provide a BTMS module, and it automatically works with every BRC-100-compatible wallet in the ecosystem.
See @bsv/btms-permission-module for the permission hook interface wallet builders register.
@bsv/btms handles the on-chain side: token lifecycle, UTXO selection, PushDrop encoding, and overlay service integration. Tokens are first-class on-chain objects identified by canonical asset IDs derived from transaction output references.
npm install @bsv/btmsimport { BTMS } from '@bsv/btms'
const btms = new BTMS({ networkPreset: 'mainnet' })
// Issue a new token
const result = await btms.issue(1000000, {
name: 'GOLD',
description: 'Represents 1 gram of gold',
iconURL: 'https://example.com/gold.png'
})
console.log('Asset ID:', result.assetId) // 'abc123...def.0'
// Check balance
const balance = await btms.getBalance(result.assetId)
console.log('Balance:', balance)
// List all owned assets
const assets = await btms.listAssets()
for (const asset of assets) {
console.log(`${asset.name}: ${asset.balance}`)
}issue(), send(), accept(), burn() for complete token lifecyclegetBalance(), listAssets(), getSpendableTokens(), listIncoming()proveOwnership(), verifyOwnership() for collateral and escrow operationsBTMSToken.decode() for extracting token data from locking scriptsisValidAssetId(), isIssuance() for token identification and verificationWalletInterface, explicit
mainnet, testnet, teratestnet, or local routing, and an optional comms
layerconst recipientIdentityKey = '025706528f0f6894b2ba505007267ccff1133e004452a1f6b72ac716f246216366'
const sendResult = await btms.send(
'abc123...def.0', // Asset ID from issuance
recipientIdentityKey,
100 // Amount
)
console.log('Txid:', sendResult.txid)
console.log('Change:', sendResult.changeAmount)const incoming = await btms.listIncoming()
for (const payment of incoming) {
console.log(`Incoming: ${payment.amount} of ${payment.assetId}`)
const result = await btms.accept(payment)
console.log(`Accepted ${result.amount} tokens`)
}// Burn specific amount
const result = await btms.burn('abc123...def.0', 100)
console.log('Burned:', result.amountBurned)
// Burn entire balance
const resultAll = await btms.burn('abc123...def.0')const verifierKey = '02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5'
const proofResult = await btms.proveOwnership(
'abc123...def.0',
500, // Amount to prove
verifierKey
)
if (!proofResult.success || !proofResult.proof) {
throw new Error(proofResult.error ?? 'Unable to prove ownership')
}
// Send proof to verifier...
// Verifier validates:
const verified = await btms.verifyOwnership(proofResult.proof)
if (verified.valid) {
console.log(`Verified ${verified.amount} tokens from ${verified.prover}`)
}{txid}.{vout} where output 0 of the issuance tx is the first token mint."ISSUE", the output is a new token mint. Asset ID becomes txid and vout after confirmation.Asset ID format — Asset IDs must be in format
{txid}.{vout}(lowercase hex txid, dot, vout number). Typos will cause asset not found.
Issuance not confirmed — Immediately after
issue(), the asset ID returned uses the pending txid. Canonical asset ID is only guaranteed after the tx is mined.
Incoming not auto-accepted — Received tokens stay in
listIncoming()until explicitlyaccept()ed. Apps must prompt users or auto-accept based on policy.
Metadata changes not tracked — If you transfer tokens with different metadata than the original issuance, the TopicManager may reject the transaction.
Burn is permanent — Once burned, tokens cannot be recovered. Apps should confirm with users before burning.