0101 XLS-101d: XRPL Smart Contracts #271
mvadari
started this conversation in
XLS Proposals
Replies: 2 comments
-
|
Question: Can a Smart Escrow currently be created by someone within a Permissioned Domain and contain a token only available within the domain and the receiving account be outside of the domain? |
Beta Was this translation helpful? Give feedback.
0 replies
-
|
This spec was updated today, 2025-09-23. The main differences are 3 new transactions: |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
XRPL Smart Contracts
Abstract
This document is a formal design of a smart contract system for the XRPL, which takes inspiration from several existing smart contract systems (including Xahau’s Hooks and the EVM).
Some sample use cases (a far-from-exhaustive list):
Note: This document is still a fairly early draft, and therefore there are a few TODOs and open questions sprinkled through it on some of the more minor points. Any input on those questions would be especially appreciated.
1. Design Objectives
The main requirements we aimed to satisfy in our design:
One of the great advantages of the XRPL is its human-readable transaction structure - unlike e.g. EVM transactions. In this design, we tried to keep that ethos of things being human-readable - such as keeping the ABI on-chain (even though that would increase storage).
1.1. General XRPL Programmability Vision
We envision programmability on the XRPL as the glue that seamlessly connects its powerful, native building blocks with the flexibility of custom on-chain business logic. This vision focuses on preserving what makes the XRPL special—its efficiency, reliability, and simplicity—while empowering builders to unlock new possibilities.
See the blog post here for more details.
2. Overview
This design for Smart Contracts combines the easy-to-learn overall design of EVM smart contracts (addresses with functions) with the familiarity of XRPL transactions. A Smart Contract lives on a pseudo-account and is triggered via a new
ContractCalltransaction, which calls a specific function on the smart contract, with provided parameters. The Smart Contract can modify its own state data, or interact with other XRPL building blocks (including other smart contracts) via submitting its own XRPL transactions via its code.The details of the WASM engine and the API will be in separate XLSes published later.
This proposal involves:
ContractSource,Contract, andContractDataContractCreate,ContractCall,ContractModify,ContractDelete,ContractUserDelete, andContractClawbackcontract_infoandevent_historyeventEmittedFeeSettingsobject that keeps track of that info)STParameters,STParameterValues, andSTData2.1. Background: Pseudo-Accounts
A pseudo-account (XLS-64d) is an XRPL account that is impossible for any person to have the keys for (it is cryptographically impossible to have those keys). It may be associated with other ledger entries.
Since it is not governed by any set of keys, it cannot be controlled by any user. Therefore, it may host smart contracts.
2.2. Background: Serialized Types
The XRPL encodes data into a set of serialized types (all of whose names begin with the letters
ST, standing for “Serialized Type”).For example:
“Account”field is of typeSTAccount(which represents XRPL account IDs)“Sequence”field is of typeSTUInt32(which represents an unsigned 32-bit integer)“Amount”field is of typeSTAmount(which represents all amount types - XRP, IOUs, and MPTs)2.3. Design Overview
Contractstores the contract infoContractSourceis an implementation detail to make code storage more efficientContractDatastores any contract-specific dataContractCreatecreates a new contract + pseudo-account, and allows the contract to do some setup workContractCallis used to trigger the transaction and call one of its functionsContractModifyallows the contract owner (or the contract itself) to modify a contractContractDeleteallows the contract owner (or the contract itself) to delete a contractContractUserDeleteallows a user of a smart contract to delete their data associated with a contract (and allows the contract to do any cleanup for that)ContractClawbackallows a token issuer to claw back from a contract (and allows the contract to do any cleanup for that)contract_infoRPC fetches the ABI of a contract and any other relevant information.event_historyRPC fetches the event emission history for a contract.eventEmittedsubscription allows you to subscribe to “events” emitted from a contract.2.4. Overview of Smart Contract Capabilities
initfunction that runs onContractCreatefor any account setup3. Ledger Entry:
ContractSourceThe objective of this object is to save space on-chain when deploying the exact same contract (i.e. if the same code is used by multiple contracts, the ledger only needs to store it once). This feature was heavily inspired by the existing Hooks
HookDefinitionobject (see this page for its documentation).This object is essentially just an implementation detail to decrease storage costs, so that duplicate contracts don't need to have their source code copied on-chain. End-users won't have to worry about it. The core object in this design is the
Contractobject.3.1. Fields
LedgerEntryTypestringUInt16ContractSource).ContractHashstringHash256ContractCodestringblobInstanceParametersarraySTParametersFunctionsarraySTArrayReferenceCountnumberUInt32Contractobjects that are based on thisContractSource. This object is deleted when that value goes to 0.3.1.1. Object ID
hash of prefix +
ContractHash3.1.2.
InstanceParametersandFunctions3.1.3
FunctionsSTypes(maybe banSTObjectsandSTArraysand other complex STypes likeTransaction)tfSendAmount- if the type is anSTAmount, then that amount will be sent to the contract from your fundstfSendNFToken- if the type is aHash256, then theNFTokenwith that ID will be sent to the contract from your holdingstfAuthorizeTokenHolding- if the type is anSTIssueorSTAmount, then you can automatically create a trustline/MPToken for that token (assuming it’s not XRP).3.2. Object Deletion
The
ContractSourceobject does not have an owner.ReferenceCountever goes to 0ContractSourceobject should exist with aReferenceCountvalue of 03.3. Example Object
4. Ledger Entry:
Contract4.1. Fields
LedgerEntryTypestringUInt16Contract).ContractAccountstringAccountIDOwnerstringAccountIDFlagsnumberUInt32SequencestringUInt16ContractSource).ContractHashstringHash256InstanceParameterValuesarraySTParameterValuesURIstringBlobTODO: should the
URIfield live onContractSourceorContract?4.1.1. Object ID
hash of prefix +
ContractHash+Sequence4.1.2.
FlagslsfImmutable- the code can’t be updated.lsfCodeImmutable- the code can’t be updated, but the instance parameters can.lsfABIImmutable- the code can be updated, but the ABI cannot. This ensures backwards compatibility.lsfUndeletable- the contract can’t be deleted.A
Contractcan have at most one oflsfImmutable,lsfCodeImmutable, andlsfABIImmutableenabled.4.1.3.
InstanceParametersThe instance parameter list must match the types of the matching
ContractSourceobject.4.2. Account Deletion
The
Contractobject is a deletion blocker.4.3. Example Object
5. Ledger Entry:
ContractDataData is serialized using the
STDataserialization format.5.1. Fields
LedgerEntryTypestringUInt16ContractData).OwnerstringAccountIDContractAccountstringAccountIDOwneris different).DatastringSTData5.1.1. Object ID
hash of prefix +
Owner[+ContractAccount]5.2. Account Deletion
The
ContractDataobject is a deletion blocker.5.3. Reserves
Probably one reserve per 256 bytes
See Reserves
5.4. Example Object
6. Transaction:
ContractCreateThis transaction creates a pseudo-account with the contract inside it.
This transaction will also trigger a special
initfunction in the contract, if it exists - which allows smart contract devs to do their own setup work as needed.6.1. Fields
TransactionTypestringUInt16ContractCreate).AccountstringAccountIDContractOwnerstringAccountIDAccount.FlagsnumberUInt32ContractCodestringBlobContractHashstringHash256FunctionsarraySTArrayInstanceParametersarraySTParametersInstanceParameterValuesarraySTParameterValues6.1.1.
FlagstfImmutable- the code can’t be changed and the instance parameters can't be updated either.tfCodeImmutable- the code can’t be updated, but the instance parameters can.tfABIImmutable- the code can be updated, but the ABI cannot.tfUndeletable- the contract can’t be deleted.A contract may have at most one of
tfImmutable,tfCodeImmutable, andlsfABIImmutableenabled.6.1.2.
ContractCodeandContractHashExactly one of these two fields must be included.
ContractCodeshould be used if the code has not already been uploaded to the XRPL (i.e. there is already a matchingContractSourceobject). This transaction will be more expensive.ContractHashshould be used if the code has already been uploaded to the XRPL. This transaction will be cheaper, since the code does not need to be re-uploaded.If
ContractCodeis provided even if the code has already been uploaded, it will have the same outcome as if theContractHashhad been provided instead (albeit with a more expensive fee).If
ContractCodeis provided,InstanceParametersandFunctionsmust also be provided.6.2. Fee
Similar to
AMMCreate, 1 object reserve fee (+ fees for running theinitcode, and probably also + fees per byte of code uploaded)6.3. Failure Conditions
ContractHashis provided but there is no existing correspondingContractSourceledger entry.ContractCodeprovided is invalid.Functionsdoesn't match the code.InstanceParametersdon't match what's in the existingContractSourceledger entry.6.4. State Changes
If the transaction is successful:
Contractis created.Contractobject is created.ContractSourceobject already exists, theReferenceCountwill be incremented.ContractSourceobject does not already exist, it will be created.6.5. Example Transaction
7. Transaction:
ContractCallThis transaction triggers a specific function in a given contract, with the provided parameters
7.1. Fields
TransactionTypestringUInt16ContractCall).AccountstringAccountIDContractAccountstringAccountIDFunctionNamestringBlobParametersarraySTParameterValues7.2. Fee
The max number of instructions you’re willing to run (gas-esque behavior)
7.3. Failure Conditions
ContractAccountdoesn't exist or isn't a smart contract pseudo-account.7.4. State Changes
If the transaction is successful, the WASM contract will be called. The WASM code will govern the state changes that are made.
7.5. Example Transaction
8. Transaction:
ContractModifyThis transaction modifies a contract's code or instance parameters, if allowed.
8.1. Fields
TransactionTypestringUInt16ContractModify).AccountstringAccountIDContractAccountstringAccountIDContractOwnerstringAccountIDFlagsnumberUInt32ContractCodestringBlobContractHashstringHash256FunctionsarraySTArrayInstanceParametersarraySTParametersInstanceParameterValuesarraySTParameterValues8.1.1.
FlagstfImmutable- the code can’t be changed anymore.tfCodeImmutable- the code can’t be updated, but the instance parameters can.tfABIImmutable- the code can be updated, but the ABI cannot.tfUndeletable- the contract can’t be deleted anymore.8.2. Fee
Will be equivalent to the per-byte/
initfees of theContractCreatetransaction8.3. Failure Conditions
ContractAccountdoesn’t exist or isn’t a contract pseudo-account.Accountisn't the contract owner.ContractAccountisn’t specified, theAccountisn’t a contract pseudo-account.lsfImmutableflag.lsfABIImmutableenabled and isn't backwards-compatible (function names or parameters are changed). (Note: functions may be added)ContractCodeorContractHashare provided but the contract has thetfCodeImmutableflag enabled.8.4. State Changes
If the transaction is successful:
Contractobject is updated accordingly.Contractobject was the only user of aContractSourceobject, theContractSourceobject is deleted.Contractobject does not have a corresponding existingContractSourceobject, it is created.9. Transaction:
ContractDeleteThis transaction deletes a contract. Only the pseudo-account itself or the owner of the transaction can do so.
9.1. Fields
TransactionTypestringUInt16ContractDelete).AccountstringAccountIDContractAccountstringAccountID9.2. Failure Conditions
ContractAccountdoesn't exist or isn't a smart contract pseudo-account.ContractAccountholds deletion blocker objects (e.g.EscroworContractData).9.3. State Changes
If the transaction is successful:
10. Transaction:
ContractUserDeleteThis transaction allows a user to delete their data associated with a contract. Only the user can submit this transaction (if the contract wants to modify user data, it can do that from the WASM code).
This transaction will also trigger a special
user_deletefunction in the contract, if it exists - which allows smart contract devs to do their own cleanup work as needed.10.1. Fields
TransactionTypestringUInt16ContractUserDelete).AccountstringAccountIDContractAccountstringAccountID10.2. Failure Conditions
ContractAccountdoesn't exist or isn't a smart contract pseudo-account.Accountdoes not have anyContractDataobject for the contract inContractAccount.10.3. State Changes
If the transaction is successful:
ContractDataobject associated with theContractAccountwill be deleted.user_deletefunction on the contract will be run to perform any cleanup work, if it exists.11. Transaction:
ContractClawbackThis transaction allows issuers to claw back tokens from a contract, while also allowing smart contract devs to perform any cleanup they need to based on this clawback result.
This transaction will trigger a special
clawbackfunction in the contract, if it exists - which allows smart contract devs to do their own cleanup work as needed.11.1. Fields
TransactionTypestringUInt16ContractClawback).AccountstringAccountIDContractAccountstringAccountIDAmountobjectAmount11.2. Failure Conditions
ContractAccountdoesn't exist or isn't a smart contract pseudo-account.Amountis invalid in some way (e.g. is negative, token doesn't exist, is XRP).Accountisn't the issuer of the token specified inAmount.ContractAccountdoesn't hold the token specified inAmount.ContractAccountholds the token specified inAmount, but holds less than the amount specified.11.3. State Changes
If the transaction is successful:
ContractAccount's token is decreased byAmount.12. Transaction Common Fields
This standard doesn't add any new field to the transaction common fields, but it does add another global transaction flag and add another metadata field.
12.1.
FlagstfContractSubmittedTxn0x20000000This flag should only be used if a transaction is submitted from a smart contract. This signifies that the transaction shouldn't be signed. Any transaction that is submitted normally that includes this flag should be rejected.
Contract-submitted transactions will be processed in a method very similar to Batch inner transactions - i.e. executed within the
ContractCallprocessing, rather than as a separate independent transaction. This allows the smart contract code to take actions based on whether the transaction was successful.12.2. Metadata
Every contract-submitted transaction will contain an extra metadata field,
ParentContractCallId, containing the hash of theContractCalltransaction that triggered its submission.13. RPC:
contract_infoThis RPC fetches info about a deployed contract.
13.1. Request Fields
contract_accountstringfunctionstringuser_accountstring13.2. Response Fields
contract_accountstringcodestringaccount_infoobjectaccount_infooutput of thecontract_account.functionsarraysource_code_uristringcontract_dataobjectuser_datauser_accountis included in the requestobject13.2.1.
functionsEach object in the array will contain the following fields:
namestringparametersarrayfeesstring14. RPC Subscription:
eventEmittedSubscribe to events emitted from a contract.
14.1. Request Fields
contract_accountstringeventsarrayarrayofstrings. If omitted, all events from the contract will be subscribed to.TODO: maybe you should also be able to subscribe to all instances of a
ContractSource?14.2. Response Fields
contract_accountstringeventsarrayhashstringledger_indexnumber14.2.1.
eventsEach object in the
eventsarray will contain the following fields:namestringdataobjectThe rest of the fields in this object will be dev-defined fields from the emitted event.
15. RPC Subscription:
event_historyFetch a list of historical events emitted from a given contract account.
15.1. Request Fields
contract_accountstringeventsarrayarrayofstrings. If omitted, all events from the contract will be retrieved.ledger_index_minnumber-1instructs the server to use the earliest validated ledger version available.ledger_index_maxnumber-1instructs the server to use the most recent validated ledger version available.ledger_indexLedgerIndexledger_hashstringbinarybooleanfalse. If set totrue, returns events as hex strings instead of JSON.limitnumbermarkeranytransactionsbooleanfalse. If set totrue, returns the whole transaction in addition to the event. If set to falls, returns only the transaction hash.15.2. Response Fields
contract_accountstringeventsarrayledger_index_minnumberledger_index_maxnumberlimitnumbermarkerany15.2.1.
eventsEach object in the
eventsarray will contain the following fields:namestringdataobjectbinaryis set tofalse.data_blobstringbinaryis set totrue.tx_jsonobjecttransactionsis set totrueandbinaryis set tofalse.tx_blobstringtransactionsis set totrueandbinaryis set totrue.tx_hashstringtransactionsis set tofalse.validatedboolean15.3. Implementation Details
This RPC will use the account transactions database. It will iterate through all
ContractCalltransactions sent to the providedcontract_accountand filter out the events based on the other provided parameters.16. UNL-Votable Parameters
Initial values will be high fees/low maxes, but via the standard UNL voting process for fees, this can be adjusted over time as needed.
17. Serialized Type:
STParametersThis object is essentially just a list of
(Flag, SType value)groupings.STypeto describe a parameterUInt32for flagsUInt16for STypeJSON representation is a name/number and the string names of the STypes - for example:
18. Serialized Type:
STParameterValuesThis object is essentially just a list of
(Flag, SType value, value that is of type `SType`)groupings.Each of these groupings looks like this:
JSON representation is a name/number and the string representation of the values - for example:
19. Serialized Type:
STDataThe following, serialized one after another:
STypeif needed? Or just a number that the ABI defines?)STJsoninstead and just handle JSON data?JSON representation is a dictionary with the key-value pairs - for example:
20. Examples
20.1. Sample Code
Note: this is entirely made up, and is only intended to provide a rough idea of the concepts described in this document. The exact syntax is heavily subject to change (during both design iterations and in the implementation phase, as more details are figured out). For example, the final version will likely be in Rust.
init()function that allows you to do any initial account setup (instead of needing to do it in a separate function call)emitEventis used to emit an event that is stored in the metadata21. Invariants
ContractSourceobject should have aReferenceCountof 0Contractobject should have an existing correspondingContractSourceobjectContractcannot have bothlsfImmutableandlsfCodeImmutableenabled.22. Security
22.1. Pseudo-Account Account-Level Permissions
These settings will all be enabled by default on a pseudo-account:
DepositAuthThese functions will all be disallowed from pseudo-accounts:
SignerListSetSetRegularKeyDepositAuthAccountPermissionsSetThis prevents the contract account from receiving any funds directly (i.e. outside of the
ContractCalltransaction) and prevents any other account (malicious or otherwise) from submitting transactions directly from the contract account. This ensures that the contract account’s logic is entirely governed by the code, and nothing else.22.2. Scam Contracts
Any amount you send to a contract can be rug-pulled. This is the same level of risk as any other scam on the XRPL right now - such as buying a rug-pulled token.
22.3. Fee-Scavenging/Dust Attacks
Don’t think this is possible here.
22.4. Re-entrancy Attacks
These will need to be guarded against in this design. One option for this is to disallow any recursion in calls - i.e. disallow
ContractCalltransactions from a contract account to itself. Other options are being investigated.Open Questions
STData?STDatatypeContractDataobjects be stored in a separate directory structure (e.g. a new one, not the standard owner directory)?user_deleteorclawbackfails or crashes in some way?ComputationAmountthe transaction should probably fail, if it crashes/fails for anything else (that is a result of the WASM code) the transaction should probably succeedReserves
The biggest remaining question (from the core ledger design perspective) is how to handle object reserves for any contract-specific data (reserves for all existing ledger entries can be handled as they are now).
The most naive option is for the contract account to hold all necessary funds for the reserves. However, this is quite the burden on the contract account (and therefore the deployer of the contract). Ideally, there should be some way to put some of the burden on the contract users for the parts of the data that they use.
One example to illustrate the difference: an ERC-20 contract holds all of its data (e.g. holders and the amount they hold) in the contract itself. However, on the XRPL, an MPT issuer does not need to cover reserves for all of its holders and their data - only the reserves for the issuance data. The EVM doesn’t have the concept of reserves (it’s essentially amortized in the transaction fees), this concern doesn’t apply to those chains.
Account Reserve
The account reserve is essentially covered by the non-refundable
ContractCreatetransaction fee. This also covers the reserve for theContract/ContractSourceledger entry, if needed.TODO: perhaps there should also be limits on fields you can set in the
AccountRoot.Object Reserve
How should object reserves be covered? Some options (numbered for ease of discussion):
ContractCreatefee and there's a hard cap. Higher fees for more reserves.Contract ID+Account, only one object stores all of a user’s dataThe authors lean towards something akin to Option 6, as it feels the most XRPL-y, but supporting data deletion becomes complicated, because otherwise contract developers can lock up users’ reserves without them being able to free that reserve easily.
Appendix
Appendix A: FAQ
A.1: How does this compare to Hooks?
The main similarities:
The main differences:
ContractCalltransaction to trigger the smart contract.A.2: How does this compare to EVM?
The main similarities:
The main differences:
A.3: How can I implement account logic (like in Hooks) with this form of smart contracts?
Use something akin to Ethereum’s Account Abstraction design (ERC-4337).
Might involve XLS-75d (Delegating Account Permissions).
We're also investigating whether additional Smart Features can help with this problem.
A.4: Will I be able to transfer (or copy/paste) EVM/SolVM/MoveVM/etc. bytecode to the XRPL?
No (well, not without a special tool that will do the conversion for you).
A.5: Will I be able to write smart contracts in Solidity?
Solidity will not be prioritized by our team right now, but there are Solidity-to-WASM compilers that someone could use to make this possible.
Note that the syntax would likely not be the exact same as in the EVM. In addition, the conceptual meanings may not map either - for example, addresses are different between the EVM and the XRPL.
A.6: Will I be able to implement something akin to ERC-20 with an XRPL smart contract?
Yes, but it will be more expensive and less efficient than the existing token standards (IOUs and MPTs) and won’t be integrated into the DEX or other parts of the XRPL.
An alternative strategy would be to create a smart contract that essentially acts as a wrapper to the XRPL’s native functionalities (e.g. a
mintfunction that just issues a token via aPaymenttransaction).A.7. How will fees be handled for contract-submitted transactions?
It’ll be included in the fees paid for the contract call.
A.8. What happens if a smart contract pseudo-account’s funds are clawed back, or are locked/frozen?
That’s for the smart contract author to deal with, just like in the EVM world.
A.9: What languages can/will be supported?
Any language that can compile to WASM can be supported. We will likely start with Rust.
A.10: Can a smart contract execute a multi-account Batch transaction with another account?
Yes, if the smart contract account submits the final transaction. Constructing a multi-account Batch transaction between two smart contracts will not be possible as a part of this spec. Support for that could be added with a separate design.
A.11: Can I use Smart Escrows with Smart Contracts?
Yes, a smart contract can emit an
EscrowCreatetransaction that has aFinishFunction.A.12: Will this design support read-only functions like EVM?
Not in the initial version/design, to keep it simple. This could be added in the future, though.
A.13: How do I get the transaction history of a Contract Account?
Use the existing
account_txRPC.A.14: How does this design prevent abuse/infinite loops from eating up rippled resources, while allowing for sufficient compute for smart contract developers?
The
UNL-Votable Parameterssection addresses this. The UNL can adjust the parameters based on the needs and limitations of the network, to ensure that developers have enough computing resources for their needs, while ensuring that they cannot overrun the network with their contracts.A.15: Why store the ABI on-chain instead of in an Etherscan-like system?
Having the data on-chain removes the need for a centralized party to maintain this data - all the data to interact with a contract is available on-chain. This also means that it’s much easier (and therefore faster) for
rippledto determine if the data passed into a contract function is valid, instead of needing to open up the WASM engine for that.The tradeoff is that this means a contract will take up more space on-chain, and we need new STypes to store that information properly.
Beta Was this translation helpful? Give feedback.
All reactions