Appearance
Transaction Builder
A code generator that turns a transaction JSON into ready-to-paste C for building and emitting that transaction from a Hook.
- Tool: Xahau Hook Tx Builder
Why use it
Emitting a transaction requires laying out a serialized transaction template by hand, computing each field's byte offset, and wiring up etxn_details / etxn_fee_base in the right order. The Transaction Builder does this for you: given any transaction JSON, it generates the template and pointer macros for exactly the fields present, plus a PREPARE_TXN() macro that finishes the fixed emit fields. It is the current recommended way to build emitted transactions — prefer it over hand-writing a template.
How to use it
- Paste a transaction JSON into the tool, e.g.
{ "TransactionType": "AccountSet" }. - Copy the generated
uint8_t txn[N]block, the*_OUTmacros, the helper macros, andPREPARE_TXN()into your hook. - Set the transaction-specific fields through the generated
*_OUTpointer macros, using the matching helper macro for each field's type, e.g.SET_NATIVE_AMOUNT(AMOUNT_OUT, drops)for a nativeAmount, orSET_ACCOUNT(DEST_OUT, accid)for a 20-byteDestination. - Call
PREPARE_TXN()to fill in the fixed emit fields (sequence numbers,sfAccount,sfEmitDetails, and the fee). - Call
emit(SBUF(hash), SBUF(txn)).
Worked example
Pasting { "TransactionType": "AccountSet" } into the tool produces the following block:
c
// clang-format off
uint8_t txn[207] =
{
/* size, upto, field name */
/* 3, 0, tt = AccountSet */ 0x12U, 0x00U, 0x03U,
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 116, 91, emit details */
/* 0, 207, */
};
// clang-format on
// TX BUILDER
#define FLAGS_OUT (txn + 4U)
#define FLS_OUT (txn + 15U)
#define LLS_OUT (txn + 21U)
#define FEE_OUT (txn + 26U)
#define ACCOUNT_OUT (txn + 71U)
#define EMIT_OUT (txn + 91U)
#define COPY_20(ptr_to, ptr_from) \
do { \
unsigned char *buf_to = (unsigned char *)ptr_to; \
unsigned char *buf_from = (unsigned char *)ptr_from; \
*(uint64_t *)(buf_to + 0) = *(uint64_t *)(buf_from + 0); \
*(uint64_t *)(buf_to + 8) = *(uint64_t *)(buf_from + 8); \
*(uint32_t *)(buf_to + 16) = *(uint32_t *)(buf_from + 16); \
} while (0)
#define FLIP_ENDIAN_32(value) \
(uint32_t)(((value & 0xFFU) << 24) | ((value & 0xFF00U) << 8) | \
((value & 0xFF0000U) >> 8) | ((value & 0xFF000000U) >> 24))
#define SET_UINT32(ptr, value) *((uint32_t *)(ptr)) = FLIP_ENDIAN_32(value);
#define SET_NATIVE_AMOUNT(ptr, amount) \
do { \
uint8_t *b = (ptr); \
*b++ = 0b01000000 + ((amount >> 56) & 0b00111111); \
*b++ = (amount >> 48) & 0xFFU; \
*b++ = (amount >> 40) & 0xFFU; \
*b++ = (amount >> 32) & 0xFFU; \
*b++ = (amount >> 24) & 0xFFU; \
*b++ = (amount >> 16) & 0xFFU; \
*b++ = (amount >> 8) & 0xFFU; \
*b++ = (amount >> 0) & 0xFFU; \
} while (0)
#define PREPARE_TXN() \
do { \
etxn_reserve(1); \
uint32_t fls = (uint32_t)ledger_seq() + 1; \
SET_UINT32(FLS_OUT, fls); \
SET_UINT32(LLS_OUT, fls + 4); \
hook_account(ACCOUNT_OUT, 20); \
etxn_details(EMIT_OUT, 116U); \
int64_t fee = etxn_fee_base(SBUF(txn)); \
SET_NATIVE_AMOUNT(FEE_OUT, fee); \
TRACEHEX(txn); \
} while (0)This particular template has no mutable Amount or Destination field (AccountSet doesn't carry one), so the generated macro set for this example is just FLAGS_OUT, FLS_OUT, LLS_OUT, FEE_OUT, ACCOUNT_OUT, and EMIT_OUT — all of which PREPARE_TXN() fills in for you. A minimal hook that emits this template as-is:
c
#include "hookapi.h"
int64_t
hook(uint32_t reserved)
{
_g(1, 1);
// ... paste the generated txn[] block, *_OUT macros, helper macros,
// and PREPARE_TXN() here ...
PREPARE_TXN();
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(txn));
if (emit_result != 32)
rollback(SBUF("tx-builder: emit failed"), 1);
accept(SBUF("tx-builder: ok"), 0);
return 0;
}For a transaction type with a mutable Amount or Destination (e.g. Payment), the tool additionally generates AMOUNT_OUT / DEST_OUT macros; set them before calling PREPARE_TXN(), e.g. SET_NATIVE_AMOUNT(AMOUNT_OUT, 1); SET_ACCOUNT(DEST_OUT, to_accid);.
Generated macros reference
*_OUT pointer macros
Each *_OUT macro is a pointer into txn, positioned past that field's serialized field-code prefix, i.e. pointing directly at the field's value bytes. One is generated per mutable field present in the pasted JSON. Common examples:
| Macro | Field |
|---|---|
TT_OUT | TransactionType |
FLAGS_OUT | Flags |
FEE_OUT | Fee |
AMOUNT_OUT | Amount |
ACCOUNT_OUT | Account |
DEST_OUT | Destination |
DTAG_OUT | DestinationTag |
FLS_OUT | FirstLedgerSequence |
LLS_OUT | LastLedgerSequence |
EMIT_OUT | EmitDetails |
Helper macros
c
#define COPY_20(ptr_to, ptr_from) \
do { \
unsigned char *buf_to = (unsigned char *)ptr_to; \
unsigned char *buf_from = (unsigned char *)ptr_from; \
*(uint64_t *)(buf_to + 0) = *(uint64_t *)(buf_from + 0); \
*(uint64_t *)(buf_to + 8) = *(uint64_t *)(buf_from + 8); \
*(uint32_t *)(buf_to + 16) = *(uint32_t *)(buf_from + 16); \
} while (0)
#define FLIP_ENDIAN_32(value) \
(uint32_t)(((value & 0xFFU) << 24) | ((value & 0xFF00U) << 8) | \
((value & 0xFF0000U) >> 8) | ((value & 0xFF000000U) >> 24))
#define SET_UINT32(ptr, value) *((uint32_t *)(ptr)) = FLIP_ENDIAN_32(value);
#define SET_NATIVE_AMOUNT(ptr, amount) \
do { \
uint8_t *b = (ptr); \
*b++ = 0b01000000 + ((amount >> 56) & 0b00111111); \
*b++ = (amount >> 48) & 0xFFU; \
*b++ = (amount >> 40) & 0xFFU; \
*b++ = (amount >> 32) & 0xFFU; \
*b++ = (amount >> 24) & 0xFFU; \
*b++ = (amount >> 16) & 0xFFU; \
*b++ = (amount >> 8) & 0xFFU; \
*b++ = (amount >> 0) & 0xFFU; \
} while (0)COPY_20(ptr_to, ptr_from)— copies 20 bytes (anAccountID) fromptr_fromtoptr_tousing three word-sized loads/stores.FLIP_ENDIAN_32(value)— byte-swaps a 32-bit value, expression macro.SET_UINT32(ptr, value)— writesvalueatptras a big-endianuint32_t, used forFLS_OUT/LLS_OUTand similar 32-bit fields.SET_NATIVE_AMOUNT(ptr, amount)— writes an 8-byte native-XAHAmount(drops) atptr, including the non-XAH-bit-clear high bit convention (0b01000000 | top6bits).SET_ACCOUNT(ptr_to, ptr_from)— generated for 20-byteAccountIDfields such asAccountandDestination; a thin wrapper aroundCOPY_20(ptr_to, ptr_from).SET_IOU_AMOUNT(ptr, issuer, currency, amount_xfl)— generated when the JSON'sAmountis a non-native (IOU) amount object; writes the 48-byte IOUAmount(XFL value, currency code, issuerAccountID) atptr.
PREPARE_TXN()
c
#define PREPARE_TXN() \
do { \
etxn_reserve(1); \
uint32_t fls = (uint32_t)ledger_seq() + 1; \
SET_UINT32(FLS_OUT, fls); \
SET_UINT32(LLS_OUT, fls + 4); \
hook_account(ACCOUNT_OUT, 20); \
etxn_details(EMIT_OUT, 116U); \
int64_t fee = etxn_fee_base(SBUF(txn)); \
SET_NATIVE_AMOUNT(FEE_OUT, fee); \
TRACEHEX(txn); \
} while (0)What PREPARE_TXN() does / caveats
- Calls
etxn_reserve(1)for you. Do not calletxn_reserveyourself before or after using this macro — a second call returnsALREADY_SET. This means a singlePREPARE_TXN()call covers exactly one emitted transaction; if you need to emit more than one, you cannot use the generatedPREPARE_TXN()as-is (it always reserves1). - Sets
FirstLedgerSequence/LastLedgerSequence.FirstLedgerSequenceisledger_seq() + 1;LastLedgerSequenceisFirstLedgerSequence + 4, giving the emitted transaction a five-ledger validity window. - Fills
sfAccountviahook_account— the emitting account is always the hook's own account. - Writes
sfEmitDetailsviaetxn_details. The size passed,116U, is theEmitDetailssize for a hook that does not exportcbak. A hook that does exportcbakneeds138Uinstead — the tool bakes in the correct size based on what you tell it, so regenerate (or hand-adjust) the block if you add/remove a callback. etxn_detailsis called beforeetxn_fee_base. This ordering is required:etxn_fee_basemeasures the fee over the whole template includingEmitDetails, soEmitDetailsmust already be written.- Computes the fee via
etxn_fee_baseover the whole template and writes it toFEE_OUT— never hardcode a fee. TRACEHEX(txn)is debug tracing fromhook/macro.h; it compiles out under release builds (NDEBUG).- The template's size is fixed at compile time. To leave out an optional field, or to send a blob shorter than the space you reserved for it, overwrite the unused bytes with the
0x99NOP byte instead of shifting the template — see NOP Bytes (0x99) for the rules and worked examples.