Skip to content

Example: Emitting a Transaction ​

A hook can originate new transactions ("emitted" transactions) that the ledger applies as a consequence of the triggering transaction. This example emits a Payment when a condition is met, and handles the result in the callback cbak.

Purpose ​

  • Reserve emission capacity, build a Payment, compute its fee, and emit it.
  • Receive the emitted transaction's outcome in cbak and react to it.

APIs used ​

APIPurposeReference
etxn_reserveDeclare, up front, how many transactions the hook will emit.Emit APIs
etxn_detailsWrite the EmitDetails object into the transaction template.Emit APIs
etxn_fee_baseCompute the required fee for the prepared transaction.Emit APIs
emitSubmit the built transaction; writes its 32-byte hash.Emit APIs
etxn_generation / etxn_burdenEmit-chain generation / burden bookkeeping.Emit APIs
hook_accountSource account for the emitted Payment.control
otxn_field / otxn_paramRead the trigger (e.g. destination) from the incoming txn.Transaction APIs
accept / rollbackTerminate the hook.Control APIs

The recommended way to build the Payment template is to generate it with the Transaction Builder: paste a Payment transaction JSON in, and it produces a ready-to-paste txn[] template plus a PREPARE_TXN() macro that fills the emit-details, fee, ledger window, and account fields for you. Helper macros still used directly: SBUF, ASSERT.

Processing flow ​

  1. Guard the entry with _g(1,1).
  2. etxn_reserve(n) — reserve exactly the number of transactions you will emit. This is mandatory: emit fails if you have not reserved capacity.
  3. Determine the recipient (here from a HookParameter via otxn_param).
  4. Set the transaction-specific fields on the generated template via its *_OUT pointer macros — SET_ACCOUNT(DEST_OUT, to_acc) for the destination, SET_NATIVE_AMOUNT(AMOUNT_OUT, drops) for the amount — then call PREPARE_TXN(), which embeds sfEmitDetails via etxn_details and prices the template via etxn_fee_base.
  5. emit(SBUF(hash), SBUF(txn)) — on success it returns 32 and writes the emitted transaction's hash.
  6. accept.
  7. When the emitted transaction is later applied (or fails), the ledger invokes the hook's cbak entry point with the outcome.

Complete code example ​

This hook, when invoked, emits a 1-drop Payment to the account named in the bob HookParameter, and confirms in cbak.

c
#include "hookapi.h"

// Generated by the Transaction Builder (https://tx-builder.xahau.tools/) from
// your Payment JSON. It declares `txn[]`, the *_OUT field pointers
// (AMOUNT_OUT, DEST_OUT, ...), the helper macros, and PREPARE_TXN().
// See /tools/tx-builder for the full generated output. Generate it for a hook
// WITH a callback if yours exports cbak (so the emit-details size is 138).

// The callback: invoked with the outcome of each emitted transaction.
int64_t
cbak(uint32_t reserved)
{
    _g(1, 1);
    // reserved carries the result: 0 = the emitted txn was applied successfully.
    // Do any bookkeeping here (e.g. record success in state). We simply accept.
    accept(SBUF("emit: callback"), 0);
    return 0;
}

int64_t
hook(uint32_t reserved)
{
    _g(1, 1);

    // 1. Read the destination AccountID from a HookParameter named "bob".
    uint8_t to[20];
    if (otxn_param(SBUF(to), "bob", 3) != 20)
        rollback(SBUF("emit: no destination param"), 2);

    // 2. Set the transaction-specific fields on the generated template.
    SET_NATIVE_AMOUNT(AMOUNT_OUT, 1);   // 1 drop
    SET_ACCOUNT(DEST_OUT, to);

    // 3. PREPARE_TXN() reserves emission capacity for you (etxn_reserve(1)),
    //    so there is no separate etxn_reserve call here. It also fills
    //    sfAccount, the ledger-sequence window, sfEmitDetails, and sfFee.
    PREPARE_TXN();

    // 4. Emit it. Success returns 32 and writes the emitted txn hash.
    uint8_t hash[32];
    if (emit(SBUF(hash), SBUF(txn)) != 32)
        rollback(SBUF("emit: emit failed"), 3);

    accept(SBUF("emit: ok"), 0);
    return 0;
}

etxn_reserve is called by PREPARE_TXN() (do not call it again yourself — a second call returns ALREADY_SET), the fee is computed by etxn_fee_base inside PREPARE_TXN() (not hardcoded), and the emit return is checked for exactly 32.

