Appearance
Emit and Emitted-Transaction APIs
This page documents the eight emit-and-etxn Hook APIs: the functions a hook uses to construct, price, and emit a new transaction of its own, plus the counters that make emitted transactions deterministic and loop-safe.
All signatures are copied verbatim from hook/extern.h. Return codes reference the shared error table in ../../glossary.md; the values quoted below come from include/xrpl/hook/Enum.h and hook/error.h, and every behaviour is taken from the implementations in src/xrpld/app/hook/detail/applyHook.cpp and src/xrpld/app/hook/detail/HookAPI.cpp.
There is no
invokeHook API.ttINVOKE(99,hook/tts.h) is a transaction type that triggers hooks, not an API function. Do not look for aninvoke()call — hooks emit transactions withemit.
The emission lifecycle
A hook does not "send" a transaction directly; it hands the ledger a fully-formed, already-serialized transaction blob, and the ledger queues it for application in a later ledger. The steps below are enforced in this order — every emit-related call first checks that a reservation exists (hookCtx.expected_etxn_count, which starts at -1), so etxn_reserve must come first.
Reserve. Call
etxn_reserve(n)once, declaring the number of transactions this hook execution intends to emit. Until this is done, every other function on this page returnsPREREQUISITE_NOT_MET(-9).nmust be1..255.Build the raw transaction. Assemble the transaction as a serialized
STObjectin a memory buffer. You can build it byte-by-byte with theENCODE_*/PREPARE_PAYMENT_*macro idiom shown in the test hooks, or (underHooksUpdate2) hand a partial template toprepare, which fills in the fixed fields for you.Insert
sfEmitDetails. Calletxn_details(buf, len)to write thesfEmitDetailsobject into the transaction. This object is what marks the transaction as hook-emitted and ties it to the originating transaction, the emitting hook, and a unique nonce.Compute the fee. Call
etxn_fee_base(buf, len)over the serialized transaction to get the minimum drops it must pay, and write that value into itssfFeefield. An emitted transaction that under-pays is rejected byemit.Emit. Call
emit(hash_out, 32, buf, len). It re-parses the transaction, checks it against every emission rule, queues it on success, and writes the 32-byte transaction hash intohash_out. It returns32.
The cbak entry point (below) lets the emitting hook observe the emitted transaction's eventual outcome.
Burden, generation, and the anti-emission-loop mechanism
Two counters ride along in sfEmitDetails and exist specifically to stop hooks from emitting transactions forever (verified in HookAPI::emit, etxn_burden, etxn_generation):
Generation —
etxn_generation()returnsotxn_generation() + 1. A user-submitted transaction has generation0; a transaction it emits carries generation1; a transaction that transaction's hook emits carries generation2, and so on.emitrejects any transaction whose generation is10or more (sfEmitGeneration >= 10→EMISSION_FAILURE). This is a hard ceiling on emission depth.Burden —
etxn_burden()returnsotxn_burden() * expected_etxn_count. Because each hop multiplies the burden by the number of children reserved, a fan-out of emissions makes the burden grow geometrically. Burden overflow is reported asFEE_TOO_LARGE(-10). Burden is carried so downstream fee/anti-abuse accounting can price deeper emission chains more expensively.
emit verifies that the generation and burden a hook wrote into sfEmitDetails match the values etxn_generation()/etxn_burden() compute, so you cannot forge a lower value. Using etxn_details to build the object guarantees the correct values.
The cbak callback entry point
If a hook's WASM exports a cbak function, the hook is said to have a callback (hasCallback), and etxn_details adds an sfEmitCallback field (the hook account) to every sfEmitDetails it writes — which is why the details object is 22 bytes larger when a callback is present. When an emitted transaction later reaches its outcome, the ledger runs the emitting hook's cbak instead of hook:
c
int64_t cbak(uint32_t r)
{
// r & 1 == 1 -> the emitted transaction failed
// r & 1 == 0 -> the emitted transaction was applied
// The "originating transaction" during cbak is the emitted transaction itself,
// so otxn_* reads describe the emitted txn.
return accept(0, 0, 0);
}The only fact the code guarantees about the argument is bit 0: emitFailure = isCallback && (wasmParam & 1) in applyHook.cpp. A callback may itself emit further transactions, but it must call etxn_reserve again first — a fresh execution starts with no reservation (see the cbak in SetHook_test.cpp, "Test emit").
Index
| Function | Purpose |
|---|---|
etxn_reserve | Declare how many transactions this hook will emit. Must precede all other emit calls. |
etxn_nonce | Write a unique nonce for an emitted transaction. |
etxn_details | Write the sfEmitDetails object required in every emitted transaction. |
etxn_fee_base | Compute the minimum fee an emitted transaction must pay. |
etxn_generation | Generation counter that an emitted transaction will carry. |
etxn_burden | Burden value that an emitted transaction will carry. |
emit | Validate and queue a fully-formed transaction; write its hash. |
prepare | Fill in the common emit fields on a template transaction (requires HooksUpdate2). |
Related documents
- ../../README.md — documentation index.
- ../../overview.md — hook execution model, strong/weak/callback executions.
- ../../glossary.md — full error-code and term reference.
- ../../macros.md — the
ENCODE_*,PREPARE_PAYMENT_*,SBUF, andASSERThelpers used to build emitted transactions. - ../../best-practices.md — structuring emissions and callbacks.
- ../../examples/emitted-transaction.md — a full emit-a-payment example.
- control.md —
accept/rollbackand how they keep or discard emissions. - transaction.md —
otxn_generation,otxn_burden, and the other originating-transaction accessors. - ledger-and-slot.md —
fee_base,ledger_seq,ledger_nonce. - float-and-amount.md — building the
sfAmountvalues you emit.