Skip to main content

Payload Transactions

Send structured data on-chain with transaction payloads for voting, data anchoring, and application protocols.

Overview

Hoosat blockchain supports arbitrary data payloads in transactions through subnetwork 0x03. This enables:

  • Voting systems - On-chain polls and governance
  • Data anchoring - Timestamped document hashes
  • Application protocols - Custom dApp communication
  • Mining pool identification - Worker and rig tracking
  • NFT metadata - Asset attributes and provenance

Payloads can contain:

  • JSON data - Structured application data
  • Plain text - Messages and identifiers
  • Binary data - Hashes and signatures

Quick Start

Send Vote Transaction

import {
HoosatBrowserClient,
HoosatTxBuilder,
HoosatUtils,
createHoosatProxyProvider
} from 'hoosat-sdk-web';

const client = new HoosatBrowserClient(createHoosatProxyProvider());

// Prepare vote data
const voteData = {
type: 'vote',
pollId: '550e8400-e29b-41d4-a716-446655440000',
optionIndex: 2,
timestamp: Date.now()
};

// Encode as hex payload
const payload = HoosatUtils.encodePayloadAsJson(voteData);
const payloadSize = payload.length / 2; // bytes

// Get UTXOs
const utxos = await client.getUtxos([wallet.address]);

// Calculate fee (including payload)
const fee = HoosatCrypto.calculateMinFee(1, 2, payloadSize);

// Build transaction
const builder = new HoosatTxBuilder();

builder
.addInput(utxos[0], wallet.privateKey)
.addOutput(voteServiceAddress, serviceFee)
.setSubnetworkId('0300000000000000000000000000000000000000')
.setPayload(payload)
.setFee(fee.toString())
.addChangeOutput(wallet.address);

const signedTx = builder.sign();
const txId = await client.submitTransaction(signedTx);

console.log('Vote cast! TX:', txId);

Encoding Payloads

HoosatUtils.encodePayload()

Encode plain text to hex payload.

import { HoosatUtils } from 'hoosat-sdk-web';

// Simple text
const hex = HoosatUtils.encodePayload('Hello World');
console.log(hex); // "48656c6c6f20576f726c64"

// Mining worker ID
const workerHex = HoosatUtils.encodePayload('RIG-GPU-01');

// Transaction memo
const memoHex = HoosatUtils.encodePayload('Payment for services');

HoosatUtils.encodePayloadAsJson()

Encode JSON object to hex payload.

import { HoosatUtils } from 'hoosat-sdk-web';

// Vote data
const voteData = {
type: 'vote',
pollId: 'abc123',
optionIndex: 1,
timestamp: Date.now()
};

const hex = HoosatUtils.encodePayloadAsJson(voteData);
// "7b2274797065223a22766f7465222c22706f6c6c4964223a22616263313233222c..."

// Poll creation
const pollData = {
type: 'poll_create',
v: 1,
title: 'Network Upgrade Vote',
options: ['Approve', 'Reject', 'Abstain'],
startDate: Date.now(),
endDate: Date.now() + (7 * 24 * 60 * 60 * 1000),
votingType: 'single',
category: 'governance'
};

const pollHex = HoosatUtils.encodePayloadAsJson(pollData);

Decoding Payloads

HoosatUtils.decodePayload()

Decode hex payload to UTF-8 text.

import { HoosatUtils } from 'hoosat-sdk-web';

const hex = '48656c6c6f20576f726c64';
const text = HoosatUtils.decodePayload(hex);
console.log(text); // "Hello World"

// With 0x prefix
const hex2 = '0x48656c6c6f';
const text2 = HoosatUtils.decodePayload(hex2);
console.log(text2); // "Hello"

HoosatUtils.parsePayloadAsJson()

Decode and parse JSON payload.

import { HoosatUtils } from 'hoosat-sdk-web';

// Vote transaction
const voteHex = '7b2274797065223a22766f7465222c22706f6c6c4964223a22616263313233227d';
const voteData = HoosatUtils.parsePayloadAsJson(voteHex);

console.log(voteData);
// { type: 'vote', pollId: 'abc123', ... }

// Typed parsing
interface VotePayload {
type: 'vote';
pollId: string;
optionIndex: number;
timestamp: number;
}

const typedVote = HoosatUtils.parsePayloadAsJson<VotePayload>(voteHex);
console.log(`Voted for option ${typedVote.optionIndex}`);

HoosatUtils.isJsonPayload()

Check if payload contains JSON.

import { HoosatUtils } from 'hoosat-sdk-web';

const jsonHex = '7b2274797065223a22766f7465227d';
console.log(HoosatUtils.isJsonPayload(jsonHex)); // true

