Tokens

Tokens in @bsv/simple are PushDrop outputs with encrypted payloads. They can hold any JSON-serializable data, be transferred between wallets, and be sent peer-to-peer via MessageBox.

Creating a Token

const result = await wallet.createToken({
  data: { type: 'loyalty', points: 100, tier: 'gold' },
  basket: 'my-tokens',
  satoshis: 1
})

console.log('Token created:', result.txid)
console.log('Basket:', result.basket)
console.log('Encrypted:', result.encrypted)  // true

How Token Creation Works

  1. Your data is serialized to JSON

  2. The JSON is encrypted using the wallet's key derivation (counterparty: 'self')

  3. The ciphertext is locked in a PushDrop script

  4. The output is stored in the specified basket with customInstructions containing the decryption parameters

TokenOptions

Parameter
Type
Default
Description

data

any

required

JSON-serializable data to encrypt

to

string

self

Recipient identity key

basket

string

'tokens'

Basket to store the token in

protocolID

[number, string]

[0, 'token']

PushDrop protocol ID

keyID

string

'1'

PushDrop key ID

satoshis

number

1

Satoshis locked in the token

Listing Tokens

The listTokenDetails method:

  1. Fetches all outputs from the basket with locking scripts and custom instructions

  2. Decodes each PushDrop locking script

  3. Reads the customInstructions to get decryption parameters

  4. Decrypts the payload and parses it as JSON

  5. Falls back to counterparty: 'anyone' if 'self' decryption fails (for compatibility with older tokens)

TokenDetail

Field
Type
Description

outpoint

string

Transaction ID and output index (txid.vout)

satoshis

number

Satoshis locked in the token

data

any

Decrypted payload (or null if decryption failed)

protocolID

any

Protocol ID used for encryption

keyID

string

Key ID used for encryption

counterparty

string

Counterparty used for encryption

Sending a Token (On-Chain)

Transfer a token to another wallet directly on-chain:

How Token Send Works

This is a two-step signing flow:

  1. The original token output is fetched with its full transaction (BEEF format)

  2. The PushDrop fields are decoded from the original locking script

  3. A new PushDrop output is created with a fresh key, locked to the recipient

  4. createAction() is called with the old token as input and new token as output

  5. The SDK returns a signableTransaction (since we need to provide the PushDrop unlock)

  6. The unlock template signs the transaction

  7. signAction() completes the transaction

The token stays in the same basket but with updated customInstructions reflecting the new key and counterparty.

Redeeming a Token

Spend a token and recover its satoshis:

This uses the same two-step signing flow as sendToken, but creates no outputs — the token's satoshis return to the wallet as change.

Sending a Token via MessageBox

Transfer a token to another wallet using MessageBox P2P messaging:

This creates the token transfer transaction on-chain, then sends the transaction bytes to the recipient via the simple_token_inbox MessageBox.

Receiving Tokens via MessageBox

acceptIncomingToken internalizes the token using basket insertion protocol and acknowledges the message to remove it from the inbox.

Incoming Token Structure

Field
Type
Description

messageId

string

MessageBox message ID

sender

string

Sender's identity key

transaction

number[]

Transaction bytes

protocolID

any

PushDrop protocol ID

keyID

string

PushDrop key ID

outputIndex

number

Output index in transaction

createdAt

string

Timestamp

Complete Example

Last updated