XChain Platform SDK: ACTION Reference
Complete reference for all 31 ACTION types supported by the XChain Platform SDK.
Creating Actions
The SDK provides two ways to create an action:
Convenience methods (recommended)
Each action has a dedicated method named after the action in lowercase:
await sdk.send({ tick: 'MYTOKEN', amount: '100', destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' })
await sdk.issue({ tick: 'MYTOKEN', maxSupply: '1000000', decimals: 8 })
await sdk.mint({ tick: 'MYTOKEN', amount: '100' })
// etc.
An optional second argument passes encoder options (see ENCODER.md):
await sdk.send({ tick: 'MYTOKEN', amount: '100', destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' }, { encoding: 'OP_RETURN' })
Generic method
await sdk.createAction({ action: 'SEND', params: { tick: 'MYTOKEN', amount: '100', destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' }, encoder: { encoding: 'OP_RETURN' } })
Return value
All action methods return a result object with the following shape:
{
action: 'SEND', // normalized ACTION name
version: 0, // format version selected (integer)
actionString: 'SEND|0|MYTOKEN|100|bc1q...', // pipe-delimited protocol string
fields: { TICK: 'MYTOKEN', AMOUNT: '100', DESTINATION: 'bc1q...' }, // normalized fields
encoding: null, // encoding type (populated when encoder is used)
psbt: null // PSBT (populated when encoder builds the transaction)
}
Field name normalization
Params may be supplied in camelCase (maxSupply, listActionIndex) or UPPER_SNAKE_CASE (MAX_SUPPLY, LIST_ACTION_INDEX). The SDK normalizes both forms before processing.
Validation dry-run
To validate params without building an action string:
let { valid, errors } = sdk.validateAction('SEND', { tick: 'MYTOKEN', amount: '100', destination: 'bc1q...' })
Actions
ADDRESS
Configure address-level preferences for fee routing and memo requirements.
Format Versions: v0 (preferences), v1 (controller bind/unbind)
Format v0: ADDRESS|VERSION|FEE_PREFERENCE|REQUIRE_MEMO|DISPENSER_PREFERENCE|MEMO
Format v1: ADDRESS|VERSION|CONTROLLER|ACTION_CLASS|COOLDOWN_BLOCKS|UNBIND|MEMO
Params (preferences (v0):)
| Param | Type | Required | Description |
|---|---|---|---|
| feePreference | integer | No | Fee routing: 1 = destroy, 2 = protocol, 3 = community |
| requireMemo | integer | No | Whether to require a memo on incoming sends (0 or 1) |
| dispenserPreference | integer | No | Who may open dispensers targeting this address: 1 = owner only (default), 2 = anyone |
| memo | string | No | Optional note |
Notes (v0):
- All fields are optional; omitting all fields is valid (no-op update).
feePreferencemust be1,2, or3if provided.
Params (controller bind/unbind (v1):)
| Param | Type | Required | Description |
|---|---|---|---|
| controller | integer | Conditional | ACTION_INDEX of the deployed guard contract. Required when unbind is 0. Ignored on unbind. |
| actionClass | string | Yes | The action class to gate or release: transfer, trade, burn, mint, or stake |
| cooldownBlocks | integer | No | Number of blocks that must pass after an unbind request before the binding is dropped (committed at bind; 0 = no cooldown) |
| unbind | integer | Yes | 0 = bind the action class to the controller, 1 = unbind it |
| memo | string | No | Optional note |
Notes (v1):
- ADDRESS v1 gates the broadcasting address itself (self-signed; no address param).
- The indexer enforces contract existence and cooldown; the SDK validates field format only.
- Use
sdk.controller.bindAddress()andsdk.controller.unbindAddress()to build the params cleanly.
// v0: set address preferences
await sdk.address({ feePreference: 2, requireMemo: 1 })
// v1: bind the 'transfer' class to a guard contract (ACTION_INDEX 500)
await sdk.address(sdk.controller.bindAddress({ controller: 500, actionClass: 'transfer', cooldownBlocks: 144 }))
// v1: unbind the 'transfer' class
await sdk.address(sdk.controller.unbindAddress({ actionClass: 'transfer' }))
See also: ../actions/ADDRESS.md
AIRDROP
Distribute tokens to all addresses on a list.
Format Versions: v0 (single tick, one list), v1 (single tick, one list, multiple destinations per entry), v2 (multiple ticks, separate lists), v3 (multiple ticks with per-entry memos)
Format v0: AIRDROP|VERSION|TICK|AMOUNT|LIST_ACTION_INDEX|MEMO
Format v1: AIRDROP|VERSION|LIST_ACTION_INDEX|TICK|AMOUNT|TICK|AMOUNT|MEMO
Format v2: AIRDROP|VERSION|TICK|AMOUNT|LIST_ACTION_INDEX|TICK|AMOUNT|LIST_ACTION_INDEX|MEMO
Format v3: AIRDROP|VERSION|TICK|AMOUNT|LIST_ACTION_INDEX|MEMO|TICK|AMOUNT|LIST_ACTION_INDEX|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to airdrop (name or ^ID reference) |
| amount | string | Yes | Amount to send per address on the list |
| listActionIndex | integer | Yes | ACTION_INDEX of the LIST action defining the recipient set |
| memo | string | No | Optional note |
await sdk.airdrop({ tick: 'MYTOKEN', amount: '10', listActionIndex: 42 })
See also: ../actions/AIRDROP.md
BATCH
Combine multiple action commands into a single transaction.
Format Versions: v0 (command string)
Format: BATCH|VERSION|COMMAND
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| command | string | Yes | Semicolon-delimited list of action strings |
Notes:
- BATCH cannot contain nested BATCH actions.
- BATCH cannot contain DEPLOY actions.
- At most one FILE action per BATCH (one rawData payload per transaction).
- At most one MINT action per BATCH.
- At most one ISSUE action per BATCH.
- See BATCH.md for the fluent builder interface (
sdk.batch()).
await sdk.batch({
command: 'SEND|0|MYTOKEN|50|bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh|;SEND|0|OTHER|25|bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh|'
})
See also: ../actions/BATCH.md
BROADCAST
Publish a data value or fee schedule on-chain, or settle a previously registered broadcast.
Format Versions: v0 (message + value), v1 (message + value + fee + memo), v2 (message + fee + memo), v3 (settle a prior broadcast)
Format v0: BROADCAST|VERSION|MESSAGE|VALUE
Format v1: BROADCAST|VERSION|MESSAGE|VALUE|FEE|MEMO
Format v2: BROADCAST|VERSION|MESSAGE|FEE|MEMO
Format v3: BROADCAST|VERSION|BROADCAST_ACTION_INDEX|VALUE|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| message | string | Conditional | Broadcast message / label (required unless broadcastActionIndex is set) |
| value | string/number | No | Numeric value associated with the broadcast |
| fee | string/number | No | Fee percentage (numeric) |
| memo | string | No | Optional note |
| broadcastActionIndex | integer | Conditional | ACTION_INDEX of a prior BROADCAST to settle (alternative to message) |
Notes:
- Must supply either
messageorbroadcastActionIndex. messagemust not contain|or;.
// Publish a feed
await sdk.broadcast({ message: 'PRICE_BTC_USD', value: 65000, fee: 0.01 })
// Settle a prior broadcast
await sdk.broadcast({ broadcastActionIndex: 120, value: 67000 })
See also: ../actions/BROADCAST.md
CALLBACK
Trigger a callback on an issued token, redeeming holder balances at the terms defined in the ISSUE.
Format Versions: v0 (single tick)
Format: CALLBACK|VERSION|TICK|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token name or ^ID reference to call back |
| memo | string | No | Optional note |
await sdk.callback({ tick: 'MYTOKEN', memo: 'Calling back all holders' })
See also: ../actions/CALLBACK.md
DEPLOY
Deploy a smart contract to the XChain VM. The contract source code is base64-encoded into the CODE_ENCODING field (base64’s alphabet has no |, so it is safe in the pipe-delimited action string, at 1.33× the source vs hex’s 2×). The SDK can accept raw source via the code param and will base64-encode it automatically.
Format Versions: v0 (standard contract), v1 (stakeable contract), v2 (chunked assemble, standard), v3 (chunked assemble, stakeable), v4 (chunk carrier)
Format v0: DEPLOY|VERSION|CODE_ENCODING|GAS_LIMIT|...CONSTRUCTOR_PARAMS
Format v1: DEPLOY|VERSION|CODE_ENCODING|GAS_LIMIT|CONSTRUCTOR_PARAMS|COOLDOWN_BLOCKS|SLASH_DESTINATION
Format v2: DEPLOY|VERSION|CODE_HASH|GAS_LIMIT|...CONSTRUCTOR_PARAMS (chunked assemble, standard)
Format v3: DEPLOY|VERSION|CODE_HASH|GAS_LIMIT|CONSTRUCTOR_PARAMS|COOLDOWN_BLOCKS|SLASH_DESTINATION (chunked assemble, stakeable)
Format v4: DEPLOY|VERSION|CODE_HASH|CHUNK_INDEX|TOTAL_CHUNKS|CODE_PART (chunk carrier)
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| code | string | Yes* | Raw JavaScript source code (auto base64-encoded by the SDK) |
| codeEncoding | string | Yes* | Pre-encoded base64 of contract source (alternative to code). For chunked deploys (v2/v3), use codeHash instead. |
| gasLimit | integer | Yes | Maximum gas units for deployment (positive integer) |
| constructorParams | string[] | No | Arguments passed to the contract constructor |
| cooldownBlocks | integer | v1/v3 only | Unstake cooldown in blocks (1-100000). Required for stakeable contracts. |
| slashDestination | string | No | Address to receive slashed stake. Required with cooldownBlocks. |
| codeHash | string | v2/v3/v4 | SHA-256 hex of the assembled contract source. Used for chunked deploys. |
| chunkIndex | integer | v4 only | Zero-based index of this chunk |
| totalChunks | integer | v4 only | Total number of chunk carriers (1-16) |
| codePart | string | v4 only | One base64 slice of the contract source (max 7800 bytes) |
* For inline deploys (v0/v1): provide either code (recommended) or codeEncoding, not both.
Notes:
- Contract source must be valid JavaScript and under 64KB.
- The SDK validates base64 encoding, code size, and gas limit before serialization.
- DEPLOY payloads typically exceed the 76-byte OP_RETURN limit, use P2SH or P2WSH encoding.
- DEPLOY actions cannot appear inside a BATCH.
- Constructor params are variable-length: each element becomes a separate pipe-delimited field in the action string.
- For contracts over ~6KB, use the chunked deploy pattern: send multiple v4 carrier actions first, then a v2/v3 assemble action referencing the
codeHash.
// Deploy a contract from raw source code
await sdk.deploy({ code: 'module.exports = { greet: function() { return "hello"; } }', gasLimit: 200000 })
// Deploy with constructor parameters
await sdk.deploy({
code: contractSource,
gasLimit: 500000,
constructorParams: ['MYTOKEN', '1000']
}, { pubkey: 'yourPubkey', encoding: 'P2WSH' })
// Pre-validate before deploying
let check = sdk.contracts.validate(contractSource);
if (!check.valid) console.log(check.error);
See also: ../actions/DEPLOY.md
DEPOSIT
Transfer tokens from the broadcaster’s address into a deployed contract’s custody.
Format Versions: v0
Format v0: DEPOSIT|VERSION|CONTRACT_ACTION_INDEX|TICK|QUANTITY
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| contractActionIndex | integer | Yes | ACTION_INDEX of the deployed contract |
| tick | string | Yes | Token ticker or ticker ID (^N) |
| quantity | string | Yes | Amount to deposit (positive number) |
await sdk.deposit({ contractActionIndex: 12345, tick: 'MYTOKEN', quantity: '1000' })
// Using a ContractClient
const amm = sdk.contract(12345);
await amm.deposit('MYTOKEN', '1000', { pubkey: 'yourPubkey' });
See also: ../actions/DEPOSIT.md
DESTROY
Permanently burn tokens, removing them from supply.
Format Versions: v0 (single tick), v1 (two ticks, same memo), v2 (two ticks, separate memos)
Format v0: DESTROY|VERSION|TICK|AMOUNT|MEMO
Format v1: DESTROY|VERSION|TICK|AMOUNT|TICK|AMOUNT|MEMO
Format v2: DESTROY|VERSION|TICK|AMOUNT|MEMO|TICK|AMOUNT|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to destroy (name or ^ID reference) |
| amount | string | Yes | Amount to burn |
| memo | string | No | Optional note |
await sdk.destroy({ tick: 'MYTOKEN', amount: '500', memo: 'Deflationary burn' })
See also: ../actions/DESTROY.md
DISPENSER
Create a vending machine that automatically exchanges one token for another.
Format Versions: v0 (create), v1 (cancel), v2 (edit)
Format v0 (create): DISPENSER|VERSION|GIVE_COIN|GIVE_TICK|GIVE_AMOUNT|GIVE_OWNERSHIP|GIVE_ESCROW|GET_COIN|GET_TICK|GET_AMOUNT|GET_ADDRESS|FIAT_CODE|FIAT_AMOUNT|ORACLE_ADDRESS|EXPIRATION|ALLOW_LIST|BLOCK_LIST|MEMO
Format v1 (cancel): DISPENSER|VERSION|DISPENSER_ACTION_INDEX|MEMO
Format v2 (edit): DISPENSER|VERSION|DISPENSER_ACTION_INDEX|GIVE_ESCROW|EXPIRATION|ALLOW_LIST|BLOCK_LIST|MEMO
Params (create (v0):)
| Param | Type | Required | Description |
|---|---|---|---|
| giveCoin | string | No | Coin being given (BTC, LTC, DOGE) |
| giveTick | string | Yes | Token being given |
| giveAmount | string | Yes | Amount given per fill (omit when giveOwnership is 1) |
| giveOwnership | integer | No | 1 = dispense the token’s issuer rights instead of a balance amount (single-shot); 0 = balance amount (default) |
| giveEscrow | string | No | Amount to escrow upfront (omit when giveOwnership is 1) |
| getCoin | string | No | Coin accepted in return (BTC, LTC, DOGE) |
| getTick | string | Yes | Token accepted in return |
| getAmount | string | Yes | Amount required per fill |
| getAddress | string | No | Address to receive the get-side funds |
| fiatCode | string | No | Fiat currency code for pricing (e.g. USD) |
| fiatAmount | string | No | Fiat price in X.XX format (used when the dispenser is fiat-priced) |
| oracleAddress | string | No | Address of a user price oracle (PRICE v1) that prices the dispensed token in fiatCode; when set, fiatAmount is ignored |
| expiration | integer | No | Block height at which the dispenser expires |
| allowList | integer | No | ACTION_INDEX of a LIST to restrict buyers |
| blockList | integer | No | ACTION_INDEX of a LIST to ban buyers |
| memo | string | No | Optional note |
Params (cancel (v1):)
| Param | Type | Required | Description |
|---|---|---|---|
| dispenserActionIndex | integer | Yes | ACTION_INDEX of the dispenser to cancel |
| memo | string | No | Optional note |
Params (edit (v2):)
| Param | Type | Required | Description |
|---|---|---|---|
| dispenserActionIndex | integer | Yes | ACTION_INDEX of the dispenser to edit |
| giveEscrow | string | No | Updated escrow amount |
| expiration | integer | No | Updated expiration block |
| allowList | integer | No | Updated allow-list ACTION_INDEX |
| blockList | integer | No | Updated block-list ACTION_INDEX |
| memo | string | No | Optional note |
// Create
await sdk.dispenser({
giveTick: 'MYTOKEN',
giveAmount: '100',
getTick: 'BTC',
getAmount: '0.001',
getAddress: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
fiatCode: 'USD',
fiatAmount: '65.00'
})
// Cancel
await sdk.dispenser({ dispenserActionIndex: 77 })
See also: ../actions/DISPENSER.md
DIVIDEND
Pay a dividend of one token proportionally to all holders of another token.
Format Versions: v0 (single tick)
Format: DIVIDEND|VERSION|TICK|DIVIDEND_TICK|AMOUNT|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token whose holders receive the dividend |
| dividendTick | string | Yes | Token being distributed as the dividend |
| amount | string | Yes | Total amount to distribute |
| memo | string | No | Optional note |
await sdk.dividend({ tick: 'MYTOKEN', dividendTick: 'REWARD', amount: '1000' })
See also: ../actions/DIVIDEND.md
EXECUTE
Call a method on a deployed XChain VM smart contract.
Format Versions: v0
Format v0: EXECUTE|VERSION|CONTRACT_ACTION_INDEX|METHOD|...PARAMS
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| contractActionIndex | integer | Yes | ACTION_INDEX of the deployed contract |
| method | string | Yes | Method name to invoke on the contract |
| params | string[] | No | Method arguments (each becomes a pipe-delimited segment) |
Notes:
paramsis a variable-length array, the SDK serializes each element as a separate pipe-delimited field after METHOD.- A successful
sdk.execute()means the transaction was constructed, not that the contract execution succeeded. Execution happens later when the indexer processes the confirmed transaction. Query results via the explorer. - Parameter values must not contain
|or;(field and command separators).
// Call a method with no arguments
await sdk.execute({ contractActionIndex: 12345, method: 'increment' })
// Call a method with arguments
await sdk.execute({ contractActionIndex: 12345, method: 'transfer', params: ['bc1q...', '100'] })
// Using a ContractClient
const amm = sdk.contract(12345);
await amm.call('swap', ['TOKENA', '100'], { pubkey: 'yourPubkey' });
// Check execution results
let exec = await sdk.getExecution(actionIndex);
if (!exec.success) console.log(exec.error);
See also: ../actions/EXECUTE.md
FILE
Attach a file to the chain. File data is supplied via the encoder (not in the action string itself).
Format Versions: v0 (public file), v0 with optional gating fields (token-gated file)
Format: FILE|VERSION|NAME|TYPE|TITLE|MEMO|GATE_TICKER|ENCRYPTION_METHOD|KEY_HASH
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | File name (e.g. image.png) |
| type | string | Yes | MIME type or file type identifier |
| title | string | No | Human-readable title |
| memo | string | No | Optional note |
| gateTicker | string | No | Token required to unlock the file (empty = public) |
| encryptionMethod | integer | No | Encryption algorithm for gated content: 1 = AES-256-GCM |
| keyHash | string | No | SHA-256 hex of the encryption key (sha256(K)), 64 lowercase hex chars |
Notes:
- Raw file data is passed via the
encoderargument, not in params. - A BATCH may contain at most one FILE action.
await sdk.file({ name: 'logo.png', type: 'image/png', title: 'Project Logo' }, { rawData: fileBuffer })
See also: ../actions/FILE.md
ISSUE
Create or update a token. Multiple update sub-formats allow targeted edits without re-specifying the full token definition.
Format Versions: v0 (full create), v1 (description update), v2 (mint params update), v3 (lock update), v4 (callback update), v5 (list update), v6 (controller bind/unbind)
Format v0 (create): ISSUE|VERSION|TICK|MAX_SUPPLY|MAX_MINT|DECIMALS|DESCRIPTION|MINT_SUPPLY|TRANSFER|TRANSFER_SUPPLY|LOCK_MAX_SUPPLY|LOCK_MAX_MINT|LOCK_DESCRIPTION|LOCK_SLEEP|LOCK_CALLBACK|CALLBACK_BLOCK|CALLBACK_TICK|CALLBACK_AMOUNT|ALLOW_LIST|BLOCK_LIST|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK|LOCK_MINT|LOCK_MINT_SUPPLY|MEMO
Format v1 (description): ISSUE|VERSION|TICK|DESCRIPTION|MEMO
Format v2 (mint params): ISSUE|VERSION|TICK|MAX_MINT|MINT_SUPPLY|TRANSFER_SUPPLY|MINT_ADDRESS_MAX|MINT_START_BLOCK|MINT_STOP_BLOCK|MEMO
Format v3 (locks): ISSUE|VERSION|TICK|LOCK_MAX_SUPPLY|LOCK_MAX_MINT|LOCK_DESCRIPTION|LOCK_SLEEP|LOCK_CALLBACK|LOCK_MINT|LOCK_MINT_SUPPLY|MEMO
Format v4 (callback): ISSUE|VERSION|TICK|CALLBACK_BLOCK|CALLBACK_TICK|CALLBACK_AMOUNT|MEMO
Format v5 (lists): ISSUE|VERSION|TICK|ALLOW_LIST|BLOCK_LIST|MEMO
Format v6 (controller bind/unbind): ISSUE|VERSION|TICK|CONTROLLER|ACTION_CLASS|COOLDOWN_BLOCKS|UNBIND|MEMO
Params (full create (v0):)
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token name (1–250 chars; see Validation Rules) |
| maxSupply | string | No | Maximum total supply (0 to 1 sextillion) |
| maxMint | string | No | Maximum per-mint amount |
| decimals | integer | No | Decimal places (0–18) |
| description | string | No | Token description (max 250 chars) |
| mintSupply | string | No | Supply made available for minting |
| transfer | string | No | Address authorized to transfer the issuance |
| transferSupply | string | No | Amount of supply available for transfer |
| lockMaxSupply | integer | No | Lock max supply from future changes (0 or 1) |
| lockMaxMint | integer | No | Lock max mint from future changes (0 or 1) |
| lockDescription | integer | No | Lock description from future changes (0 or 1) |
| lockSleep | integer | No | Lock sleep from future changes (0 or 1) |
| lockCallback | integer | No | Lock callback from future changes (0 or 1) |
| callbackBlock | integer | No | Block height at which the callback triggers |
| callbackTick | string | No | Token paid out on callback |
| callbackAmount | string | No | Amount paid per token on callback |
| allowList | integer | No | ACTION_INDEX of a LIST to restrict minters |
| blockList | integer | No | ACTION_INDEX of a LIST to ban minters |
| mintAddressMax | string | No | Maximum mints allowed per address |
| mintStartBlock | integer | No | Block height minting opens |
| mintStopBlock | integer | No | Block height minting closes |
| lockMint | integer | No | Lock minting permanently (0 or 1) |
| lockMintSupply | integer | No | Lock mint supply from future changes (0 or 1) |
| memo | string | No | Optional note |
Params (description update (v1):)
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to update |
| description | string | No | New description |
| memo | string | No | Optional note |
Params (mint params update (v2):)
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to update |
| maxMint | string | No | New max-mint value |
| mintSupply | string | No | New mint supply |
| transferSupply | string | No | New transfer supply |
| mintAddressMax | string | No | New per-address mint cap |
| mintStartBlock | integer | No | New mint open block |
| mintStopBlock | integer | No | New mint close block |
| memo | string | No | Optional note |
Params (lock update (v3):)
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to update |
| lockMaxSupply | integer | No | 0 or 1 |
| lockMaxMint | integer | No | 0 or 1 |
| lockDescription | integer | No | 0 or 1 |
| lockSleep | integer | No | 0 or 1 |
| lockCallback | integer | No | 0 or 1 |
| lockMint | integer | No | 0 or 1 |
| lockMintSupply | integer | No | 0 or 1 |
| memo | string | No | Optional note |
Params (callback update (v4):)
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to update |
| callbackBlock | integer | No | New callback block height |
| callbackTick | string | No | New callback payout token |
| callbackAmount | string | No | New callback payout amount |
| memo | string | No | Optional note |
Params (list update (v5):)
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to update |
| allowList | integer | No | New allow-list ACTION_INDEX |
| blockList | integer | No | New block-list ACTION_INDEX |
| memo | string | No | Optional note |
Params (controller bind/unbind (v6):)
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token whose action class is being bound or unbound |
| controller | integer | Conditional | ACTION_INDEX of the deployed guard contract. Required when unbind is 0. Ignored on unbind. |
| actionClass | string | Yes | The action class to gate or release: transfer, trade, burn, mint, or stake |
| cooldownBlocks | integer | No | Number of blocks that must pass after an unbind request before the binding is dropped (committed at bind; 0 = no cooldown) |
| unbind | integer | Yes | 0 = bind the action class to the controller, 1 = unbind it |
| memo | string | No | Optional note |
Notes (v6):
- Only the token issuer (the address that broadcast the original ISSUE v0) may submit ISSUE v6.
- The indexer enforces contract existence and cooldown; the SDK validates field format only.
- Use
sdk.controller.bindToken()andsdk.controller.unbindToken()to build params cleanly.
// Full create
await sdk.issue({
tick: 'MYTOKEN',
maxSupply: '1000000',
maxMint: '100',
decimals: 8,
description: 'My first XChain token',
mintStartBlock: 850000
})
// Update description only
await sdk.issue({ tick: 'MYTOKEN', description: 'Updated description' })
// Lock max supply
await sdk.issue({ tick: 'MYTOKEN', lockMaxSupply: 1 })
// Bind the 'transfer' class to a guard contract (ACTION_INDEX 500) with a 144-block cooldown
await sdk.issue(sdk.controller.bindToken({ tick: 'MYTOKEN', controller: 500, actionClass: 'transfer', cooldownBlocks: 144 }))
// Unbind the 'transfer' class
await sdk.issue(sdk.controller.unbindToken({ tick: 'MYTOKEN', actionClass: 'transfer' }))
See also: ../actions/ISSUE.md
LINK
Link two on-chain actions across chains, establishing an association between them.
Format Versions: v0 (two-action link)
Format: LINK|VERSION|COIN1|COIN1_ACTION_INDEX|COIN2|COIN2_ACTION_INDEX|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| coin1 | string | Yes | First coin (BTC, LTC, DOGE) |
| coin1ActionIndex | integer | Yes | ACTION_INDEX on coin1 |
| coin2 | string | Yes | Second coin (BTC, LTC, DOGE) |
| coin2ActionIndex | integer | Yes | ACTION_INDEX on coin2 |
| memo | string | No | Optional note |
await sdk.link({ coin1: 'BTC', coin1ActionIndex: 500, coin2: 'LTC', coin2ActionIndex: 200 })
See also: ../actions/LINK.md
LIST
Create or edit an allow/block list of ticks or addresses.
Format Versions: v0 (create), v1 (edit, add/remove items)
Format v0 (create): LIST|VERSION|TYPE|...ITEM
Format v1 (edit): LIST|VERSION|EDIT|LIST_ACTION_INDEX|...ITEM
Params (create (v0):)
| Param | Type | Required | Description |
|---|---|---|---|
| type | integer | Yes | List type: 1 = TICK list, 2 = ADDRESS list |
| item | string | Yes | Initial item to add |
Params (edit (v1):)
| Param | Type | Required | Description |
|---|---|---|---|
| edit | integer | Yes | Edit operation: 1 = ADD, 2 = REMOVE |
| listActionIndex | integer | Yes | ACTION_INDEX of the LIST to edit |
| item | string | Yes | Item to add or remove |
// Create an address list
await sdk.list({ type: 2, item: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' })
// Add to an existing list
await sdk.list({ edit: 1, listActionIndex: 55, item: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' })
See also: ../actions/LIST.md
MESSAGE
Send an encrypted or plaintext message to a destination address.
Format Versions: v0 (key exchange setup), v1 (key exchange: same as v0), v2 (encrypted message body), v3 (plaintext message)
Format v0/v1 (key exchange): MESSAGE|VERSION|COIN|DESTINATION|ENCRYPTION_METHOD|ENCRYPTION_KEY
Format v2 (encrypted body): MESSAGE|VERSION|COIN|DESTINATION|ENCRYPTED_MESSAGE
Format v3 (plaintext): MESSAGE|VERSION|COIN|DESTINATION|PLAINTEXT_MESSAGE
Params (key exchange (v0/v1):)
| Param | Type | Required | Description |
|---|---|---|---|
| coin | string | Yes | Destination coin network (BTC, LTC, DOGE) |
| destination | string | Yes | Recipient address |
| encryptionMethod | integer | Yes | 1 = ECIES, 2 = ECDH, 3 = AES |
| encryptionKey | string | Yes | Public key or shared key material (max 1 MB) |
Params (encrypted message (v2):)
| Param | Type | Required | Description |
|---|---|---|---|
| coin | string | Yes | Destination coin network (BTC, LTC, DOGE) |
| destination | string | Yes | Recipient address |
| encryptedMessage | string | Yes | Encrypted message payload (max 1 MB) |
Params (plaintext (v3):)
| Param | Type | Required | Description |
|---|---|---|---|
| coin | string | Yes | Destination coin network (BTC, LTC, DOGE) |
| destination | string | Yes | Recipient address |
| plaintextMessage | string | Yes | Plaintext message body (max 1 MB) |
// Plaintext
await sdk.message({ destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', plaintextMessage: 'Hello!' })
// Key exchange
await sdk.message({ destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', encryptionMethod: 1, encryptionKey: '<pubkey>' })
See also: ../actions/MESSAGE.md
MINT
Mint new tokens from an existing ISSUE’s mintable supply.
Format Versions: v0 (single mint)
Format: MINT|VERSION|TICK|AMOUNT|DESTINATION|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to mint (name or ^ID reference) |
| amount | string | Yes | Amount to mint |
| destination | string | No | Address to receive the minted tokens (defaults to sender) |
| memo | string | No | Optional note |
await sdk.mint({ tick: 'MYTOKEN', amount: '100', destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' })
See also: ../actions/MINT.md
ORDER
Create a peer-to-peer token exchange order.
Format Versions: v0 (create), v1 (cancel), v2 (edit)
Format v0 (create): ORDER|VERSION|GIVE_COIN|GIVE_TICK|GIVE_AMOUNT|GIVE_OWNERSHIP|GET_COIN|GET_TICK|GET_AMOUNT|GET_OWNERSHIP|GET_ADDRESS|EXPIRATION|ALLOW_LIST|BLOCK_LIST|MEMO
Format v1 (cancel): ORDER|VERSION|ORDER_ACTION_INDEX|MEMO
Format v2 (edit): ORDER|VERSION|ORDER_ACTION_INDEX|EXPIRATION|ALLOW_LIST|BLOCK_LIST|MEMO
Params (create (v0):)
| Param | Type | Required | Description |
|---|---|---|---|
| giveCoin | string | No | Coin being offered (BTC, LTC, DOGE) |
| giveTick | string | Yes | Token being offered |
| giveAmount | string | Yes | Amount being offered (omit when giveOwnership is 1) |
| giveOwnership | integer | No | 1 = escrow the token’s issuer rights instead of a balance amount; 0 = balance amount (default) |
| getCoin | string | No | Coin requested in return (BTC, LTC, DOGE) |
| getTick | string | Yes | Token requested in return |
| getAmount | string | Yes | Amount requested (omit when getOwnership is 1) |
| getOwnership | integer | No | 1 = require the matcher to transfer getTick ownership (issuer rights) instead of a balance amount; 0 = balance amount (default) |
| getAddress | string | No | Address to receive the get-side funds |
| expiration | integer | No | Block height at which the order expires |
| allowList | integer | No | ACTION_INDEX of a LIST to restrict takers |
| blockList | integer | No | ACTION_INDEX of a LIST to ban takers |
| memo | string | No | Optional note |
Params (cancel (v1):)
| Param | Type | Required | Description |
|---|---|---|---|
| orderActionIndex | integer | Yes | ACTION_INDEX of the order to cancel |
| memo | string | No | Optional note |
Params (edit (v2):)
| Param | Type | Required | Description |
|---|---|---|---|
| orderActionIndex | integer | Yes | ACTION_INDEX of the order to edit |
| expiration | integer | No | Updated expiration block |
| allowList | integer | No | Updated allow-list ACTION_INDEX |
| blockList | integer | No | Updated block-list ACTION_INDEX |
| memo | string | No | Optional note |
// Create
await sdk.order({
giveTick: 'MYTOKEN',
giveAmount: '500',
getTick: 'OTHER',
getAmount: '1000',
getAddress: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
expiration: 900000
})
// Cancel
await sdk.order({ orderActionIndex: 99 })
See also: ../actions/ORDER.md
PRICE
Publish a token/fiat price feed on-chain. PRICE v1 is the permissionless, user-run oracle: any address can submit a price quote for a token denominated in a fiat currency. Dispensers with an oracleAddress field use these quotes to price fills dynamically.
PRICE v0 is a validator-broadcast COIN/FIAT snapshot (not SDK-encodable). Only v1 is available via the SDK.
Format Versions: v1 (user-run TOKEN/FIAT oracle)
Format v1: PRICE|VERSION|COIN|TICK|FIAT|VALUE|FEE|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| coin | string | Yes | The blockchain the token lives on (BTC, LTC, DOGE) |
| tick | string | Yes | Token ticker to price |
| fiat | string | Yes | Fiat currency code (e.g. USD). Must be one of the supported FIAT_CODE values. |
| value | string/number | Yes | Token price in the given fiat currency (numeric) |
| fee | string/number | No | Oracle operator fee percentage charged on each dispenser fill that references this oracle |
| memo | string | No | Optional note |
Notes:
- No stake is required; any address may post a PRICE v1.
- A DISPENSER that references this oracle address via its
oracleAddressfield will use the most recent PRICE v1 broadcast from that address to compute fill prices. fiatmust be one of:USD,CAD,AUD,MXN,GBP,JPY,CNY,CHF,BRL,INR.
// Publish a price of 0.05 USD per MYTOKEN on Bitcoin, charging 1% oracle fee
await sdk.price({ coin: 'BTC', tick: 'MYTOKEN', fiat: 'USD', value: 0.05, fee: 1 })
// Price feed with no oracle fee
await sdk.price({ coin: 'BTC', tick: 'MYTOKEN', fiat: 'USD', value: 0.05 })
COINPAY
Fulfill a native coin payment obligation from an ORDER_MATCH.
Format v0: COINPAY|VERSION|ORDER_MATCH_ACTION_INDEX
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderMatchActionIndex | integer | Yes | ACTION_INDEX of the ORDER_MATCH being paid |
Encoder Options
COINPAY transactions require a customOutputs array in the encoder options to include the native coin payment output:
await sdk.coinpay({
orderMatchActionIndex: 12345
}, {
pubkey: '1BuyerAddress...',
change: '1BuyerChange...',
customOutputs: [{ address: '1SellerAddress...', value: 5000000 }] // 0.05 BTC in satoshis
})
The customOutputs array contains objects with address (the seller’s GET_ADDRESS) and value (the native coin amount in satoshis). The encoder adds these as additional outputs alongside the OP_RETURN data.
Note on validation: ORDER_MATCH_ACTION_INDEX is required and must be numeric. It is validated as a required field in the COINPAY action entry and is handled separately from the generic ACTION_INDEX fields group listed in the Validation Rules section below, which covers the cancel/edit index fields for other actions.
See also: ../actions/COINPAY.md
SEND
Transfer tokens to one or more destination addresses.
Format Versions: v0 (single send), v1 (one tick, multiple destinations), v2 (multiple ticks, one memo), v3 (multiple ticks, per-send memos)
Format v0: SEND|VERSION|TICK|AMOUNT|DESTINATION|MEMO
Format v1: SEND|VERSION|TICK|AMOUNT|DESTINATION|AMOUNT|DESTINATION|MEMO
Format v2: SEND|VERSION|TICK|AMOUNT|DESTINATION|TICK|AMOUNT|DESTINATION|MEMO
Format v3: SEND|VERSION|TICK|AMOUNT|DESTINATION|MEMO|TICK|AMOUNT|DESTINATION|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| tick | string | Yes | Token to send (name or ^ID reference) |
| amount | string | Yes | Amount to send |
| destination | string | Yes | Recipient address |
| memo | string | No | Optional note attached to the send |
await sdk.send({ tick: 'MYTOKEN', amount: '100', destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' })
// With memo
await sdk.send({ tick: 'MYTOKEN', amount: '100', destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', memo: 'Payment for invoice 42' })
See also: ../actions/SEND.md
SLEEP
Pause all activity on an address (or a specific token on an address) until a given block height.
Format Versions: v0 (address-wide sleep), v1 (tick-specific sleep)
Format v0: SLEEP|VERSION|RESUME_BLOCK|MEMO
Format v1: SLEEP|VERSION|RESUME_BLOCK|TICK|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| resumeBlock | integer | Yes | Block height at which sleep ends |
| tick | string | No | If set, sleep applies only to this token |
| memo | string | No | Optional note |
// Pause all activity until block 900000
await sdk.sleep({ resumeBlock: 900000 })
// Pause only MYTOKEN until block 900000
await sdk.sleep({ resumeBlock: 900000, tick: 'MYTOKEN' })
See also: ../actions/SLEEP.md
SWAP
Fulfill an open ORDER by providing the requested side of the exchange.
Format Versions: v0 (create swap / accept an order), v1 (cancel), v2 (edit)
Format v0: SWAP|VERSION|GIVE_COIN|GIVE_TICK|GIVE_AMOUNT|GIVE_OWNERSHIP|GET_COIN|GET_TICK|GET_AMOUNT|GET_OWNERSHIP|GET_ADDRESS|EXPIRATION|ALLOW_LIST|BLOCK_LIST|MEMO
Format v1: SWAP|VERSION|SWAP_ACTION_INDEX|MEMO
Format v2: SWAP|VERSION|SWAP_ACTION_INDEX|EXPIRATION|ALLOW_LIST|BLOCK_LIST|MEMO
Params (create (v0):)
| Param | Type | Required | Description |
|---|---|---|---|
| giveCoin | string | No | Coin being given (BTC, LTC, DOGE) |
| giveTick | string | Yes | Token being given |
| giveAmount | string | Yes | Amount being given (omit when giveOwnership is 1) |
| giveOwnership | integer | No | 1 = give the token’s issuer rights instead of a balance amount; 0 = balance amount (default) |
| getCoin | string | No | Coin requested in return (BTC, LTC, DOGE) |
| getTick | string | Yes | Token requested |
| getAmount | string | Yes | Amount requested (omit when getOwnership is 1) |
| getOwnership | integer | No | 1 = require the matched ORDER to transfer getTick ownership (issuer rights) instead of a balance amount; 0 = balance amount (default) |
| getAddress | string | No | Address to receive the get-side funds |
| expiration | integer | No | Block height at which the swap offer expires |
| allowList | integer | No | ACTION_INDEX of a LIST to restrict counterparties |
| blockList | integer | No | ACTION_INDEX of a LIST to ban counterparties |
| memo | string | No | Optional note |
Params (cancel (v1):)
| Param | Type | Required | Description |
|---|---|---|---|
| swapActionIndex | integer | Yes | ACTION_INDEX of the swap to cancel |
| memo | string | No | Optional note |
Params (edit (v2):)
| Param | Type | Required | Description |
|---|---|---|---|
| swapActionIndex | integer | Yes | ACTION_INDEX of the swap to edit |
| expiration | integer | No | Updated expiration block |
| allowList | integer | No | Updated allow-list ACTION_INDEX |
| blockList | integer | No | Updated block-list ACTION_INDEX |
| memo | string | No | Optional note |
// Accept an order
await sdk.swap({
giveTick: 'OTHER',
giveAmount: '1000',
getTick: 'MYTOKEN',
getAmount: '500',
getAddress: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'
})
// Cancel
await sdk.swap({ swapActionIndex: 103 })
See also: ../actions/SWAP.md
SWEEP
Transfer all balances, ownerships, and/or escrows from the current address to a destination address.
Format Versions: v0 (full sweep configuration)
Format: SWEEP|VERSION|DESTINATION|BALANCES|OWNERSHIPS|ORDERS|SWAPS|DISPENSERS|MEMO
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| destination | string | Yes | Address to receive swept assets |
| balances | integer | No | 1 = sweep token balances (default), 0 = skip |
| ownerships | integer | No | 1 = sweep token ownerships/issuer rights (default), 0 = skip |
| orders | integer | No | 1 = cancel open ORDERs and credit escrowed amounts to destination, 0 = skip (default) |
| swaps | integer | No | 1 = cancel open SWAPs and credit escrowed amounts to destination, 0 = skip (default) |
| dispensers | integer | No | 1 = close open DISPENSERs and credit escrowed amounts to destination, 0 = skip (default) |
| memo | string | No | Optional note |
// Sweep everything
await sdk.sweep({ destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh' })
// Sweep balances and ownerships but not open escrows
await sdk.sweep({ destination: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', balances: 1, ownerships: 1, orders: 0, swaps: 0, dispensers: 0 })
See also: ../actions/SWEEP.md
WITHDRAW
Withdraw tokens from a deployed contract’s custody back to the contract owner.
Format Versions: v0
Format v0: WITHDRAW|VERSION|CONTRACT_ACTION_INDEX|TICK|QUANTITY
Params:
| Param | Type | Required | Description |
|---|---|---|---|
| contractActionIndex | integer | Yes | ACTION_INDEX of the deployed contract |
| tick | string | Yes | Token ticker or ticker ID (^N) |
| quantity | string | Yes | Amount to withdraw (positive number) |
Notes:
- Only the contract owner (the address that broadcast the DEPLOY) can withdraw.
- Withdrawals work even if the contract is disabled.
await sdk.withdraw({ contractActionIndex: 12345, tick: 'MYTOKEN', quantity: '500' })
// Using a ContractClient
const amm = sdk.contract(12345);
await amm.withdraw('MYTOKEN', '500', { pubkey: 'yourPubkey' });
See also: ../actions/WITHDRAW.md
VOTE
Token-weighted governance: create a poll, cast a ballot, or set a standing delegation. sdk.vote(params) is the raw wrapper; the version is taken from params.version (0 create, 1 ballot, 3 delegate). Build the params with the sdk.voting.* helpers or hand-roll them. Version 2 (finalize) is system-synthesized at the poll’s end block and never user-broadcast.
Format Versions: v0 (create poll), v1 (cast ballot), v3 (set/clear delegation)
Format v0: VOTE|0|TICK|END_BLOCK|OPTIONS|MAX_SELECTIONS|TALLY_MODE|WEIGHT_MODE|QUORUM|MIN_VOTERS|MIN_VOTE_BALANCE|DECIDE_THRESHOLD|QUESTION|DEPOSIT|CALLBACK_CONTRACT|CALLBACK_METHOD|CALLBACK_PARAMS|CALLBACK_ON|GAS_ESCROW
Format v1: VOTE|1|POLL_REF|BALLOT|MEMO
Format v3: VOTE|3|TICK|DELEGATE_TO|MEMO
Param builders (sdk.voting.*):
| Builder | Produces | Key params |
|---|---|---|
createPollParams({...}) |
v0 | tick, endBlock, options (array or comma string, min 2), maxSelections (default 1), tallyMode (approval/split, default approval), weightMode (balance/flat/quadratic/time_weighted, default balance), optional quorum, minVoters, minVoteBalance, decideThreshold, question, deposit, and the binding-poll callback fields (callbackContract, callbackMethod, callbackParams, callbackOn, gasEscrow) |
castBallotParams({...}) |
v1 | pollRef (the poll’s action_index), ballot, optional memo. ballot accepts a single option (0), an approval array ([0, 2]), a split as entry objects ([{option: 0, share: 60}, ...]) or an option-to-share map ({0: 60, 2: 40}) |
delegateParams({...}) |
v3 | tick, delegateTo, optional memo |
clearDelegationParams({...}) |
v3 | tick, optional memo (clears the standing delegation via a blank DELEGATE_TO) |
sdk.voting also exposes WEIGHT_MODES, TALLY_MODES, and CALLBACK_ON so UIs can populate dropdowns without hardcoding.
Notes:
quadraticweighting requiresminVoteBalance > 0(the builder enforces this): without a per-voter floor a holder could split across addresses to inflate quadratic weight.- Setting
callbackContractmakes the poll binding (callbackMethodthen required): finalization fires a synthesized EXECUTE with the poll result. - A later valid ballot from the same voter replaces the earlier one (last-write-wins); storage is append-only, so a reorg that removes the replacement restores the prior ballot.
stateDiagram-v2
[*] --> Created: v0, create poll
Created --> BallotsAccumulating: v1, ballot cast
BallotsAccumulating --> BallotsAccumulating: v1, another ballot cast
BallotsAccumulating --> Finalized: v2, system-synthesized at end block
Finalized --> [*]
// Create an advisory poll
await sdk.vote(sdk.voting.createPollParams({
tick: 'GOVTOKEN', endBlock: 850000, options: ['YES', 'NO'], question: 'Adopt proposal 7?'
}))
// Cast a split ballot
await sdk.vote(sdk.voting.castBallotParams({ pollRef: 307, ballot: { 0: 60, 2: 40 } }))
For signed and broadcast round trips use the workflow recipes sdk.createPoll / sdk.castBallot / sdk.delegateVote / sdk.clearVoteDelegation (see WORKFLOWS.md), or session.vote(params) on a wallet session.
See also: ../actions/VOTE.md
BET
Parimutuel betting markets: create a market, place a bet, resolve it, or cancel it. sdk.bet(params) is the raw wrapper; the version is taken from params.version (0 create, 1 cancel, 2 place, 3 resolve). Build the params with the sdk.betting.* helpers, which pin the version explicitly: a resolve and a place-bet differ only by the presence of AMOUNT, so auto-selection is too sharp an edge to rely on here.
Format Versions: v0 (create market), v1 (cancel market), v2 (place bet), v3 (resolve market)
Format v0: BET|0|LABEL|OUTCOMES|TICK|FEE|DEADLINE|REFUND_WINDOW|MIN_AMOUNT|ALLOW_LIST|BLOCK_LIST|DETAILS|MEMO
Format v1: BET|1|FEED_ACTION_INDEX|MEMO
Format v2: BET|2|FEED_ACTION_INDEX|OUTCOME|AMOUNT|MEMO
Format v3: BET|3|FEED_ACTION_INDEX|OUTCOME|MEMO
Param builders (sdk.betting.*):
| Builder | Produces | Key params |
|---|---|---|
createMarketParams({...}) |
v0 | label, outcomes (array or comma string, 2-16 entries), tick (required; betting is token-only), optional fee (percent of the pot, 1.00 = 1%, max 10), deadline (Unix time, required), refundWindow (seconds, 3600-31536000, default 1209600), minAmount, allowList, blockList (LIST action indexes, must differ), details (object or base64 string), memo, now (injectable clock for deadline pre-flight) |
placeBetParams({...}) |
v2 | feedActionIndex, outcome (zero-based index, or a label when outcomes is passed), amount, optional outcomes (enables label lookup and range checking), memo |
resolveMarketParams({...}) |
v3 | feedActionIndex, outcome, optional outcomes, memo |
cancelMarketParams({...}) |
v1 | feedActionIndex, optional memo |
Market definition helpers:
| Helper | Purpose |
|---|---|
buildBetDetails(definition, { outcomes }) |
Validates a market definition against DETAILS_SCHEMA, cross-checks its outcomes against the market’s OUTCOMES (filling them in when absent), and returns strict base64 |
parseBetDetails(details) |
Decodes a DETAILS field from the chain under the consensus shape rules (strict canonical base64, size cap, JSON object, depth cap). Use this on any market you did not author |
projectPayout({ pools, outcome, stake, feePct, decimals }) |
Display-only projected payout, computed in mathjs bignumber in settlement’s exact order and floor direction. Returns fixed-decimal strings |
projectFeedCreateFee({ deadline, refundWindow, blockTime }) |
What the protocol will charge in XCHAIN to open the market, so a wallet can quote it before signing. Also accepts durationSeconds directly, and per-chain freeDays / perDay / gasPrice overrides. Returns { durationSeconds, days, billableDays, free, fee } |
outcomeCaseCollisions(outcomes) |
Advisory: outcome labels differing only by case. Legal on-chain, almost always a mistake |
DETAILS_SCHEMA / LIMITS |
The market-definition schema and the protocol limits, as data, for generating forms |
Notes:
FEEis a percent of the pot, not a fraction:1.00is one percent and0.01is one hundredth of a percent.- Two unrelated quantities are both called a fee.
FEEis the oracle’s cut of the pot, paid by bettors and set per market.projectFeedCreateFeereturns the protocol’s charge for running the market, paid inXCHAINby whoever creates it. Do not label them both “fee” in a UI. - Creation is duration-priced on the market’s full life (
DEADLINE + REFUND_WINDOW, notDEADLINE), with the first 90 days free. The day count rounds half-up, matchingbcdiv(seconds, 86400, 0)on-chain, so a projection that floors under-quotes by a whole day’s fee at every fractional-day boundary. OUTCOMEis a zero-based index intoOUTCOMES, never a label, on the wire. Passoutcomesto a builder to use labels safely; a numeric value is always read as an index, because labels that look like numbers are legal.- Compose
DETAILSthroughcreateMarketParamsrather than encoding it yourself: a market whoseDETAILS.outcomesdisagrees with itsOUTCOMESis rejected on-chain. DETAILSis capped at 4096 decoded bytes. It rides the wire base64-encoded and shares one 8192-byte ACTION ceiling with every other field, so a create at the cap encodes as a multi-chunkP2SH/P2WSHpayload (the SDK selects this automatically).- Markets are immutable from creation; there is no edit format. The pre-bet fix path is cancel and recreate.
- The market’s creator may not bet on its own market, and bets are final once placed.
// Open a market, composing OUTCOMES and DETAILS together
await sdk.bet(sdk.betting.createMarketParams({
label: 'Superbowl LX winner',
outcomes: ['Chiefs', '49ers'],
tick: 'PEPECASH',
fee: '1.00',
deadline: 1770000000,
details: { title: 'Who wins Superbowl LX?', category: 'sports' }
}))
// Bet 25 on an outcome by label, then resolve it
await sdk.bet(sdk.betting.placeBetParams({
feedActionIndex: 1234, outcome: 'Chiefs', outcomes: ['Chiefs', '49ers'], amount: '25.00000000'
}))
await sdk.bet(sdk.betting.resolveMarketParams({ feedActionIndex: 1234, outcome: 0 }))
For signed and broadcast round trips use the workflow recipes sdk.workflows.openMarket / placeBet / resolveMarket / cancelMarket (see WORKFLOWS.md), or session.bet(params) on a wallet session. Read markets back with sdk.explorer.getBetFeeds / getBetFeed / getBets / getOracleStats, and follow one live with sdk.ws.subscribeBetFeed(index).
See also: ../actions/BET.md
Validation Rules
The SDK enforces these rules before serializing any action. Violations throw an SDKValidationError.
TICK names (for ISSUE)
- Length: 1–250 characters.
- Allowed characters:
a-z A-Z 0-9 ~ ! @ # $ % ^ & * ( ) _ + - = { } [ ] \ : < > . ? - Cannot start with
^(that prefix is reserved for ACTION_INDEX references). - Cannot contain
|(field separator) or;(command separator). - Cannot contain
/(directory separator). .is the parent/child separator for sub-tokens (e.g.PARENT.CHILD). It is allowed, but no segment may be empty: no leading, trailing, or consecutive dots.
TICK references (everywhere except ISSUE)
When referencing an existing token by its indexer ID rather than its name, prefix with ^:
^42 // reference ACTION_INDEX 42
The numeric part after ^ must be a valid integer.
MEMO and DESCRIPTION text fields
- Cannot contain
|(pipe) or;(semicolon). DESCRIPTIONis additionally limited to 250 characters.
MESSAGE fields (PLAINTEXT_MESSAGE, ENCRYPTED_MESSAGE, ENCRYPTION_KEY)
- Maximum length: 1,048,576 characters (1 MB).
- Cannot contain
|or;.
DECIMALS
- Must be an integer in the range 0–18.
MAX_SUPPLY
- Must be a non-negative integer.
- Maximum value: 1,000,000,000,000,000,000,000 (1 sextillion).
Lock fields
LOCK_MAX_SUPPLY, LOCK_MAX_MINT, LOCK_DESCRIPTION, LOCK_SLEEP, LOCK_CALLBACK, LOCK_MINT, LOCK_MINT_SUPPLY
- Must be
0or1.
BALANCES / OWNERSHIPS / ORDERS / SWAPS / DISPENSERS (SWEEP)
- Must be
0or1.
FIAT_CODE
Must be one of: USD, CAD, AUD, MXN, GBP, JPY, CNY, CHF, BRL, INR
FIAT_AMOUNT
- Must match the pattern
X.XX; a numeric value with exactly two decimal places (e.g.65.00,1234.99).
Coin fields (GIVE_COIN, GET_COIN, COIN1, COIN2)
Must be one of: BTC, LTC, DOGE
Address fields (DESTINATION, GET_ADDRESS, TRANSFER)
Must be a valid cryptocurrency address (validated by the SDK utility layer).
ENCRYPTION_METHOD (MESSAGE)
Must be 1 (ECIES), 2 (ECDH), or 3 (AES).
FEE_PREFERENCE (ADDRESS)
Must be 1 (destroy), 2 (protocol), or 3 (community).
LIST TYPE
Must be 1 (TICK list) or 2 (ADDRESS list).
LIST EDIT
Must be 1 (ADD) or 2 (REMOVE).
ACTION_INDEX fields
All *_ACTION_INDEX fields (BROADCAST_ACTION_INDEX, DISPENSER_ACTION_INDEX, ORDER_ACTION_INDEX, SWAP_ACTION_INDEX, LIST_ACTION_INDEX, COIN1_ACTION_INDEX, COIN2_ACTION_INDEX, CONTRACT_ACTION_INDEX) must be numeric.
ORDER_MATCH_ACTION_INDEX (COINPAY) is not in this group. It is required and validated separately: COINPAY has no cancel/edit sub-operation, so ORDER_MATCH_ACTION_INDEX is always required rather than being an optional action-index reference.
VM action fields
CODE_ENCODINGmust be a valid base64 string; decoded size must not exceed 64KB.GAS_LIMITmust be a positive integer.QUANTITY(DEPOSIT/WITHDRAW) must be a positive number.METHODmust be a non-empty string that does not contain|or;.PARAMS/CONSTRUCTOR_PARAMSare variable-length arrays. Each element must not contain|or;.
STAKE
Stake tokens. Two flavors with different chain reach:
- v1 / v2, capability staking. BTC-only. XCHAIN-only. Per-pubkey aggregate active stake auto-qualifies the pubkey for each of five independent capabilities (
price,cross_chain,oracle_publish,attestation,full_node) per governance-configurablemin_stake[capability]. - v3: contract-targeted staking.** Works on any chain (BTC, LTC, DOGE). Any token. Targets a specific stakeable contract deployed via DEPLOY v1.
See protocol/actions/STAKE.md.
// New capability stake against a fresh pubkey
await sdk.stake({ version: 1, amount: '1000', signingPubkey: 'aabb...' });
// Top up the existing capability stake on the same pubkey
await sdk.stake({ version: 2, amount: '500', signingPubkey: 'aabb...' });
// Contract-targeted stake (any token); convenience wrapper forces version: 3
await sdk.session(wif).stakeToContract({
amount: '250',
signingPubkey: 'aabb...',
targetContractIndex: 500,
tick: 'MYTOKEN'
});
| Field | Required | Description |
|---|---|---|
version |
Yes | 1 for a new capability stake, 2 for a capability top-up, 3 for contract-targeted. v1/v2 share a wire format; callers must pass version explicitly because the auto-selector can’t disambiguate. |
amount |
Yes | Token amount to add to this stake row (decimal string, ≤ 8 fractional digits, > 0). XCHAIN for v1/v2; any token for v3. |
signingPubkey |
Yes | Ed25519 public key (64 hex characters) |
targetContractIndex |
v3 only | action_index of the stakeable contract |
tick |
v3 only | Ticker of the token being staked |
Formats
| Version | Format |
|---|---|
| 1 | VERSION|AMOUNT|SIGNING_PUBKEY (new capability stake) |
| 2 | VERSION|AMOUNT|SIGNING_PUBKEY (capability top-up) |
| 3 | VERSION|AMOUNT|SIGNING_PUBKEY|TARGET_CONTRACT_INDEX|TICK (contract-targeted) |
Cross-field validation (enforced by the indexer)
version=1:signingPubkeymust NOT already have an active capability stake.version=2:signingPubkeyMUST have an existing active capability stake whose original source is the broadcasting address.version=3: target contract must be stakeable (deployed via DEPLOY v1 withCOOLDOWN_BLOCKS+SLASH_DESTINATION); new-vs-topup is auto-detected by(target, pubkey, tick, source).
UNSTAKE
Begin unstaking cooldown for a previously staked pubkey. Two flavors with different chain reach:
- v0: capability unstake.** BTC-only. Returns the full aggregate capability stake for the pubkey (v1 original + any v2 top-ups).
- v1: contract-targeted unstake.** Works on any chain (BTC, LTC, DOGE). Releases the single
(targetContractIndex, signingPubkey, tick)stake row.
// Capability unstake
await sdk.unstake({ signingPubkey: 'aabb...' });
// Contract-targeted unstake (convenience wrapper forces version: 1)
await sdk.session(wif).unstakeFromContract({
signingPubkey: 'aabb...',
targetContractIndex: 500,
tick: 'MYTOKEN'
});
| Field | Required | Description |
|---|---|---|
version |
v1 only | 1 for contract-targeted; omit (defaults to 0) for capability unstake |
signingPubkey |
Yes | Ed25519 pubkey of the stake to release |
targetContractIndex |
v1 only | action_index of the stakeable contract |
tick |
v1 only | Ticker of the stake row to release |
Formats
| Version | Format |
|---|---|
| 0 | VERSION|SIGNING_PUBKEY (capability) |
| 1 | VERSION|SIGNING_PUBKEY|TARGET_CONTRACT_INDEX|TICK (contract-targeted) |
DELEGATE
Rotate or revoke the signing key for a staked validator. Four versions with different chain reach:
- v0: capability delegation.** BTC-only. Rotates the signing key for the broadcaster’s capability stake.
- v1: contract-targeted delegation.** Works on any chain (BTC, LTC, DOGE). Rotates the signing key for a single
(targetContractIndex, tick)stake row. - v2: capability revoke.** BTC-only.
- v3: contract revoke.** Works on any chain.
// Capability delegation
await sdk.delegate({ newSigningPubkey: 'ccdd...' });
// Contract-targeted delegation (convenience wrapper forces version: 1)
await sdk.session(wif).delegateForContract({
newSigningPubkey: 'ccdd...',
targetContractIndex: 500,
tick: 'MYTOKEN'
});
DELEGATE also covers revoke (removing a previously delegated key without replacing it) via v2/v3:
// Capability revoke
await sdk.delegate({ version: 2, signingPubkey: 'aabb...' });
// Contract-targeted revoke
await sdk.delegate({ version: 3, signingPubkey: 'aabb...', targetContractIndex: 500, tick: 'MYTOKEN' });
| Field | Required | Description |
|---|---|---|
version |
Optional | 0 capability rotate (default), 1 contract rotate, 2 capability revoke, 3 contract revoke |
newSigningPubkey |
v0/v1 | New Ed25519 public key (64 hex characters) |
signingPubkey |
v2/v3 | Existing Ed25519 public key to revoke (64 hex characters) |
targetContractIndex |
v1/v3 | action_index of the stakeable contract |
tick |
v1/v3 | Ticker of the stake row whose key is being managed |
Formats
| Version | Format |
|---|---|
| 0 | VERSION|NEW_SIGNING_PUBKEY (capability rotate) |
| 1 | VERSION|NEW_SIGNING_PUBKEY|TARGET_CONTRACT_INDEX|TICK (contract rotate) |
| 2 | VERSION|SIGNING_PUBKEY (capability revoke) |
| 3 | VERSION|SIGNING_PUBKEY|TARGET_CONTRACT_INDEX|TICK (contract revoke) |
COLLECT
Collect all accrued validator rewards (BTC chain only). No additional parameters required.
await sdk.collect({});
Formats
| Version | Format |
|---|---|
| 0 | VERSION |
BATCH constraints
- BATCH cannot contain nested BATCH actions.
- BATCH cannot contain DEPLOY actions.
- At most one FILE per BATCH (one rawData payload per transaction).
- At most one MINT per BATCH.
- At most one ISSUE per BATCH.
Encoding size limits
When an encoder with an explicit encoding is passed, the SDK validates that the serialized action string fits within the encoding’s byte budget:
| Encoding | Max data bytes |
|---|---|
OP_RETURN |
76 bytes (80 − 4-byte magic XCHN) |
MULTISIGN |
60 bytes per chunk |
P2SH |
476 bytes (520 − 44-byte script overhead) |
P2WSH |
476 bytes per witness-script chunk (520 - 44-byte overhead), chunked across outputs up to the 8,192-byte compiled ceiling |
If the action string exceeds the limit, an ENCODING_DATA_TOO_LARGE error is thrown with a suggested alternative encoding.
The per-chunk figures above are the single-script-element bound. The end-to-end cap on total compiled ACTION data is 8,192 bytes (MAX_COMPILED_ACTION_DATA_LENGTH in the encoder’s validator.js), which the chunked P2SH/P2WSH encodings reach by spreading data across multiple outputs.
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.