Complex example: emitting a Remit ​

Remit is one of the most structurally complex Xahau transactions, which makes it a good stress test for the Transaction Builder. A single Remit can carry an STArray of STObjects (Amounts, each wrapping an AmountEntry), a variable-length vector of Hash256 values (URITokenIDs, serialized as concatenated hashes with no per-entry array markers — unlike Amounts, which uses 0xF0/0xF1/0xE0/0xE1 framing), and a nested STObject (MintURIToken) with its own inner fields — and it can mix a native (XAH/drops) amount with an issued-currency (IOU) amount in the same array. Laying this byte layout out by hand is extremely error-prone; the Transaction Builder generates the whole template plus one pointer macro per leaf field, so you never compute an offset yourself.

Paste this into the Transaction Builder:

json
{
  "TransactionType": "Remit",
  "Amounts": [
    { 
      "AmountEntry": { "Amount": "1000000" }
    },
    { 
      "AmountEntry": {
        "Amount":{ 
          "currency": "USD",
          "issuer": "rExKpRKXNz25UAjbckCRtQsJFcSfjL9Er3",
          "value": "1"
        }
      }
    }
  ],
  "Destination": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
  "URITokenIDs": ["714F206C865D334721B2F3388BEAF33AA91BC1D78C71941D10A2A653C873EDD3"],
  "MintURIToken": {
    "Digest": "6F11A4DF4EE794E2800BB361173D454BFBECB3D7506C4F4CB0EC5AE98BE43747",
    "Flags": 1,
    "URI": "697066733A2F2F"
  }
}

This is the generated output:

c
// clang-format off
uint8_t txn[388] =
{
/* size, upto, field name               */
/*    3,    0, tt = Remit               */   0x12U, 0x00U, 0x5FU,
/*    5,    3, flags                    */   0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
/*    5,    8, sequence                 */   0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
/*    5,   13, destinationtag           */   0x2EU, 0x00U, 0x00U, 0x00U, 0x00U,
/*    6,   18, firstledgersequence      */   0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
/*    6,   24, lastledgersequence       */   0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
/*    9,   30, fee                      */   0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
/*   35,   39, 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,   74, account                  */   0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/*   22,   96, destination              */   0x83U, 0x14U, 0xAEU, 0x12U, 0x3AU, 0x85U, 0x56U, 0xF3U, 0xCFU, 0x91U, 0x15U, 0x47U, 0x11U, 0x37U, 0x6AU, 0xFBU, 0x0FU, 0x89U, 0x4FU, 0x83U, 0x2BU, 0x3DU,
/*    2,  118, amounts                  */   0xF0U, 0x5CU,
/*    2,  120,   .amountentry           */   0xE0U, 0x5BU,
/*    9,  122,     .amount              */   0x61U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0FU, 0x42U, 0x40U,
/*    3,  131,   .amountentry.end       */   0xE1U,
/*    2,  132,   .amountentry           */   0xE0U, 0x5BU,
/*   49,  134,     .amount              */   0x61U, 0xD4U, 0x83U, 0x8DU, 0x7EU, 0xA4U, 0xC6U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x55U, 0x53U, 0x44U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0xA4U, 0x07U, 0xAFU, 0x58U, 0x56U, 0xCCU, 0xF3U, 0xC4U, 0x26U, 0x19U, 0xDAU, 0xA9U, 0x25U, 0x81U, 0x3FU, 0xC9U, 0x55U, 0xC7U, 0x29U, 0x83U,
/*    3,  183,   .amountentry.end       */   0xE1U,
/*    3,  184, .amounts.end             */   0xF1U,
/*    4,  185, uritokenids              */   0x00U, 0x13U, 0x63U, 0x20U,
/*   32,  189,   .uritokenids           */   0x71U, 0x4FU, 0x20U, 0x6CU, 0x86U, 0x5DU, 0x33U, 0x47U, 0x21U, 0xB2U, 0xF3U, 0x38U, 0x8BU, 0xEAU, 0xF3U, 0x3AU, 0xA9U, 0x1BU, 0xC1U, 0xD7U, 0x8CU, 0x71U, 0x94U, 0x1DU, 0x10U, 0xA2U, 0xA6U, 0x53U, 0xC8U, 0x73U, 0xEDU, 0xD3U,
/*    2,  221, minturitoken             */   0xE0U, 0x5CU,
/*   34,  223,   .digest                */   0x50U, 0x15U, 0x6FU, 0x11U, 0xA4U, 0xDFU, 0x4EU, 0xE7U, 0x94U, 0xE2U, 0x80U, 0x0BU, 0xB3U, 0x61U, 0x17U, 0x3DU, 0x45U, 0x4BU, 0xFBU, 0xECU, 0xB3U, 0xD7U, 0x50U, 0x6CU, 0x4FU, 0x4CU, 0xB0U, 0xECU, 0x5AU, 0xE9U, 0x8BU, 0xE4U, 0x37U, 0x47U,
/*    5,  257,   .flags                 */   0x22U, 0x00U, 0x00U, 0x00U, 0x01U,
/*    9,  262,   .uri                   */   0x75U, 0x07U, 0x69U, 0x70U, 0x66U, 0x73U, 0x3AU, 0x2FU, 0x2FU,
/*    3,  271, .minturitoken.end        */   0xE1U,
/*  116,  272, emit details             */ 
/*    0,  388,                          */ 
};
// clang-format on