const textHex = '48656c6c6f';
console.log(HoosatUtils.isJsonPayload(textHex)); // false

// Conditional parsing
if (HoosatUtils.isJsonPayload(payload)) {
const data = HoosatUtils.parsePayloadAsJson(payload);
console.log('Structured data:', data);
} else {
const text = HoosatUtils.decodePayload(payload);
console.log('Text payload:', text);
}

HoosatUtils.decodePayloadSafe()

Safe decoding with validation metadata.

import { HoosatUtils } from 'hoosat-sdk-web';

const result = HoosatUtils.decodePayloadSafe(payload);

console.log({
decoded: result.decoded, // Decoded text
isValidUtf8: result.isValidUtf8, // UTF-8 valid?
isJson: result.isJson, // Valid JSON?
raw: result.raw // Original hex
});

// Safe transaction processing
function processPayload(hexPayload: string) {
const { decoded, isValidUtf8, isJson } = HoosatUtils.decodePayloadSafe(hexPayload);

if (isJson) {
const data = JSON.parse(decoded);
handleJsonPayload(data);
} else if (isValidUtf8) {
handleTextPayload(decoded);
} else {
handleBinaryPayload(hexPayload);
}
}

Building Payload Transactions

HoosatTxBuilder with Payload

import {
HoosatTxBuilder,
HoosatUtils,
HoosatCrypto
} from 'hoosat-sdk-web';

const builder = new HoosatTxBuilder();

// Prepare payload
const payload = HoosatUtils.encodePayloadAsJson({
type: 'vote',
pollId: 'abc123',
optionIndex: 1
});

// Build transaction
builder
.addInput(utxo, wallet.privateKey)
.addOutput(recipient, amount)
.setSubnetworkId('0300000000000000000000000000000000000000') // REQUIRED for payloads
.setPayload(payload) // Set hex-encoded payload
.setFee(fee.toString())
.addChangeOutput(wallet.address);

const signedTx = builder.sign();

Fee Calculation with Payload

Payloads increase transaction size, affecting fees:

import { HoosatCrypto, HoosatUtils } from 'hoosat-sdk-web';

// Prepare payload
const payload = HoosatUtils.encodePayloadAsJson(data);
const payloadSize = payload.length / 2; // Bytes

// Calculate fee including payload
const fee = HoosatCrypto.calculateMinFee(
inputs.length, // Number of inputs
outputs.length, // Number of outputs
payloadSize // Payload size in bytes
);

builder.setFee(fee.toString());

Practical Examples

Vote Service Integration

Complete vote casting workflow:

import {
HoosatBrowserClient,
HoosatTxBuilder,
HoosatUtils,
HoosatCrypto,
createHoosatProxyProvider
} from 'hoosat-sdk-web';

interface VoteParams {
wallet: { address: string; privateKey: Buffer };
voteServiceAddress: string;
serviceFee: string; // sompi
pollId: string;
optionIndex: number;
}

async function castVote(params: VoteParams): Promise<string> {
const client = new HoosatBrowserClient(createHoosatProxyProvider());

// 1. Encode vote payload
const voteData = {
type: 'vote',
pollId: params.pollId,
optionIndex: params.optionIndex,
timestamp: Date.now()
};

const payload = HoosatUtils.encodePayloadAsJson(voteData);
const payloadSize = payload.length / 2;

console.log(`Vote payload: ${payloadSize} bytes`);

// 2. Get UTXOs
const utxos = await client.getUtxos([params.wallet.address]);

if (utxos.length === 0) {
throw new Error('No UTXOs available');
}

// 3. Calculate fee (service fee + tx fee)
const txFee = HoosatCrypto.calculateMinFee(1, 2, payloadSize);

// 4. Build transaction
const builder = new HoosatTxBuilder();

builder.addInput(utxos[0], params.wallet.privateKey);
builder.addOutput(params.voteServiceAddress, params.serviceFee);
builder.setSubnetworkId('0300000000000000000000000000000000000000');
builder.setPayload(payload);
builder.setFee(txFee.toString());
builder.addChangeOutput(params.wallet.address);

// 5. Sign and submit
const signedTx = builder.sign();
const txId = await client.submitTransaction(signedTx);

console.log('Vote cast! TX:', txId);
return txId;
}

// Usage
const txId = await castVote({
wallet: myWallet,
voteServiceAddress: 'hoosat:qz95mwas8ja7ucsernv9z335rdxxqswff7wvzenl29qukn5qs3lsqfsa4pd74',
serviceFee: HoosatUtils.amountToSompi('0.01'),
pollId: '550e8400-e29b-41d4-a716-446655440000',
optionIndex: 2
});

