Indexer Hardening
Draft, pending SDF review
This guide has not yet been reviewed by a stellar-core or platform engineer. The canonical copy will be on developers.stellar.org once the review is done.
Most of a transaction envelope is chosen by whoever submitted the transaction. stellar-core checks some of it, checks some of it only in part, and passes the rest through to the ledger unchanged. An indexer that trusts an unchecked field, decodes without limits, or reads participants from the envelope can store wrong data or stop ingesting on a single transaction.
This page covers, in order: which envelope fields core validates, field by field; depth, length and count caps that never refuse data the network could have produced; why participants come from transaction meta and not from the envelope; how to store every strkey form defined by SEP-23, including muxed and signed-payload addresses; and a checklist for auditing an indexer written in any language.
The field table, the caps table and the checklist are generated from the shapekit registry, where every row cites the stellar-core v30.0.0, stellar-xdr v30.0 or soroban-env-host v30.0.0 source it was read from. The field table is checked against the shapekit corpus manifest, which records the corpus entries that exercise each field. "Protocol" on this page always means the protocol version of the ledger that holds the data.
This page assumes you already read ledgers, for example with the Ingest SDK or the Token Transfer Processor. For how muxed accounts and memos are used by applications, see Pooled accounts: muxed accounts and memos, and for converting between address types in the SDKs, see Convert an address to other types.
Validated and unvalidated fields
The table below gives one of three answers for each field:
- yes: core rejects the transaction, or fails the operation, when the value is wrong.
- partial: core checks some properties of the value, or the check happens later inside the Soroban host and only on some paths.
- no: core passes the value through without looking at its content.
A "yes" does not make a value true. Many checks run when the transaction is applied, and they only fail that transaction or operation. A ledger still carries the failed transaction: LedgerCloseMetaV2 holds the whole transaction set and one TransactionResultMetaV1 per transaction with its result (Stellar-ledger.x, LedgerCloseMetaV2). From protocol 13 core stops checking signatures of a transaction that already failed validation, so a failed transaction can carry signatures that were never verified (TransactionFrame::processSignatures). Treat an envelope value as known good only when the transaction result and the operation result are successes.
Fields core never checks
Mux ids. Core reduces every MuxedAccount to its ed25519 key with toAccountID, which drops the 64-bit id (toAccountID). Outside tests the id is read only to copy it into classic and Soroban events (makeMuxIDSCVal, makeMuxedAccountAddress) and into core's JSON output (XDRCereal.cpp). It is never compared, looked up or stored in a ledger entry, so a mux id is any 64-bit value the submitter chose, with no account behind it. The only protocol rule about muxed accounts is their key type: before protocol 13 core rejects a transaction that contains any muxed account with txNOT_SUPPORTED (MuxChecker, TransactionFrame::commonValidPreSeqNum).
Memos. MEMO_TEXT is limited only by its XDR type, string text<28>, which bounds bytes, not characters (Memo). Core runs no UTF-8 or content check on it. MEMO_ID, MEMO_HASH and MEMO_RETURN are not checked either; a MEMO_RETURN hash need not name any transaction. Core copies the memo of a classic payment to a plain account into the to_muxed_id field of the transfer or mint event, as a string, a u64 or bytes depending on the memo type (makeClassicMemoSCVal, getPossibleMuxedData), so the same unchecked bytes reappear inside event data.
Data entry values. ManageDataOp.dataValue is up to 64 opaque bytes. Core copies it into the data entry as it is (ManageDataOpFrame::doApply).
Fields core checks only in part
Signed payloads. A signed-payload signer key carries 1 to 64 payload bytes: the upper bound comes from XDR and core rejects an empty payload (TransactionFrame::commonValidPreSeqNum, SetOptionsOpFrame::doCheckValid). The payload content is never checked.
Soroban footprints. Core limits the key types, key sizes and entry counts of a footprint and rejects duplicate keys, but the keys need not exist, and the host only enforces that the contract stays inside the footprint (TransactionFrame::checkSorobanResources, invoke_host_function). A footprint is what the submitter allowed a contract to touch, not what it touched.
Soroban authorization entries. Core never inspects auth entries; it serializes them and passes them to the Soroban host (invokeHostFunction). The host converts every entry to host objects at the start of execution, so an entry that cannot be converted fails the operation. It checks signatures, nonces and expiration only for an entry that a require_auth call matches, and nothing checks that every entry was used (require_auth_enforcing, maybe_authorize_invocation). An entry that no call matches can name any address with any signature and still appear in a successful transaction. Source-account credentials carry no signature at all and rest on the transaction signatures (authenticate).
Contract call arguments. Core checks only that the envelope parses within the XDR depth limit and fits the transaction size limit. The host converts arguments when it runs the call and fails the operation on a map whose keys are not strictly sorted (validateXDRForProtocol, MeteredOrdMap::from_map_with_host). A failed invocation can therefore carry unsorted or colliding map keys.
Assets, claimants and names. From protocol 13 core does not check that an asset issuer exists (PathPaymentOpFrameBase::checkIssuer). Claimable balance claimants must be unique within the operation but need not exist (CreateClaimableBalanceOpFrame::doCheckValid). Home domains and data entry names must be printable ASCII, and nothing more (isStringValid).
Ordering rules
Liquidity pool assets order by raw bytes. A pool-share trustline requires assetA < assetB (isPoolShareAssetValid). The comparison is by asset type, then the asset code as raw bytes including zero padding, then the issuer as its raw 32 key bytes, and a pair in the wrong order fails with CHANGE_TRUST_MALFORMED. Strkey text order is different, because base32 puts the digits 2 to 7 before A to Z in ASCII. With the same asset code, an issuer of 32 zero bytes (GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF) comes first by bytes, while an issuer of 32 bytes of 0x3a (GA5DUOR2HI5DUOR2HI5DUOR2HI5DUOR2HI5DUOR2HI5DUOR2HI5DVTUJ) comes first as text. js-stellar-sdk sorted issuers as text until version 17.2.1 and rejected valid pairs (js-stellar-sdk#1766).
ScMap keys order by the Soroban host's comparison, not by XDR bytes. The host requires map keys to be strictly increasing (from_map_with_host). Its comparison orders values of different types by their ScValType discriminant, then compares bytes, strings and symbols bytewise, vectors and maps element by element, and numbers by value (Compare<ScVal>). XDR byte order differs: a length prefix puts symbol "b" before "aa", and two's complement puts -1 after 1 (js-stellar-sdk#1766, RFC 4506). In host order, symbols b, aa, a sort to a, aa, b, i128 keys 1, -1, 0, -200 sort to -200, -1, 0, 1, and bytes keys [10], [9], [1,2], [2] sort to [1,2], [2], [9], [10]. Use this order in any code that builds, re-encodes or checks a map.
Signatures and hints
A signed-payload signature is not bound to a network or a transaction. For a signed-payload signer core verifies an ed25519 signature over the raw payload bytes, with no network ID and no hashing (verifyEd25519SignedPayload). A transaction signature, by contrast, signs a hash of the network ID, the envelope type and the transaction (TransactionFrame::getContentsHash). The same signed-payload signature therefore satisfies the same signer on every network and in every transaction that lists it.
Hints are a filter, not an identity. Core tries a signer only when the 4-byte hint of a signature matches it (SignatureUtils::verify). For a signed-payload signer the expected hint is the last 4 bytes of the ed25519 key XORed with a 4-byte hint of the payload (getSignedPayloadHint). The payload hint is the last 4 payload bytes when the payload has 4 or more bytes; a payload of 1 to 3 bytes is copied into the start of the hint instead (getHint). Two signers can share a hint, so never attribute a signature to a signer by its hint.
Soroban memo and muxed source rule
From protocol 25 core rejects with txSOROBAN_INVALID a transaction whose single INVOKE_HOST_FUNCTION operation comes with a memo, a muxed transaction source or a muxed operation source (TransactionFrame::validateSorobanMemo, TransactionFrame::commonValidPreSeqNum). Before protocol 25 the same check exists only as an admission policy of a node's transaction queue (TransactionQueue::canAdd), which is not a consensus rule, so ledgers before protocol 25 can contain Soroban invocations with a memo or a muxed source. Extend and restore transactions are not covered by the rule.
Field table
Each row lists what core checks, the risk of trusting the value, how to handle it, the source the row was read from, and how many corpus entries exercise the field.
88 fields: 52 validated, 25 partly validated, 11 not validated by stellar-core.
| Field | Core validates | What core checks | Risk if trusted, and handling | Source | Corpus |
|---|---|---|---|---|---|
tx.envelope.typeTransactionEnvelope.typeall protocols | yes | Before protocol 13 any ENVELOPE_TYPE_TX or any muxed account anywhere in the envelope gives txNOT_SUPPORTED; from protocol 13 ENVELOPE_TYPE_TX_V0 gives txNOT_SUPPORTED. fee bump envelopes give txNOT_SUPPORTED before protocol 13. every envelope must also parse with the rust xdr parser of the ledger protocol (depth 500 c++ check before protocol 28, rust depth 1000 from 28) or it is txMALFORMED. | Risk: An indexer that only handles v1 envelopes drops or crashes on v0 envelopes in pre-13 history and on fee bumps from 13 on. Handling: Switch on all three arms; treat v0 as history-only and fee bump as a wrapper whose inner v1 transaction carries the operations. | TransactionFrame::commonValidPreSeqNumFeeBumpTransactionFrame::commonValidPreSeqNumvalidateXDRForProtocolTransactionEnvelope | 8 |
tx.v0.source_account_ed25519TransactionV0Envelope.tx.sourceAccountEd25519protocols 0 to 12 | yes | Core rebuilds an AccountID from the raw 32 bytes and applies the same existence and signature checks as for v1 sources; the whole v0 arm is rejected from protocol 13. | Risk: v0 has no key type discriminant, so code that expects a MuxedAccount here misreads the bytes. Handling: Wrap the 32 bytes as an ed25519 account id (G strkey); never expect a mux id in v0. | TransactionFrame::getSourceIDTransactionV0 | 7 |
tx.signaturesTransactionV1Envelope.signaturesall protocols | yes | At most 20 by xdr. source account signers must reach the low threshold (txBAD_AUTH), each operation source must reach its op threshold (opBAD_AUTH), and from protocol 19 every extra signer must be satisfied. every signature must be used by some signer or the transaction is txBAD_AUTH_EXTRA. from protocol 28 at most 1000 ed25519 verifications per transaction. at apply, from protocol 13, signature checking stops early when the transaction already failed validation, so a failed transaction can carry signatures that were never verified. | Risk: Signatures on a failed transaction are not proof of anything; a signature count says nothing about which signer signed. Handling: Derive signer participation from signer changes in meta, not from the signature list; only treat signatures as verified on successful transactions. | SignatureChecker::checkSignatureSignatureChecker::checkAllSignaturesUsedTransactionFrame::processSignaturesTransactionFrame::checkAllTransactionSignatures | 16 |
tx.signatures.hintDecoratedSignature.hintall protocols | partial | The hint is only a filter: core tries a signer only when the hint equals the last 4 bytes of the ed25519 key (or of the hash-x hash). for a signed-payload signer the expected hint is the last 4 bytes of the key xor the payload hint; getHint copies the last 4 payload bytes, or, for a payload shorter than 4 bytes, the payload into the start of the hint. pre-auth-tx signers use no signature and no hint. | Risk: 4 bytes are not unique; two signers can share a hint, so a hint does not identify the signer. Handling: Never key or attribute signers by hint; if a signer must be attributed, verify the signature against candidate keys yourself. | SignatureUtils::verifySignatureUtils::getSignedPayloadHintSignatureUtils::getHintSignatureUtils::doesHintMatch | 6 |
tx.signatures.signatureDecoratedSignature.signatureall protocols | yes | ed25519 signers: ed25519 verify over the transaction contents hash, sha256(networkID, envelope type, tx). hash-x signers: sha256(signature) must equal the hash, so the signature field is the preimage (up to 64 bytes). signed-payload signers: ed25519 verify over the raw payload bytes, with no network id and no hashing. | Risk: A signed-payload signature is not bound to any network or transaction; hash-x signatures reveal a preimage, not a signature. Handling: Do not treat a signed-payload signature as transaction-specific or network-specific; store signature bytes as opaque, up to 64 bytes. | SignatureUtils::verifyEd25519SignedPayloadSignatureUtils::verifyHashXTransactionFrame::getContentsHashSignature | none |
tx.source_accountTransaction.sourceAccountall protocols | yes | Core reduces the MuxedAccount to its ed25519 key with toAccountID; that account must exist (txNO_ACCOUNT) and its signers must reach the low threshold (txBAD_AUTH); it pays the fee and its sequence number is consumed. | Risk: The claimed source is real, but it is not the full list of affected accounts. Handling: Use the base key (G) as the account identity; take the full participant set from meta changes and events. | TransactionFrame::commonValidPreSeqNumTransactionFrame::commonValidtoAccountID | 18 |
tx.source_account.mux_idTransaction.sourceAccount(KEY_TYPE_MUXED_ED25519).med25519.idprotocol 13 and later | no | Never compared, never looked up, never stored in a ledger entry. toAccountID discards it. outside tests the only reads of med25519().id copy it into classic and soroban events (makeMuxedAccountAddress, makeMuxIDSCVal) and the json dumper. before protocol 13 any muxed account makes the transaction txNOT_SUPPORTED (hasMuxedAccount checks only the key type). from protocol 25 a soroban invoke transaction with a muxed source is txSOROBAN_INVALID. | Risk: Any 64-bit value, chosen by the submitter, with no account behind it. trusting it as an account id, or storing a muxed address in a 32-byte column, truncates or fabricates identities (wallet-backend#716). Handling: Store the base account and the mux id separately, the id as an unsigned 64-bit value; never treat an M address as an account that exists; key balances by the base account. | toAccountIDmakeMuxIDSCVal, makeMuxedAccountAddressdetail::MuxCheckerTransactionFrame::validateSorobanMemoMuxedAccount | 19 |
tx.feeTransaction.feeall protocols | yes | Inclusion fee (fee minus declared soroban resource fee) must be at least baseFee times max(1, op count) (txINSUFFICIENT_FEE), and the source must afford it above reserve (txINSUFFICIENT_BALANCE). the fee actually charged is computed at apply, min(inclusion fee, baseFee times ops) plus the resource fee, then soroban refunds; it is not the envelope value. | Risk: The envelope fee is a bid, not what was paid. Handling: Read feeCharged from the transaction result and fee changes from meta. | TransactionFrame::commonValidPreSeqNumTransactionFrame::getFeeTransactionFrame::processFeeSeqNumgetMinInclusionFee | none |
tx.seq_numTransaction.seqNumall protocols | yes | Must be account seqNum + 1, or, from protocol 19 with minSeqNum set, account seqNum in [minSeqNum, tx.seqNum); a seqNum equal to the ledger starting sequence (ledgerSeq << 32) or INT64_MAX is rejected (txBAD_SEQ). on apply the account seqNum is set to tx.seqNum. | Risk: low; the value is checked. gaps are legal with minSeqNum, so seqNum is not always previous + 1. Handling: Safe to store; do not assume contiguity per account. | TransactionFrame::isBadSeqTransactionFrame::processSeqNum | 3 |
tx.cond.typeTransaction.cond.typeprotocol 13 and later | yes | PRECOND_V2 before protocol 19 gives txNOT_SUPPORTED; PRECOND_TIME and PRECOND_NONE are accepted in all v1 envelopes. | Risk: Low. Handling: Handle all three arms; v0 envelopes carry an optional TimeBounds instead. | TransactionFrame::commonValidPreSeqNumPreconditions | 8 |
tx.cond.time_boundsTransaction.cond(PRECOND_TIME).timeBounds | Transaction.cond(PRECOND_V2).v2.timeBoundsall protocols | yes | minTime must be <= ledger close time (txTOO_EARLY), maxTime 0 means unbounded, otherwise maxTime must be >= close time (txTOO_LATE). minTime > maxTime is not rejected as malformed, it just can never be valid. | Risk: Low. Handling: Safe to store; treat maxTime 0 as no bound. | TransactionFrame::isTooEarlyTransactionFrame::isTooLateTransactionFrame::getTimeBounds | 2 |
tx.cond.ledger_boundsTransaction.cond(PRECOND_V2).v2.ledgerBoundsprotocol 19 and later | yes | From protocol 19: minLedger must be <= ledger seq (txTOO_EARLY); maxLedger 0 means unbounded, otherwise ledger seq must be < maxLedger (txTOO_LATE). | Risk: Low. Handling: Safe to store; maxLedger is exclusive. | TransactionFrame::isTooEarlyTransactionFrame::isTooLate | 4 |
tx.cond.min_seq_numTransaction.cond(PRECOND_V2).v2.minSeqNumprotocol 19 and later | yes | From protocol 19, when set, relaxes the sequence check to minSeqNum <= account seqNum < tx.seqNum. | Risk: An indexer that assumes seqNum = previous + 1 sees gaps. Handling: Do not reconstruct account sequence numbers from envelopes; read them from meta. | TransactionFrame::isBadSeq | 2 |
tx.cond.min_seq_ageTransaction.cond(PRECOND_V2).v2.minSeqAgeprotocol 19 and later | yes | From protocol 19, close time minus minSeqAge must be >= the source account seqTime (AccountEntryExtensionV3, 0 when absent), else txBAD_MIN_SEQ_AGE_OR_GAP. | Risk: Low. Handling: Safe to store. | TransactionFrame::isTooEarlyForAccount | 4 |
tx.cond.min_seq_ledger_gapTransaction.cond(PRECOND_V2).v2.minSeqLedgerGapprotocol 19 and later | yes | From protocol 19, ledger seq minus minSeqLedgerGap must be >= the source account seqLedger, else txBAD_MIN_SEQ_AGE_OR_GAP. | Risk: Low. Handling: Safe to store. | TransactionFrame::isTooEarlyForAccount | 6 |
tx.cond.extra_signersTransaction.cond(PRECOND_V2).v2.extraSignersprotocol 19 and later | yes | At most 2 by xdr; two identical entries give txMALFORMED; a signed-payload signer with an empty payload gives txMALFORMED; from protocol 19 each extra signer needs a matching signature (weight 1 each, needed weight = count), else txBAD_AUTH. the signer keys need not belong to any account. | Risk: Extra signers are arbitrary keys, not accounts; a P (signed payload) signer carries up to 64 payload bytes that a 32-byte column truncates. Handling: Store signer keys with their type and full payload; do not treat them as participants unless meta shows them. | TransactionFrame::commonValidPreSeqNumTransactionFrame::checkExtraSignersPreconditionsV2 | 7 |
signer_key.signed_payload.payloadSignerKey(SIGNER_KEY_TYPE_ED25519_SIGNED_PAYLOAD).ed25519SignedPayload.payloadprotocol 19 and later | partial | Length 1 to 64: the 64 upper bound is xdr, the empty check is core (txMALFORMED in extraSigners, SET_OPTIONS_BAD_SIGNER in set options). content is never checked. the signature is ed25519 over these raw bytes, not over the transaction, not bound to the network. | Risk: Variable length payload: storing it in a fixed 32-byte column truncates it and makes the row undecodable (wallet-backend#716, #729); a valid signature can be replayed on any network. Handling: Store the full payload with its length; encode as a P strkey (SEP-23) when rendering. | TransactionFrame::commonValidPreSeqNumSetOptionsOpFrame::doCheckValidSignatureUtils::verifyEd25519SignedPayloadSignerKey | 38 |
tx.memo.typeTransaction.memo.typeall protocols | partial | Any memo type is accepted for classic transactions. from protocol 25 a soroban transaction whose single operation is INVOKE_HOST_FUNCTION must use MEMO_NONE (txSOROBAN_INVALID) as a consensus rule; extend and restore transactions may carry a memo. before protocol 25 the same rule is only a transaction queue admission policy of the receiving node, so ledgers before 25 can contain soroban invocations with a memo or a muxed source. | Risk: Low for the type itself; the memo values below are unvalidated. Handling: Switch on all five arms. | TransactionFrame::validateSorobanMemoTransactionFrame::commonValidPreSeqNumTransactionQueue::canAdd | 27 |
tx.memo.textTransaction.memo(MEMO_TEXT).textall protocols | no | XDR length limit 28 bytes only; no utf-8, character set or content check. searched src/ outside tests for memo handling: the only use is makeClassicMemoSCVal, which copies the bytes into an SCV_STRING in the to_muxed_id field of transfer and mint events. | Risk: Arbitrary bytes up to 28: invalid utf-8, nul bytes, control characters. decoding as a utf-8 string can throw, replace bytes (js-stellar-sdk#1823) or break a text column; the same bytes reappear in event data. Handling: Store as bytes; if a string is needed, validate utf-8 explicitly and keep the raw bytes alongside; never use it as a key or a destination identifier. | MemomakeClassicMemoSCValgetPossibleMuxedData | 12 |
tx.memo.idTransaction.memo(MEMO_ID).idall protocols | no | Any uint64; no check. copied into event to_muxed_id when the destination is a plain account. | Risk: submitter-chosen; values above 2^63-1 overflow signed 64-bit columns and above 2^53-1 lose precision as js numbers. Handling: Store as unsigned 64-bit (or decimal string); never treat as an account reference. | MemomakeClassicMemoSCVal | 7 |
tx.memo.hashTransaction.memo(MEMO_HASH).hashall protocols | no | 32 opaque bytes by xdr; no check. | Risk: Arbitrary bytes; it is not a hash of anything core knows. Handling: Store as 32 raw bytes or hex. | MemomakeClassicMemoSCVal | 6 |
tx.memo.returnTransaction.memo(MEMO_RETURN).retHashall protocols | no | 32 opaque bytes by xdr; core does not check that it names a real transaction. | Risk: A fabricated reference to a transaction that may not exist. Handling: Store as 32 raw bytes; do not join on it without checking the referenced transaction exists. | MemomakeClassicMemoSCVal | 3 |
tx.operationsTransaction.operationsall protocols | yes | At most 100 by xdr; empty gives txMISSING_OPERATION; soroban and classic operations cannot be mixed and a soroban transaction has exactly one operation (txMALFORMED); soroban operations before protocol 20 are txMALFORMED. | Risk: Low. Handling: safe; per-operation fields are covered by their own rows. | TransactionFrame::commonValidPreSeqNumTransactionFrame::validateSorobanOpsConsistency | 4 |
tx.extTransaction.extprotocol 20 and later | yes | A soroban transaction must use ext v1 with sorobanData (txMALFORMED otherwise); from protocol 21 a classic transaction with ext v1 is txMALFORMED. | Risk: Low. Handling: Read sorobanData only when ext.v is 1. | TransactionFrame::XDRProvidesValidFeeTransactionFrame::commonValidPreSeqNum | 10 |
tx.soroban.resource_feeTransaction.ext(1).sorobanData.resourceFeeprotocol 20 and later | yes | Must be in [0, 2^50] (txMALFORMED); must be <= tx.fee (from protocol 23 only when the fee is charged, so a fee bump inner transaction may exceed its own fee); must be >= the computed non-refundable plus refundable resource fee (txSOROBAN_INVALID). | Risk: declared, not charged; the refundable part is returned. Handling: Use feeCharged and the soroban fee fields in meta for what was paid. | TransactionFrame::XDRProvidesValidFeeTransactionFrame::commonValidPreSeqNum | 6 |
tx.soroban.resources.limitsTransaction.ext(1).sorobanData.resources.{instructions,diskReadBytes,writeBytes}protocol 20 and later | yes | Each must be <= the network per-transaction limit (txSOROBAN_INVALID); the whole transaction size must be <= txMaxSizeBytes. they are upper bounds enforced during execution, not consumption. | Risk: Treating declared resources as usage overstates cost. Handling: Read actual consumption from soroban meta and diagnostic or metrics events. | TransactionFrame::checkSorobanResources | none |
tx.soroban.footprintTransaction.ext(1).sorobanData.resources.footprint.{readOnly,readWrite}protocol 20 and later | partial | Key types limited to ACCOUNT, TRUSTLINE (valid non-native asset, account not issuer), CONTRACT_DATA, CONTRACT_CODE; each key xdr size <= maxContractDataKeySizeBytes; entry counts within network limits (from protocol 23 total entries <= txMaxFootprintEntries); no duplicate keys within or across the two sets. keys need not exist, and the host enforces only that accesses stay inside the footprint. | Risk: The footprint is what the submitter allowed the contract to touch, not what it touched; keys can name entries that do not exist or were never read or written. Handling: Derive touched entries and participants from ledger entry changes in meta, never from the footprint. | TransactionFrame::checkSorobanResourcesTransactionFrame::commonValidPreSeqNuminvoke_host_function | 15 |
tx.soroban.ext.archived_soroban_entriesTransaction.ext(1).sorobanData.ext(1).resourceExt.archivedSorobanEntriesprotocol 23 and later | yes | Ext v1 before protocol 23 is txSOROBAN_INVALID; indexes must be strictly ascending, < readWrite length, and point at persistent CONTRACT_DATA or CONTRACT_CODE keys. | Risk: low; it marks entries the submitter claims are archived, the restore itself shows in meta. Handling: Use meta restore changes to learn what was restored. | TransactionFrame::checkSorobanResources | 1 |
feebump.fee_sourceFeeBumpTransaction.feeSourceprotocol 13 and later | yes | Reduced to its base key; must exist (txNO_ACCOUNT), its signers must reach the low threshold over the fee bump contents hash (txBAD_AUTH), must afford the fee (txINSUFFICIENT_BALANCE). | Risk: The fee source pays but is not the inner transaction source. Handling: Record the fee payer separately from the transaction source. | FeeBumpTransactionFrame::commonValidPreSeqNumFeeBumpTransactionFrame::commonValidFeeBumpTransactionFrame::getFeeSourceID | 6 |
feebump.fee_source.mux_idFeeBumpTransaction.feeSource(KEY_TYPE_MUXED_ED25519).med25519.idprotocol 13 and later | no | Discarded by toAccountID in getFeeSourceID; not checked. | Risk: submitter-chosen 64-bit value with no account behind it (stellar-etl#477 historical fee_account_muxed rows). Handling: Store base account and mux id separately. | FeeBumpTransactionFrame::getFeeSourceIDtoAccountID | 2 |
feebump.feeFeeBumpTransaction.feeprotocol 13 and later | yes | Must be >= 0 (txMALFORMED); inclusion fee must be >= baseFee times (inner ops + 1); the per-op inclusion rate must be >= the inner rate (txINSUFFICIENT_FEE), and the inner inclusion fee must be >= 0 except, from protocol 23, for soroban inner transactions (txFEE_BUMP_INNER_FAILED). | Risk: A bid; the inner transaction fee is not what was paid. Handling: Use feeCharged from the fee bump result. | FeeBumpTransactionFrame::XDRProvidesValidFeeFeeBumpTransactionFrame::commonValidPreSeqNum | 6 |
feebump.signaturesFeeBumpTransactionEnvelope.signaturesprotocol 13 and later | yes | Checked against the fee source over sha256(networkID, ENVELOPE_TYPE_TX_FEE_BUMP, feeBump.tx); every signature must be used (txBAD_AUTH_EXTRA). | Risk: Outer and inner signature lists sign different hashes. Handling: Keep outer and inner signatures apart. | FeeBumpTransactionFrame::checkValidImplFeeBumpTransactionFrame::getContentsHash | 6 |
feebump.inner_txFeeBumpTransaction.innerTx(ENVELOPE_TYPE_TX).v1protocol 13 and later | yes | Only the ENVELOPE_TYPE_TX arm exists in xdr. core rebuilds a v1 envelope and runs the full transaction validation on it without charging its fee, so inner signatures are checked over the inner contents hash and every inner field row applies. | Risk: Using the outer hash for the inner transaction, or the inner fee as paid. Handling: Index the inner transaction with both hashes (outer full hash and inner hash) and the outer fee source as payer. | FeeBumpTransactionFrame::convertInnerTxToV1FeeBumpTransactionFrame::checkValidImplFeeBumpTransaction | 9 |
op.source_accountOperation.sourceAccountall protocols | yes | optional; when absent the transaction source is used. reduced to its base key; must exist (opNO_ACCOUNT) and its signers must reach the operation threshold (opBAD_AUTH). | Risk: An operation source is a real account, but not every affected account. Handling: Use the base key; resolve the absent case to the transaction source. | OperationFrame::checkSignatureOperationFrame::checkValidOperationFrame::getSourceID | 7 |
op.source_account.mux_idOperation.sourceAccount(KEY_TYPE_MUXED_ED25519).med25519.idprotocol 13 and later | no | Discarded by toAccountID; copied into events only. from protocol 25 a muxed operation source on INVOKE_HOST_FUNCTION is txSOROBAN_INVALID; the soroban host is given only the base AccountID. | Risk: Same as the transaction source mux id; a muxed operation source plus its base account can collide as two keys for one account (wallet-backend#753). Handling: Store base account and mux id separately; de-duplicate participants by base account. | OperationFrame::getSourceIDTransactionFrame::validateSorobanMemoInvokeHostFunctionApplyHelper::invokeHostFunction | 2 |
op.create_account.destinationCreateAccountOp.destinationall protocols | yes | Must differ from the source (CREATE_ACCOUNT_MALFORMED); must not exist at apply (CREATE_ACCOUNT_ALREADY_EXIST). | Risk: Low on success. Handling: Confirm creation from the account entry created in meta. | CreateAccountOpFrame::doCheckValidCreateAccountOpFrame::doApply | 5 |
op.create_account.starting_balanceCreateAccountOp.startingBalanceall protocols | yes | Must be >= 0 from protocol 14, > 0 before (CREATE_ACCOUNT_MALFORMED); reserve and source balance checked at apply. | Risk: Low. Handling: Safe on success; 0 is legal from protocol 14 with sponsorship. | CreateAccountOpFrame::doCheckValid | 3 |
op.payment.destinationPaymentOp.destinationall protocols | yes | Base key must exist at apply (PAYMENT_NO_DESTINATION through the internal path payment) and hold a trustline for non-native assets; a native payment to self succeeds instantly. | Risk: On a failed payment the destination may not exist at all. Handling: Only treat the destination as a participant when the payment succeeded or meta shows a change. | PaymentOpFrame::doApplyPathPaymentStrictReceiveOpFrame::doApply | 42 |
op.payment.destination.mux_idPaymentOp.destination(KEY_TYPE_MUXED_ED25519).med25519.idprotocol 13 and later | no | Reduced away by toAccountID; copied into the transfer event as to_muxed_id (protocol 23 event format). | Risk: An arbitrary id that does not correspond to anything on ledger; truncating M addresses to 32 bytes loses it (wallet-backend#716). Handling: Store base account plus unsigned 64-bit id; read the id from the event when present. | PaymentOpFrame::doApplygetPossibleMuxedData | 16 |
op.payment.assetPaymentOp.assetall protocols | partial | isAssetValid: alphanum4 code 1 to 4 and alphanum12 code 5 to 12 ascii [a-zA-Z0-9] characters with only trailing zero padding (PAYMENT_MALFORMED). from protocol 13 the issuer is not checked for existence. | Risk: The issuer account may not exist; codes compare by raw bytes including padding. Handling: Key assets by (type, raw code bytes, issuer key bytes), not by display string. | PaymentOpFrame::doCheckValidisAssetValidPathPaymentOpFrameBase::checkIssuer | 17 |
op.payment.amountPaymentOp.amountall protocols | yes | Must be > 0 (PAYMENT_MALFORMED); balance, limits and authorization checked at apply. | Risk: low; int64 stroops lose precision as float64 (stellar-etl#454). Handling: Store as int64 or decimal, never float. | PaymentOpFrame::doCheckValid | 6 |
op.path_payment.destinationPathPaymentStrictReceiveOp.destination | PathPaymentStrictSendOp.destinationall protocols | yes | Base key must exist at apply (NO_DESTINATION) unless the issuer bypass applies; trustline checked when crediting. | Risk: As for payment destination. Handling: As for payment destination. | PathPaymentStrictReceiveOpFrame::doApplyPathPaymentStrictSendOpFrame::doApplyPathPaymentOpFrameBase::getDestID | 31 |
op.path_payment.destination.mux_idPathPaymentStrictReceiveOp.destination(KEY_TYPE_MUXED_ED25519).med25519.id | PathPaymentStrictSendOp.destination(KEY_TYPE_MUXED_ED25519).med25519.idprotocol 13 and later | no | Discarded by getDestID (toAccountID); copied into the transfer event. | Risk: As for payment mux id. Handling: As for payment mux id. | PathPaymentOpFrameBase::getDestIDtoAccountID | 7 |
op.path_payment.amountsPathPaymentStrictReceiveOp.{sendMax,destAmount} | PathPaymentStrictSendOp.{sendAmount,destMin}all protocols | yes | All must be > 0 (MALFORMED); sendMax and destMin are limits, the exchanged amounts come from the result. | Risk: sendMax and destMin are bounds, not amounts moved. Handling: Take actual amounts from the operation result (claim atoms and last amount) or from events. | PathPaymentStrictReceiveOpFrame::doCheckValidPathPaymentStrictSendOpFrame::doCheckValid | 27 |
op.path_payment.pathPathPaymentStrictReceiveOp.path | PathPaymentStrictSendOp.pathall protocols | partial | At most 5 by xdr; each asset must pass isAssetValid (MALFORMED); send, dest and path assets are all checked with isAssetValid. distinctness and existence of order books are not checked. | Risk: The path is the submitter's route request; hops with no liquidity or repeated assets are legal and the trades actually made are only in the result. Handling: Derive trades from claim atoms in the result and from meta, not from the path. | PathPaymentStrictReceiveOpFrame::doCheckValidPathPaymentStrictSendOpFrame::doCheckValidPathPaymentStrictReceiveOp | 6 |
op.manage_offer.offer_idManageSellOfferOp.offerID | ManageBuyOfferOp.offerIDall protocols | yes | From protocol 15 must be >= 0 (MALFORMED); 0 with a delete (amount 0) is MALFORMED from protocol 11; a nonzero id must name an offer owned by the source at apply (NOT_FOUND). 0 means create, and the new id is assigned by core and returned in the result. | Risk: offerID 0 is not the offer's id; ids above 2^53-1 lose precision in js; negative ids appear in pre-15 history. Handling: Take the created or updated offer id from the operation result or meta; store as int64. | ManageOfferOpFrameBase::doCheckValidManageOfferOpFrameBase::doApply | 32 |
op.manage_offer.assets_amount_priceManageSellOfferOp.{selling,buying,amount,price} | ManageBuyOfferOp.{selling,buying,buyAmount,price} | CreatePassiveSellOfferOp.{selling,buying,amount,price}all protocols | yes | Both assets isAssetValid and different; amount >= 0; price n and d > 0 (MALFORMED). trustlines and authorization at apply (only before protocol 13 also issuer existence). | Risk: An offer may be fully or partly crossed immediately, so the envelope amount is not the resting amount. Handling: Read offer state and trades from meta and the result. | ManageOfferOpFrameBase::doCheckValidManageOfferOpFrameBase::checkOfferValid | 33 |
op.set_options.home_domainSetOptionsOp.homeDomainall protocols | partial | XDR string32 (at most 32 bytes); every byte must be printable ascii 0x20 to 0x7e (SET_OPTIONS_INVALID_HOME_DOMAIN). empty is allowed. not checked to be a domain name. | Risk: Any printable ascii, not necessarily a resolvable domain; fetching stellar.toml from it is an outbound request to a submitter-chosen host. Handling: Store as text; validate as a hostname before any network use. | SetOptionsOpFrame::doCheckValidisStringValidisAsciiNonControl | 3 |
op.set_options.inflation_destSetOptionsOp.inflationDestall protocols | yes | Must be the source or an existing account at apply (SET_OPTIONS_INVALID_INFLATION). | Risk: low; inflation has been disabled since protocol 12 but the field can still be set. Handling: Safe on success. | SetOptionsOpFrame::doApply | 2 |
op.set_options.signerSetOptionsOp.signerall protocols | yes | Key must not be the source's own key; weight <= 255 from protocol 10; non-ed25519 keys are rejected before protocol 3; a signed-payload key is rejected before protocol 19 and when its payload is empty (SET_OPTIONS_BAD_SIGNER). weight 0 removes the signer. the key need not be an existing account. | Risk: Signer keys of four types, including P keys with up to 64 payload bytes; a 32-byte signer column halts ingestion or truncates (wallet-backend#729). Handling: Store signer type, key and full payload; take the resulting signer set from the account entry in meta. | SetOptionsOpFrame::doCheckValidSigner | 24 |
op.set_options.thresholds_and_flagsSetOptionsOp.{clearFlags,setFlags,masterWeight,lowThreshold,medThreshold,highThreshold}all protocols | yes | Flags must be within the protocol's account flag mask (SET_OPTIONS_UNKNOWN_FLAG) and set and clear must not overlap (SET_OPTIONS_BAD_FLAGS); weight and thresholds <= 255 (SET_OPTIONS_THRESHOLD_OUT_OF_RANGE); auth flags cannot change on an immutable account at apply. | Risk: Low. Handling: Take resulting flags and thresholds from the account entry in meta. | SetOptionsOpFrame::doCheckValid | 4 |
op.change_trust.lineChangeTrustOp.lineall protocols | yes | isAssetValid; native rejected from protocol 10; the source must not be the issuer from protocol 16 (CHANGE_TRUST_MALFORMED). issuer existence is not checked. | Risk: The issuer may not exist. Handling: Key the trustline asset by raw bytes. | ChangeTrustOpFrame::doCheckValidChangeTrustOpFrame::doApply | 9 |
op.change_trust.line.liquidity_poolChangeTrustOp.line(ASSET_TYPE_POOL_SHARE).liquidityPool.constantProductprotocol 18 and later | yes | From protocol 18; assetA and assetB must each pass isAssetValid, assetA < assetB under the xdr-generated operator< (type, then raw code bytes with padding, then issuer key bytes), fee must equal LIQUIDITY_POOL_FEE_V18 = 30, else CHANGE_TRUST_MALFORMED. | Risk: An sdk that orders issuers by strkey text builds a pool id core rejects (js-stellar-sdk#1766); the pool id is sha256 of these parameters. Handling: Order assets by raw xdr bytes, never by display string; take the pool id from meta. | isPoolShareAssetValid(ChangeTrustAsset)LiquidityPoolConstantProductParameters | 16 |
op.change_trust.limitChangeTrustOp.limitall protocols | yes | Must be >= 0 (CHANGE_TRUST_MALFORMED); at apply must be >= current balance plus buying liabilities (CHANGE_TRUST_INVALID_LIMIT); 0 deletes the trustline. | Risk: Low. Handling: Take the resulting limit from the trustline entry in meta. | ChangeTrustOpFrame::doCheckValidChangeTrustOpFrame::doApply | 8 |
op.allow_trustAllowTrustOp.{trustor,asset,authorize}all protocols | yes | Asset not native and its code valid; authorize <= AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG and a valid trustline flag for the protocol; trustor must differ from the source from protocol 16 (ALLOW_TRUST_MALFORMED). | Risk: Low. Handling: Take authorization state from the trustline entry in meta. | AllowTrustOpFrame::doCheckValid | 11 |
op.manage_data.data_nameManageDataOp.dataNameprotocol 2 and later | partial | XDR string64 (at most 64 bytes); at least 1 byte and every byte printable ascii 0x20 to 0x7e (MANAGE_DATA_INVALID_NAME). no other content check. | Risk: Printable ascii only, but any printable ascii, including quotes and separators. Handling: Store as text with proper escaping; key data entries by (account, name bytes). | ManageDataOpFrame::doCheckValidisStringValid | 5 |
op.manage_data.data_valueManageDataOp.dataValueprotocol 2 and later | no | XDR opaque up to 64 bytes; doCheckValid does not read it and doApply copies it verbatim into the data entry. null deletes the entry (MANAGE_DATA_NAME_NOT_FOUND when absent). | Risk: Arbitrary binary; decoding as text or as base64 of text fails or corrupts. Handling: Store as bytes. | ManageDataOpFrame::doApplyDataValue | 6 |
op.bump_sequence.bump_toBumpSequenceOp.bumpToprotocol 10 and later | yes | Must be >= 0 (BUMP_SEQUENCE_BAD_SEQ); applied only when greater than the current sequence. | Risk: A lower bumpTo is a successful no-op. Handling: Read the resulting sequence from meta. | BumpSequenceOpFrame::doCheckValidBumpSequenceOpFrame::doApply | 9 |
op.create_claimable_balance.claimants.destinationCreateClaimableBalanceOp.claimants[].v0.destinationprotocol 14 and later | partial | 1 to 10 claimants (xdr upper bound, core rejects empty); destinations must be unique within the operation (CREATE_CLAIMABLE_BALANCE_MALFORMED). destinations are not checked for existence. | Risk: Claimants can be accounts that do not exist and never will; they are not participants of the transaction. Handling: Record claimants as potential claimers only; a claimant participates when a claim succeeds. | CreateClaimableBalanceOpFrame::doCheckValidCreateClaimableBalanceOpFrame::doApply | 6 |
op.create_claimable_balance.claimants.predicateCreateClaimableBalanceOp.claimants[].v0.predicateprotocol 14 and later | yes | Recursion depth at most 4 (the claimant predicate is depth 1); AND and OR must have exactly 2 children; NOT must be non-null; absBefore and relBefore must be >= 0 (CREATE_CLAIMABLE_BALANCE_MALFORMED). at apply every relBefore is rewritten to absBefore = close time + relBefore, so the stored entry differs from the envelope. | Risk: A recursive structure; the envelope predicate and the ledger entry predicate differ for relative times. Handling: Bound recursion at depth 4 when walking; read the stored predicate from the claimable balance entry in meta. | validatePredicateupdatePredicatesForApplyClaimPredicate | 3 |
op.create_claimable_balance.asset_amountCreateClaimableBalanceOp.{asset,amount}protocol 14 and later | yes | isAssetValid and amount > 0 (MALFORMED); funds, trustline and authorization at apply. the balance id is derived by core and returned in the result. | Risk: The balance id is not in the envelope. Handling: Take the balance id from the result or meta. | CreateClaimableBalanceOpFrame::doCheckValid | 9 |
op.claim_claimable_balance.balance_idClaimClaimableBalanceOp.balanceIDprotocol 14 and later | yes | No static check (doCheckValid returns true); at apply the entry must exist (CLAIM_CLAIMABLE_BALANCE_DOES_NOT_EXIST) and the source must be a claimant whose predicate holds (CANNOT_CLAIM). | Risk: On a failed claim the id may name nothing. Handling: Trust only on success. | ClaimClaimableBalanceOpFrame::doCheckValidClaimClaimableBalanceOpFrame::doApply | 8 |
op.clawback.fromClawbackOp.fromprotocol 17 and later | yes | Compared as a full MuxedAccount against the source (CLAWBACK_MALFORMED when equal); at apply reduced to its base key, which must hold a clawback-enabled trustline (CLAWBACK_NO_TRUST, CLAWBACK_NOT_CLAWBACK_ENABLED). | Risk: Low on success. Handling: Use the base key. | ClawbackOpFrame::doCheckValidClawbackOpFrame::doApply | 9 |
op.clawback.from.mux_idClawbackOp.from(KEY_TYPE_MUXED_ED25519).med25519.idprotocol 17 and later | no | Discarded by toAccountID at apply; copied into the clawback event. | Risk: Arbitrary 64-bit value. Handling: Store separately from the base account. | ClawbackOpFrame::doApply | 2 |
op.clawback.asset_amountClawbackOp.{asset,amount}protocol 17 and later | yes | Amount >= 1; asset not native, valid, and issued by the source (CLAWBACK_MALFORMED); balance checked at apply. | Risk: Low. Handling: Safe on success. | ClawbackOpFrame::doCheckValid | 7 |
op.clawback_claimable_balance.balance_idClawbackClaimableBalanceOp.balanceIDprotocol 17 and later | yes | No static check; at apply the entry must exist (DOES_NOT_EXIST), the source must be the asset issuer (NOT_ISSUER) and clawback must be enabled on the balance. | Risk: On failure the id may name nothing. Handling: Trust only on success. | ClawbackClaimableBalanceOpFrame::doApply | 7 |
op.set_trust_line_flagsSetTrustLineFlagsOp.{trustor,asset,clearFlags,setFlags}protocol 17 and later | yes | Asset not native, valid and issued by the source; trustor differs from source; set and clear disjoint; setFlags a valid trustline flag set without the clawback flag; clearFlags within the mask (SET_TRUST_LINE_FLAGS_MALFORMED). | Risk: Low. Handling: Take resulting flags from the trustline entry in meta. | SetTrustLineFlagsOpFrame::doCheckValid | 15 |
op.begin_sponsoring.sponsored_idBeginSponsoringFutureReservesOp.sponsoredIDprotocol 14 and later | partial | Must differ from the source (BEGIN_SPONSORING_FUTURE_RESERVES_MALFORMED); at apply must not already be sponsored or sponsoring (ALREADY_SPONSORED, RECURSIVE). the account is not loaded, so it need not exist (it may be created later in the same transaction); from protocol 14 a transaction that leaves a sponsorship open fails with txBAD_SPONSORSHIP. | Risk: The sponsored id may be an account that does not exist yet. Handling: Derive sponsorship relations from sponsoring ids on ledger entries in meta. | BeginSponsoringFutureReservesOpFrame::doCheckValidBeginSponsoringFutureReservesOpFrame::doApplyTransactionFrame::applyOperations | 5 |
op.revoke_sponsorshipRevokeSponsorshipOp.ledgerKey | RevokeSponsorshipOp.signerprotocol 14 and later | partial | For ledger keys only ACCOUNT, TRUSTLINE (valid non-native asset, account not issuer), OFFER (offerID > 0), DATA (name 1 to 64 printable ascii) and CLAIMABLE_BALANCE are allowed (REVOKE_SPONSORSHIP_MALFORMED); existence and sponsorship are checked at apply. the signer arm is not checked statically. | Risk: On failure the key may name nothing. Handling: Read sponsorship changes from meta. | RevokeSponsorshipOpFrame::doCheckValid | 8 |
op.account_merge.destinationOperation.body(ACCOUNT_MERGE).destinationall protocols | yes | Base key must differ from the source (ACCOUNT_MERGE_MALFORMED) and exist at apply (ACCOUNT_MERGE_NO_ACCOUNT); the mux id is discarded by toAccountID and copied into the transfer event. | Risk: The mux id part is unvalidated as for payments. Handling: Store base account and mux id separately; take the merged amount from the result. | MergeOpFrame::doCheckValidMergeOpFrame::doApplyFromV16 | 12 |
op.liquidity_pool_depositLiquidityPoolDepositOp.{liquidityPoolID,maxAmountA,maxAmountB,minPrice,maxPrice}protocol 18 and later | yes | Max amounts > 0, both prices with n and d > 0, minPrice <= maxPrice (LIQUIDITY_POOL_DEPOSIT_MALFORMED); the pool id must have a pool-share trustline of the source at apply (NO_TRUST). | Risk: Max amounts are limits, not deposited amounts. Handling: Take deposited amounts from meta or events. | LiquidityPoolDepositOpFrame::doCheckValidLiquidityPoolDepositOpFrame::doApply | 10 |
op.liquidity_pool_withdrawLiquidityPoolWithdrawOp.{liquidityPoolID,amount,minAmountA,minAmountB}protocol 18 and later | yes | Amount > 0, min amounts >= 0 (LIQUIDITY_POOL_WITHDRAW_MALFORMED); pool-share trustline must exist at apply (NO_TRUST). | Risk: Min amounts are bounds. Handling: Take withdrawn amounts from meta or events. | LiquidityPoolWithdrawOpFrame::doCheckValidLiquidityPoolWithdrawOpFrame::doApply | 5 |
op.invoke_host_function.invoke_contract.contract_addressInvokeHostFunctionOp.hostFunction(HOST_FUNCTION_TYPE_INVOKE_CONTRACT).invokeContract.contractAddressprotocol 20 and later | partial | Core validation does not read it (doCheckValidForSoroban checks only wasm size and asset preimages). the host resolves it at execution, where a missing contract or one outside the footprint fails the operation; the transaction is still in the ledger. | Risk: On a failed invocation the address can name nothing; even on success the called contract is not the full set of contracts touched. Handling: Take invoked contracts from events and meta; treat the envelope address as the submitter's target only. | InvokeHostFunctionOpFrame::doCheckValidForSorobaninvoke_host_function | 21 |
op.invoke_host_function.invoke_contract.function_nameInvokeHostFunctionOp.hostFunction(HOST_FUNCTION_TYPE_INVOKE_CONTRACT).invokeContract.functionNameprotocol 20 and later | partial | XDR limit 32 bytes only in core. the host checks the [a-zA-Z0-9_] symbol character set when converting it at execution. | Risk: On a failed transaction the name can contain any bytes up to 32. Handling: Store as bytes or validate the symbol charset before treating as text. | SCSymbolSymbolError | 21 |
op.invoke_host_function.invoke_contract.argsInvokeHostFunctionOp.hostFunction(HOST_FUNCTION_TYPE_INVOKE_CONTRACT).invokeContract.argsprotocol 20 and later | partial | Core checks only that the envelope parses within the xdr depth limit and that the transaction fits txMaxSizeBytes. the host converts args to host values at execution, rejecting maps not strictly sorted by key and invalid symbols; a failed conversion fails the operation, not inclusion. | Risk: Nested ScVal trees bounded only by xdr depth and tx size; unsorted or colliding map keys and invalid symbols can appear in failed transactions (js-stellar-sdk#1789). Handling: Decode with depth and length limits; treat map key order and uniqueness as untrusted; tolerate refusal per value. | validateXDRForProtocolMeteredOrdMap::from_map_with_host | 59 |
op.invoke_host_function.upload_wasmInvokeHostFunctionOp.hostFunction(HOST_FUNCTION_TYPE_UPLOAD_CONTRACT_WASM).wasmprotocol 20 and later | partial | Size must be <= the network maxContractSizeBytes at validation; whether it is a valid module is decided by the host at execution. | Risk: Large opaque blob; may not be valid wasm on failed transactions. Handling: Store by hash with a size cap; take the uploaded code entry from meta. | InvokeHostFunctionOpFrame::doCheckValidForSoroban | 1 |
op.invoke_host_function.create_contract.preimageInvokeHostFunctionOp.hostFunction(HOST_FUNCTION_TYPE_CREATE_CONTRACT|HOST_FUNCTION_TYPE_CREATE_CONTRACT_V2).{createContract,createContractV2}.contractIDPreimageprotocol 20 and later | partial | Core checks only that an asset preimage passes isAssetValid (v1 create only). the host requires the address in an address preimage to authorize the creation, and rejects mismatched preimage and executable pairs, at execution. | Risk: The deployer address in the envelope is a claim until the host authorizes it; contract-creation state read from declared auth entries is wrong (wallet-backend#746). Handling: Take the created contract id and its creator from meta (new contract instance entry) and events. | InvokeHostFunctionOpFrame::doCheckValidForSorobanHost::create_contract_with_optional_auth | 8 |
op.invoke_host_function.create_contract_v2.constructor_argsInvokeHostFunctionOp.hostFunction(HOST_FUNCTION_TYPE_CREATE_CONTRACT_V2).createContractV2.constructorArgsprotocol 22 and later | partial | Not read by core validation; converted and passed to the constructor by the host at execution. | Risk: As for invoke args. Handling: As for invoke args. | InvokeHostFunctionOpFrame::doCheckValidForSorobanHost::create_contract_with_optional_auth | 1 |
op.invoke_host_function.authInvokeHostFunctionOp.authprotocol 20 and later | partial | Core never inspects auth entries; it serializes them and hands them to the host. at the start of execution the host decodes every entry and converts its address, signature ScVal and invocation tree to host objects, so an entry that cannot be converted fails the operation. signatures, nonces and expiration are then checked lazily, only for an entry that a require_auth call matches. there is no check that every entry was used, so a declared entry that is never matched is never authenticated. | Risk: Declared auth entries are submitter claims; an unmatched entry can name any address with any signature and still appear in a successful transaction. unbounded trees halted wallet-backend ingestion. Handling: Never derive participants or authorizations from declared entries; use events and meta; cap recursion when walking. | InvokeHostFunctionApplyHelper::invokeHostFunctioninvoke_host_functionAuthorizationManager::new_enforcingAuthorizationManager::require_auth_enforcingAccountAuthorizationTracker::maybe_authorize_invocation | 22 |
auth.credentials.source_accountSorobanAuthorizationEntry.credentials(SOROBAN_CREDENTIALS_SOURCE_ACCOUNT)protocol 20 and later | partial | Binds the entry to the operation source account (the base AccountID core passes to the host); no signature, nonce or expiration; authenticate and verify_and_consume_nonce return immediately. authorization rests on the transaction signatures. | Risk: No per-entry proof exists. Handling: Attribute to the operation source only when a matching authorization shows in events or meta. | AccountAuthorizationTracker::from_authorization_entryAccountAuthorizationTracker::authenticateHost::source_account_address | 4 |
auth.credentials.address.addressSorobanAddressCredentials.addressprotocol 20 and later | partial | Not checked by core. converted to a host address object up front; checked only when the entry is matched, then an account address must authenticate with signatures and a contract address through __check_auth; any other address type fails authentication with an internal error. | Risk: An unmatched entry can name any address, including muxed, claimable balance or liquidity pool addresses. Handling: Do not treat the address as a participant unless meta or events confirm it. | AccountAuthorizationTracker::authenticateAccountAuthorizationTracker::from_authorization_entry | 14 |
auth.credentials.address.nonceSorobanAddressCredentials.nonceprotocol 20 and later | partial | Consumed by the host only when the entry is matched: the nonce must not already exist as a temporary ledger entry for the address (Auth ExistingValue) and must be in the footprint; it is then written. unmatched entries consume nothing. | Risk: A nonce in an unmatched entry was never consumed and proves nothing. Handling: Read consumed nonces from contract data changes in meta. | AccountAuthorizationTracker::verify_and_consume_nonceHost::consume_nonce | 14 |
auth.credentials.address.signature_expiration_ledgerSorobanAddressCredentials.signatureExpirationLedgerprotocol 20 and later | partial | Checked by the host only when the entry is matched: ledger seq must be <= it (signature has expired) and it must be <= the maximum live-until ledger (signature expiration is too late). | Risk: Unchecked on unmatched entries. Handling: Do not use it as evidence of a live authorization. | AccountAuthorizationTracker::verify_and_consume_nonce | 14 |
auth.credentials.address.signatureSorobanAddressCredentials.signatureprotocol 20 and later | partial | Converted to a host value up front (so it must be a convertible ScVal). verified only when matched: for an account address it must be a vec of 1 to 20 {public_key, signature} entries in strictly increasing key order, each an ed25519 signature over sha256 of the HashIdPreimage (network id, nonce, expiration, invocation, plus the address for the v2 and delegates credential types), each key a signer of the account, total weight >= the medium threshold; for a contract address the value is passed to the contract's __check_auth. | Risk: An arbitrary nested ScVal on unmatched entries; even on matched contract entries its shape is contract-defined. Handling: Treat as opaque; decode with depth limits; never infer signers from it. | check_account_authenticationcheck_account_contract_authAccountAuthorizationTracker::get_signature_payload | 14 |
auth.credentials.address_with_delegates.delegatesSorobanAddressCredentialsWithDelegates.delegatesprotocol 27 and later | partial | Decoded by the host up front for every entry: at each level delegate addresses must be strictly increasing with no duplicates, and each nested signature is converted to a host value; signatures are only used when the entry is matched. | Risk: A recursive tree of addresses and signatures that core never looks at. Handling: Cap recursion; never derive participants from delegates. | DelegatedAccountAuthSignerTreeNode::from_xdrSorobanDelegateSignature | 2 |
auth.root_invocationSorobanAuthorizationEntry.rootInvocationprotocol 20 and later | partial | Core does not look at it. the host converts the whole tree up front (contract addresses, function names as symbols, args to host values), then matches nodes lazily against actual require_auth calls; subtrees that never match are never compared with anything that ran. | Risk: recursive, bounded only by xdr depth and tx size; an unmatched subtree can describe calls that never happened. Handling: Cap depth and node count when walking; take actual calls from events. | AuthorizedInvocation::from_xdrAuthorizedFunction::from_xdrSorobanAuthorizedInvocation | 18 |
op.extend_footprint_ttl.extend_toExtendFootprintTTLOp.extendToprotocol 20 and later | yes | Must be <= maxEntryTTL - 1; the read-write footprint must be empty and every read-only key must be contract data or code (EXTEND_FOOTPRINT_TTL_MALFORMED). | Risk: The requested extension, not the resulting live-until ledger. Handling: Read new TTLs from meta. | ExtendFootprintTTLOpFrame::doCheckValidForSoroban | 4 |
op.restore_footprintOperation.body(RESTORE_FOOTPRINT) with footprintprotocol 20 and later | yes | The read-only footprint must be empty and every read-write key must be a persistent soroban entry (RESTORE_FOOTPRINT_MALFORMED). | Risk: Keys that were not archived are legal and restore nothing. Handling: Read restored entries from meta. | RestoreFootprintOpFrame::doCheckValidForSoroban | 5 |
Corpus entries by field
The shapekit corpus holds ledger data and test vectors for rare shapes, each recorded in the corpus manifest with the fields it exercises. These are the entries for each field.
tx.envelope.type(8): cap15.fee-bump.p30-fee-source-is-inner-source-envelope, cap15.fee-bump.p30-fee-source-is-inner-source-pre-auth-signer-envelope, cap15.fee-bump.p30-inner-failed-envelope, cap15.fee-bump.p30-inner-success-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-failed-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-success-envelope, cap26.inflation.p12-inflation-not-supported, classic.failed-tx.p1-manage-data-not-supported-yettx.v0.source_account_ed25519(7): cap26.inflation.p11-inflation-not-time, cap26.inflation.p11-inflation-payout, classic.failed-tx.p12-manage-buy-offer-buy-no-issuer, classic.failed-tx.p12-manage-buy-offer-sell-no-issuer, classic.failed-tx.p12-manage-sell-offer-buy-no-issuer, classic.failed-tx.p12-manage-sell-offer-sell-no-issuer, classic.failed-tx.p9-change-trust-native-internal-error-envelopetx.signatures(16): cap15.fee-bump.p30-inner-bad-auth, cap15.fee-bump.p30-inner-bad-auth-extra, cap21.preconditions.p30-two-extra-signers-envelope, cap40.signed-payload-signer.p28-authorised-by-payload-32, cap40.signed-payload-signer.p28-extra-signer-payload-64-envelope, cap40.signed-payload-signer.p30-authorised-by-payload-32, cap40.signed-payload-signer.p30-extra-signer-payload-64-envelope, classic.failed-tx.p30-op-bad-auth, classic.failed-tx.p30-pre-auth-failed-removes-signer, classic.failed-tx.p30-tx-bad-auth-extra, classic.max-length.p30-home-domain-32, classic.max-length.p30-home-domain-del-rejected, classic.max-length.p30-home-domain-utf8-rejected, classic.max-length.p30-signatures-20, classic.max-length.synth-signature-65-bytes, classic.max-length.synth-tx-21-signaturestx.signatures.hint(6): cap21.preconditions.p30-two-extra-signers-envelope, cap40.signed-payload-signer.p28-authorised-by-payload-32, cap40.signed-payload-signer.p28-extra-signer-payload-64-envelope, cap40.signed-payload-signer.p30-authorised-by-payload-32, cap40.signed-payload-signer.p30-extra-signer-payload-64-envelope, classic.max-length.p30-signatures-20tx.source_account(18): cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p64-1, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p64-1, classic.failed-tx.p30-pre-auth-failed-removes-signer, classic.failed-tx.p30-unknown-destination, soroban.muxed-source.p28-invoke-muxed-source, soroban.muxed-source.p28-muxed-and-base-source-one-ledger, soroban.muxed-source.p28-restore-muxed-source, soroban.muxed-source.p30-extend-ttl-muxed-source, soroban.muxed-source.p30-extend-ttl-noop-muxed-source, soroban.muxed-source.p30-invoke-muxed-source, soroban.muxed-source.p30-restore-muxed-source, soroban.muxed-source.p30-restore-noop-muxed-sourcetx.source_account.mux_id(19): cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p64-1, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p64-1, cap27.muxed-accounts.synth-textual-muxed-id-canonical, cap27.muxed-accounts.synth-textual-muxed-id-implementation-defined, cap27.muxed-accounts.synth-textual-muxed-id-noncanonical, soroban.muxed-source.p28-invoke-muxed-source, soroban.muxed-source.p28-muxed-and-base-source-one-ledger, soroban.muxed-source.p28-restore-muxed-source, soroban.muxed-source.p30-extend-ttl-muxed-source, soroban.muxed-source.p30-extend-ttl-noop-muxed-source, soroban.muxed-source.p30-invoke-muxed-source, soroban.muxed-source.p30-restore-muxed-source, soroban.muxed-source.p30-restore-noop-muxed-sourcetx.seq_num(3): cap15.fee-bump.p30-inner-bad-seq, cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-min-seq-num-jump-envelopetx.cond.type(8): cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-ledger-and-time-bounds-envelope, cap21.preconditions.p30-ledger-bounds-ended, cap21.preconditions.p30-ledger-bounds-no-maximum-envelope, cap21.preconditions.p30-min-seq-age-and-gap-envelope, cap21.preconditions.p30-min-seq-age-and-gap-not-met, cap21.preconditions.p30-min-seq-num-jump-envelope, cap21.preconditions.p30-two-extra-signers-envelopetx.cond.time_bounds(2): cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-ledger-and-time-bounds-envelopetx.cond.ledger_bounds(4): cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-ledger-and-time-bounds-envelope, cap21.preconditions.p30-ledger-bounds-ended, cap21.preconditions.p30-ledger-bounds-no-maximum-envelopetx.cond.min_seq_num(2): cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-min-seq-num-jump-envelopetx.cond.min_seq_age(4): cap21.preconditions.p30-account-seq-ledger-and-time, cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-min-seq-age-and-gap-envelope, cap21.preconditions.p30-min-seq-age-and-gap-not-mettx.cond.min_seq_ledger_gap(6): cap15.fee-bump.p30-inner-bad-min-seq-gap, cap21.preconditions.p30-account-seq-ledger-and-time, cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-min-seq-age-and-gap-envelope, cap21.preconditions.p30-min-seq-age-and-gap-not-met, classic.failed-tx.p30-tx-bad-min-seq-gaptx.cond.extra_signers(7): cap21.preconditions.p30-all-preconditions-envelope, cap21.preconditions.p30-two-extra-signers-envelope, cap21.preconditions.synth-extra-signers-3, cap40.signed-payload-signer.p28-extra-signer-empty-payload, cap40.signed-payload-signer.p28-extra-signer-payload-64-envelope, cap40.signed-payload-signer.p30-extra-signer-empty-payload, cap40.signed-payload-signer.p30-extra-signer-payload-64-envelopesigner_key.signed_payload.payload(38): cap40.signed-payload-signer.p28-account-four-payload-signers, cap40.signed-payload-signer.p28-authorised-by-payload-32, cap40.signed-payload-signer.p28-extra-signer-empty-payload, cap40.signed-payload-signer.p28-extra-signer-payload-64-envelope, cap40.signed-payload-signer.p28-extra-signer-payload-64-meta, cap40.signed-payload-signer.p28-remove-signer-payload-1-envelope, cap40.signed-payload-signer.p28-remove-signer-payload-1-meta, cap40.signed-payload-signer.p28-revoke-sponsorship-payload-64-envelope, cap40.signed-payload-signer.p28-revoke-sponsorship-payload-64-meta, cap40.signed-payload-signer.p28-set-options-empty-payload, cap40.signed-payload-signer.p28-set-options-payload-1-envelope, cap40.signed-payload-signer.p28-set-options-payload-1-meta, cap40.signed-payload-signer.p28-set-options-payload-32-envelope, cap40.signed-payload-signer.p28-set-options-payload-32-meta, cap40.signed-payload-signer.p28-set-options-payload-4-envelope, cap40.signed-payload-signer.p28-set-options-payload-4-meta, cap40.signed-payload-signer.p28-sponsored-set-options-payload-64-envelope, cap40.signed-payload-signer.p28-sponsored-set-options-payload-64-meta, cap40.signed-payload-signer.p30-account-four-payload-signers, cap40.signed-payload-signer.p30-authorised-by-payload-32, cap40.signed-payload-signer.p30-extra-signer-empty-payload, cap40.signed-payload-signer.p30-extra-signer-payload-64-envelope, cap40.signed-payload-signer.p30-extra-signer-payload-64-meta, cap40.signed-payload-signer.p30-remove-signer-payload-1-envelope, cap40.signed-payload-signer.p30-remove-signer-payload-1-meta, cap40.signed-payload-signer.p30-revoke-sponsorship-payload-64-envelope, cap40.signed-payload-signer.p30-revoke-sponsorship-payload-64-meta, cap40.signed-payload-signer.p30-set-options-empty-payload, cap40.signed-payload-signer.p30-set-options-payload-1-envelope, cap40.signed-payload-signer.p30-set-options-payload-1-meta, cap40.signed-payload-signer.p30-set-options-payload-32-envelope, cap40.signed-payload-signer.p30-set-options-payload-32-meta, cap40.signed-payload-signer.p30-set-options-payload-4-envelope, cap40.signed-payload-signer.p30-set-options-payload-4-meta, cap40.signed-payload-signer.p30-sponsored-set-options-payload-64-envelope, cap40.signed-payload-signer.p30-sponsored-set-options-payload-64-meta, cap40.signed-payload-signer.synth-empty-payload-signer, cap40.signed-payload-signer.synth-payload-65-bytestx.memo.type(27): cap67.unified-events.p22-backfill-payment-memo-hash, cap67.unified-events.p22-backfill-payment-memo-id, cap67.unified-events.p22-backfill-payment-memo-text, cap67.unified-events.p22-payment-memo-hash, cap67.unified-events.p22-payment-memo-id, cap67.unified-events.p22-payment-memo-text, cap67.unified-events.p30-payment-memo-hash, cap67.unified-events.p30-payment-memo-id, cap67.unified-events.p30-payment-memo-text, classic.max-length.p30-memo-text-28, classic.memo-arms.p30-memo-hash-ff, classic.memo-arms.p30-memo-hash-sha256, classic.memo-arms.p30-memo-hash-zero, classic.memo-arms.p30-memo-id-2-63, classic.memo-arms.p30-memo-id-max, classic.memo-arms.p30-memo-id-muxed-destination, classic.memo-arms.p30-memo-id-zero, classic.memo-arms.p30-memo-return-ff, classic.memo-arms.p30-memo-return-sha256, classic.memo-arms.p30-memo-return-zero, classic.memo-bytes.p30-memo-above-max-code-point, classic.memo-bytes.p30-memo-ff, classic.memo-bytes.p30-memo-lone-continuation, classic.memo-bytes.p30-memo-overlong-nul, classic.memo-bytes.p30-memo-surrogate, classic.memo-bytes.p30-memo-truncated-sequence-28, classic.memo-bytes.synth-memo-type-out-of-rangetx.memo.text(12): cap67.unified-events.p22-backfill-payment-memo-text, cap67.unified-events.p22-payment-memo-text, cap67.unified-events.p30-payment-memo-text, classic.max-length.p30-memo-text-28, classic.max-length.synth-memo-text-29-bytes, classic.memo-bytes.p30-memo-above-max-code-point, classic.memo-bytes.p30-memo-ff, classic.memo-bytes.p30-memo-lone-continuation, classic.memo-bytes.p30-memo-overlong-nul, classic.memo-bytes.p30-memo-surrogate, classic.memo-bytes.p30-memo-truncated-sequence-28, classic.memo-bytes.synth-memo-text-nonzero-paddingtx.memo.id(7): cap67.unified-events.p22-backfill-payment-memo-id, cap67.unified-events.p22-payment-memo-id, cap67.unified-events.p30-payment-memo-id, classic.memo-arms.p30-memo-id-2-63, classic.memo-arms.p30-memo-id-max, classic.memo-arms.p30-memo-id-muxed-destination, classic.memo-arms.p30-memo-id-zerotx.memo.hash(6): cap67.unified-events.p22-backfill-payment-memo-hash, cap67.unified-events.p22-payment-memo-hash, cap67.unified-events.p30-payment-memo-hash, classic.memo-arms.p30-memo-hash-ff, classic.memo-arms.p30-memo-hash-sha256, classic.memo-arms.p30-memo-hash-zerotx.memo.return(3): classic.memo-arms.p30-memo-return-ff, classic.memo-arms.p30-memo-return-sha256, classic.memo-arms.p30-memo-return-zerotx.operations(4): classic.max-length.p30-operations-100, classic.max-length.synth-operation-type-out-of-range, classic.max-length.synth-operations-count-overrun, classic.max-length.synth-tx-101-operationstx.ext(10): soroban.invoke-basics.p30-contract-deploys-with-constructor-envelope, soroban.invoke-basics.p30-create-contract-v1-envelope, soroban.invoke-basics.p30-create-contract-v2-constructor-envelope, soroban.invoke-basics.p30-extend-ttl-envelope, soroban.invoke-basics.p30-failed-events-discarded-envelope, soroban.invoke-basics.p30-failed-trapped-envelope, soroban.invoke-basics.p30-invoke-restored-contract-envelope, soroban.invoke-basics.p30-invoke-temporary-entry-envelope, soroban.invoke-basics.p30-restore-archived-instance-and-code-envelope, soroban.invoke-basics.p30-upload-wasmtx.soroban.resource_fee(6): cap67.unified-events.p30-soroban-fee-refund, soroban.invoke-basics.p30-contract-deploys-with-constructor-envelope, soroban.invoke-basics.p30-failed-events-discarded-envelope, soroban.invoke-basics.p30-failed-trapped-envelope, soroban.invoke-basics.p30-invoke-restored-contract-envelope, soroban.invoke-basics.p30-invoke-temporary-entry-envelopetx.soroban.footprint(15): cap66.archival.p30-auto-restore-invoke-envelope, cap66.archival.p30-restore-footprint, soroban.invoke-basics.p30-contract-deploys-with-constructor-envelope, soroban.invoke-basics.p30-extend-ttl-envelope, soroban.invoke-basics.p30-failed-events-discarded-envelope, soroban.invoke-basics.p30-failed-trapped-envelope, soroban.invoke-basics.p30-invoke-restored-contract-envelope, soroban.invoke-basics.p30-invoke-temporary-entry-envelope, soroban.invoke-basics.p30-restore-archived-instance-and-code-envelope, soroban.muxed-source.p28-muxed-and-base-source-one-ledger, soroban.muxed-source.p28-restore-muxed-source, soroban.muxed-source.p30-extend-ttl-muxed-source, soroban.muxed-source.p30-extend-ttl-noop-muxed-source, soroban.muxed-source.p30-restore-muxed-source, soroban.muxed-source.p30-restore-noop-muxed-sourcetx.soroban.ext.archived_soroban_entries(1): cap66.archival.p30-auto-restore-invoke-envelopefeebump.fee_source(6): cap15.fee-bump.p30-fee-source-is-inner-source-envelope, cap15.fee-bump.p30-fee-source-is-inner-source-pre-auth-signer-envelope, cap15.fee-bump.p30-inner-failed-envelope, cap15.fee-bump.p30-inner-success-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-failed-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-success-envelopefeebump.fee_source.mux_id(2): cap15.fee-bump.p30-muxed-fee-source-inner-failed-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-success-envelopefeebump.fee(6): cap15.fee-bump.p30-fee-source-is-inner-source-envelope, cap15.fee-bump.p30-fee-source-is-inner-source-pre-auth-signer-envelope, cap15.fee-bump.p30-inner-failed-envelope, cap15.fee-bump.p30-inner-success-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-failed-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-success-envelopefeebump.signatures(6): cap15.fee-bump.p30-fee-source-is-inner-source-envelope, cap15.fee-bump.p30-fee-source-is-inner-source-pre-auth-signer-envelope, cap15.fee-bump.p30-inner-failed-envelope, cap15.fee-bump.p30-inner-success-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-failed-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-success-envelopefeebump.inner_tx(9): cap15.fee-bump.p30-fee-source-is-inner-source-envelope, cap15.fee-bump.p30-fee-source-is-inner-source-pre-auth-signer-envelope, cap15.fee-bump.p30-inner-bad-auth, cap15.fee-bump.p30-inner-bad-seq, cap15.fee-bump.p30-inner-bad-sponsorship, cap15.fee-bump.p30-inner-failed-envelope, cap15.fee-bump.p30-inner-success-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-failed-envelope, cap15.fee-bump.p30-muxed-fee-source-inner-success-envelopeop.source_account(7): cap27.muxed-accounts.p28-operation-sources-four-ids, cap27.muxed-accounts.p30-operation-sources-four-ids, cap27.muxed-accounts.synth-crypto-key-type-out-of-range, cap27.muxed-accounts.synth-optional-flag-2, classic.failed-tx.p30-op-bad-auth, classic.failed-tx.p30-passive-offer-operation-source, classic.failed-tx.p30-unknown-destinationop.source_account.mux_id(2): cap27.muxed-accounts.p28-operation-sources-four-ids, cap27.muxed-accounts.p30-operation-sources-four-idsop.create_account.destination(5): cap33.sponsorship.p30-sponsored-account-envelope, classic.failed-tx.p30-create-account-already-exist, classic.failed-tx.p30-create-account-low-reserve, classic.failed-tx.p30-create-account-underfunded, classic.failed-tx.p30-unknown-destinationop.create_account.starting_balance(3): classic.failed-tx.p30-create-account-already-exist, classic.failed-tx.p30-create-account-low-reserve, classic.failed-tx.p30-create-account-underfundedop.payment.destination(42): cap27.muxed-accounts.p28-issued-payment-destination-id-2p53, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p64-1, cap27.muxed-accounts.p30-issued-payment-destination-id-2p53, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p64-1, cap67.unified-events.p22-backfill-payment-burn, cap67.unified-events.p22-backfill-payment-memo-hash, cap67.unified-events.p22-backfill-payment-memo-id, cap67.unified-events.p22-backfill-payment-memo-text, cap67.unified-events.p22-backfill-payment-mint, cap67.unified-events.p22-backfill-payment-muxed-destination, cap67.unified-events.p22-backfill-payment-to-self, cap67.unified-events.p22-backfill-payment-transfer, cap67.unified-events.p22-payment-burn, cap67.unified-events.p22-payment-memo-hash, cap67.unified-events.p22-payment-memo-id, cap67.unified-events.p22-payment-memo-text, cap67.unified-events.p22-payment-mint, cap67.unified-events.p22-payment-muxed-destination, cap67.unified-events.p22-payment-to-self, cap67.unified-events.p22-payment-transfer, cap67.unified-events.p30-payment-burn, cap67.unified-events.p30-payment-memo-hash, cap67.unified-events.p30-payment-memo-id, cap67.unified-events.p30-payment-memo-text, cap67.unified-events.p30-payment-mint, cap67.unified-events.p30-payment-muxed-destination, cap67.unified-events.p30-payment-to-self, cap67.unified-events.p30-payment-transfer, classic.failed-tx.p12-payment-no-issuer, classic.failed-tx.p30-payment-line-full, classic.failed-tx.p30-payment-no-trust, classic.failed-tx.p30-payment-not-authorized, classic.failed-tx.p30-payment-src-no-trust, classic.failed-tx.p30-payment-src-not-authorized, classic.failed-tx.p30-pre-auth-failed-removes-signer, classic.failed-tx.p30-unknown-destinationop.payment.destination.mux_id(16): cap27.muxed-accounts.p28-issued-payment-destination-id-2p53, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p28-tx-source-and-payment-destination-id-2p64-1, cap27.muxed-accounts.p30-issued-payment-destination-id-2p53, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-0, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-1, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p53, cap27.muxed-accounts.p30-tx-source-and-payment-destination-id-2p64-1, cap27.muxed-accounts.synth-textual-muxed-id-canonical, cap27.muxed-accounts.synth-textual-muxed-id-implementation-defined, cap27.muxed-accounts.synth-textual-muxed-id-noncanonical, cap67.unified-events.p22-backfill-payment-muxed-destination, cap67.unified-events.p22-payment-muxed-destination, cap67.unified-events.p30-payment-muxed-destinationop.payment.asset(17): cap27.muxed-accounts.p28-issued-payment-destination-id-2p53, cap27.muxed-accounts.p30-issued-payment-destination-id-2p53, cap67.unified-events.p22-backfill-payment-burn, cap67.unified-events.p22-backfill-payment-mint, cap67.unified-events.p22-backfill-payment-transfer, cap67.unified-events.p22-payment-burn, cap67.unified-events.p22-payment-mint, cap67.unified-events.p22-payment-transfer, cap67.unified-events.p30-payment-burn, cap67.unified-events.p30-payment-mint, cap67.unified-events.p30-payment-transfer, classic.failed-tx.p12-payment-no-issuer, classic.failed-tx.p30-payment-line-full, classic.failed-tx.p30-payment-no-trust, classic.failed-tx.p30-payment-not-authorized, classic.failed-tx.p30-payment-src-no-trust, classic.failed-tx.p30-payment-src-not-authorizedop.payment.amount(6): classic.failed-tx.p12-payment-no-issuer, classic.failed-tx.p30-payment-line-full, classic.failed-tx.p30-payment-no-trust, classic.failed-tx.p30-payment-not-authorized, classic.failed-tx.p30-payment-src-no-trust, classic.failed-tx.p30-payment-src-not-authorizedop.path_payment.destination(31): cap03.offer-liabilities.p17-path-payment-strict-receive-claim-atom-v0, cap03.offer-liabilities.p17-path-payment-strict-send-claim-atom-v0, cap27.muxed-accounts.p28-path-payment-strict-receive-destination-id-2p64-1, cap27.muxed-accounts.p28-path-payment-strict-send-destination-id-1, cap27.muxed-accounts.p30-path-payment-strict-receive-destination-id-2p64-1, cap27.muxed-accounts.p30-path-payment-strict-send-destination-id-1, cap67.unified-events.p22-backfill-path-payment-muxed-destination, cap67.unified-events.p22-path-payment-muxed-destination, cap67.unified-events.p30-path-payment-muxed-destination, classic.failed-tx.p12-path-payment-strict-receive-no-issuer, classic.failed-tx.p12-path-payment-strict-send-no-issuer, classic.failed-tx.p30-path-receive-exceeded-work-limit, classic.failed-tx.p30-path-receive-line-full, classic.failed-tx.p30-path-receive-no-destination, classic.failed-tx.p30-path-receive-no-trust, classic.failed-tx.p30-path-receive-not-authorized, classic.failed-tx.p30-path-receive-offer-cross-self, classic.failed-tx.p30-path-receive-src-no-trust, classic.failed-tx.p30-path-receive-src-not-authorized, classic.failed-tx.p30-path-receive-too-few-offers, classic.failed-tx.p30-path-receive-underfunded, classic.failed-tx.p30-path-send-line-full, classic.failed-tx.p30-path-send-no-destination, classic.failed-tx.p30-path-send-no-trust, classic.failed-tx.p30-path-send-not-authorized, classic.failed-tx.p30-path-send-offer-cross-self, classic.failed-tx.p30-path-send-src-no-trust, classic.failed-tx.p30-path-send-src-not-authorized, classic.failed-tx.p30-path-send-too-few-offers, classic.failed-tx.p30-path-send-under-destmin, classic.failed-tx.p30-path-send-underfundedop.path_payment.destination.mux_id(7): cap27.muxed-accounts.p28-path-payment-strict-receive-destination-id-2p64-1, cap27.muxed-accounts.p28-path-payment-strict-send-destination-id-1, cap27.muxed-accounts.p30-path-payment-strict-receive-destination-id-2p64-1, cap27.muxed-accounts.p30-path-payment-strict-send-destination-id-1, cap67.unified-events.p22-backfill-path-payment-muxed-destination, cap67.unified-events.p22-path-payment-muxed-destination, cap67.unified-events.p30-path-payment-muxed-destinationop.path_payment.amounts(27): cap03.offer-liabilities.p17-path-payment-strict-receive-claim-atom-v0, cap03.offer-liabilities.p17-path-payment-strict-send-claim-atom-v0, cap38.liquidity-pools.p30-path-payment-through-pool, classic.failed-tx.p12-path-payment-strict-receive-no-issuer, classic.failed-tx.p12-path-payment-strict-send-no-issuer, classic.failed-tx.p30-path-receive-exceeded-work-limit, classic.failed-tx.p30-path-receive-line-full, classic.failed-tx.p30-path-receive-no-destination, classic.failed-tx.p30-path-receive-no-trust, classic.failed-tx.p30-path-receive-not-authorized, classic.failed-tx.p30-path-receive-offer-cross-self, classic.failed-tx.p30-path-receive-src-no-trust, classic.failed-tx.p30-path-receive-src-not-authorized, classic.failed-tx.p30-path-receive-too-few-offers, classic.failed-tx.p30-path-receive-underfunded, classic.failed-tx.p30-path-send-line-full, classic.failed-tx.p30-path-send-no-destination, classic.failed-tx.p30-path-send-no-trust, classic.failed-tx.p30-path-send-not-authorized, classic.failed-tx.p30-path-send-offer-cross-self, classic.failed-tx.p30-path-send-src-no-trust, classic.failed-tx.p30-path-send-src-not-authorized, classic.failed-tx.p30-path-send-too-few-offers, classic.failed-tx.p30-path-send-under-destmin, classic.failed-tx.p30-path-send-underfunded, classic.max-length.p30-path-strict-receive-5, classic.max-length.p30-path-strict-send-5op.path_payment.path(6): cap38.liquidity-pools.p30-path-payment-through-pool, classic.failed-tx.p12-path-payment-strict-receive-no-issuer, classic.failed-tx.p12-path-payment-strict-send-no-issuer, classic.max-length.p30-path-strict-receive-5, classic.max-length.p30-path-strict-send-5, classic.max-length.synth-path-6-assetsop.manage_offer.offer_id(32): cap03.offer-liabilities.p17-manage-buy-offer-claim-atom-v0, cap03.offer-liabilities.p17-manage-sell-offer-claim-atom-v0, cap03.offer-liabilities.p30-buy-offer-crosses-and-rests-envelope, cap03.offer-liabilities.p30-buy-offer-near-int64-max-envelope, cap03.offer-liabilities.p30-buy-offer-takes-two-makers-envelope, cap03.offer-liabilities.p30-credit-pair-cross-partial, cap03.offer-liabilities.p30-issuer-sells-own-asset, cap03.offer-liabilities.p30-sell-credit-trustline-liabilities, cap03.offer-liabilities.p30-sell-native-selling-liabilities, cap03.offer-liabilities.p30-update-offer-by-id, cap03.offer-liabilities.synth-negative-offer-id, cap03.offer-liabilities.synth-textual-offer-id-canonical, cap03.offer-liabilities.synth-textual-offer-id-implementation-defined, cap03.offer-liabilities.synth-textual-offer-id-noncanonical, classic.failed-tx.p30-manage-buy-buy-no-trust, classic.failed-tx.p30-manage-buy-buy-not-authorized, classic.failed-tx.p30-manage-buy-cross-self, classic.failed-tx.p30-manage-buy-line-full, classic.failed-tx.p30-manage-buy-low-reserve, classic.failed-tx.p30-manage-buy-not-found, classic.failed-tx.p30-manage-buy-sell-no-trust, classic.failed-tx.p30-manage-buy-sell-not-authorized, classic.failed-tx.p30-manage-buy-underfunded, classic.failed-tx.p30-manage-sell-buy-no-trust, classic.failed-tx.p30-manage-sell-buy-not-authorized, classic.failed-tx.p30-manage-sell-cross-self, classic.failed-tx.p30-manage-sell-line-full, classic.failed-tx.p30-manage-sell-low-reserve, classic.failed-tx.p30-manage-sell-not-found, classic.failed-tx.p30-manage-sell-sell-no-trust, classic.failed-tx.p30-manage-sell-sell-not-authorized, classic.failed-tx.p30-manage-sell-underfundedop.manage_offer.assets_amount_price(33): cap03.offer-liabilities.p17-manage-buy-offer-claim-atom-v0, cap03.offer-liabilities.p17-manage-sell-offer-claim-atom-v0, cap03.offer-liabilities.p30-buy-offer-crosses-and-rests-envelope, cap03.offer-liabilities.p30-buy-offer-near-int64-max-envelope, cap03.offer-liabilities.p30-buy-offer-takes-two-makers-envelope, cap03.offer-liabilities.p30-credit-pair-cross-partial, cap03.offer-liabilities.p30-issuer-sells-own-asset, cap03.offer-liabilities.p30-sell-credit-trustline-liabilities, cap03.offer-liabilities.p30-sell-native-selling-liabilities, cap03.offer-liabilities.p30-update-offer-by-id, classic.failed-tx.p12-manage-buy-offer-buy-no-issuer, classic.failed-tx.p12-manage-buy-offer-sell-no-issuer, classic.failed-tx.p12-manage-sell-offer-buy-no-issuer, classic.failed-tx.p12-manage-sell-offer-sell-no-issuer, classic.failed-tx.p30-manage-buy-buy-no-trust, classic.failed-tx.p30-manage-buy-buy-not-authorized, classic.failed-tx.p30-manage-buy-cross-self, classic.failed-tx.p30-manage-buy-line-full, classic.failed-tx.p30-manage-buy-low-reserve, classic.failed-tx.p30-manage-buy-not-found, classic.failed-tx.p30-manage-buy-sell-no-trust, classic.failed-tx.p30-manage-buy-sell-not-authorized, classic.failed-tx.p30-manage-buy-underfunded, classic.failed-tx.p30-manage-sell-buy-no-trust, classic.failed-tx.p30-manage-sell-buy-not-authorized, classic.failed-tx.p30-manage-sell-cross-self, classic.failed-tx.p30-manage-sell-line-full, classic.failed-tx.p30-manage-sell-low-reserve, classic.failed-tx.p30-manage-sell-not-found, classic.failed-tx.p30-manage-sell-sell-no-trust, classic.failed-tx.p30-manage-sell-sell-not-authorized, classic.failed-tx.p30-manage-sell-underfunded, classic.failed-tx.p30-passive-offer-operation-sourceop.set_options.home_domain(3): classic.max-length.p30-home-domain-32, classic.max-length.p30-home-domain-del-rejected, classic.max-length.p30-home-domain-utf8-rejectedop.set_options.inflation_dest(2): cap26.inflation.p11-inflation-payout, classic.failed-tx.p30-set-options-invalid-inflationop.set_options.signer(24): cap15.fee-bump.p30-inner-bad-auth, cap15.fee-bump.p30-inner-bad-auth-extra, cap33.sponsorship.p30-sponsored-account-entry, cap33.sponsorship.p30-sponsored-signer-envelope, cap40.signed-payload-signer.p28-remove-signer-payload-1-envelope, cap40.signed-payload-signer.p28-set-options-empty-payload, cap40.signed-payload-signer.p28-set-options-payload-1-envelope, cap40.signed-payload-signer.p28-set-options-payload-32-envelope, cap40.signed-payload-signer.p28-set-options-payload-4-envelope, cap40.signed-payload-signer.p28-sponsored-set-options-payload-64-envelope, cap40.signed-payload-signer.p30-remove-signer-payload-1-envelope, cap40.signed-payload-signer.p30-set-options-empty-payload, cap40.signed-payload-signer.p30-set-options-payload-1-envelope, cap40.signed-payload-signer.p30-set-options-payload-32-envelope, cap40.signed-payload-signer.p30-set-options-payload-4-envelope, cap40.signed-payload-signer.p30-sponsored-set-options-payload-64-envelope, cap40.signed-payload-signer.synth-empty-payload-signer, cap40.signed-payload-signer.synth-payload-65-bytes, classic.failed-tx.p30-add-pre-auth-signer-envelope, classic.failed-tx.p30-op-bad-auth, classic.failed-tx.p30-set-options-low-reserve, classic.failed-tx.p30-tx-bad-auth-extra, classic.max-length.p30-account-20-signers, classic.max-length.p30-signer-21-too-manyop.set_options.thresholds_and_flags(4): cap15.fee-bump.p30-inner-bad-auth-extra, classic.failed-tx.p30-set-options-auth-revocable-required, classic.failed-tx.p30-set-options-cant-change, classic.failed-tx.p30-tx-bad-auth-extraop.change_trust.line(9): cap33.sponsorship.p30-sponsored-trustline-envelope, cap38.liquidity-pools.p30-change-trust-pool-byte-order-envelope, cap38.liquidity-pools.p30-change-trust-pool-strkey-order, classic.failed-tx.p12-change-trust-self-not-allowed, classic.failed-tx.p30-change-trust-cannot-delete, classic.failed-tx.p30-change-trust-invalid-limit, classic.failed-tx.p30-change-trust-low-reserve, classic.failed-tx.p30-change-trust-no-issuer, classic.failed-tx.p9-change-trust-native-internal-error-envelopeop.change_trust.line.liquidity_pool(16): cap38.liquidity-pools.p30-asset-trustline-pool-use-count, cap38.liquidity-pools.p30-change-trust-pool-byte-order-envelope, cap38.liquidity-pools.p30-change-trust-pool-strkey-order, cap38.liquidity-pools.synth-assets-code-case, cap38.liquidity-pools.synth-assets-code-padding-length, cap38.liquidity-pools.synth-assets-code-padding-prefix, cap38.liquidity-pools.synth-assets-dotnet257, cap38.liquidity-pools.synth-assets-equal, cap38.liquidity-pools.synth-assets-issuer-high-bit, cap38.liquidity-pools.synth-assets-js1766-issuers, cap38.liquidity-pools.synth-assets-py1246-testnet-pool, cap38.liquidity-pools.synth-assets-type-4-vs-12, cap38.liquidity-pools.synth-assets-type-native, cap38.liquidity-pools.synth-change-trust-strkey-order, classic.failed-tx.p30-change-trust-not-auth-maintain-liabilities, classic.failed-tx.p30-change-trust-trust-line-missingop.change_trust.limit(8): cap38.liquidity-pools.p30-change-trust-pool-byte-order-envelope, cap38.liquidity-pools.p30-change-trust-pool-strkey-order, classic.failed-tx.p12-change-trust-self-not-allowed, classic.failed-tx.p30-change-trust-cannot-delete, classic.failed-tx.p30-change-trust-invalid-limit, classic.failed-tx.p30-change-trust-low-reserve, classic.failed-tx.p30-change-trust-no-issuer, classic.failed-tx.p9-change-trust-native-internal-error-envelopeop.allow_trust(11): cap18.maintain-liabilities.p30-allow-trust-authorize-alphanum4, cap18.maintain-liabilities.p30-allow-trust-authorized-to-maintain-alphanum4, cap18.maintain-liabilities.p30-allow-trust-both-flags, cap18.maintain-liabilities.p30-allow-trust-maintain-to-revoked-alphanum4, cap18.maintain-liabilities.p30-allow-trust-unauthorized-to-maintain-alphanum12, cap18.maintain-liabilities.p30-set-flags-and-allow-trust-maintain, classic.failed-tx.p12-allow-trust-self-not-allowed, classic.failed-tx.p12-allow-trust-trust-not-required, classic.failed-tx.p30-allow-trust-cant-revoke, classic.failed-tx.p30-allow-trust-low-reserve, classic.failed-tx.p30-allow-trust-no-trust-lineop.manage_data.data_name(5): classic.failed-tx.p1-manage-data-not-supported-yet, classic.failed-tx.p30-manage-data-low-reserve, classic.failed-tx.p30-manage-data-name-not-found, classic.failed-tx.p30-manage-data-removed, classic.max-length.p30-data-name-value-64-envelopeop.manage_data.data_value(6): classic.failed-tx.p1-manage-data-not-supported-yet, classic.failed-tx.p30-manage-data-low-reserve, classic.failed-tx.p30-manage-data-name-not-found, classic.failed-tx.p30-manage-data-removed, classic.max-length.p30-data-name-value-64-envelope, classic.max-length.synth-data-value-65-bytesop.bump_sequence.bump_to(9): cap01.bump-sequence.p30-bump-behind, cap01.bump-sequence.p30-bump-forward, cap01.bump-sequence.p30-bump-negative, cap01.bump-sequence.p30-bump-past-starting-sequence, cap01.bump-sequence.p30-bump-then-merge-seqnum-too-far-envelope, cap01.bump-sequence.p30-bump-to-i64-max, cap15.fee-bump.p30-inner-bad-min-seq-gap, cap15.fee-bump.p30-inner-bad-seq, classic.failed-tx.p30-tx-bad-min-seq-gapop.create_claimable_balance.claimants.destination(6): cap23.claimable-balances.p30-create-ten-claimants-depth-4-envelope, cap23.claimable-balances.p30-entry-ten-claimants-depth-4, cap23.claimable-balances.synth-claimants-11, classic.failed-tx.p30-create-claimable-balance-low-reserve, classic.failed-tx.p30-create-claimable-balance-not-authorized, classic.failed-tx.p30-create-claimable-balance-underfundedop.create_claimable_balance.claimants.predicate(3): cap23.claimable-balances.p30-create-predicate-depth-5, cap23.claimable-balances.p30-create-ten-claimants-depth-4-envelope, cap23.claimable-balances.p30-entry-ten-claimants-depth-4op.create_claimable_balance.asset_amount(9): cap23.claimable-balances.p30-create-ten-claimants-depth-4-envelope, cap33.sponsorship.p30-sponsored-claimable-balance-envelope, cap35.clawback.p30-create-clawback-enabled-balance, cap67.unified-events.p22-backfill-create-claimable-balance, cap67.unified-events.p22-create-claimable-balance, cap67.unified-events.p30-create-claimable-balance, classic.failed-tx.p30-create-claimable-balance-low-reserve, classic.failed-tx.p30-create-claimable-balance-not-authorized, classic.failed-tx.p30-create-claimable-balance-underfundedop.claim_claimable_balance.balance_id(8): cap23.claimable-balances.p30-claim-through-depth-4-envelope, cap67.unified-events.p22-backfill-claim-claimable-balance, cap67.unified-events.p22-claim-claimable-balance, cap67.unified-events.p30-claim-claimable-balance, classic.failed-tx.p30-claim-does-not-exist, classic.failed-tx.p30-claim-line-full, classic.failed-tx.p30-claim-no-trust, classic.failed-tx.p30-claim-not-authorizedop.clawback.from(9): cap27.muxed-accounts.p28-clawback-from-id-2p64-1, cap27.muxed-accounts.p30-clawback-from-id-2p64-1, cap35.clawback.p30-clawback, cap35.clawback.p30-clawback-not-enabled, cap67.unified-events.p22-backfill-clawback, cap67.unified-events.p22-clawback, cap67.unified-events.p30-clawback, classic.failed-tx.p30-clawback-no-trust, classic.failed-tx.p30-clawback-underfundedop.clawback.from.mux_id(2): cap27.muxed-accounts.p28-clawback-from-id-2p64-1, cap27.muxed-accounts.p30-clawback-from-id-2p64-1op.clawback.asset_amount(7): cap35.clawback.p30-clawback, cap35.clawback.p30-clawback-not-enabled, cap67.unified-events.p22-backfill-clawback, cap67.unified-events.p22-clawback, cap67.unified-events.p30-clawback, classic.failed-tx.p30-clawback-no-trust, classic.failed-tx.p30-clawback-underfundedop.clawback_claimable_balance.balance_id(7): cap35.clawback.p30-clawback-claimable-balance-envelope, cap67.unified-events.p22-backfill-clawback-claimable-balance, cap67.unified-events.p22-clawback-claimable-balance, cap67.unified-events.p30-clawback-claimable-balance, classic.failed-tx.p30-clawback-claimable-balance-does-not-exist, classic.failed-tx.p30-clawback-claimable-balance-not-clawback-enabled, classic.failed-tx.p30-clawback-claimable-balance-not-issuerop.set_trust_line_flags(15): cap18.maintain-liabilities.p30-set-flags-and-allow-trust-maintain, cap35.clawback.p30-set-flags-authorized-to-maintain, cap35.clawback.p30-set-flags-clawback-enabled, cap35.clawback.p30-set-flags-reauthorize-clear-clawback, cap38.liquidity-pools.p30-revoke-redeems-pool-shares, cap67.unified-events.p22-backfill-set-authorized-false, cap67.unified-events.p22-backfill-set-authorized-true, cap67.unified-events.p22-set-authorized-false, cap67.unified-events.p22-set-authorized-true, cap67.unified-events.p30-set-authorized-false, cap67.unified-events.p30-set-authorized-true, classic.failed-tx.p30-set-trust-line-flags-cant-revoke, classic.failed-tx.p30-set-trust-line-flags-invalid-state, classic.failed-tx.p30-set-trust-line-flags-low-reserve, classic.failed-tx.p30-set-trust-line-flags-no-trust-lineop.begin_sponsoring.sponsored_id(5): cap15.fee-bump.p30-inner-bad-sponsorship, cap33.sponsorship.p30-sponsored-account-envelope, cap33.sponsorship.p30-sponsored-claimable-balance-envelope, cap33.sponsorship.p30-sponsored-signer-envelope, cap33.sponsorship.p30-sponsored-trustline-envelopeop.revoke_sponsorship(8): cap33.sponsorship.p30-revoke-signer-sponsorship-envelope, cap33.sponsorship.p30-revoke-trustline-sponsorship-envelope, cap40.signed-payload-signer.p28-revoke-sponsorship-payload-64-envelope, cap40.signed-payload-signer.p30-revoke-sponsorship-payload-64-envelope, classic.failed-tx.p30-revoke-does-not-exist, classic.failed-tx.p30-revoke-low-reserve, classic.failed-tx.p30-revoke-not-sponsor, classic.failed-tx.p30-revoke-only-transferableop.account_merge.destination(12): cap01.bump-sequence.p30-bump-then-merge-seqnum-too-far-envelope, cap01.bump-sequence.p30-merge-seqnum-too-far, cap03.offer-liabilities.p30-merge-dest-full, cap27.muxed-accounts.p28-account-merge-destination-id-2p53, cap27.muxed-accounts.p30-account-merge-destination-id-2p53, cap67.unified-events.p22-account-merge-muxed-destination, cap67.unified-events.p22-backfill-account-merge-muxed-destination, cap67.unified-events.p30-account-merge-muxed-destination, classic.failed-tx.p30-merge-has-sub-entries, classic.failed-tx.p30-merge-immutable-set, classic.failed-tx.p30-merge-is-sponsor, classic.failed-tx.p30-merge-no-accountop.liquidity_pool_deposit(10): cap38.liquidity-pools.p30-deposit-envelope, cap67.unified-events.p22-backfill-liquidity-pool-deposit, cap67.unified-events.p22-liquidity-pool-deposit, cap67.unified-events.p30-liquidity-pool-deposit, classic.failed-tx.p30-deposit-bad-price, classic.failed-tx.p30-deposit-line-full, classic.failed-tx.p30-deposit-no-trust, classic.failed-tx.p30-deposit-not-authorized, classic.failed-tx.p30-deposit-pool-full, classic.failed-tx.p30-deposit-underfundedop.liquidity_pool_withdraw(5): cap38.liquidity-pools.p30-withdraw-envelope, classic.failed-tx.p30-withdraw-line-full, classic.failed-tx.p30-withdraw-no-trust, classic.failed-tx.p30-withdraw-under-minimum, classic.failed-tx.p30-withdraw-underfundedop.invoke_host_function.invoke_contract.contract_address(21): cap73.sac-trust.p28-trust-again, cap73.sac-trust.p28-trust-envelope, cap73.sac-trust.p28-xlm-transfer-creates-account-envelope, cap73.sac-trust.p30-trust-again, cap73.sac-trust.p30-trust-envelope, cap73.sac-trust.p30-xlm-transfer-creates-account-envelope, cap85.executable-tag.p30-contract-deploys-external-ref-envelope, cap85.executable-tag.p30-echo-executable-tag-value-envelope, cap85.executable-tag.p30-publish-executable-tag, cap85.executable-tag.p30-update-to-external-ref-envelope, sep41.muxed-addresses.p28-credit-mint-to-muxed, sep41.muxed-addresses.p28-credit-transfer-to-muxed, sep41.muxed-addresses.p28-xlm-transfer-to-muxed, sep41.muxed-addresses.p30-credit-mint-to-muxed, sep41.muxed-addresses.p30-credit-transfer-to-muxed, sep41.muxed-addresses.p30-xlm-transfer-to-muxed, soroban.invoke-basics.p30-contract-deploys-with-constructor-envelope, soroban.invoke-basics.p30-failed-events-discarded-envelope, soroban.invoke-basics.p30-failed-trapped-envelope, soroban.invoke-basics.p30-invoke-restored-contract-envelope, soroban.invoke-basics.p30-invoke-temporary-entry-envelopeop.invoke_host_function.invoke_contract.function_name(21): cap73.sac-trust.p28-trust-again, cap73.sac-trust.p28-trust-envelope, cap73.sac-trust.p28-xlm-transfer-creates-account-envelope, cap73.sac-trust.p30-trust-again, cap73.sac-trust.p30-trust-envelope, cap73.sac-trust.p30-xlm-transfer-creates-account-envelope, cap85.executable-tag.p30-contract-deploys-external-ref-envelope, cap85.executable-tag.p30-echo-executable-tag-value-envelope, cap85.executable-tag.p30-publish-executable-tag, cap85.executable-tag.p30-update-to-external-ref-envelope, sep41.muxed-addresses.p28-credit-mint-to-muxed, sep41.muxed-addresses.p28-credit-transfer-to-muxed, sep41.muxed-addresses.p28-xlm-transfer-to-muxed, sep41.muxed-addresses.p30-credit-mint-to-muxed, sep41.muxed-addresses.p30-credit-transfer-to-muxed, sep41.muxed-addresses.p30-xlm-transfer-to-muxed, soroban.invoke-basics.p30-contract-deploys-with-constructor-envelope, soroban.invoke-basics.p30-failed-events-discarded-envelope, soroban.invoke-basics.p30-failed-trapped-envelope, soroban.invoke-basics.p30-invoke-restored-contract-envelope, soroban.invoke-basics.p30-invoke-temporary-entry-envelopeop.invoke_host_function.invoke_contract.args(59): cap67.unified-events.p30-sac-issuer-transfer-to-muxed-contract, cap73.sac-trust.p28-trust-again, cap73.sac-trust.p28-trust-envelope, cap73.sac-trust.p28-xlm-transfer-creates-account-envelope, cap73.sac-trust.p30-trust-again, cap73.sac-trust.p30-trust-envelope, cap73.sac-trust.p30-xlm-transfer-creates-account-envelope, cap84.muxed-contract.p30-issuer-transfer-to-muxed-contract, cap84.muxed-contract.p30-mint-to-muxed-contract-refused, cap84.muxed-contract.p30-xlm-transfer-to-muxed-contract, cap85.executable-tag.p30-contract-deploys-external-ref-envelope, cap85.executable-tag.p30-echo-executable-tag-value-envelope, cap85.executable-tag.p30-publish-executable-tag, cap85.executable-tag.p30-update-to-external-ref-envelope, sep41.muxed-addresses.p28-credit-mint-to-muxed, sep41.muxed-addresses.p28-credit-transfer-to-muxed, sep41.muxed-addresses.p28-xlm-transfer-to-muxed, sep41.muxed-addresses.p30-credit-mint-to-muxed, sep41.muxed-addresses.p30-credit-transfer-to-muxed, sep41.muxed-addresses.p30-xlm-transfer-to-muxed, soroban.events-max.p30-echo-bytes-at-limit, soroban.events-max.p30-echo-bytes-at-limit-over, soroban.events-max.p30-emit-topics-bytes-at-limit, soroban.events-max.p30-emit-topics-bytes-at-limit-over, soroban.invoke-basics.p30-contract-deploys-with-constructor-envelope, soroban.invoke-basics.p30-failed-events-discarded-envelope, soroban.invoke-basics.p30-failed-trapped-envelope, soroban.invoke-basics.p30-invoke-restored-contract-envelope, soroban.invoke-basics.p30-invoke-temporary-entry-envelope, soroban.scval-shapes.p30-errors-auth-crypto-events-envelope, soroban.scval-shapes.p30-integer-extremes-envelope, soroban.scval-shapes.p30-map-depth-max-envelope, soroban.scval-shapes.p30-map-depth-over, soroban.scval-shapes.p30-map-keys-collide-native-envelope, soroban.scval-shapes.p30-map-mixed-keys-sorted-envelope, soroban.scval-shapes.p30-map-mixed-keys-unsorted, soroban.scval-shapes.p30-string-not-utf8-envelope, soroban.scval-shapes.p30-symbol-not-utf8, soroban.scval-shapes.p30-vec-depth-max-envelope, soroban.scval-shapes.p30-vec-depth-over, soroban.scval-shapes.synth-map-address-and-string, soroban.scval-shapes.synth-map-bool-and-text, soroban.scval-shapes.synth-map-bytes-and-string, soroban.scval-shapes.synth-map-js1789-u32-symbol, soroban.scval-shapes.synth-map-keys-addresses, soroban.scval-shapes.synth-map-keys-bytes, soroban.scval-shapes.synth-map-keys-i128, soroban.scval-shapes.synth-map-keys-mixed-types, soroban.scval-shapes.synth-map-keys-symbols, soroban.scval-shapes.synth-map-keys-u64-small-and-object, soroban.scval-shapes.synth-map-keys-vecs, soroban.scval-shapes.synth-map-numeric-types, soroban.scval-shapes.synth-map-u32-string-symbol, soroban.scval-shapes.synth-map-vec-and-string, soroban.scval-shapes.synth-map-void-and-text, soroban.scval-shapes.synth-scmap-depth-197-deepest-accepted, soroban.scval-shapes.synth-scmap-depth-198-over-cap, soroban.scval-shapes.synth-scvec-depth-246-deepest-accepted, soroban.scval-shapes.synth-scvec-depth-247-over-capop.invoke_host_function.upload_wasm(1): soroban.invoke-basics.p30-upload-wasmop.invoke_host_function.create_contract.preimage(8): cap73.sac-trust.p28-deploy-credit-sac-envelope, cap73.sac-trust.p28-deploy-xlm-sac-envelope, cap73.sac-trust.p30-deploy-credit-sac-envelope, cap73.sac-trust.p30-deploy-xlm-sac-envelope, cap85.executable-tag.p30-create-external-ref-envelope, cap85.executable-tag.p30-create-external-ref-missing-tag, soroban.invoke-basics.p30-create-contract-v1-envelope, soroban.invoke-basics.p30-create-contract-v2-constructor-envelopeop.invoke_host_function.create_contract_v2.constructor_args(1): soroban.invoke-basics.p30-create-contract-v2-constructor-envelopeop.invoke_host_function.auth(22): cap71.credentials.p30-address-v2, cap71.credentials.p30-with-delegates-nested, cap71.credentials.p30-with-delegates-unsorted, cap84.muxed-contract.p30-issuer-transfer-to-muxed-contract, cap84.muxed-contract.p30-mint-to-muxed-contract-refused, cap84.muxed-contract.p30-xlm-transfer-to-muxed-contract, soroban.auth-trees.p30-address-credentials, soroban.auth-trees.p30-declared-depth-245, soroban.auth-trees.p30-declared-depth-246, soroban.auth-trees.p30-declared-depth-492, soroban.auth-trees.p30-declared-depth-493, soroban.auth-trees.p30-failed-carries-auth, soroban.auth-trees.p30-fanout-323, soroban.auth-trees.p30-fanout-324-fails, soroban.auth-trees.p30-matched-depth-31, soroban.auth-trees.p30-matched-depth-32-fails, soroban.auth-trees.p30-source-account-credentials, soroban.auth-trees.p30-unmatched-create-claimable-balance-deployer, soroban.auth-trees.p30-unmatched-create-failed-tx, soroban.auth-trees.p30-unmatched-create-success, soroban.auth-trees.synth-auth-depth-493-deepest-accepted, soroban.auth-trees.synth-auth-depth-494-over-capauth.credentials.source_account(4): soroban.auth-trees.p30-source-account-credentials, soroban.auth-trees.p30-unmatched-create-claimable-balance-deployer, soroban.auth-trees.p30-unmatched-create-failed-tx, soroban.auth-trees.p30-unmatched-create-successauth.credentials.address.address(14): cap71.credentials.p30-address-v2, cap71.credentials.p30-with-delegates-nested, cap71.credentials.p30-with-delegates-unsorted, soroban.auth-trees.p30-address-credentials, soroban.auth-trees.p30-declared-depth-245, soroban.auth-trees.p30-declared-depth-246, soroban.auth-trees.p30-declared-depth-492, soroban.auth-trees.p30-declared-depth-493, soroban.auth-trees.p30-failed-carries-auth, soroban.auth-trees.p30-fanout-323, soroban.auth-trees.p30-fanout-324-fails, soroban.auth-trees.p30-matched-depth-31, soroban.auth-trees.p30-matched-depth-32-fails, soroban.auth-trees.p30-unmatched-create-successauth.credentials.address.nonce(14): cap71.credentials.p30-address-v2, cap71.credentials.p30-with-delegates-nested, cap71.credentials.p30-with-delegates-unsorted, soroban.auth-trees.p30-address-credentials, soroban.auth-trees.p30-declared-depth-245, soroban.auth-trees.p30-declared-depth-246, soroban.auth-trees.p30-declared-depth-492, soroban.auth-trees.p30-declared-depth-493, soroban.auth-trees.p30-failed-carries-auth, soroban.auth-trees.p30-fanout-323, soroban.auth-trees.p30-fanout-324-fails, soroban.auth-trees.p30-matched-depth-31, soroban.auth-trees.p30-matched-depth-32-fails, soroban.auth-trees.p30-unmatched-create-successauth.credentials.address.signature_expiration_ledger(14): cap71.credentials.p30-address-v2, cap71.credentials.p30-with-delegates-nested, cap71.credentials.p30-with-delegates-unsorted, soroban.auth-trees.p30-address-credentials, soroban.auth-trees.p30-declared-depth-245, soroban.auth-trees.p30-declared-depth-246, soroban.auth-trees.p30-declared-depth-492, soroban.auth-trees.p30-declared-depth-493, soroban.auth-trees.p30-failed-carries-auth, soroban.auth-trees.p30-fanout-323, soroban.auth-trees.p30-fanout-324-fails, soroban.auth-trees.p30-matched-depth-31, soroban.auth-trees.p30-matched-depth-32-fails, soroban.auth-trees.p30-unmatched-create-successauth.credentials.address.signature(14): cap71.credentials.p30-address-v2, cap71.credentials.p30-with-delegates-nested, cap71.credentials.p30-with-delegates-unsorted, soroban.auth-trees.p30-address-credentials, soroban.auth-trees.p30-declared-depth-245, soroban.auth-trees.p30-declared-depth-246, soroban.auth-trees.p30-declared-depth-492, soroban.auth-trees.p30-declared-depth-493, soroban.auth-trees.p30-failed-carries-auth, soroban.auth-trees.p30-fanout-323, soroban.auth-trees.p30-fanout-324-fails, soroban.auth-trees.p30-matched-depth-31, soroban.auth-trees.p30-matched-depth-32-fails, soroban.auth-trees.p30-unmatched-create-successauth.credentials.address_with_delegates.delegates(2): cap71.credentials.p30-with-delegates-nested, cap71.credentials.p30-with-delegates-unsortedauth.root_invocation(18): cap71.credentials.p30-address-v2, cap71.credentials.p30-with-delegates-nested, soroban.auth-trees.p30-address-credentials, soroban.auth-trees.p30-declared-depth-245, soroban.auth-trees.p30-declared-depth-246, soroban.auth-trees.p30-declared-depth-492, soroban.auth-trees.p30-declared-depth-493, soroban.auth-trees.p30-failed-carries-auth, soroban.auth-trees.p30-fanout-323, soroban.auth-trees.p30-fanout-324-fails, soroban.auth-trees.p30-matched-depth-31, soroban.auth-trees.p30-matched-depth-32-fails, soroban.auth-trees.p30-source-account-credentials, soroban.auth-trees.p30-unmatched-create-claimable-balance-deployer, soroban.auth-trees.p30-unmatched-create-failed-tx, soroban.auth-trees.p30-unmatched-create-success, soroban.auth-trees.synth-auth-depth-493-deepest-accepted, soroban.auth-trees.synth-auth-depth-494-over-capop.extend_footprint_ttl.extend_to(4): soroban.invoke-basics.p30-extend-ttl-envelope, soroban.muxed-source.p28-muxed-and-base-source-one-ledger, soroban.muxed-source.p30-extend-ttl-muxed-source, soroban.muxed-source.p30-extend-ttl-noop-muxed-sourceop.restore_footprint(5): cap66.archival.p30-restore-footprint, soroban.invoke-basics.p30-restore-archived-instance-and-code-envelope, soroban.muxed-source.p28-restore-muxed-source, soroban.muxed-source.p30-restore-muxed-source, soroban.muxed-source.p30-restore-noop-muxed-source
Recommended caps
Each cap below equals the tightest bound the network itself guarantees, so applying it never refuses data that core could have put in a ledger. A value above a cap is malformed or hostile input.
The bounds that matter most:
- Envelope depth. From protocol 28 core rejects with
txMALFORMEDany envelope that its Rust XDR parser cannot read at depth 1000. Before protocol 28 it used a depth check of 500 in its C++ XDR library (validateXDRForProtocol). - ScVal depth. The Soroban host does not produce values nested deeper than
DEFAULT_HOST_DEPTH_LIMIT, 100 (limits.rs). Arguments in an envelope are bounded only by the envelope depth, so a failed transaction can carry deeper ones. - Authorization trees. The host converts declared invocation trees without a depth limit (
AuthorizedInvocation::from_xdr). Only the envelope depth and size bound them, which allows a single chain of 493 levels and up to 2540 invocation nodes in one transaction (auth.invocation.depthandauth.invocation.nodesbelow). - Unbounded XDR vectors.
SCVec,SCMap,SCBytes,SCString, authorization entry lists and sub-invocation lists have no XDR length bound (Stellar-contract.x, Stellar-transaction.x). Their caps below are derived from the transaction size limit.
Decoders differ in what they do by default. rs-stellar-xdr decodes with the Limits the caller passes, and Limits::none() turns off both the depth and the length limit (generated.rs). go-xdr, which go-stellar-sdk uses, defaults to a depth limit of 1500 and caps slice preallocation at 256 elements (decode.go). Whatever the language, pass the caps below explicitly.
Caps marked "network setting" come from CONFIG_SETTING ledger entries that validators can change by vote. The values shown were read from testnet and mainnet on the date listed; read the current values from the network rather than hardcoding them.
When a value is refused
A cap, or a decoder, will sometimes refuse a value that is in a ledger. SDKs add refusals as they fix bugs: from version 17.3.0, js-stellar-sdk's scValToNative() throws a TypeError when two keys of an scvMap convert to the same object property, and its release notes say that humanizeEvents() and contract-client decoding can now throw on data read from the network, because any contract can emit such a map (v17.3.0 release notes, js-stellar-sdk#1789).
An indexer that has no plan for a refusal tends to halt. In wallet-backend, a SetOptions operation that added a signed-payload signer made the database copy fail, the persist rolled back together with the cursor, and ingestion retried the same ledger forever (wallet-backend#729). A muxed Soroban source and its base account produced a duplicate key that stopped ingestion at that ledger after every restart (wallet-backend#753). A muxed contract address failed the whole ledger persist (wallet-backend#792). stellar-etl has an open request for a documented policy for non-conforming events (stellar-etl#467).
Decide the policy before it is needed, and test it:
- Skip and count a single value that exceeds a cap or that a decoder refuses: store the rest of the transaction, record what was skipped and why, and expose the count as a metric.
- Quarantine and continue a transaction that cannot be stored: write it, with the error, to a quarantine table, store the rest of the ledger, advance the cursor, and alert. Reprocess quarantined transactions after a fix.
- Halt only when the ledger itself cannot be read, for example when a
LedgerCloseMetafails to decode within the caps, because skipping it would leave a silent gap.
Caps table
| Cap | Applies to | Recommended | Derived from | Why |
|---|---|---|---|---|
xdr.envelope.depth | TransactionEnvelope decode (Limits.depth in rs-stellar-xdr, equivalent recursion guard elsewhere). | 1000 levels | validateXDRForProtocol (rustDepthLimit) = 1000validateXDRForProtocol (xdr::check_xdr_depth before protocol 28) = 500can_parse_transaction (Limits { depth: depth_limit, len: xdr.data.len() }) = depth_limit | From protocol 28 core rejects with txMALFORMED any envelope that the Rust XDR parser cannot read at depth 1000, so no ledger at protocol 28 or later holds a deeper envelope. Before protocol 28 core applied the xdrpp depth checker at 500, which counts every struct, union, vector and optional as one level. The Rust decoder also counts typedef newtypes, and the measured cost of the deepest recursions is 4 Rust levels per 3 xdrpp levels for nested ScVec and 2 per 2 for auth invocations, which keeps those envelopes inside 1000 as well. Decoders that leave depth unlimited (for example Limits::none(), which the stellar-xdr CLI decode command uses) can overflow the stack on hostile input. |
xdr.envelope.lennetwork setting | TransactionEnvelope decode (Limits.len), and any buffer allocated for one envelope. | 132096 bytes | CONFIG_SETTING_CONTRACT_BANDWIDTH_V0.txMaxSizeBytes = 132096 on testnet, read 2026-11-12 at ledger 5694341CONFIG_SETTING_CONTRACT_BANDWIDTH_V0.txMaxSizeBytes = 132096 on mainnet, read 2026-11-12 at ledger 65441861TransactionFrame::checkSorobanResources (getSize() > txMaxSizeBytes) = txMaxSizeBytesMAX_CLASSIC_TX_SIZE_BYTES = 102400 | Soroban envelopes are rejected when xdr_size(envelope) exceeds txMaxSizeBytes, 132096 on both networks. Classic envelopes are held to MAX_CLASSIC_TX_SIZE_BYTES = 100 * 1024 = 102400 by the overlay flood limit; every classic operation also has only fixed-size or XDR-bounded fields. The cap is max(132096, 102400) = 132096. Pass the actual input length as Limits.len when it is smaller, which is what core does in can_parse_transaction. |
xdr.meta.depth | LedgerCloseMeta decode (Limits.depth), including the transaction set it carries. | 1012 levels | validateXDRForProtocol (rustDepthLimit) = 1000LedgerCloseMeta::V2 -> GeneralizedTransactionSet -> TransactionPhase::V1 -> ParallelTxsComponent -> DependentTxCluster -> TransactionEnvelope = 12 levels above the envelope, measured: depthprobe: deepest valid envelope (997) decodes alone at 997 and inside LedgerCloseMeta V2 at 1009DEFAULT_HOST_DEPTH_LIMIT = 100 | The deepest part of a ledger is an envelope in its transaction set. Envelopes are bounded at depth 1000 (xdr.envelope.depth) and the parallel Soroban phase of a LedgerCloseMeta V2 adds 12 levels above the envelope (measured), so 1000 + 12 = 1012. Host-produced values in transaction meta are shallower: at most 100 ScVal levels at 5 decode levels per ScMap level, 500 levels plus the meta prefix. Ten recent testnet and mainnet ledgers decoded at depths 41 to 53. |
xdr.meta.len | LedgerCloseMeta decode (Limits.len) from a datastore file, RPC getLedgers or a captive-core stream. | 33554432 bytes | DEFAULT_XDR_RW_LIMITS.len = 32 * 1024 * 1024 | No network setting bounds the size of a whole LedgerCloseMeta. Set Limits.len to the length of the buffer being decoded, as soroban-env-host does in metered_from_xdr, and refuse buffers above 32 * 1024 * 1024 = 33554432 bytes, the host's last-resort XDR length ceiling. For scale, ten mainnet ledgers read on 2026-11-12 (65441894 to 65441903) were 1.49 MB to 2.49 MB. |
scval.depth | Nested ScVal traversal and native conversion (ScVec, ScMap) for values from transaction meta: contract events, contract data and code entries, return values, diagnostic events. | 100 levels | DEFAULT_HOST_DEPTH_LIMIT = 100Host::from_host_val and Host::to_host_val (with_limited_depth per nested Val) = one level per ScVal | Every ScVal the host writes is produced by from_host_val, which enters one budget depth level per nested value and fails with ExceededLimit past DEFAULT_HOST_DEPTH_LIMIT = 100. Values the host emitted therefore nest at most 100 deep. Use this cap for recursive walks and native conversion of meta-borne ScVals. |
scval.depth.envelope | Nested ScVal traversal for values read from a transaction envelope (InvokeContractArgs.args, constructor args, auth invocation args). | 246 levels | validateXDRForProtocol (rustDepthLimit) = 1000ReadXdr for ScVal, Option<ScVec>, ScVec, VecM (one with_limited_depth each) = 4 levels per ScVec nesting, 5 per ScMap nesting, measured: depthprobe: largest ScVec nesting in an InvokeContract argument that decodes at depth 1000 | Envelope arguments are only bounded by the envelope depth check, not by the host. Nesting through ScVec costs 4 decode levels and through ScMap 5, so an InvokeHostFunction argument can nest at most 246 ScVec levels (measured; (1000 - envelope prefix) / 4) or 197 ScMap levels inside a valid envelope. The host refuses to convert anything past 100 levels, so such a transaction fails, but it is still in the ledger with its arguments. Walk envelope arguments with this bound, and treat anything between 101 and 246 levels as a value the contract never saw. |
scval.vec.lennetwork setting | ScVec element count, and any allocation sized by a decoded ScVec length. | 33024 elements | CONFIG_SETTING_CONTRACT_BANDWIDTH_V0.txMaxSizeBytes = 132096 on mainnet, read 2026-11-12 at ledger 65441861ScVal::Void encoding = 4 bytes, measured: depthprobe: ScVal::Void.to_xdr()typedef SCVal SCVec<> = unbounded | SCVec<> has no XDR bound. The largest ScVal container any ledger holds is in a Soroban envelope (contract data entries are capped at 65536 bytes and events plus return value at 16384, both smaller). The smallest element is ScVal::Void at 4 bytes, so a vector inside a 132096-byte envelope has at most 132096 / 4 = 33024 elements. Check the decoded length against this before reserving capacity (the bug fixed in rs-stellar-xdr#581). |
scval.map.lennetwork setting | ScMap entry count, and any allocation sized by a decoded ScMap length. | 16512 entries | CONFIG_SETTING_CONTRACT_BANDWIDTH_V0.txMaxSizeBytes = 132096 on mainnet, read 2026-11-12 at ledger 65441861ScMapEntry { key: Void, val: Void } encoding = 8 bytes, measured: depthprobe: ScMapEntry.to_xdr()typedef SCMapEntry SCMap<> = unbounded | SCMap<> has no XDR bound. The smallest entry is two ScVal::Void values, 8 bytes, so a map inside a 132096-byte envelope has at most 132096 / 8 = 16512 entries. |
scval.bytes.lennetwork setting | ScBytes and ScString length (SCV_BYTES, SCV_STRING), and any buffer or text column sized by one. | 132096 bytes | CONFIG_SETTING_CONTRACT_BANDWIDTH_V0.txMaxSizeBytes = 132096 on mainnet, read 2026-11-12 at ledger 65441861CONFIG_SETTING_CONTRACT_DATA_ENTRY_SIZE_BYTES = 65536 on mainnet, read 2026-11-12 at ledger 65441861CONFIG_SETTING_CONTRACT_EVENTS_V0.txMaxContractEventsSizeBytes = 16384 on mainnet, read 2026-11-12 at ledger 65441861typedef opaque SCBytes<>; typedef string SCString<> = unbounded | SCBytes<> and SCString<> have no XDR bound. A byte string lives in an envelope (at most txMaxSizeBytes = 132096), a contract data entry (at most 65536) or an event or return value (at most 16384 in total), so max(132096, 65536, 16384) = 132096. SCString is not required to be UTF-8; store it as bytes or validate before converting. |
scval.symbol.len | ScSymbol length (SCV_SYMBOL, function names, event topics). | 32 bytes | SCSYMBOL_LIMIT = 32 | Typedef string SCSymbol<SCSYMBOL_LIMIT> with SCSYMBOL_LIMIT = 32. |
auth.invocation.depth | SorobanAuthorizedInvocation tree walk (subInvocations recursion) in declared auth entries. | 493 levels | validateXDRForProtocol (rustDepthLimit) = 1000ReadXdr for SorobanAuthorizedInvocation and VecM (one with_limited_depth each) = 2 levels per invocation, measured: depthprobe: deepest single-chain invocation tree in an InvokeHostFunction auth entry that decodes at depth 1000AuthorizedInvocation::from_xdr (recursion without a depth limiter) = no host depth checkwith_frame_and_initial_mem_charge (context stack >= DEFAULT_HOST_DEPTH_LIMIT) = 100 | The XDR declares subInvocations<> without a bound and the host converts the tree recursively without a depth limiter, so the only bound is the envelope depth check. Each nesting costs 2 decode levels, and a single chain in an auth entry reaches 493 levels before the envelope exceeds depth 1000 (measured). The host's call stack is capped at DEFAULT_HOST_DEPTH_LIMIT = 100 frames, so only the top 100 levels can ever match a real call; deeper nodes are declared but never authorized, and a transaction carrying them can still succeed. This is the unbounded authorization tree that halted wallet-backend ingestion. |
auth.invocation.nodesnetwork setting | Total SorobanAuthorizedInvocation nodes across all auth entries of one transaction. | 2540 nodes | CONFIG_SETTING_CONTRACT_BANDWIDTH_V0.txMaxSizeBytes = 132096 on mainnet, read 2026-11-12 at ledger 65441861minimal node: ContractFn, ScAddress::Contract, empty symbol, no args, no children = 52 bytes, measured: depthprobe: encoded size difference per added node is 56 bytes with a 1-byte symbol (4 bytes of padded name), so 52 with an empty symboltypedef SorobanAuthorizationEntry SorobanAuthorizationEntries<>; SorobanAuthorizedInvocation subInvocations<> = unbounded | Neither the entry list nor subInvocations has an XDR bound, so the tree size is bounded only by the envelope size. The smallest node is 4 (function type) + 36 (contract address) + 4 (empty symbol) + 4 (args length) + 4 (subInvocations length) = 52 bytes, so a 132096-byte envelope holds at most floor(132096 / 52) = 2540 nodes. Count nodes during the walk and stop at this cap; do not allocate per declared child before checking it. |
events.contract.bytes_per_txnetwork setting | Total encoded size of contract events plus the return value of one Soroban transaction. | 16384 bytes | CONFIG_SETTING_CONTRACT_EVENTS_V0.txMaxContractEventsSizeBytes = 16384 on testnet, read 2026-11-12 at ledger 5694341CONFIG_SETTING_CONTRACT_EVENTS_V0.txMaxContractEventsSizeBytes = 16384 on mainnet, read 2026-11-12 at ledger 65441861InvokeHostFunctionApplyHelper (mEmitEventByte > txMaxContractEventsSizeBytes) = INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED | Core sums the encoded size of every contract event and then the return value, and fails the operation with INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED once the sum passes txMaxContractEventsSizeBytes = 16384. Diagnostic events are not counted. |
events.contract.per_txnetwork setting | Number of contract events (type CONTRACT or SYSTEM) in one Soroban transaction's meta. | 682 events | CONFIG_SETTING_CONTRACT_EVENTS_V0.txMaxContractEventsSizeBytes = 16384 on mainnet, read 2026-11-12 at ledger 65441861minimal ContractEvent (no contract id, no topics, void data) = 24 bytes, measured: depthprobe: ContractEvent.to_xdr() | The smallest ContractEvent encodes to 24 bytes: ext 4, absent contractID 4, type 4, body arm 4, empty topics 4, void data 4. floor(16384 / 24) = 682. Classic operation events added by CAP-67 are not counted against this setting; bound them through the operation count and offers crossed instead. |
events.contract.per_ledgernetwork setting | Number of contract events across all Soroban transactions of one ledger. | 1364000 events | CONFIG_SETTING_CONTRACT_EXECUTION_LANES.ledgerMaxTxCount = 2000 on mainnet, read 2026-11-12 at ledger 65441861CONFIG_SETTING_CONTRACT_EXECUTION_LANES.ledgerMaxTxCount = 2000 on testnet, read 2026-11-12 at ledger 5694341 | ledgerMaxTxCount is the maximum number of Soroban transactions per ledger (2000 on both networks). 2000 transactions * 682 events (events.contract.per_tx) = 1364000. |
events.contract.bytes_per_ledgernetwork setting | Total encoded contract event and return value bytes across one ledger. | 32768000 bytes | CONFIG_SETTING_CONTRACT_EXECUTION_LANES.ledgerMaxTxCount = 2000 on mainnet, read 2026-11-12 at ledger 65441861CONFIG_SETTING_CONTRACT_EVENTS_V0.txMaxContractEventsSizeBytes = 16384 on mainnet, read 2026-11-12 at ledger 65441861 | 2000 Soroban transactions * 16384 bytes = 32768000 bytes. |
soroban.footprint.entriesnetwork setting protocol 23 and later | read-only plus read-write footprint keys in SorobanTransactionData of one transaction. | 400 ledger keys | CONFIG_SETTING_CONTRACT_LEDGER_COST_EXT_V0.txMaxFootprintEntries = 400 on testnet, read 2026-11-12 at ledger 5694341CONFIG_SETTING_CONTRACT_LEDGER_COST_EXT_V0.txMaxFootprintEntries = 400 on mainnet, read 2026-11-12 at ledger 65441861TransactionFrame::checkSorobanResources (totalReads > txMaxFootprintEntries from protocol 23) = txMaxFootprintEntries | From protocol 23 core rejects a transaction whose total footprint exceeds txMaxFootprintEntries = 400. Before protocol 23 the bound was txMaxDiskReadEntries over the whole footprint; that setting reads 200 today, and core initialised txMaxFootprintEntries from it at the protocol 23 upgrade (initialLedgerCostExtEntry). Past values of txMaxDiskReadEntries were not read for this registry, so for ledgers before protocol 23 re-derive the cap from the setting in force at that ledger. |
soroban.footprint.write_entriesnetwork setting | read-write footprint keys of one transaction (bounds ledger entry changes it can write). | 200 ledger keys | CONFIG_SETTING_CONTRACT_LEDGER_COST_V0.txMaxWriteLedgerEntries = 200 on mainnet, read 2026-11-12 at ledger 65441861TransactionFrame::checkSorobanResources (writeEntries.size() > txMaxWriteLedgerEntries) = txMaxWriteLedgerEntries | Core rejects a Soroban transaction with more read-write footprint keys than txMaxWriteLedgerEntries = 200. |
soroban.contract_data.entry_bytesnetwork setting | Encoded size of one CONTRACT_DATA ledger entry in meta changes. | 65536 bytes | CONFIG_SETTING_CONTRACT_DATA_ENTRY_SIZE_BYTES = 65536 on testnet, read 2026-11-12 at ledger 5694341CONFIG_SETTING_CONTRACT_DATA_ENTRY_SIZE_BYTES = 65536 on mainnet, read 2026-11-12 at ledger 65441861validateContractLedgerEntry (maxContractDataEntrySizeBytes < entrySize) = maxContractDataEntrySizeBytes | Core refuses to write a contract data entry larger than contractDataEntrySizeBytes = 65536. |
soroban.contract_data.key_bytesnetwork setting | Encoded size of one CONTRACT_DATA ledger key (footprint keys and meta). | 250 bytes | CONFIG_SETTING_CONTRACT_DATA_KEY_SIZE_BYTES = 250 on mainnet, read 2026-11-12 at ledger 65441861TransactionFrame::checkSorobanResources (xdr_size(key) > maxContractDataKeySizeBytes) = maxContractDataKeySizeBytes | Core rejects a footprint key larger than contractDataKeySizeBytes = 250. |
soroban.contract_code.bytesnetwork setting | Wasm byte length in upload envelopes and CONTRACT_CODE entries. | 131072 bytes | CONFIG_SETTING_CONTRACT_MAX_SIZE_BYTES = 131072 on testnet, read 2026-11-12 at ledger 5694341CONFIG_SETTING_CONTRACT_MAX_SIZE_BYTES = 131072 on mainnet, read 2026-11-12 at ledger 65441861validateContractLedgerEntry (maxContractSizeBytes < entrySize) = maxContractSizeBytes | Core refuses Wasm larger than contractMaxSizeBytes = 131072. |
soroban.txs.per_ledgernetwork setting | Soroban transactions in one ledger. | 2000 transactions | CONFIG_SETTING_CONTRACT_EXECUTION_LANES.ledgerMaxTxCount = 2000 on testnet, read 2026-11-12 at ledger 5694341CONFIG_SETTING_CONTRACT_EXECUTION_LANES.ledgerMaxTxCount = 2000 on mainnet, read 2026-11-12 at ledger 65441861 | ledgerMaxTxCount is defined in Stellar-contract-config-setting.x as the maximum number of Soroban transactions per ledger. |
state_changes.per_tx.query | State change rows materialised per transaction when serving an account-history page (query time only, never at ingestion). | 2000 rows | maxAccountStateChangesPerToID = 2000 | wallet-backend PR #757 bounds BatchGetAccountStateChangesByToIDs at 2000 rows per transaction with a ROW_NUMBER window; its comment calls the value a ceiling chosen from mainnet data. It is not a protocol bound: rows beyond it are dropped from the page, so apply it only to serving and keep every row at ingestion. The 4096 figure that circulates for this PR is the TOID operation-index range used by the operations loader (internal/data/operations.go), not a state-change cap. |
claim_predicate.depth | ClaimPredicate recursion (and, or, not) in CreateClaimableBalance operations and ClaimableBalanceEntry. | 4 levels | validatePredicate (depth > 4 fails; called with depth 1) = 4ClaimPredicate andPredicates<2>, orPredicates<2>, notPredicate* = 2 children | Core starts validatePredicate at depth 1 and fails CreateClaimableBalance when depth exceeds 4, so a stored predicate has at most 4 levels and, with at most 2 children per node, at most 2^4 - 1 = 15 nodes. A failed CreateClaimableBalance still carries a deeper predicate in its envelope, which is bounded only by xdr.envelope.depth. |
claimants.per_balance | Claimants in CreateClaimableBalanceOp and ClaimableBalanceEntry. | 10 claimants | ClaimableBalanceEntry claimants<10> = 10CreateClaimableBalanceOp claimants<10> = 10 | XDR bound. |
path.len | Intermediate assets in PathPaymentStrictReceive and PathPaymentStrictSend. | 5 assets | PathPaymentStrictReceiveOp path<5>, PathPaymentStrictSendOp path<5> = 5 | XDR bound. The full conversion path is the send asset, up to 5 hops and the destination asset. |
offers.crossed.per_opprotocol 11 and later | order-book claim atoms in one ManageOffer or path payment operation result, summed over all hops. | 1000 offers | MAX_OFFERS_TO_CROSS = 1000maxOffersToCross (one budget for the whole operation, decremented per hop) = 1000convertWithOffers (maxOffersToCross == INT64_MAX before protocol version 11) = unbounded before protocol 11 | From protocol 11 an operation crosses at most MAX_OFFERS_TO_CROSS = 1000 offers across all of its hops. Before protocol 11 there was no limit, so do not apply this cap to older ledgers. |
tx.operations | Operations in one transaction. | 100 operations | MAX_OPS_PER_TX = 100 | Transaction and TransactionV0 declare operations<MAX_OPS_PER_TX> with MAX_OPS_PER_TX = 100. A Soroban transaction has exactly one operation. |
tx.signatures | DecoratedSignature entries in one envelope signature list. | 20 signatures | TransactionV0Envelope, TransactionV1Envelope, FeeBumpTransactionEnvelope signatures<20> = 20 | XDR bound per signature list. A fee-bump envelope has its own list and the inner envelope's, so up to 2 * 20 = 40 signatures in total. |
tx.extra_signers | PreconditionsV2.extraSigners. | 2 signer keys | PreconditionsV2 extraSigners<2> = 2 | XDR bound (CAP-21). |
tx.memo_text.len | MEMO_TEXT bytes. | 28 bytes | Memo text<28> = 28 | XDR bound. The bound is in bytes, not characters, and the bytes are not required to be UTF-8; store them as bytes or validate before converting to text. |
signer.signed_payload.len | Payload of an ED25519_SIGNED_PAYLOAD signer key (strkey P). | 64 bytes | SignerKey ed25519SignedPayload payload<64> = 64 | XDR bound (CAP-40). A signed-payload signer is therefore up to 32 + 4 + 64 = 100 bytes of key material, which does not fit a 32-byte account column. |
account.signers | Signers on one AccountEntry and its signerSponsoringIDs. | 20 signers | MAX_SIGNERS = 20 | AccountEntry signers<MAX_SIGNERS> and AccountEntryExtensionV2 signerSponsoringIDs<MAX_SIGNERS> with MAX_SIGNERS = 20. |
account.subentriesprotocol 11 and later | numSubEntries of one account (trustlines, offers, data entries, signers). | 1000 subentries | ACCOUNT_SUBENTRY_LIMIT = 1000numSubEntries > getAccountSubEntryLimit() - mult, from FIRST_PROTOCOL_SUPPORTING_OPERATION_LIMITS = protocol 11 | From protocol 11 (FIRST_PROTOCOL_SUPPORTING_OPERATION_LIMITS) core refuses to add subentries that would take an account past ACCOUNT_SUBENTRY_LIMIT = 1000. Older ledgers had no such limit. |
ledger.upgrades | UpgradeType entries in StellarValue.upgrades. | 6 upgrades | StellarValue upgrades<6>; typedef opaque UpgradeType<128> = 6 entries of at most 128 bytes | XDR bound: at most 6 upgrades per ledger, each at most 128 bytes. |
Participants from meta, not from the envelope
The envelope names who a transaction meant to involve. Transaction meta records which ledger entries actually changed. A participant set built from the envelope is both incomplete and too large:
- It includes accounts that were never touched. A failed transaction keeps its envelope. Claimants of a claimable balance need not exist (
CreateClaimableBalanceOpFrame::doCheckValid), footprint keys need not exist, and a declared authorization entry that no call matched was never checked (see the field table). - It misses accounts that were touched. The Stellar Asset Contract's
trustfunction creates a trustline without emitting an event, and an XLM transfer through it can create an account (CAP-73). Contract calls change balances and data of accounts and contracts that no envelope field names. - It names the same account twice. A muxed source and its base
Gaccount are one account (toAccountID). Keeping both as participants produced the duplicate key in wallet-backend#753. - It treats declared intent as a fact. wallet-backend emitted contract-creation state changes from declared authorization entries whether or not the deployment ran (wallet-backend#746), and later moved Soroban participants to executed meta (commit 9538b3c).
Every change to the ledger is in one of these places (Stellar-ledger.x):
feeProcessingin each transaction's result meta: the fee charge, which happens even when the transaction fails.txChangesBefore, the changes of each operation, andtxChangesAfterinTransactionMeta.postTxApplyFeeProcessinginTransactionResultMetaV1: Soroban fee refunds from protocol 23.- Events: contract events, and from
TransactionMetaV4(protocol 23) the per-operation events and the transaction-level fee events of CAP-67.
Read all of them. In go-stellar-sdk, LedgerTransaction.GetChanges() returns the transaction and operation changes but not the fee changes, which come from GetFeeChanges() and GetPostApplyFeeChanges() (ledger_transaction.go).
To build a transaction's participant set:
- Take the owner of every ledger entry that changed in the places above, keyed by base account (
G), contract (C), liquidity pool (L) or claimable balance (B). - Add the addresses in event topics and data, reduced to their base account or contract, with mux ids recorded separately.
- Keep envelope accounts that do not appear in meta, if you need them, in a separate "declared" set, and never merge the two.
Muxed and signed-payload addresses
Strkey forms
SEP-23 defines ten strkey forms. The first character is set by the top 5 bits of the version byte; the low 3 bits select the algorithm, ed25519 or SHA-256, both 0 today. The binary form is the version byte, the payload, and a 2-byte CRC16 checksum, encoded as base32 without padding.
| Prefix | SEP-23 type | Version byte | Payload | Where it appears in ledger data | How to store it |
|---|---|---|---|---|---|
G | STRKEY_PUBKEY | 6 << 3 | 32-byte ed25519 public key | AccountID, the plain arm of MuxedAccount, ed25519 signer keys, account ScAddress | 32 key bytes with the key type |
M | STRKEY_MUXED | 12 << 3 | 32-byte ed25519 key, then 8-byte id, most significant byte first | MuxedAccount (CAP-27, protocol 13), muxed account ScAddress (CAP-67, protocol 23) | base G key, and the id as an unsigned 64-bit value in its own column |
S | STRKEY_PRIVKEY | 18 << 3 | 32-byte ed25519 secret seed | a secret key, used to sign | never store; treat one in ledger-derived data as a bug |
T | STRKEY_PRE_AUTH_TX | 19 << 3 | 32-byte transaction hash | pre-authorized transaction signer keys | signer column, with the key type |
X | STRKEY_HASH_X | 23 << 3 | 32-byte SHA-256 hash | hash-x signer keys | signer column, with the key type |
P | STRKEY_SIGNED_PAYLOAD | 15 << 3 | 32-byte ed25519 key, 4-byte payload length, 1 to 64 payload bytes, zero padding to a multiple of 4 | signed-payload signer keys (CAP-40, protocol 19) | signer column wide enough for the whole payload |
C | STRKEY_CONTRACT | 2 << 3 | 32-byte contract id | contract ScAddress, ContractID | 32 bytes with the address type |
L | STRKEY_LIQUIDITY_POOL | 11 << 3 | 32-byte pool id | PoolID, liquidity pool ScAddress (CAP-67) | 32 bytes with the address type |
B | STRKEY_CLAIMABLE_BALANCE | 1 << 3 | 1-byte balance id type (0 for V0), then 32-byte hash | ClaimableBalanceID, claimable balance ScAddress (CAP-67) | 33 bytes, type byte included |
W | STRKEY_MUXED_CONTRACT | 22 << 3 | 32-byte contract id, then 8-byte id, most significant byte first | muxed contract ScAddress (CAP-84, protocol 30) | contract C id, and the id as an unsigned 64-bit value in its own column |
The XDR types behind these forms are MuxedAccount (Stellar-transaction.x), SignerKey (Stellar-types.x), PoolID and ClaimableBalanceID (Stellar-types.x) and SCAddress with its six address types (Stellar-contract.x).
SEP-23 requires decoders to reject a strkey that does not re-encode to the same string, including lengths congruent to 1, 3 or 6 mod 8, nonzero unused bits and padding characters. Some base32 libraries accept those inputs, so check them yourself or round-trip every decoded strkey.
Storing muxed addresses
Store the base account and the mux id separately. Core never validates or stores a mux id (see Fields core never checks), so the account that exists on the ledger is the G key. Key balances, trustlines and participants by the base key, and keep the id in a separate column. The id is an unsigned 64-bit value: the SEP-23 test vectors include id 9223372036854775808, above the largest signed 64-bit integer. Store it as an unsigned 64-bit integer or a decimal string. wallet-backend stores it as a decimal string so that ids above 2^63-1 round-trip (toMuxedIDValue).
Never truncate. An M or W payload is 40 bytes. Fitting it into a 32-byte column either fails the write or silently produces rows that do not decode back (wallet-backend#716). wallet-backend now reduces M and W addresses to their 32-byte base key and rejects any other payload length rather than truncating it (AddressBytea.Value).
Build strkeys from parsed fields, not from XDR bytes. XDR puts the id first: MuxedAccount has { id, ed25519 } and MuxedContract has { id, contractId }. The strkey payload puts the key first and the id last. Copying the XDR bytes into a strkey yields a valid-looking address for the wrong key.
Read the destination id of a transfer from its event. From protocol 23 classic and Stellar Asset Contract transfer and mint events carry the destination's id in a to_muxed_id field of the event data (CAP-67), and from protocol 30 a transfer to a W address carries the contract as the destination and the id in to_muxed_id (CAP-84). For a classic payment to a plain G account the same field holds the transaction memo instead, as a string, a u64 or bytes (makeClassicMemoSCVal), so check its type before treating it as an id.
Storing signed-payload signers
Store the full payload with its length. A signed-payload key is a 32-byte ed25519 key plus 1 to 64 payload bytes, up to 100 bytes of key material, so it does not fit an account column. wallet-backend stores signer keys as the version byte plus the full payload, 33 bytes for G, T and X keys and up to 101 bytes for P keys, and rejects any other key type in a signer column (SignerKeyBytea, wallet-backend#729). Two P signers over the same ed25519 key with different payloads are different signers, and both differ from the plain G signer of that key.
Do not treat a signed-payload signature as transaction-specific. It signs only the payload (see Signatures and hints), so the same signature can authorize transactions on any network.
Take signer sets from meta. A SetOptions signer, an extra signer in preconditions and a revoked signer sponsorship are requests. The account entry in meta shows the resulting signers.
Self-audit checklist
Use this list to review an indexer in any language. The first three items are the rules of indexerlint, the shapekit analyzer for Go indexers built on go-stellar-sdk, with the rule IDs and wording it prints.
-
IDX001unvalidated-field-trusted: a transaction field that stellar-core does not validate is trusted without a check. Before you store, key, display or convert to text any field listed as not validated in the field table, validate it yourself (length, encoding, range) or confirm it against what the transaction meta and events say actually happened. -
IDX002missing-depth-or-size-cap: a recursive or length-prefixed xdr structure is walked or decoded without a depth or size cap. Decode external XDR with an explicit depth limit and a length limit no larger than the input buffer, carry a depth counter through every recursive walk of ScVal, auth invocation and claim predicate trees, and check each decoded length against the cap in the caps table before allocating or iterating. -
IDX003participants-from-envelope: participants are derived from the transaction envelope instead of meta changes or events. Build each transaction's participant set from the ledger entry changes (fee changes included) and events in its meta, and treat accounts named only in the envelope as declared intent, not participation. - Muxed accounts are stored as a base account plus an id. Every M address is reduced to its 32-byte G key for account identity, and its 64-bit id is kept in a separate unsigned 64-bit column or as a decimal string. No column truncates an M address to 32 bytes. Fields:
tx.source_account.mux_id,op.source_account.mux_id,feebump.fee_source.mux_id,op.payment.destination.mux_id,op.path_payment.destination.mux_id,op.clawback.from.mux_id. Sources: SEP-23, wallet-backend#716. - Muxed contract (W) addresses are handled. From protocol 30 an ScAddress can be a muxed contract. Its contract id (C) and its 64-bit id are stored separately, and a W address anywhere in events or arguments does not fail the ledger. Sources: CAP-84, wallet-backend#792.
- Signer keys keep their type and full payload. Signer columns store the key type with the key, and are wide enough for a signed-payload (P) key with a 64-byte payload. Signers are never stored in an account column. Fields:
signer_key.signed_payload.payload,op.set_options.signer,tx.cond.extra_signers,op.revoke_sponsorship. Caps:signer.signed_payload.len. Sources: wallet-backend#729. - Strkeys and textual ids are decoded strictly. Strkey decoding rejects every form SEP-23 lists as invalid, and a textual mux id or offer id is accepted only in its canonical decimal form. Fields:
tx.source_account.mux_id,op.manage_offer.offer_id. Sources: SEP-23, js-stellar-sdk#1785. - 64-bit ids stay exact. Mux ids and memo ids are unsigned 64-bit values and can exceed 2^63-1. They are not stored in signed 64-bit columns or converted to floating point numbers, and amounts are never stored as floats. Fields:
tx.memo.id,tx.source_account.mux_id,op.manage_offer.offer_id,op.payment.amount. - Memo text is stored as bytes. MEMO_TEXT is kept as its raw bytes (at most 28). If a text form is needed, UTF-8 is validated explicitly and the raw bytes are kept alongside it. Fields:
tx.memo.text. Caps:tx.memo_text.len. Sources: js-stellar-sdk#1823. - Ordering follows the network's rules. Liquidity pool assets are ordered by asset type, raw code bytes and raw issuer key bytes, never by strkey text. Code that sorts or checks ScMap keys uses the Soroban host's ScVal comparison, not string order and not XDR byte order. Fields:
op.change_trust.line.liquidity_pool,op.invoke_host_function.invoke_contract.args. Sources: js-stellar-sdk#1766. - Failed transactions are not treated as effects. An envelope value is only treated as having taken effect when the transaction result and the operation result are successes. For a failed transaction only the fee changes in meta are recorded as effects. Fields:
tx.signatures,op.payment.destination,op.invoke_host_function.invoke_contract.contract_address. - Caps are applied when decoding. Every XDR decode of external input sets an explicit depth limit and a length limit no larger than the input buffer, using the caps in the table. A decoder is never run with its limits turned off. Caps:
xdr.envelope.depth,xdr.envelope.len,xdr.meta.depth,xdr.meta.len. - Serving limits stay at query time. Limits that protect an API, such as rows per transaction on a history page, are applied when serving. Ingestion stores every row. Caps:
state_changes.per_tx.query. - Caps that come from network settings are re-read. Caps marked as network settings are read from the CONFIG_SETTING ledger entries the network currently uses, not hardcoded, because validators can change them by vote. Caps:
xdr.envelope.len,soroban.footprint.entries,soroban.contract_data.entry_bytes,events.contract.bytes_per_tx. - There is a written refusal policy. For every place a decoder can throw or a value can exceed a cap, the indexer has a tested, documented choice: skip the value and count it, or halt. Nothing is dropped without being counted, and SDK upgrades are checked for new refusals. Sources: js-stellar-sdk 17.3.0 release notes, stellar-etl#467.
- One poison transaction cannot stop ingestion. When a single transaction cannot be stored, the indexer records it in a quarantine with the error, stores the rest of the ledger, advances the cursor and alerts, instead of retrying the same ledger forever. Sources: wallet-backend#729, wallet-backend#753.
- Soroban invocations before protocol 25 can carry a memo or a muxed source. Code that handles INVOKE_HOST_FUNCTION transactions accepts a memo and muxed source accounts in ledgers before protocol 25, where the rule was only a transaction queue policy. Fields:
tx.memo.type,tx.source_account.mux_id,op.source_account.mux_id.
This page is generated from docs/guide/indexer-hardening.mdx in the repository.