XChain Platform SDK — Encoder Reference
Overview
The SDK’s encoder client wraps the xchain-encoder JSON-RPC service. Its primary job is to take an ACTION string (produced by createAction or an action helper such as sdk.send()) and encode it into a Bitcoin/Litecoin/Dogecoin transaction, returning a PSBT hex string ready for signing and broadcasting.
There are two ways to use the encoder:
- Via action helper methods — pass an
encoderobject alongsideparamswhen callingsdk.send(),sdk.issue(), etc. The SDK builds the ACTION string and calls the encoder in one step. - Via
sdk.encodeTx(params)directly — supply your owndatastring and encoder options for full control.
const sdk = new XChainSDK({
network: 'bitcoin-mainnet',
encoderUrl: 'localhost',
encoderPort: 3000
});
Encoder Options
These options are passed inside the encoder object when using action helpers, or as top-level fields when calling sdk.encodeTx() directly.
| Option | Type | Required | Description |
|---|---|---|---|
pubkey |
string | Yes | Sender’s public key (hex) or address. Used to select UTXOs and build the change output. |
change |
string | No | Change address. Defaults to pubkey on the encoder side if omitted. |
utxos |
array | No | Explicit list of UTXO objects to spend. Pass null or omit to let the encoder auto-fetch from the UTXO tracker. |
encoding |
string | No | Force a specific encoding type: OP_RETURN, P2SH, P2WSH, or MULTISIGN. Auto-selected by the encoder if omitted. |
fee |
number | No | Fixed fee in satoshis. Takes precedence over feePerKb. |
feePerKb |
number | No | Fee rate in satoshis per kilobyte for automatic fee calculation. |
rbf |
boolean | No | Enable Replace-by-Fee signalling on all inputs. |
dust |
number | No | Dust threshold override in satoshis. |
unconfirmed |
boolean | No | Include unconfirmed UTXOs in selection. Defaults to true on the encoder side. |
compressedPubKey |
string | No | A compressed public key (33-byte hex). Required when encoding is MULTISIGN. |
customOutputs |
array | No | Additional transaction outputs to include beyond the data output and change. |
rawData |
string | No | Raw binary data appended after the ACTION string. Used by the FILE action for large payloads. |
Encoding Types
The encoder supports four encoding strategies. Each trades off cost, data capacity, and transaction structure.
| Type | Max data per chunk | Notes |
|---|---|---|
OP_RETURN |
80 bytes total (76 bytes user data) | Cheapest single-transaction encoding. Each output is 80 bytes: 4-byte XCHN magic prefix + 76 bytes for ACTION data. Auto-selected for small payloads. |
P2SH |
476 bytes | Two-phase transaction. Data is committed in a P2SH script (520 bytes minus 44 bytes of script overhead). Suitable for medium-sized payloads. |
P2WSH |
9,956 bytes | SegWit two-phase transaction. Supports very large payloads (10,000 bytes minus 44 bytes overhead). Best for FILE actions or large BATCH sequences. |
MULTISIGN |
~61 bytes | Data spread across fake public keys in a multisig output. Requires compressedPubKey. Rarely used directly. |
Auto-Selection
When encoding is omitted the encoder chooses based on the byte length of the ACTION string:
- ACTION data fits in 76 bytes (user-data limit; 80 bytes total per output) →
OP_RETURN - ACTION data is larger →
P2SH(orP2WSHfor very large payloads)
Relying on auto-selection is recommended for most use cases. Explicitly setting encoding is useful when you need deterministic output size or are working with the FILE action.
Pre-flight Validation
The SDK validates encoding constraints before sending the request to the encoder. This catches obvious misconfigurations immediately, without a network round-trip.
Validation only runs when encoding is explicitly set in the encoder options.
OP_RETURN size check
If encoding is OP_RETURN and the ACTION string exceeds 76 bytes (the user-data limit; 80 bytes total per output including the 4-byte XCHN prefix), the SDK throws immediately:
- Error code:
ENCODING_DATA_TOO_LARGE - Message: includes the actual byte count, the 76-byte user-data limit, and a suggested alternative (
P2SHorP2WSH) err.details:{ encoding, dataBytes, maxBytes, suggestion }
// This throws before any network call if the SEND action string is > 76 bytes (user-data limit)
sdk.send({ params: { ... }, encoder: { pubkey: '...', encoding: 'OP_RETURN' } });
// SDKValidationError: ENCODING_DATA_TOO_LARGE
// ACTION string is 89 bytes but OP_RETURN supports max 76 bytes of data
// (80 - 4 byte magic word). Use P2SH or omit encoding for auto-selection.
MULTISIGN compressed key check
If encoding is MULTISIGN and compressedPubKey is absent, the SDK throws immediately:
- Error code:
MISSING_COMPRESSED_PUBKEY - Message:
'MULTISIGN encoding requires compressedPubKey in encoder options.'
P2SH / P2WSH
No pre-flight size check is performed. The encoder handles chunking internally for both types.
Usage with Action Helper Methods
Pass encoder options alongside action params. The SDK constructs the ACTION string, validates encoding, then calls the encoder.
let result = await sdk.send({
params: {
tick: 'MYTOKEN',
amount: 100,
destination: 'bc1qrecipient...'
},
encoder: {
pubkey: 'bc1qsender...',
encoding: 'OP_RETURN', // optional — auto-selected if omitted
feePerKb: 1000
}
});
console.log(result.psbt); // hex-encoded PSBT, ready to sign
console.log(result.encoding); // 'OP_RETURN'
Other action helpers follow the same pattern: sdk.issue(), sdk.airdrop(), sdk.destroy(), sdk.dispenser(), etc.
Direct Encoder Access
Call sdk.encodeTx(params) to encode arbitrary data without going through an action helper. This is useful for advanced scenarios or when you have already built the ACTION string yourself.
let result = await sdk.encodeTx({
data: 'SEND|0|MYTOKEN|100|bc1qrecipient...',
pubkey: 'bc1qsender...',
encoding: 'P2SH',
feePerKb: 2000
});
encodeTx requires at minimum data and pubkey. All other encoder options are optional.
P2SH / P2WSH Two-Phase Transactions
P2SH and P2WSH encoding use a two-transaction pattern:
-
Phase 1 — Fund:
createTxbuilds a transaction that sends coin to a P2SH/P2WSH output that encodes the ACTION data in its redeem script. Broadcast this transaction and wait for it to be included in a block (or at minimum propagated to the mempool). -
Phase 2 — Spend:
spendP2shbuilds a second transaction that spends the P2SH/P2WSH output, revealing the redeem script (and therefore the ACTION data) to the network. The decoder reads the data from this spend transaction.
// Phase 1: create the funding transaction
let phase1 = await sdk.encodeTx({
data: actionString,
pubkey: 'bc1qsender...',
encoding: 'P2SH'
});
// sign phase1.psbt and broadcast it, then obtain the resulting txid and hex
// Phase 2: spend the P2SH output to reveal the data
let phase2 = await sdk.encoder.spendP2sh({
pubkey: 'bc1qsender...',
p2shHash: '<txid of the phase 1 transaction>',
p2shHex: '<full hex of the phase 1 transaction>',
feePerKb: 1000
});
// sign phase2.psbt and broadcast it
spendP2sh is exposed on the encoder client directly (sdk.encoder.spendP2sh(params)).
spendP2sh parameters
| Parameter | Required | Description |
|---|---|---|
pubkey |
Yes | Sender’s public key or address |
p2shHash |
Yes | txid of the phase 1 (funding) transaction |
p2shHex |
Yes | Full transaction hex of the phase 1 transaction |
change |
No | Change address |
fee |
No | Fixed fee in satoshis |
feePerKb |
No | Fee rate in sat/KB |
rbf |
No | Enable Replace-by-Fee |
dust |
No | Dust threshold override |
unconfirmed |
No | Include unconfirmed UTXOs |
Return Value
Both encodeTx and spendP2sh return the same structure:
{
psbt: '<hex string>', // Partially Signed Bitcoin Transaction, ready to sign
encoding: '<string>' // Encoding type actually used: 'OP_RETURN', 'P2SH', 'P2WSH', or 'MULTISIGN'
}
The encoding field reflects what the encoder actually chose, which may differ from what was requested if the encoder applied auto-selection.
Error Handling
Encoder methods throw SDKEncoderError on failure. Pre-flight validation failures throw SDKValidationError.
SDKEncoderError codes
| Code | Cause |
|---|---|
ENCODER_RPC_ERROR |
The encoder returned a JSON-RPC error response (e.g. insufficient funds, invalid UTXO, unsupported encoding for the data size). |
ENCODER_HTTP_<N> |
The encoder HTTP server returned a non-2xx status (code includes the HTTP status number). |
ENCODER_TIMEOUT |
The request exceeded the configured timeout. |
ENCODER_NETWORK |
Connection refused, DNS failure, or other network-level error. |
MISSING_DATA |
createTx was called without a data field. |
MISSING_PUBKEY |
createTx or spendP2sh was called without a pubkey field. |
SDKValidationError codes (pre-flight)
| Code | Cause |
|---|---|
ENCODING_DATA_TOO_LARGE |
encoding: 'OP_RETURN' set but ACTION string exceeds 76 bytes (the user-data limit; 80 bytes total per output). |
MISSING_COMPRESSED_PUBKEY |
encoding: 'MULTISIGN' set but compressedPubKey is absent. |
const { SDKEncoderError, SDKValidationError } = require('xchain-sdk');
try {
let result = await sdk.send({
params: { tick: 'MYTOKEN', amount: 100, destination: 'bc1q...' },
encoder: { pubkey: 'bc1q...', encoding: 'OP_RETURN' }
});
} catch (err) {
if (err instanceof SDKValidationError && err.code === 'ENCODING_DATA_TOO_LARGE') {
// Switch to P2SH and retry
console.log('Suggested encoding:', err.details.suggestion);
} else if (err instanceof SDKEncoderError && err.code === 'ENCODER_RPC_ERROR') {
console.error('Encoder rejected the transaction:', err.message);
}
}
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.