Create Poll

interface PollParams {
wallet: { address: string; privateKey: Buffer };
serviceAddress: string;
title: string;
description: string;
options: string[];
durationDays: number;
votingType: 'single' | 'multiple';
votingMode: 'standard' | 'weighted';
category: string;
minBalance?: number;
}

async function createPoll(params: PollParams): Promise<string> {
const client = new HoosatBrowserClient(createHoosatProxyProvider());

// 1. Build poll payload
const pollData = {
type: 'poll_create',
v: 1,
title: params.title,
description: params.description,
options: params.options,
startDate: Date.now(),
endDate: Date.now() + (params.durationDays * 24 * 60 * 60 * 1000),
votingType: params.votingType,
votingMode: params.votingMode,
category: params.category,
minBalance: params.minBalance || 0
};

const payload = HoosatUtils.encodePayloadAsJson(pollData);
const payloadSize = payload.length / 2;

console.log(`Poll payload: ${payloadSize} bytes`);
console.log('Poll data:', pollData);

// 2. Get UTXOs
const utxos = await client.getUtxos([params.wallet.address]);

// 3. Calculate fees
const serviceFee = HoosatUtils.amountToSompi('0.1'); // 0.1 HTN for poll creation
const txFee = HoosatCrypto.calculateMinFee(1, 2, payloadSize);

// 4. Build transaction
const builder = new HoosatTxBuilder();

builder.addInput(utxos[0], params.wallet.privateKey);
builder.addOutput(params.serviceAddress, serviceFee);
builder.setSubnetworkId('0300000000000000000000000000000000000000');
builder.setPayload(payload);
builder.setFee(txFee.toString());
builder.addChangeOutput(params.wallet.address);

// 5. Submit
const signedTx = builder.sign();
const txId = await client.submitTransaction(signedTx);

console.log('Poll created! TX:', txId);
return txId;
}

// Usage
const txId = await createPoll({
wallet: myWallet,
serviceAddress: 'hoosat:qz95mwas8ja7ucsernv9z335rdxxqswff7wvzenl29qukn5qs3lsqfsa4pd74',
title: 'Community Treasury Vote',
description: 'How should we allocate Q1 2025 funds?',
options: [
'Development (40%)',
'Marketing (30%)',
'Security (20%)',
'Reserve (10%)'
],
durationDays: 7,
votingType: 'single',
votingMode: 'weighted',
category: 'governance',
minBalance: 100
});

Data Anchoring

Anchor document hashes on-chain:

async function anchorDocument(
wallet: { address: string; privateKey: Buffer },
documentHash: string,
metadata: {
filename: string;
size: number;
parties: string[];
}
): Promise<string> {
const client = new HoosatBrowserClient(createHoosatProxyProvider());

// 1. Prepare anchor payload
const anchorData = {
type: 'anchor',
v: 1,
documentHash,
timestamp: Date.now(),
metadata
};

const payload = HoosatUtils.encodePayloadAsJson(anchorData);
const payloadSize = payload.length / 2;

// 2. Get UTXOs
const utxos = await client.getUtxos([wallet.address]);

// 3. Calculate fee
const fee = HoosatCrypto.calculateMinFee(1, 1, payloadSize);

// 4. Build self-transaction (send to self with payload)
const builder = new HoosatTxBuilder();

const minAmount = '1000'; // Dust threshold

builder.addInput(utxos[0], wallet.privateKey);
builder.addOutput(wallet.address, minAmount);
builder.setSubnetworkId('0300000000000000000000000000000000000000');
builder.setPayload(payload);
builder.setFee(fee.toString());
builder.addChangeOutput(wallet.address);

// 5. Submit
const signedTx = builder.sign();
const txId = await client.submitTransaction(signedTx);

console.log('Document anchored! TX:', txId);
return txId;
}

// Usage
const txId = await anchorDocument(
myWallet,
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
{
filename: 'contract_v2.pdf',
size: 245760,
parties: ['Alice Corp', 'Bob LLC']
}
);

Browser Wallet Integration

Window Injected Provider

