diff --git a/XLS-0102-wasm-vm/README.md b/XLS-0102-wasm-vm/README.md
new file mode 100644
index 00000000..2634d5a0
--- /dev/null
+++ b/XLS-0102-wasm-vm/README.md
@@ -0,0 +1,391 @@
+
+ title: WASM VM
+ description: WebAssembly VM integration into rippled
+ created: 2025-08-08
+ author: Mayukha Vadari (@mvadari), Peng Wang (@pwang200), Oleksandr Pidskopnyi (@oleks_rip), David Fuellling (@sappenin)
+ status: Draft
+ category: Amendment
+
+
+# WASM VM Configuration
+
+## Abstract
+
+This document describes the integration of WebAssembly (WASM) into the XRP Ledger as a secure and deterministic execution environment for smart contract logic. WASM-based execution allows developers to write custom logic in a wide range of languages, compile to a portable binary format, and run within a sandboxed virtual machine governed by the consensus process. The standard outlines the interface, constraints, and security model for deploying, invoking, and validating WASM subroutines on-ledger, while ensuring compatibility with existing ledger primitives and transaction flow.
+
+## 1. Overview
+
+This document addresses several parts of the WASM integration into rippled:
+
+- The implementation chosen
+- The gas model
+- The set of host functions
+- Security measures
+
+This feature does not (directly) involve any new transactions, ledger objects, or RPCs. It will be gated by the `SmartEscrow` amendment. Any modification to the details in this spec will require an amendment, as it will affect transaction processing (e.g. success/failure of an `EscrowFinish` transaction for a Smart Escrow).
+
+### 1.1. How the WASM Engine Integrates into `rippled`
+
+
+
+Using [Smart Escrows](https://github.com/XRPLF/XRPL-Standards/discussions/270) as an example:
+
+1. Process the transaction until it has done everything it needs to do before processing anything that requires the WASM engine (in this case, running the `FinishFunction` code to determine if the escrow is finishable).
+2. Enter the WASM engine, where the WASM environment is set up to run the code.
+3. Run the WASM code, using host functions to fetch on-ledger information.
+4. Return the output (whether or not the escrow can be finished) to the transaction processing engine, and continue onwards with the rest of the transaction code.
+
+### 1.2. Background: What is a “Host Function”?
+
+A host function is a function expressed outside WebAssembly but passed to a module as an import. They’re somewhat analogous to precompiles in the EVM world.
+
+In other words, it’s basically an API call that fetches/interacts with data or native compute outside of the WASM VM.
+
+### 1.3. Background: WASM Native Types
+
+There are only 4 native types in the [WASM spec](https://webassembly.github.io/spec/core/syntax/types.html): `i32` (a signed 32-bit integer), `i64` (a signed 64-bit integer), `f32` (a 32-bit floating point number), and `f64` (a 64-bit floating point number). However, the floating point numbers use a different encoding from what `rippled` uses.
+
+So essentially, we only have `i32` and `i64` in terms of useful types. **Every parameter and return type must be represented as these two types.** This is manifested as [pointers](https://en.wikipedia.org/wiki/Pointer_%28computer_programming%29) and lengths. _Note that any language that has full support for extensions will have helper functions to abstract away most of the complexity (especially involving pointers and lengths)._
+
+## 2. VM Runtime Choice
+
+While WebAssembly has a [core specification](https://webassembly.github.io/spec/), different runtimes have flexibility in how they implement certain features that are not a part of the formal specification. For example, not all WASM runtimes can easily be embedded in a C++ project (such as `rippled`).
+
+The most relevant part for the purpose of consensus is the gas cost for any operation or function. Different implementations may have different gas costs for executing a given function, due to implementation differences - e.g. some calculate gas costs by inserting additional instructions, while others have a counter in the VM logic. For instance, one basic Smart Escrow function cost 110 gas to run with [WasmEdge](https://wasmedge.org/), while it only cost 4 gas with [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime). This would cause consensus issues if the computation limit was set at 100, for example - one runtime would succeed, while the other would fail.
+
+There are other metrics that are important as well, such as performance considerations. See Appendix A for the full analysis comparing different runtimes.
+
+### 2.1. Gas
+
+Gas consumption is determined by the WASM runtime used for execution. Different implementations may use different metering strategies, which yields different gas costs for identical WASM code. For example, [this issue](https://github.com/bytecodealliance/wasm-micro-runtime/issues/3927) discusses how WAMR measures gas consumption.
+
+Gas will accumulate from:
+
+- Execution of individual WASM instructions
+- Memory operations (e.g. `grow_memory`)
+- Calling host functions (e.g. accessing ledger data)
+
+Exceeding the provided gas budget triggers immediate execution halting, with a deterministic failure consistent across implementations.
+
+## 3. Execution Limits
+
+The XRPL cannot allow unbounded execution, as there is a time limit for ledgers to close in order for consensus to execute in a timely manner. There are three methods of ensuring that the WASM code does not take too many resources:
+
+- A size limit to the WASM code
+- A computation limit
+- A price for each unit of gas
+
+All of these parameters will be UNL-votable, so that they do not need a separate amendment to be modified.
+
+## 4. Memory Management Strategies
+
+WebAssembly does not include built-in memory management - there is no garbage collector or automatic heap allocation. Instead, memory is a contiguous linear buffer that can only grow (in fixed 64 KiB pages) and never shrink. That means WASM code must allocate and manage its own memory, typically via a custom allocator or language runtime, and explicitly track when memory is no longer needed. While there is [progress](https://developer.chrome.com/blog/wasmgc) on this front with WasmGC, it does not have full-fledged tooling support yet, and is currently only really useful for browser applications of WASM.
+
+This is a bit of a problem for host functions, since data has to go back and forth between the caller (WASM dev) and the engine (`rippled`). Some data (e.g. parameters) may be generated on the WASM side, while some data (e.g. the return data) may be generated on the rippled side.
+
+Therefore, in this design, the caller is responsible for allocating memory in advance, and must reuse or deallocate memory manually. See Appendix B for alternative designs that were considered and rejected.
+
+## 5. Extension Host Functions
+
+This section introduces WASM host functions for extensions on the XRP Ledger, enabling WASM bytecode (in an extension or smart contract) to securely interact with ledger data and the ledger’s native features. These functions provide controlled access to ledger state, transaction execution, and XRPL primitives while maintaining efficiency and security.
+
+WASM code, whether in an extension or a smart contract, needs access to XRPL ledger data in order to be useful. A host function allows that access, in a secure way. Host functions can also be used to save gas/compute in WASM, as they can perform those same functions in C++ code instead, which will likely be more performant.
+
+Some examples from [XLS-100d](https://github.com/XRPLF/XRPL-Standards/discussions/270):
+
+- A notary escrow extension needs access to the triggering `EscrowFinish` transaction (to know who is sending the transaction).
+- An escrow checking for KYC needs access to ledger state, to determine if the destination has a given credential.
+
+This spec only covers Smart Escrow host functions at this time.
+
+These host functions will be accessible from extensions and smart contracts.
+
+Note: all these functions return an `i32`, unless otherwise noted (or there is no buffer parameter). If the value is positive, it's a length. If it's negative, it's an error code.
+
+### 5.1. General Ledger Data
+
+This section includes ledger header data, amendments, and fees.
+
+| Function Signature | Description | Gas Cost |
+| :---------------------------------------------------------------------------------------------- | :------------------------------------------------ | :------- |
+| `get_ledger_sqn()` | Get the sequence number of the last ledger. | 60 |
+| `get_parent_ledger_time()` | Get the time (in Ripple Time) of the last ledger. | 60 |
+| `get_parent_ledger_hash(`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get the hash of the last ledger. | 60 |
+| `amendment_enabled(`
`amendment_ptr: i32,`
`amendment_len: i32`
`)` | Check if a given amendment is enabled. | 60 |
+| `get_base_fee()` | Get the current transaction base fee. | 60 |
+
+### 5.2. Current Ledger Object data
+
+The current ledger object is the ledger object that the extension lives on - for Smart Escrows that's an `Escrow` object.
+
+| Function Signature | Description | Gas Cost |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | :------- |
+| `get_current_ledger_obj_field(`
`field: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get a top-level field from the ledger object that the extension is on. | 70 |
+| `get_current_ledger_obj_nested_field(`
`locator_ptr: i32,`
`locator_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get a nested field from the ledger object that the extension is on. | 110 |
+| `get_current_ledger_obj_array_len(`
`field: i32`
`)` | Get the length of an array field on the ledger object that the extension is on. | 40 |
+| `get_current_ledger_obj_nested_array_len(`
`locator_ptr: i32,`
`locator_len: i32`
`)` | Get the length of a nested array field on the ledger object that the extension is on. | 70 |
+
+#### 5.2.1. Locators
+
+A Locator allows a WASM developer located any field in any object (even nested fields) by specifying a `slot_num` (1 byte); a `locator_field_type` (1 byte); then one of an `sfield` (4 bytes) or an `index` (4 bytes).
+
+### 5.3. Current Transaction Data
+
+The current transaction is the `EscrowFinish` that is executing the WASM logic
+
+| Function Signature | Description | Gas Cost |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | :------- |
+| `get_tx_field(`
`field: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get a top-level field from the transaction that triggered the extension. | 70 |
+| `get_tx_nested_field(`
`locator_ptr: i32,`
`locator_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get a nested field from the transaction that triggered the extension. | 110 |
+| `get_tx_array_len(`
`field: i32`
`)` | Get the length of an array field from the transaction that triggered the extension. | 40 |
+| `get_tx_nested_array_len(`
`locator_ptr: i32,`
`locator_len: i32`
`)` | Get the length of a nested array field on the ledger object that the extension is on. | 70 |
+
+### 5.4. Any Ledger Object Data
+
+Fetch data from any other ledger object
+
+| Function Signature | Description | Gas Cost |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :------- |
+| `cache_ledger_obj(`
`keylet_ptr: i32,`
`keylet_len: i32,`
`cache_num: i32`
`)` | Cache a ledger object so that it can be used later. | 5000 |
+| `get_ledger_obj_field(`
`cache_num: i32,`
`field: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get a top-level field from any ledger object. | 70 |
+| `get_ledger_obj_nested_field(`
`cache_num: i32,`
`locator_ptr: i32,`
`locator_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get a nested field from any ledger object. | 110 |
+| `get_ledger_obj_array_len(`
`cache_num: i32,`
`field: i32`
`)` | Get the length of an array field from any ledger object. | 40 |
+| `get_ledger_obj_nested_array_len(`
`cache_num: i32,`
`locator_ptr: i32,`
`locator_len: i32`
`)` | Get the length of a nested array field from any ledger object. | 70 |
+
+### 5.5. Keylets
+
+A keylet is a unique hash that represents a ledger object on the XRP Ledger. It is a 256-bit hash, constructed from unique identifiers for an object. For example, an `AccountRoot`'s hash is constructed from its `AccountID`, and an `Oracle`'s hash is constructed from its `Owner` and `DocumentID`.
+
+| Function Signature | Description | Gas Cost |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :------- |
+| `account_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `AccountRoot`'s keylet from its pieces. | 350 |
+| `amm_keylet(`
`issue1_ptr: i32,`
`issue1_len: i32,`
`issue2_ptr: i32,`
`issue2_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `AMM`’s keylet from its pieces. | 350 |
+| `check_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `Check`'s keylet from its pieces. | 350 |
+| `credential_keylet(`
`subject_ptr: i32,`
`subject_len: i32,`
`issuer_ptr: i32,`
`issuer_len: i32,`
`cred_type_ptr: i32,`
`cred_type_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `Credential`'s keylet from its pieces. | 350 |
+| `delegate_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`authorize_ptr: i32,`
`authorize_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `Delegate`'s keylet from its pieces. | 350 |
+| `deposit_preauth_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`authorize_ptr: i32,`
`authorize_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `DepositPreauth`'s keylet from its pieces. | 350 |
+| `did_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `DID`'s keylet from its pieces. | 350 |
+| `escrow_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `Escrow`'s keylet from its pieces. | 350 |
+| `line_keylet(`
`account1_ptr: i32,`
`account1_len: i32,`
`account2_ptr: i32,`
`account2_len: i32,`
`currency_ptr: i32,`
`currency_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a trustline’s keylet from its pieces. | 350 |
+| `mpt_issuance_keylet(`
`issuer_ptr: i32,`
`issuer_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `MPTIssuance`’s keylet from its pieces. | 350 |
+| `mptoken_keylet(`
`mptid_ptr: i32,`
`mptid_len: i32,`
`holder_ptr: i32,`
`holder_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `MPToken`’s keylet from its pieces. | 350 |
+| `nft_offer_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `NFTOffer`'s keylet from its pieces. | 350 |
+| `offer_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `Offer`'s keylet from its pieces. | 350 |
+| `oracle_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`document_id: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate an `Oracle`'s keylet from its pieces. | 350 |
+| `paychan_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`destination_ptr: i32,`
`destination_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `PayChannel`’s keylet from its pieces. | 350 |
+| `permissioned_domain_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `PermissionedDomain`’s keylet from its pieces. | 350 |
+| `signers_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `SignerListSet`'s keylet from its pieces. | 350 |
+| `ticket_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `Ticket`'s keylet from its pieces. | 350 |
+| `vault_keylet(`
`account_ptr: i32,`
`account_len: i32,`
`sequence: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate a `Vault`’s keylet from its pieces. | 350 |
+
+The singleton keylets (e.g. `Amendments`) are a bit unnecessary to include, as a dev can simply copy the keylet directly instead. They will be included as constants in `craft` as well.
+
+The directory keylets and `NFTokenPage` were not included, since they are a bit more complex to parse through and it seemed unnecessary for now. These can always be added in the future.
+
+### 5.6. NFTs
+
+Fetch information about NFTs.
+
+| Function Signature | Description | Gas Cost |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- | :------- |
+| `get_nft(`
`owner_ptr: i32,`
`owner_len: i32,`
`nft_id_ptr: i32,`
`nft_id_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Get an NFT URI from its owner and ID. | 1000 |
+| `get_nft_issuer(`
`nft_id_ptr: i32,`
`nft_id_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Extract the NFT issuer from the NFT ID. | 350 |
+| `get_nft_taxon(`
`nft_id_ptr: i32,`
`nft_id_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Extract the NFT taxon from the NFT ID. | 350 |
+| `get_nft_flags(`
`nft_id_ptr: i32,`
`nft_id_len: i32`
`)` | Extract the NFT flags from the NFT ID. | 350 |
+| `get_nft_transfer_fee(`
`nft_id_ptr: i32,`
`nft_id_len: i32`
`)` | Extract the NFT transfer fee from the NFT ID. | 350 |
+| `get_nft_serial(`
`nft_id_ptr: i32,`
`nft_id_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Extract the NFT serial from the NFT ID. | 350 |
+
+### 5.7. Utils
+
+Miscellaneous utility functions.
+
+| Function Signature | Description | Gas Cost |
+| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :------- |
+| `check_sig(`
`message_ptr: i32,`
`message_len: i32,`
`signature_ptr: i32,`
`signature_len: i32,`
`pubkey_ptr: i32,`
`pubkey_len: i32,`
`)` | Check the validity of a signature. Returns a `0` for invalid and `1` for valid. Supports both `ED25519` and `SECP256K1.` | 2000 |
+| `compute_sha512_half(`
`data_ptr: i32,`
`data_len: i32,`
`out_buff_ptr: i32,`
`out_buff_len: i32`
`)` | Calculate the `sha512` half hash of provided data. | 2000 |
+
+### 5.8. Floats
+
+Helper functions for working with rippled-encoded floats (e.g. IOU amounts).
+
+| Function Signature | Description | Gas Cost |
+| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------- | :------- |
+| `float_from_int(`
`in_int: i64,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Create a float in rippled format from a 64-bit integer. | 1000 |
+| `float_from_uint(`
`in_uint_ptr: i32,`
`in_uint_len: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Create a float in rippled format from a 64-bit unsigned integer. | 1000 |
+| `float_set(`
`exponent: i32,`
`mantissa: i64,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Create a float in rippled format from an exponent and a mantissa. | 1000 |
+| `float_compare(`
`in_buf1: i32,`
`in_len1: i32,`
`in_buf2: i32,`
`in_len2: i32`
`)` | Compare two floats in rippled format. | 1000 |
+| `float_add(`
`in_buf1: i32,`
`in_len1: i32,`
`in_buf2: i32,`
`in_len2: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Add two floats in rippled format. | 1000 |
+| `float_subtract(`
`in_buf1: i32,`
`in_len1: i32,`
`in_buf2: i32,`
`in_len2: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Subtract two floats in rippled format. | 1000 |
+| `float_multiply(`
`in_buf1: i32,`
`in_len1: i32,`
`in_buf2: i32,`
`in_len2: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Multiply two floats in rippled format. | 1000 |
+| `float_divide(`
`in_buf1: i32,`
`in_len1: i32,`
`in_buf2: i32,`
`in_len2: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Divide two floats in rippled format. | 1000 |
+| `float_pow(`
`in_buf: i32,`
`in_len: i32,`
`n: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Compute the nth power of a float in rippled format. | 1000 |
+| `float_root(`
`in_buf: i32,`
`in_len: i32,`
`n: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Compute the nth root of a float in rippled format. | 1000 |
+| `float_log(`
`in_buf: i32,`
`in_len: i32,`
`out_buf: i32,`
`out_len: i32,`
`rounding_modes: i32`
`)` | Compute the 10 based log of a float in rippled format. | 1000 |
+
+### 5.9. Trace
+
+Output debug info to the `rippled` debug log.
+
+| Function Signature | Description | Gas Cost |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------ | :------- |
+| `trace(`
`msg_ptr: i32,`
`msg_len: i32,`
`data_ptr: i32,`
`data_len: i32,`
`as_hex: i32`
`)` | A logging helper function. | 500 |
+| `trace_num(`
`msg_ptr: i32,`
`msg_len: i32,`
`number: i64`
`)` | A logging helper function for numbers. | 500 |
+| `trace_opaque_float(`
`msg_ptr: i32,`
`msg_len: i32,`
`opaque_float_ptr: i32,`
`opaque_float_len: i32`
`)` | A logging helper function for floats in rippled format. | 500 |
+| `trace_account(`
`msg_ptr: i32,`
`msg_len: i32,`
`account_ptr: i32,`
`account_len: i32`
`)` | A logging helper function for accounts. | 500 |
+| `trace_amount(`
`msg_ptr: i32,`
`msg_len: i32,`
`amount_ptr: i32,`
`amount_len: i32`
`)` | A logging helper function for amounts. | 500 |
+
+### 5.10. Updating Fields
+
+Update on-chain data associated with the WASM code.
+
+This section is the only section of functions that will likely be different for each Smart Feature. Each may have its own way of storing data.
+
+| Function Signature | Description | Gas Cost |
+| :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | :------- |
+| `update_data(`
`data_ptr: i32,`
`data_len: i32`
`)` | Update the `Data` field in the ledger object that hosts the WASM code, e.g. a Smart Escrow. | 50 |
+
+## 6. Security
+
+### 6.1. Consensus
+
+The WASM VM and spec guarantees that all WASM code will run identically on all machines (though, of course, a lot of testing will be done to ensure that this is the case).
+
+WebAssembly is designed with deterministic execution in mind, and the specification ensures that properly constrained WASM code will produce the same output across all (compliant) runtimes. This XLS relies on those guarantees to ensure that all validators in the XRP Ledger network reach the same result when executing WASM code as part of transaction processing.
+
+To that end:
+
+- The runtime environment is fixed across all validator nodes, with an agreed-upon WebAssembly implementation (WAMR), WAMR version and a deterministic configuration (interpreted compile mode).
+- Non-deterministic WASM features, such as floating point operations, access to time, randomness, or host system I/O, are explicitly disallowed or omitted from the runtime.
+- A fixed, deterministic gas cost model is applied to all instructions, with enforced gas limits and metering to ensure bounded execution.
+
+To ensure that all of this is the case, thorough testing across platforms and architectures will be conducted. Any divergence will be considered a critical consensus-breaking bug.
+
+### 6.2. Mitigations for Bugs
+
+If there happens to be a bug in the WASM execution layer, the UNL can shut down all usage of WASM code by setting the computation limit to 0.
+
+### 6.3. Data Security
+
+User-provided WASM code is executed within a strict sandbox. It has no access to system-level resources and can only interact with the XRP Ledger via an explicitly defined host function interface. These host functions enforce strict boundaries on what ledger data is visible and what operations are permitted. For example, there is no way for user-provided WASM code to directly modify a ledger object (to e.g. transfer XRP between accounts without permission).
+
+WASM code cannot directly traverse arbitrary ledger directories or iterate through global ledger state. All access must be via bounded, predefined inputs (e.g., keylets or account IDs passed into the subroutine). This design ensures that malicious WASM code cannot manipulate or exfiltrate ledger state beyond the narrow scope allowed by the host API.
+
+### 6.4. Resource Limiting
+
+As discussed above, there is a strict gas limit and exceeding it will result in execution being immediately terminated with an exception.
+
+Additionally, memory and stack usage are tightly constrained - the linear memory size is bounded to a fixed number of pages, and stack depth is capped to prevent runaway recursion or stack overflows.
+
+These constraints prevent denial-of-service attacks and ensure that WASM execution remains fast and predictable, without any WASM-related transaction taking more than its share of `rippled` resources.
+
+### 6.5. Future-Proofing
+
+All future changes to this spec (even just a simple change to the gas cost of a host function) will need to be gated by an amendment. Updates to the `wamr` package may also need to be gated by an amendment - every update will need to be tested for the potential of breaking changes.
+
+For example, this is what it might look like to add a new host function:
+
+```c
+WASM_IMPORT_FUNC2(i, didKeylet, "did_keylet", hfs, 350);
+WASM_IMPORT_FUNC2(i, escrowKeylet, "escrow_keylet", hfs, 350);
+if (hfs->isAmendmentEnabled(featureLendingProtocol))
+ WASM_IMPORT_FUNC2(i, loanKeylet, "loan_keylet", hfs, 350);
+```
+
+_(in `WasmVM.cpp`)_
+
+This ensures that smart escrows cannot use the `loan_keylet` host function at all before the `LendingProtocol` amendment is activated, as the amendment proce`ss ensures that all nodes and validators have the code before it is run.
+
+# Appendix
+
+## Appendix A: Other WASM VMs Considered
+
+### A.1. The Different WASM Compilation Modes
+
+#### A.1.1. AOT (Ahead of Time)
+
+AOT compiles WASM straight to native machine code ahead of time. If we were to use this compilation mode, users would have to store native machine code on the ledger.
+
+**This isn’t useful for our needs**, as the compiled AOT code will be architecture-specific.
+
+Can we figure out a way to support AOT? Possibly, but likely not without restricting rippled hardware to certain CPU architectures, and even then likely only one. Alternatively, to support AOT we would need to force WASM developers to supply variants that can run on any native architectures supported by rippled. Even if we imagine limiting rippled to 3 architectures, that would mean every smart contract developer would need to supply 3 different versions of their WASM, which would be wasteful from a space perspective. Last but not least, limiting rippled to 3 architectures seems counterproductive to decentralization.
+
+#### A.1.2. JIT (Just-In-Time)
+
+JIT compiles essentially as the code is run, or just before. This allows for additional caching and optimizations.
+
+However, there are a few issues with JIT. From [this Stellar blog](https://stellar.org/blog/developers/why-doesnt-soroban-use-a-jit), JIT-based VMs are also not as secure and are susceptible to “JIT Bombs.” It has longer start times and greater memory usage. Mac also does not support JIT. The use of JIT may also result in different gas costs on different machines depending on what is in the cache, which would be a consensus-breaking change. Therefore, **JIT cannot be used for our needs**.
+
+#### A.1.3. Interpreted
+
+Interpreted mode just runs the code, like a REPL.
+
+We decided on this mode because, well, the other two don't work for our needs, even though they're often more performant.
+
+### A.2. The Different WASM Implementations
+
+The 5 WASM VM implementations we investigated were:
+
+- [WasmEdge](https://wasmedge.org/) (used by Xahau)
+- [WasmTime](https://wasmtime.dev/)
+- [Wasmer](https://github.com/wasmerio/wasmer)
+- [Wasmi](https://github.com/wasmi-labs/wasmi) (used by Polkadot and Stellar’s Soroban)
+- [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime)
+
+#### A.2.1. Initial Investigation
+
+
+
+Based on these findings, we narrowed down the search to **WasmEdge** and **WAMR**, which we then did further performance testing and analysis on.
+
+#### A.2.2. Performance Analysis
+
+

+
+These graphs clearly show that WAMR is much more performant.
+
+## Appendix B: Other Memory Management Strategies Considered
+
+Options:
+
+1. Caller-Allocated: The contract developer (on the WASM side) allocates fixed size arrays for returning data. The user knows the pointer and the length.
+ - This is really easy to implement, but means that the WASM dev needs to do their own memory allocation.
+2. Host-Allocated: The host (rippled) allocates WASM memory in host functions and passes the pointer and the length to the WASM program.
+ - This is super easy to use for devs, as they don’t need to worry about allocation and all of that. However, more research is needed to determine how possible it is, because currently the only way that we know how to do this involves allocating a new page every time (to ensure the host isn’t overwriting addresses in use).
+3. Static Allocation: There is a static 4KB array in wasm that holds all output data. The pointer and length are fixed.
+ - Pros
+ 1. This is pretty simple and clean to use
+ - Cons
+ 1. Require an extra (2nd) copy step if the developer wants to use data from a previous host memory call (which is likely common)
+4. The host uses a WASM-side defined allocator function in wasm to allocate, and returns a pointer and length tuple (this is what the devnet currently uses).
+ - Pros
+ 1. This is pretty clean to use
+ - Cons
+ 1. Increases the size of the WASM program because we use Vector allocation. To get around this we would need our own allocator.
+ 2. Takes more gas/makes the WASM bytecode long.
+
+We decided on **Option 1** for the purpose of simplicity.
+
+## Appendix C: FAQ
+
+### C.1: How does this list of host functions compare to the Xahau Hooks [host functions](https://xrpl-hooks.readme.io/reference/hook-api-conventions)?
+
+The host functions on this list are heavily inspired by the Hooks host functions. Most of the changes are just naming and simplifying the functions, and reworking how they're organized.
+
+### C.2: Can we add a host function for [insert request here]?
+
+Please share any request you have in the comments of this spec.
+
+Some limitations:
+
+- The transaction engine cannot access historical data - only current ledger state (since nodes aren’t required to hold any amount of past data).
+- Due to security reasons, we don’t want to give host functions write access to raw ledger data (that would make exploits much easier to implement and it would be much harder for us to protect against them).
+
+### C.3: Will gas fees be refundable if I pay for too much gas, like EVM?
+
+Not at this time. This may be revisited later, and can be added in a future amendment.
+
+Not all smart contract chains support refundable gas - for example, Solana does not.
+
+### C.4: Will transactions that use the WASM VM be testable via `simulate`?
+
+Yes, though that needs to be tested. This should make it easier for users to estimate gas usage.