// TX BUILDER
#define FLAGS_OUT (txn + 4U)
#define DTAG_OUT (txn + 14U)
#define FLS_OUT (txn + 20U)
#define LLS_OUT (txn + 26U)
#define FEE_OUT (txn + 31U)
#define ACCOUNT_OUT (txn + 76U)
#define DEST_OUT (txn + 98U)
#define AMOUNTS_0_AMOUNTENTRY_AMOUNT_OUT (txn + 123U)
#define AMOUNTS_1_AMOUNTENTRY_AMOUNT_OUT (txn + 135U)
#define URITOKENIDS_0_OUT (txn + 189U)
#define MINTURITOKEN_DIGEST_OUT (txn + 225U)
#define MINTURITOKEN_FLAGS_OUT (txn + 258U)
#define MINTURITOKEN_URI_OUT (txn + 263U)
#define EMIT_OUT (txn + 272U)

#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_HASH256(ptr_to, ptr_from)                                          \
  {                                                                            \
    const unsigned char *buf_from = (const unsigned char *)ptr_from;           \
    unsigned char *buf_to = (unsigned char *)ptr_to;                           \
    *(uint64_t *)(buf_to + 0) = *(const uint64_t *)(buf_from + 0);             \
    *(uint64_t *)(buf_to + 8) = *(const uint64_t *)(buf_from + 8);             \
    *(uint64_t *)(buf_to + 16) = *(const uint64_t *)(buf_from + 16);           \
    *(uint64_t *)(buf_to + 24) = *(const uint64_t *)(buf_from + 24);           \
  }

#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 SET_ACCOUNT(ptr_to, ptr_from)                                          \
  {                                                                            \
    COPY_20(ptr_to, ptr_from);                                                 \
  }

#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)

/* 
SET_UINT32(DTAG_OUT, 0);
SET_UINT32(MINTURITOKEN_FLAGS_OUT, 0);
uint8_t hash[32]; 
SET_HASH256(MINTURITOKEN_DIGEST_OUT, hash);
SET_NATIVE_AMOUNT(AMOUNTS_0_AMOUNTENTRY_AMOUNT_OUT, 100);
SET_NATIVE_AMOUNT(AMOUNTS_1_AMOUNTENTRY_AMOUNT_OUT, 100);
uint8_t account_buffer[20]; 
SET_ACCOUNT(DEST_OUT, account_buffer);
SET_HASH256(URITOKENIDS_0_OUT, hash);
PREPARE_TXN(); 
uint8_t emithash[32]; 
int64_t emit_result = emit(SBUF(emithash), SBUF(txn)); 
*/

Reading the template ​

The size, upto, field name comments are annotations from the tool: upto is the byte offset where the field begins, size its length. You never edit these bytes directly. The array and object framing bytes are placed for you: 0xF0/0xF1 open and close the Amounts STArray, and 0xE0/0xE1 open and close each AmountEntry and the MintURIToken STObject. Your job is only to write the leaf values, through the *_OUT pointer macros.

Two precision caveats when reading the annotations:

  • The .end lines (e.g. /* 3, 131, .amountentry.end */ 0xE1U,) are the exception: the tool prints size: 3 next to a single closing byte. Don't trust the printed size there — count bytes yourself.
  • MINTURITOKEN_URI_OUT (txn + 263U) points at the .uri field's length-prefix byte (0x07), not its content, which starts one byte later at offset 264. For a variable-length (Blob) field, the *_OUT macro addresses the prefix, not the value — leave it alone (see "Structure is fixed at generation time" below, which covers why the URI length can't change through this pointer anyway).