// Wallet extension: inject provider
window.hoosat = {
async sendTransaction(params: {
to: string;
amount: number | string;
payload?: string;
}): Promise<{ transactionId: string }> {
// Get active wallet
const wallet = await getActiveWallet();

// Build transaction with payload
const builder = new HoosatTxBuilder();
const utxos = await getUtxos(wallet.address);

const amount = typeof params.amount === 'number'
? HoosatUtils.amountToSompi(params.amount.toString())
: params.amount;

let payloadSize = 0;

builder.addInput(utxos[0], wallet.privateKey);
builder.addOutput(params.to, amount);

// Add payload if provided
if (params.payload) {
builder.setSubnetworkId('0300000000000000000000000000000000000000');
builder.setPayload(params.payload);
payloadSize = params.payload.length / 2;
}

// Calculate fee
const fee = HoosatCrypto.calculateMinFee(1, 2, payloadSize);
builder.setFee(fee.toString());
builder.addChangeOutput(wallet.address);

// Sign and submit
const signedTx = builder.sign();
const txId = await submitTransaction(signedTx);

return { transactionId: txId };
}
};

DApp Usage

// DApp: use injected provider
async function castVote(pollId: string, optionIndex: number) {
if (!window.hoosat) {
throw new Error('Hoosat wallet not detected');
}

// Encode vote payload
const voteData = {
type: 'vote',
pollId,
optionIndex,
timestamp: Date.now()
};

const payload = HoosatUtils.encodePayloadAsJson(voteData);

// Request transaction with payload
const result = await window.hoosat.sendTransaction({
to: 'hoosat:qz95mwas8ja7ucsernv9z335rdxxqswff7wvzenl29qukn5qs3lsqfsa4pd74',
amount: 0.01, // Service fee in HTN
payload
});

console.log('Vote cast! TX:', result.transactionId);
return result.transactionId;
}

Best Practices

1. Always Use Subnetwork 0x03

Payloads ONLY work on subnetwork 0x03:

// Correct
builder.setSubnetworkId('0300000000000000000000000000000000000000');
builder.setPayload(payload);

// Wrong - will fail
builder.setPayload(payload);
// Missing setSubnetworkId() ❌

2. Include Payload Size in Fee

// Calculate fee including payload
const payload = HoosatUtils.encodePayloadAsJson(data);
const payloadSize = payload.length / 2; // bytes

const fee = HoosatCrypto.calculateMinFee(
inputs.length,
outputs.length,
payloadSize // IMPORTANT
);

3. Validate Payload Size

const MAX_PAYLOAD_SIZE = 10000; // 10KB

const payload = HoosatUtils.encodePayloadAsJson(data);

if (payload.length / 2 > MAX_PAYLOAD_SIZE) {
throw new Error('Payload too large');
}

4. Version Your Payloads

// Good - future-proof
const payload = {
v: 1, // Schema version
type: 'vote',
// ... data
};

// Can upgrade schema later:
// v: 2 → new fields
// v: 3 → breaking changes

5. Include Timestamps

const payload = {
type: 'vote',
timestamp: Date.now(), // UNIX timestamp
// ... data
};

6. Handle Encoding Errors

try {
const payload = HoosatUtils.encodePayloadAsJson(data);
builder.setPayload(payload);
} catch (error) {
console.error('Payload encoding failed:', error);
// Handle: invalid JSON, circular refs, BigInt, etc.
}

7. Safe Decoding

// Use safe decode for unknown payloads
const { decoded, isValidUtf8, isJson } = HoosatUtils.decodePayloadSafe(payload);

if (isJson) {
try {
const data = JSON.parse(decoded);
processVote(data);
} catch {
console.error('Invalid JSON payload');
}
} else if (isValidUtf8) {
console.log('Text payload:', decoded);
} else {
console.log('Binary payload');
}

Real-World Examples

Vote Service Poll

// Actual vote service payload
const pollPayload = {
type: 'poll_create',
v: 1,
title: 'Super pupper',
description: 'So test description',
options: ['1', '3', '4', '10'],
startDate: 1765307520000,
endDate: 1765566720000,
votingType: 'single',
votingMode: 'standard',
category: 'marketing',
minBalance: 11
};

const hex = HoosatUtils.encodePayloadAsJson(pollPayload);
// Transaction sent on Dec 9, 2025

Mining Worker ID

// Mining pool coinbase payload
const workerInfo = '1.6.2/hoo_gpu/1.2.12 via htn-stratum-bridge_v1.6.0 as worker RIG02';
const hex = HoosatUtils.encodePayload(workerInfo);
// Used by mining pools to identify workers

Payload Utilities Reference

MethodDescriptionInputOutput
encodePayload(text)Encode UTF-8 to hexstringstring (hex)
encodePayloadAsJson(data)Encode JSON to hexunknownstring (hex)
decodePayload(hex)Decode hex to UTF-8string (hex)string
parsePayloadAsJson<T>(hex)Decode and parse JSONstring (hex)T
isJsonPayload(hex)Check if JSONstring (hex)boolean
decodePayloadSafe(hex)Safe decode with metadatastring (hex)object

Next Steps