XChain Platform Encoder
What is xchain-encoder
xchain-encoder is the PSBT encoding service of the XChain Platform. It takes an ACTION string, a set of UTXOs, and a public key, and returns an unsigned Partially Signed Bitcoin Transaction (PSBT) ready for the caller to sign and broadcast. The encoder is fully stateless — it holds no database and no persistent state between calls.
The encoder’s sole responsibility is to embed XChain protocol data into a transaction correctly and efficiently. The caller is responsible for signing and broadcasting.
Features
- Stateless — no database, no persistent connections; every call is independent
- Four encoding formats — OP_RETURN (80B total, 76B user data), P2SH (476B), P2WSH (8,192B), and multisig (~61B/key); auto-selected by payload size
- Two-transaction orchestration — automatic tx1 (fund) → tx2 (spend/reveal) pattern for P2SH and P2WSH with OP_RETURN marker
- AES-128-CTR obfuscation — derives key and IV from the first input’s txid;
XCHNmagic prefix on all payloads - UTXO selection — largest-first selection, duplicate removal, optional unconfirmed filtering, automatic change output
- Fee estimation — byte-accurate transaction size estimation per format via
TxSizeEstimator; dust floor enforcement - Fee rate cap — configurable maximum fee rate prevents runaway estimates (e.g., regtest feedback loops)
- Input validation — centralized parameter validation with typed errors (TypeError/RangeError) for all 15
createTransactionparameters - Multi-chain support — Bitcoin, Litecoin, and Dogecoin on mainnet, testnet, and regtest (9 network configs with chain-specific dust thresholds)
- Replace-By-Fee — optional RBF signaling via UTXO sequence number
- Custom outputs — arbitrary address/value outputs for COINPay native coin payments and other use cases
- JSON-RPC API — Express server with Helmet security headers, optional API key authentication, configurable rate limiting, and CORS
- Browser bundle — Browserify build for client-side PSBT generation without routing private keys through a server
Encoding Process
Every encode call follows the same sequence regardless of format:
- Prepend magic prefix —
XCHN(4 bytes) is prepended to the ACTION string - Obfuscate — the prefixed payload is encrypted with AES-128-CTR using the first input’s txid:
- Key: first 16 hex characters of the txid (8 bytes)
- IV: next 16 hex characters of the txid (8 bytes)
- Select format — the encoder picks the most efficient encoding format based on the obfuscated payload length (see Format Selection)
- Build transaction — inputs are selected from the provided UTXOs, outputs are constructed per the chosen format, fees are calculated, and a change output is added if needed
- Return PSBT — the unsigned PSBT is returned to the caller in base64 format
Encoding Formats
OP_RETURN
Maximum payload: 76 bytes of user data (80 bytes total per output, including the 4-byte XCHN prefix)
The obfuscated payload is embedded in an OP_RETURN output. This is a single transaction — the encoder constructs it, the caller signs and broadcasts once. OP_RETURN outputs are provably unspendable and are the cheapest encoding method. Best for most SEND, ISSUE, and MINT actions.
P2SH
Maximum payload: 476 bytes
The payload is embedded in a redeem script, which is hashed and locked to a P2SH output in a funding transaction. A second spending transaction then reveals the full redeem script in the scriptSig, making the payload visible on-chain. Two transactions must be signed and broadcast in order:
- Fund — locks coin to the P2SH output containing the hashed script
- Spend — spends from the P2SH output, revealing the full script (and therefore the payload) in the scriptSig
The decoder reads the spend transaction’s scriptSig to extract the payload.
P2WSH
Maximum payload: 8,192 bytes (decoder-enforced ceiling)
Functionally identical to P2SH but uses SegWit. The payload is embedded in a witness script locked to a P2WSH output. The two-transaction pattern is the same:
- Fund — locks coin to the P2WSH output
- Spend — spends from the P2WSH output, revealing the witness script
Because SegWit witness data is discounted when calculating transaction weight, P2WSH is more fee-efficient than P2SH for large payloads. Use this for FILE actions, large BROADCAST payloads, or any action requiring more than 476 bytes.
Multisig
Payload capacity: approximately 61 bytes per key
The payload is split across the public key positions of a bare multisig output (OP_m ... OP_n OP_CHECKMULTISIG). This is a single-transaction format. The decoder reads the fake public keys from the output to extract the payload.
Multisig encoding is an alternative for payloads that exceed OP_RETURN’s 76-byte user-data limit but where the caller prefers a single-transaction flow. The encoder handles splitting and padding automatically.
Format Auto-Selection
The encoder selects the most efficient format automatically based on obfuscated payload length. See Format Selection for the full decision logic and size limits.
UTXO Management
The encoder accepts UTXOs from the caller (typically sourced from xchain-utxo-tracker). It selects the minimum set of inputs needed to cover the output value plus estimated fees, constructs a change output back to the source address if change exceeds the dust threshold, and includes all selected inputs in the PSBT.
Fee rates use the coin node’s estimatesmartfee recommendation by default. The caller may pass an override fee rate.
API
The encoder exposes a JSON-RPC API via Express with express-json-rpc-router.
Methods
| Method | Description |
|---|---|
create_tx |
Encode an ACTION string into a PSBT given UTXOs and a public key |
broadcast_tx |
Submit a signed raw transaction hex to the coin node for broadcast |
get_utxos |
Fetch the UTXO set for a given address from xchain-utxo-tracker |
ping |
Health check — returns { status: "success" } |
create_tx Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
string |
Yes | ACTION string to encode (e.g., SEND|0|JDOG|100|addr) |
pubkey |
string |
Yes | Sender’s public address |
utxos |
Array<UTXO> |
No | UTXOs to spend; if omitted, fetched from UTXO Tracker |
customOutputs |
Array<{address, value}> |
No | Additional outputs (e.g., COINPay payments) |
rawData |
string |
No | Additional data ignored by decoder |
fee |
number |
No | Fixed fee in satoshis (auto-calculated if omitted) |
feePerKb |
number |
No | Fee rate in BTC/kB (bypasses node’s estimatesmartfee) |
rbf |
boolean |
No | Enable Replace-By-Fee signaling |
encoding |
string |
No | Force encoding type: OP_RETURN, P2SH, P2WSH, or MULTISIGN |
change |
string |
No | Change address |
p2shHash |
string |
No | Previous tx ID for P2SH/P2WSH tx2 (paired with p2shHex) |
p2shHex |
string |
No | Previous tx hex for P2SH/P2WSH tx2 (paired with p2shHash) |
compressedPubKey |
string |
No | Compressed public key for MULTISIGN encoding |
unconfirmed |
boolean |
No | Allow unconfirmed UTXOs (default: true) |
dust |
number |
No | Override dust amount for outputs |
UTXO Structure
{
"txid": "64-char hex string",
"vout": 0,
"value": 100000,
"scriptPubKey": "hex"
}
Response
{
"psbt": "hex-encoded PSBT (BIP 174)",
"encoding": "OP_RETURN | P2SH | P2WSH | MULTISIGN"
}
broadcast_tx Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tx_hex |
string |
Yes | Signed raw transaction hex to broadcast |
broadcast_tx Response
{ "txid": "64-char hex string" }
get_utxos Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
address |
string |
Yes | Address whose UTXOs to retrieve |
get_utxos Response
Returns an array of UTXO objects matching the UTXO Structure documented in the create_tx section:
[
{
"txid": "64-char hex string",
"vout": 0,
"value": 100000,
"scriptPubKey": "hex"
}
]
Error Codes
| Code | Meaning |
|---|---|
-32602 |
Invalid params — validation error (TypeError or RangeError from validator.js) |
-32603 |
Internal error — encoder failure (e.g., insufficient UTXOs, unsupported network) |
-32001 |
Unauthorized — missing or incorrect x-api-key header |
-32029 |
Rate limited — too many requests |
Browser Bundle
A Browserify build is available for client-side use. This allows web applications to construct PSBTs in the browser without routing the private key through a server. The bundle exposes window.XChainEncoder with the same encoding logic.
npm run build # Production (minified) → dist/xchain_encoder.min.js
npm run build:dev # Development (unminified) → dist/xchain_encoder.min.js
Installation
git clone https://github.com/XChain-Platform/xchain-encoder.git
cd xchain-encoder
npm install
npm run api
Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
NETWORK |
Yes | — | Coin and network (bitcoin-mainnet, dogecoin-testnet, litecoin-regtest, etc.) |
NODE_URL |
Yes | — | Coin node RPC host |
NODE_PORT |
Yes | — | Coin node RPC port |
NODE_USER |
Yes | — | RPC username |
NODE_PASSWORD |
Yes | — | RPC password |
ENCODER_API_PORT |
No | 3000 |
JSON-RPC API port |
DUST_AMOUNT |
No | Network default | Minimum output value in satoshis |
UTXO_TRACKER_URL |
No | — | xchain-utxo-tracker service host |
UTXO_TRACKER_API_PORT |
No | — | xchain-utxo-tracker service port |
MAX_FEE_RATE_KB |
No | Uncapped | Absolute maximum fee rate in sat/kB |
MAX_FEE_RATE_MULTIPLIER |
No | 100 |
Caps caller-supplied fee/feePerKb at this multiple of the node’s fee estimate (0 disables) |
API_KEY |
No | Disabled | API key for x-api-key header authentication |
ENCODER_RATE_LIMIT_RPM |
No | 60 |
Maximum requests per minute per IP |
CORS_ORIGIN |
No | Disabled | CORS origin (* to allow all) |
Testing
The encoder maintains a comprehensive test suite spanning 10 testing disciplines with approximately 769 tests total. All tests except the root-level regtest integration suite run offline with mocked connectors — no live coin node required.
Test Scripts
npm run smoke-test # Operational health checks (~10 tests, <1s)
npm run test:unit # Isolated method tests (114 tests)
npm run test:integration # Multi-component pipeline tests (108 tests)
npm run test:boundary # Edge-case and limit tests (~120 tests)
npm run test:chaos # Failure injection tests (61 tests)
npm run test:regression # Curated critical-path regression suite (196 tests)
npm run mutate # Full mutation testing via StrykerJS
npm run mutate:quick # Incremental mutation check (XChainEncoder.js only)
npm run bench # Performance benchmarks
npm run bench:full # Extended benchmarks with JSON output
npm test # Regtest integration tests (requires local bitcoind)
Regression Test Suite
The regression suite (test/regression/) provides a curated safety net covering all critical encoder paths. It runs entirely offline in under 100ms and is designed to catch regressions after any code change.
| File | Tests | Coverage Area |
|---|---|---|
reg-01-encoding-types |
16 | All 4 encoding types (OP_RETURN, P2SH, P2WSH, MULTISIGN) |
reg-02-obfuscation |
13 | AES-128-CTR round-trip, key derivation, TXID sensitivity |
reg-03-fee-utxo |
17 | UTXO selection, fee calculation, dust floor, change output |
reg-04-validator |
56 | All input validation functions (validator.js) |
reg-05-multi-chain |
20 | Bitcoin, Litecoin, Dogecoin configs and dust thresholds |
reg-06-p2sh-p2wsh-sequence |
14 | Two-transaction tx1→tx2 chaining integrity |
reg-07-action-pipeline |
14 | Key ACTION types through full encode/decode pipeline |
reg-08-api-contract |
12 | JSON-RPC parameter flow and PSBT serialization |
Test Helpers
Shared test utilities in test/integration/helpers/:
- utxoFactory.js — Encoder, UTXO, and address fixture factories with deterministic TXIDs
- actionFactory.js — ACTION payload builders for all supported action types
- deobfuscate.js — Payload extraction and AES-128-CTR decryption utilities
Related
- Format Selection — decision guide for choosing an encoding format
- UTXO Tracker — the service that supplies UTXOs to the encoder
- Data Pipeline — full platform ingestion flow
- Testing — platform-wide testing philosophy and coverage
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.