Nested-field macro names ​

The tool flattens each leaf field's JSON path into an uppercase pointer-macro name: array indices and nested field/object names are joined with underscores.

JSON pathMacro
Amounts[0].AmountEntry.AmountAMOUNTS_0_AMOUNTENTRY_AMOUNT_OUT
Amounts[1].AmountEntry.AmountAMOUNTS_1_AMOUNTENTRY_AMOUNT_OUT
URITokenIDs[0]URITOKENIDS_0_OUT
MintURIToken.DigestMINTURITOKEN_DIGEST_OUT
MintURIToken.FlagsMINTURITOKEN_FLAGS_OUT
MintURIToken.URIMINTURITOKEN_URI_OUT

JSON values are baked in; placeholders are zeroed ​

Any concrete value you put in the JSON is compiled into the template as literal bytes — here the Destination address, both Amount values (the first is 0x0F4240 = 1000000 at offset 122, matching the JSON string exactly), the URITokenIDs entry, MintURIToken.Digest, MintURIToken.Flags (baked in as 1, bytes 0x22 00 00 00 01 at offset 257 — it's in the JSON), and MintURIToken.URI all appear filled in the array above. Fields left out of the JSON are zeroed for you to set at runtime: the top-level Flags (offset 3 — note this is distinct from MintURIToken.Flags, which is set), DestinationTag, and Sequence. You can either keep a baked-in value or overwrite a same-width field through its *_OUT pointer.

New helper: SET_HASH256 ​

In addition to the helpers from the Transaction Builder page, complex transactions with 32-byte hash fields get a SET_HASH256(ptr, src32) macro (a 32-byte copy). Use it for MintURIToken.Digest and each URITokenIDs entry.

Native vs. IOU amounts — read this carefully ​

The trailing usage stub the tool emits applies SET_NATIVE_AMOUNT to BOTH amount slots, but only the first is correct. Amounts[0].AmountEntry.Amount is a native amount: a 9-byte field (0x61 marker + 8-byte value), so SET_NATIVE_AMOUNT(AMOUNTS_0_AMOUNTENTRY_AMOUNT_OUT, drops) is right. Amounts[1].AmountEntry.Amount is an issued-currency (IOU) amount: a 49-byte field (0x61 marker + a 48-byte value = 8-byte XFL number + 20-byte currency code + 20-byte issuer AccountID). The native/issued distinction lives in a bit inside that first 8-byte value, not in a separate length field — so writing SET_NATIVE_AMOUNT's 8 bytes into this slot doesn't just corrupt the trailing currency/issuer bytes, it flips that bit and makes the deserializer treat the field as 8 bytes long instead of 48. Every byte after it is then misread as the start of the next field, desyncing the rest of the transaction. For an IOU field, either keep the value baked from the JSON, or build a full 48-byte serialized amount (e.g. with float_sto, see Float & Amount APIs) and copy it in — do NOT use SET_NATIVE_AMOUNT on it. If float_sto's output includes its own field header, copy only the trailing 48 bytes past that header into AMOUNTS_1_AMOUNTENTRY_AMOUNT_OUT, not the header byte itself. Treat the auto-generated SET_NATIVE_AMOUNT on AMOUNTS_1_... in the tool's usage comment as a placeholder to replace, not as correct code.

Structure is fixed at generation time ​

The number of Amounts and URITokenIDs entries and the length of the URI blob are baked into the layout (the URI here is 0x75 0x07 + 7 bytes). The *_OUT pointers only let you rewrite values of the same width; to change the shape — add an array entry, use a longer URI — regenerate the template from a JSON with that shape.

PREPARE_TXN() is unchanged ​

No matter how complex the body, PREPARE_TXN() does the same job: etxn_reserve(1), fill the ledger-sequence window, set sfAccount via hook_account, insert sfEmitDetails via etxn_details, then price the whole 388-byte blob with etxn_fee_base and write the fee. As before, do not call etxn_reserve yourself, and regenerate with the 138-byte emit-details size if your hook exports cbak (this template uses 116, i.e. no callback).

c
// 1. Read the destination AccountID from a HookParameter named "bob".
uint8_t to[20];
if (otxn_param(SBUF(to), "bob", 3) != 20)
    rollback(SBUF("emit: no destination param"), 2);

// 2. Read the digest of the URI content being minted, from a HookParameter
//    named "digest". This is MintURIToken.Digest — the content being minted.
uint8_t digest[32];
if (otxn_param(SBUF(digest), "digest", 6) != 32)
    rollback(SBUF("emit: no digest param"), 3);

// 3. Read the ID of an existing, already-minted URIToken being remitted
//    alongside the mint, from a HookParameter named "utid". This is
//    URITokenIDs[0] — unrelated to MintURIToken.Digest above.
uint8_t utid[32];
if (otxn_param(SBUF(utid), "utid", 4) != 32)
    rollback(SBUF("emit: no uritoken id param"), 4);

// 4. Set the transaction-specific fields on the generated template.
//    AMOUNTS_0_AMOUNTENTRY_AMOUNT_OUT and MINTURITOKEN_FLAGS_OUT already
//    carry these exact values baked in from the JSON (1000000 and 1); the
//    calls below are shown only to demonstrate that a baked-in, same-width
//    field can still be overwritten at runtime through its *_OUT pointer.
SET_NATIVE_AMOUNT(AMOUNTS_0_AMOUNTENTRY_AMOUNT_OUT, 1000000); // 1 XAH
SET_ACCOUNT(DEST_OUT, to);
SET_UINT32(MINTURITOKEN_FLAGS_OUT, 1);
SET_UINT32(DTAG_OUT, 0);
SET_HASH256(MINTURITOKEN_DIGEST_OUT, digest);
SET_HASH256(URITOKENIDS_0_OUT, utid);

// Amounts[1].AmountEntry.Amount is an IOU: left as baked in from the JSON
// here. To set it at runtime, build a full 48-byte amount (e.g. via
// float_sto) and copy it into AMOUNTS_1_AMOUNTENTRY_AMOUNT_OUT instead —
// do NOT call SET_NATIVE_AMOUNT on it.

// 5. PREPARE_TXN() reserves emission capacity for you (etxn_reserve(1)).
PREPARE_TXN();

// 6. Emit it. Success returns 32 and writes the emitted txn hash.
uint8_t hash[32];
if (emit(SBUF(hash), SBUF(txn)) != 32)
    rollback(SBUF("emit: emit failed"), 5);

Variations ​

Emit conditionally. Wrap the reserve/build/emit block in a condition (for example, only emit when the incoming amount exceeds a threshold). Call etxn_reserve(1) only on the path that actually emits, or reserve 0 otherwise.

Emit more than one. Reserve the total up front — etxn_reserve(2) — then call emit twice with two distinct templates. Emitting more than you reserved returns TOO_MANY_EMITTED_TXN (-13).

Building the template. The recommended path is to generate the template with the Transaction Builder, as above. If you are not using the Transaction Builder, the manual sequence is: encode the transaction fields into a buffer, call etxn_details(details_ptr, len) to write the EmitDetails block, call etxn_fee_base(SBUF(tx)) to get the fee, patch the fee field, then emit. The prepare API (requires featureHooksUpdate2) can fill the common emit fields on a template for you — see Emit APIs.

Chained emissions in cbak. A callback can itself reserve and emit further transactions. Each hop increases the emit generation and burden; check etxn_generation() / etxn_burden() if your logic depends on chain depth.

Caveats ​

  • Emitted transactions have a null signing key and are authorized by the ledger's emission rules, not by a signature — you cannot emit on behalf of an account the hook is not installed on.
  • The emitted transaction is not applied during the current hook execution; it is queued and applied afterward, and only then does cbak run.
  • HookCanEmit and related amendments gate emission behavior; confirm the network you target has emission enabled for your hook's context.
  • The fee must come from etxn_fee_base for the exact template you emit. A fee that is too small is rejected; an absurdly large one returns FEE_TOO_LARGE (-10).

Common mistakes ​

  • Forgetting etxn_reserve. Calling emit without a prior reservation fails. Reserve the exact count before building.
  • Hardcoding the fee. Fees depend on the transaction size and network load; a stale constant will under- or over-pay. Always use etxn_fee_base (which PREPARE_TXN() calls for you) and re-run it if you change the template.
  • Computing the fee before etxn_details. The EmitDetails block is part of the transaction, so it must be present before etxn_fee_base measures it. PREPARE_TXN() orders these correctly; hand-built templates must too.
  • Ignoring EMISSION_FAILURE (-11). emit returning this (rather than 32) means the ledger rejected the emitted transaction; check the return and handle it instead of assuming success.
  • Reserving too few. Reserve 1 but emit twice and the second emit returns TOO_MANY_EMITTED_TXN (-13).
  • Expecting the emitted transaction's effects immediately. They apply after the current transaction; read results in cbak, not inline.