TransactionEncoder decodes Stellar's binary XDR transaction format, which the K semantics cannot parse. It turns a transaction envelope into the JSON request envelope consumed by node.md, computes the transaction hash and contract ids, and (for wasm uploads only) parses the bytecode into a ModuleDecl.
class TransactionEncoder:
network_passphrase: strThe encoder is stateless apart from this one configuration value; it holds no ledger or transaction state (that lives in K).
build_tx_request is the entry point. It decodes the XDR envelope and returns a (request, program_steps) pair:
request = {
'method': method, # "sendTransaction"
'id': rpc_id, # JSON-RPC id, echoed back in the response
'now': now, # epoch seconds (string); wall-clock can't live in K
'txHash': envelope.hash_hex(),
'envelopeXdr': transaction_xdr,
'steps': [ ... ] or [], # JSON steps, or [] for the wasm path
}- For the common case, every operation is encoded as a JSON step and
program_stepsisNone. - For a wasm-upload transaction, the operations cannot be expressed as JSON (the
ModuleDeclhas no JSON form), sostepsis[]andprogram_stepscarries the kasmeruploadWasmstep for the interpreter to splice into the<program>cell. Soroban allows only one host-function operation per transaction, so such a transaction is exactly one upload op.
now is read from the wall clock here, in Python, and passed through the envelope because K has no clock; the semantics use it to fill createdAt / latestLedgerCloseTime.
Each operation is encoded by _encode_operation. Key ordering is significant: the K JSON sort is ordered, so these dicts must produce keys in the same order as the #decodeStep rules in node.md.
{ "op": "setAccount", "account": "<hex32>", "balance": <int> }
{ "op": "deployContract", "from": "<hex32>", "address": "<hex32>", "wasmHash": "<hex32>" }
{ "op": "callTx", "from": "<hex32>", "fromIsContract": <bool>,
"func": "<name>", "to": "<hex32>", "args": [ ... ] }_encode_operation returns None for a wasm upload, which is the signal that the whole transaction takes the <program>-injection path instead.
Contract-call arguments are encoded by scval_to_json (in scval.py), alongside the XDR→SCValue decoder. Key ordering matters here too — it must match the #decodeArg rules.
| SCVal type | JSON encoding |
|---|---|
SCV_BOOL |
{"type": "bool", "value": true|false} |
SCV_I32 / SCV_U32 |
{"type": "i32"|"u32", "value": <int>} |
SCV_I64 / SCV_U64 |
{"type": "i64"|"u64", "value": <int>} |
SCV_I128 / SCV_U128 |
{"type": "i128"|"u128", "value": <int>} (combined hi/lo) |
SCV_SYMBOL |
{"type": "symbol", "value": "<str>"} |
SCV_BYTES |
{"type": "bytes", "value": "<lowercase hex>"} |
SCV_ADDRESS (account) |
{"type": "address", "addrType": "account", "value": "<hex32>"} |
SCV_ADDRESS (contract) |
{"type": "address", "addrType": "contract", "value": "<hex32>"} |
SCV_VEC / SCV_MAP are not yet encoded; they raise NotImplementedError.
decode_account_id(addr)— G-strkey → 32-byte public keydecode_contract_id(addr)— C-strkey → 32-byte contract IDcontract_id_from_preimage(preimage)— SHA-256 of theHashIDPreimageas used by Stellar (network-passphrase dependent)contract_address_from_deployer_address(deployer, salt)— computes the C-strkey thatCREATE_CONTRACTwill assign when deploying from an account with the given salt