├── site ├── assets │ └── favicon.ico ├── templates │ ├── contribute.html │ ├── xls.html │ └── base.html ├── requirements.txt ├── build_site.py └── xls_parser.py ├── .gitignore ├── XLS-0047-PriceOracles └── README.pdf ├── .pre-commit-config.yaml ├── .github ├── workflows │ ├── lint.yml │ ├── validate-xls.yml │ └── deploy.yml └── dependabot.yml ├── README.md ├── XLS-0004-trustline-uri └── README.md ├── LICENSE ├── XLS-0006-visual-account-icons └── README.md ├── XLS-0002-destination-information ├── README.md └── reference.js ├── XLS-0003-deeplink-signed-transactions └── README.md ├── XLS-0060-default-autobridge └── README.md ├── XLS-0052-NFTokenMintOffer └── README.md ├── XLS-0037-concise-transaction-identifier-ctid ├── QUICKSTART.md ├── ctid.php ├── ctid.cpp ├── ctid.py ├── ctid.ts └── ctid.js ├── XLS-0063-signin └── README.md ├── XLS-0067-charge └── README.md ├── XLS-0018-bootstrapping └── README.md ├── XLS-0071-initial-owner-reserve-exemptio └── README.md ├── XLS-0055-remit └── README.md ├── XLS-0012-secret-numbers └── README.md ├── XLS-0046-dynamic-non-fungible-tokens └── README.md ├── XLS_TEMPLATE.md ├── XLS-0061-cross-currency-nftokenacceptof └── README.md ├── XLS-0041-xpop └── README.md ├── XLS-0051-nftoken-escrow └── README.md ├── XLS-0010-non-transferable-tokens └── README.md ├── XLS-0021-asset-code-prefixes └── README.md ├── XLS-0017-xfl └── README.md ├── XLS-0050-validator-toml-infra-details └── README.md ├── CONTRIBUTING.md ├── XLS-0022-api-versioning └── README.md ├── XLS-0064-pseudo-account └── README.md ├── XLS-0025-enhanced-secret-numbers └── README.md ├── XLS-0016-nft-metadata └── README.md ├── XLS-0023-lite-accounts └── README.md ├── XLS-0095-rename-rippled-to-xrpld └── README.md └── XLS-0054-nftokenoffer-destination-tag └── README.md /site/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XRPLF/XRPL-Standards/HEAD/site/assets/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.idea 3 | _site/ 4 | 5 | # Python cache files 6 | __pycache__/ 7 | *.pyc 8 | *.pyo 9 | -------------------------------------------------------------------------------- /XLS-0047-PriceOracles/README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XRPLF/XRPL-Standards/HEAD/XLS-0047-PriceOracles/README.pdf -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: mixed-line-ending 8 | - id: check-merge-conflict 9 | args: [--assume-in-merge] 10 | 11 | - repo: https://github.com/rbubley/mirrors-prettier 12 | rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2 13 | hooks: 14 | - id: prettier 15 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | prettier: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v6 15 | 16 | - name: Set up Node.js 17 | uses: actions/setup-node@v6 18 | with: 19 | node-version: "24" 20 | 21 | - name: Install dependencies 22 | run: npm install -g prettier@3.6.2 # match version in pre-commit-config.yaml 23 | 24 | - name: Run Prettier 25 | run: prettier --check . 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XRP Ledger Standards 2 | 3 | XRP Ledger Standards (XLSs) describe standards and specifications relating to the XRP Ledger ecosystem that help achieve the following goals: 4 | 5 | - Ensure interoperability and compatibility between XRP Ledger core protocol, ecosystem applications, tools, and platforms. 6 | - Maintain a continued, excellent user experience around every application or system. 7 | - Drive alignment and agreement in the XRPL community (i.e., developers, users, operators, etc). 8 | 9 | # [Contributing](./CONTRIBUTING.md) 10 | 11 | The exact process for organizing and contributing to this repository is defined in [CONTRIBUTING.md](./CONTRIBUTING.md). If you would like to contribute, please read more there. 12 | -------------------------------------------------------------------------------- /site/templates/contribute.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block content %} 2 |
3 |

Contributing to XLS Standards

4 | 5 |
{{ content|safe }}
6 |
7 | {% endblock %} {% block scripts %} 8 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /site/requirements.txt: -------------------------------------------------------------------------------- 1 | # Python dependencies for XLS Standards site generation and validation 2 | # 3 | # Core site generation dependencies 4 | markdown>=3.4.4,<4.0.0 # Markdown to HTML conversion with extensions 5 | jinja2>=3.1.2,<4.0.0 # HTML template engine 6 | beautifulsoup4>=4.12.0,<5.0.0 # HTML/XML parsing and metadata extraction 7 | pyyaml>=6.0,<7.0 # YAML configuration parsing 8 | 9 | # Additional dependencies for enhanced markdown processing 10 | # Note: These are included automatically with markdown[extra] but listed for clarity 11 | # - tables: GitHub-flavored markdown tables 12 | # - codehilite: Syntax highlighting for code blocks 13 | # - toc: Table of contents generation 14 | # - extra: Additional markdown extensions bundle -------------------------------------------------------------------------------- /XLS-0004-trustline-uri/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 4
 3 |   title: Trustline Add URI
 4 |   description: A URI standard for instructing wallets to add trustlines following the design of XLS-2d
 5 |   author: Richard Holland (@RichardAH)
 6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/25
 7 |   status: Stagnant
 8 |   category: Ecosystem
 9 |   created: 2019-03-06
10 | 
11 | 12 | I suggest we follow on closely from the design of XLS-2d 13 | 14 | Query parameters: 15 | `action=trustline` 16 | `limit=` 17 | `currency=:` 18 | `rippling=true|false (optional, default = false)` 19 | 20 | I.e. 21 | 22 | `{ url } ? { trust line instructions }` 23 | 24 | So for example 25 | 26 | `anything://any.domain/url/?action=trustline&limit=10000¤cy=rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B:USD&rippling=false` 27 | 28 | I realise compounding the currency and issuer information is a little tacky here but we did it in the previous standard 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration for keeping dependencies updated 2 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 3 | 4 | version: 2 5 | updates: 6 | # Python dependencies (requirements.txt) 7 | - package-ecosystem: "pip" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | day: "monday" 12 | time: "09:00" 13 | open-pull-requests-limit: 5 14 | reviewers: 15 | - "mvadari" 16 | - "intelliot" 17 | commit-message: 18 | prefix: "deps" 19 | include: "scope" 20 | 21 | # GitHub Actions dependencies 22 | - package-ecosystem: "github-actions" 23 | directory: "/" 24 | schedule: 25 | interval: "weekly" 26 | day: "monday" 27 | time: "09:00" 28 | open-pull-requests-limit: 5 29 | reviewers: 30 | - "mvadari" 31 | - "intelliot" 32 | commit-message: 33 | prefix: "ci" 34 | include: "scope" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2023 XRP Ledger Foundation (MTU XRP Ledger Trust) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /site/templates/xls.html: -------------------------------------------------------------------------------- 1 | 10 | {% extends "base.html" %} {% block content %} 11 |
12 | 13 |
14 | 15 |
XLS-{{ doc.number }}
16 | 17 |
18 | {{ doc.status.title() }} 19 |
20 |
21 | 22 | 23 | 24 |
{{ content|safe }}
25 | 26 | 27 |
28 | 29 | ← Back to All Standards 30 | 31 | View on GitHub 35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /.github/workflows/validate-xls.yml: -------------------------------------------------------------------------------- 1 | name: Validate XLS Documents 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | validate-xls: 11 | runs-on: ubuntu-latest 12 | name: Validate XLS Document Parsing 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v6 17 | 18 | - name: Setup Python 19 | uses: actions/setup-python@v6 20 | with: 21 | python-version: "3.11" 22 | 23 | - name: Cache Python dependencies 24 | id: cache-deps 25 | uses: actions/cache@v5 26 | with: 27 | path: ~/.cache/pip 28 | key: ${{ runner.os }}-pip-${{ hashFiles('**/site/requirements.txt') }} 29 | restore-keys: | 30 | ${{ runner.os }}-pip- 31 | 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip 35 | pip install -r site/requirements.txt 36 | 37 | - name: Validate XLS document parsing 38 | run: | 39 | echo "Running XLS document validation..." 40 | python site/xls_parser.py 41 | 42 | - name: Report validation results 43 | if: always() 44 | run: | 45 | echo "XLS validation completed" 46 | echo "This pipeline validates that all XLS documents can be parsed correctly" 47 | echo "If this fails, it indicates issues with XLS document formatting or metadata" 48 | -------------------------------------------------------------------------------- /XLS-0006-visual-account-icons/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 6
 3 |   title: Standard for Visual Account Icons
 4 |   description: A standard for visually distinguishing XRPL accounts by generating unique icons for each account, regardless of address format.
 5 |   author: Richard Holland (@RichardAH)
 6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/24
 7 |   status: Final
 8 |   category: Ecosystem
 9 |   created: 2019-09-22
10 | 
11 | 12 | Following from [XLS-5d](https://github.com/XRPLF/XLS-0005-standards-for-addressing), it has become necessary to provide XRPL users a way to identify their XRPL account, which effectively now has two different identifiers: an 'r-address' and an 'X-address'. 13 | 14 | To solve this problem XLS-6d provides for a standard way to visually identify accounts irrespective of which addressing system is used by the rest of the user interface. 15 | 16 | For a user account take the X-address of the account without destination tag and feed it into hashicon https://www.npmjs.com/package/hashicon 17 | 18 | For an exchange, take the X-address of the account with the destination tag and feed it into hashicon. 19 | 20 | It's recommended that these icons are displayed alongside addresses to help reduce user confusion. See examples in Figures 1 and 2. 21 | 22 | ![image](https://user-images.githubusercontent.com/19866478/65387069-ba273a80-dd86-11e9-8680-34488e15401d.png) 23 | Figure 1 24 | 25 | ![image](https://user-images.githubusercontent.com/19866478/65387078-dc20bd00-dd86-11e9-90af-126edf511060.png) 26 | Figure 2 27 | -------------------------------------------------------------------------------- /XLS-0002-destination-information/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 2
 3 |   title: XRPL destination information
 4 |   description: A standard for encoding destination information with backwards compatibility for web browsers
 5 |   author: Wietse Wind 
 6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/27
 7 |   status: Stagnant
 8 |   category: Ecosystem
 9 |   created: 2019-02-25
10 | 
11 | 12 | Currently several apps are using a variety of methods to encode destination information; 13 | 14 | - rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY?dt=123 15 | - rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY:123 16 | - Deprecated Ripple URI syntax: https://ripple.com//send?to=rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY&amount=30&dt=123 17 | 18 | I feel the best way to implement this, is by allowing Apps to register Deep Links (URI handlers) while keeping the destinations backwards compatible with Web browsers, so users without any app to handle a URI can display a page by the app creator with an instruction. 19 | 20 | I propose to allow any URL prefix, with a default set of GET URL params, supporting the existing params proposed in the (deprecated) Ripple URI standard; 21 | 22 | so: 23 | 24 | { any URL } ? { params } 25 | 26 | Where params may _all_ be optional except for the account address: 27 | 28 | - `to` (account address, rXXXX..) 29 | - `dt` (destination tag, uint32) 30 | - `amount` (float, default currency: XRP (1000000 drops)) 31 | - ... 32 | 33 | So App 1 may use: 34 | https://someapp.com/sendXrp?to=... 35 | 36 | ... While App 2 uses: 37 | https://anotherapp.net/deposit-xrp?to=...&dt=1234&amount=10 38 | -------------------------------------------------------------------------------- /XLS-0003-deeplink-signed-transactions/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 3
 3 |   title: Sharing and deeplinking signed transactions
 4 |   description: A standard for encoding HEX signed transactions for air-gapped submissions with deep link support
 5 |   author: Wietse Wind 
 6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/26
 7 |   status: Stagnant
 8 |   category: Ecosystem
 9 |   created: 2019-02-25
10 | 
11 | 12 | Currently several apps are using a variety of methods to encode HEX signed transactions (to be submitted air gapped); 13 | 14 | - { HEX } 15 | - ripple:signed-transaction:{ HEX } 16 | - ripple://signed/{ HEX } 17 | 18 | I feel the best way to implement this, is by allowing Apps to register Deep Links (URI handlers) while keeping the destinations backwards compatible with Web browsers, so users without any app to handle a URI can display a page by the app creator with an instruction. 19 | 20 | I propose to allow any URL prefix: 21 | { any URI with exactly one folder } / { HEX } 22 | 23 | So App 1 may use: 24 | https://someapp.com/submitTx/{ HEX } 25 | 26 | ... While App 2 uses: 27 | https://someapp.com/send-xrpl-tx/{ HEX } 28 | 29 | I would propose to limit the amount of slashes before the HEX to a _fixed amount of slashes_ (eg. one folder, as the App 1 and App 2 examples show) so parsers can easily split the URI without having to use regular expressions. 30 | 31 | Limitations: 32 | MSIE limits URL parsing to 2,083 chars. Safari 65k chars. Todo: need to test this on modern mobile OS'es. 33 | 34 | P.S. Another option (proposal) would be to use a specific syntax (prefix) instead of fixed (one) folder; eg. 35 | `https://xrpl-labs.com/xrpl:signed-transaction:` 36 | 37 | ... Where the fixed prefix in this case is `xrpl:signed-transaction:`. 38 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy XLS Standards to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: false 16 | 17 | jobs: 18 | build: 19 | if: github.repository == 'XRPLF/XRPL-Standards' 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v6 24 | 25 | - name: Setup Python 26 | uses: actions/setup-python@v6 27 | with: 28 | python-version: "3.11" 29 | 30 | - name: Cache Python dependencies 31 | uses: actions/cache@v5 32 | id: cache-deps 33 | with: 34 | path: ~/.cache/pip 35 | key: ${{ runner.os }}-pip-${{ hashFiles('**/site/requirements.txt') }} 36 | restore-keys: | 37 | ${{ runner.os }}-pip- 38 | 39 | - name: Install dependencies 40 | run: | 41 | python -m pip install --upgrade pip 42 | pip install -r site/requirements.txt 43 | 44 | - name: Build site 45 | run: python site/build_site.py 46 | env: 47 | GITHUB_PAGES_BASE_URL: "" 48 | 49 | - name: Setup Pages 50 | uses: actions/configure-pages@v5 51 | 52 | - name: Upload artifact 53 | uses: actions/upload-pages-artifact@v4 54 | with: 55 | path: "site/_site" 56 | 57 | deploy: 58 | if: github.repository == 'XRPLF/XRPL-Standards' 59 | environment: 60 | name: github-pages 61 | url: ${{ steps.deployment.outputs.page_url }} 62 | runs-on: ubuntu-latest 63 | needs: build 64 | steps: 65 | - name: Deploy to GitHub Pages 66 | id: deployment 67 | uses: actions/deploy-pages@v4 68 | -------------------------------------------------------------------------------- /XLS-0060-default-autobridge/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 60
 3 |   title: Default AutoBridge
 4 |   description: Use autobridging in IOU-IOU Payment transactions
 5 |   author: tequ (@tequdev)
 6 |   created: 2024-02-05
 7 |   status: Stagnant
 8 |   category: Amendment
 9 | 
10 | 11 | ## Abstract 12 | 13 | Currently, if the Path field is not specified in an IOU-IOU Payment transaction, only direct pairs of two currencies are used internally. This proposal would change this default behavior to use IOU-XRP-IOU as well as OfferCreate transaction's. 14 | 15 | ## Changes 16 | 17 | If the Path field is unspecified in the IOU_A->IOU_B Payment transaction, the path through IOU_A/XRP and IOU_B/XRP is used in addition to the IOU_A/IOU_B path. 18 | 19 | If existing transaction types or future transaction types to be implemented are similarly cross-currency transactions, it will be possible to use bridge paths in addition to direct paths by default. 20 | 21 | ### Technical 22 | 23 | Add an additional path with XRP as the intermediate between two currencies to in flow() method in Flow.cpp when `defaultPaths`=`true`. 24 | 25 | Use XRP-mediated paths to the paths under the following conditions: 26 | 27 | - Path is not specified 28 | - SendMax is set. 29 | - The issuer and currency of Amount and SendMax are different. 30 | 31 | ### Payment transaction 32 | 33 | If the Path field is not specified and the SendMax field is set, the transaction will use the XRP-mediated path. 34 | 35 | No impact if the NoRippleDirect flag is set. 36 | 37 | ### OfferCreate transaction 38 | 39 | No impact as Path is always set. 40 | 41 | ### CheckCash transaction 42 | 43 | (using flow()) 44 | 45 | No impact as it only processes when the fields corresponding to Amount and SendMax are the same. 46 | 47 | _If future amendments allow cross-currency checks, composite paths will be available._ 48 | 49 | ### XChainBridge transaction 50 | 51 | (using flow()) 52 | 53 | No impact as SendMax is processed as null. 54 | 55 | ### PathFind command 56 | 57 | (using flow()) 58 | No impact as DefaultPaths is false. 59 | -------------------------------------------------------------------------------- /XLS-0052-NFTokenMintOffer/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 52
 3 |   title: NFTokenMintOffer
 4 |   description: Extension to NFTokenMint transaction to allow NFToken Sell Offer creation at the same time as minting
 5 |   author: tequ (@tequdev)
 6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/147
 7 |   status: Final
 8 |   category: Amendment
 9 |   requires: [XLS-20](../XLS-0020-non-fungible-tokens/README.md)
10 |   created: 2023-11-21
11 | 
12 | 13 | ## Abstract 14 | 15 | This proposal extends the minting capabilities of NFToken (XLS-20). 16 | 17 | NFToken are not only held by the issuer, but are often distributed or sold by the issuer to others. 18 | 19 | XRPL NFToken requires two transactions (`NFTokenMint` and `NFTokenCreateOffer`) to be sent by the issuer before the NFToken can be minted and distributed (or sold). 20 | NFTokenOfferMint extends the existing `NFTokenMint` transaction to allow an **NFToken Sell Offer** to be created at the same time as the NFToken is minted. 21 | 22 | NFTokenOfferMint is expected to significantly improve the experience of NFT projects and users. 23 | 24 | ## Specification 25 | 26 | ### New `NFTokenMint` Transaction Field 27 | 28 | Add 3 new fields to the`NFTokenMint` transaction. 29 | 30 | | Field Name | Required? | JSON Type | Internal Type | 31 | | ------------- | :-------: | :---------------: | :-----------: | 32 | | `Amount` | | `Currency Amount` | `AMOUNT` | 33 | | `Destination` | | `string` | `AccountID` | 34 | | `Expiration` | | `number` | `UINT32` | 35 | 36 | These fields have the same meaning as the field of the same name used in the `NFTokenCreateOffer` transaction, but the `Amount` field is not a required field. 37 | 38 | If the `Amount` field (and the other 2 fields) are not specified, `NFTokenMint` transaction behaves exactly like a previous `NFTokenMint` transaction. 39 | 40 | ### Creating an `NFTokenOffer` 41 | 42 | Since NFToken issuers can only create a sell offer for their NFToken, the `Owner` field is not set and `tfSellNFToken` flag is always set in the `NFTokenOffer` created in an `NFTokenMint` transaction. 43 | 44 | In an extended `NFTokenMint` transaction, an `NFTokenOffer` is created when 45 | 46 | - `Amount` field is specified. 47 | 48 | An error occurs in the following cases 49 | 50 | - One or both of `Destination` and `Expiration` fields are specified, but the `Amount` field is not . 51 | 52 | ### Owner Reserve 53 | 54 | The `NFTokenPage` and `NFTokenOffer` reserves will not change, but will create the possibility of up to 2 incremental reserves (NFTokenPage, NFTokenOffer) in an `NFTokenMint` transaction. 55 | -------------------------------------------------------------------------------- /site/templates/base.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% block title %}{{ title }}{% endblock %} 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 |
39 |
40 | 41 |

XRP Ledger Standards

42 | 48 |
49 |
50 | 51 | 52 |
{% block content %}{% endblock %}
53 | 54 | 55 |
56 |
57 |

58 | © 2025 XRP Ledger Foundation. Licensed under 59 | Apache License 2.0. 62 |

63 |
64 |
65 | 66 | 67 | {% block scripts %}{% endblock %} 68 | 69 | 70 | -------------------------------------------------------------------------------- /XLS-0037-concise-transaction-identifier-ctid/QUICKSTART.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | ## Improved Concise Transaction Identifier (CTID) 4 | 5 | CTIDs are composed of 16 hex nibbles, and begin with a `C`. 6 | 7 | ``` 8 | CXXXXXXXYYYYZZZZ 9 | ``` 10 | 11 | The identifier is divided into three fields. 12 | 13 | | Char Offset | Field | Size (bits) | Explanation | 14 | | ----------- | ------- | ----------- | --------------------------------------------- | 15 | | 0 | C | 4 | Lead-in (ignore) | 16 | | 1-7 | XXXXXXX | 28 | Ledger Sequence | 17 | | 8-11 | YYYY | 16 | Transaction index (offset) within that ledger | 18 | | 12-16 | ZZZZ | 16 | Network ID. | 19 | 20 | Reference implementations are available for several languages. Click below to dive in. 21 | 22 | | Language | Implementation | 23 | | ---------- | ------------------------------------------------------------ | 24 | | Javascript | [ctid.js](https://github.com/XRPLF/ctid/blob/main/ctid.js) | 25 | | Typescript | [ctid.ts](https://github.com/XRPLF/ctid/blob/main/ctid.ts) | 26 | | C++ | [ctid.cpp](https://github.com/XRPLF/ctid/blob/main/ctid.cpp) | 27 | | Python 3 | [ctid.py](https://github.com/XRPLF/ctid/blob/main/ctid.py) | 28 | | PHP 5 | [ctid.php](https://github.com/XRPLF/ctid/blob/main/ctid.php) | 29 | 30 | ### Function prototypes (pseudocode) 31 | 32 | In this repo there are several reference implementations available for various languages but they all use the same function model. 33 | 34 | ```js 35 | function encodeCTID ( 36 | ledger_seq : number, 37 | txn_index : number, 38 | network_id : number) -> string; 39 | ``` 40 | 41 | ```js 42 | function decodeCTID (ctid : string or number) -> { 43 | ledger_seq : number, 44 | txn_index : number, 45 | network_id : number }; 46 | ``` 47 | 48 | ### Mainnet example 49 | 50 | [This transaction](https://livenet.xrpl.org/transactions/D42BE7DF63B4C12E5B56B4EFAD8CBB096171399D93353A8A61F61066160DFE5E/raw) encodes in the following way: 51 | 52 | ```js 53 | encodeCTID( 54 | 77727448, // ledger sequence number the txn appeared in 55 | 54, // `TransactionIndex` as per metadata 56 | 0, 57 | ); // Network ID of mainnet is 0 58 | ("C4A206D800360000"); 59 | ``` 60 | 61 | ### Hooks testnet v3 example 62 | 63 | [This transaction](https://hooks-testnet-v3-explorer.xrpl-labs.com/tx/C4E284010276F8457C4BF96D0C534B7383087680C159F9B8C18D5EE876F7EFE7) encodes in the following way: 64 | 65 | ```js 66 | encodeCTID( 67 | 428986, // ledger sequence number the txn appeared in 68 | 0, // `TransactionIndex` as per metadata 69 | 21338, 70 | ); // Network ID of hooks v3 is 21338 71 | ("C0068BBA0000535A"); 72 | ``` 73 | -------------------------------------------------------------------------------- /XLS-0063-signin/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 63
 3 |   title: SignIn Transaction
 4 |   description: A dedicated transaction type for off-chain signing in with wallets
 5 |   author: Denis Angell (@dangell7)
 6 |   created: 2024-03-26
 7 |   status: Stagnant
 8 |   category: Ecosystem
 9 | 
10 | 11 | # Problem Statement 12 | 13 | In the XRPL ecosystem, certain wallets (Ledger) restrict users from signing arbitrary hex messages as a security measure to protect against malicious activities. This limitation poses a challenge for applications that require user authentication through signature verification. As a result, some applications resort to using low drop Payment transactions as a workaround for authentication, which is not an ideal solution and can lead to unnecessary ledger bloat. To provide a more secure and efficient method for user authentication, a dedicated transaction type for signing in is necessary. 14 | 15 | # Proposal 16 | 17 | We propose the introduction of a new transaction type called "SignIn" that includes only the common transaction fields along with an additional field, `sfData`, which is an arbitrary data hex field. This transaction type will be specifically designed for applications to authenticate users by allowing them to sign a piece of data that can be verified by the application. 18 | 19 | > Importantly, `SignIn` transactions are not intended to be submitted to the ledger. 20 | 21 | ## New Transaction Type: `SignIn` 22 | 23 | The `SignIn` transaction is a new transaction type that allows users to sign an arbitrary piece of data for the purpose of authentication. This transaction type is not intended to transfer any funds or alter the ledger state in any way, but rather to provide a verifiable signature that applications can use to authenticate users. 24 | 25 | The transaction has the following fields: 26 | 27 | | Field | Type | Required | Description | 28 | | ----------------- | -------------- | -------- | ------------------------------------------------------------------------- | 29 | | sfTransactionType | String | ✔️ | The type of transaction, which is "SignIn" for this proposal. | 30 | | sfAccount | AccountID | ✔️ | The account of the user signing in. | 31 | | sfData | VariableLength | ✔️ | The arbitrary data to be signed by the user, represented as a hex string. | 32 | 33 | Example `SignIn` transaction: 34 | 35 | ```json 36 | { 37 | "Account": "rExampleAccountAddress", 38 | "TransactionType": "SignIn", 39 | "Data": "48656C6C6F205852504C2041757468656E7469636174696F6E" 40 | } 41 | ``` 42 | 43 | In this example, the `Data` field contains a hex-encoded string that the user's wallet will sign. The application can then verify the signature against the user's public key to authenticate the user. 44 | -------------------------------------------------------------------------------- /XLS-0067-charge/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 67
 3 |   title: Charge
 4 |   description: A feature that focuses on fee collection and makes monetization easier and simpler for platforms, wallet services, and users to use
 5 |   author: tequ (@tequdev)
 6 |   created: 2024-04-22
 7 |   status: Stagnant
 8 |   category: Amendment
 9 | 
10 | 11 | # Abstract 12 | 13 | One way to monetize NFT, AMM and other platforms that use XRP Ledger is to charge a fee from separate Payment transaction. XLS-56d (Batch/Atomic Transaction) has already been proposed as a way to do this in a single transaction. Here, I propose a feature that focuses on fee collection and makes monetization easier and simpler for platforms, wallet services, and users to use. 14 | 15 | # Specification 16 | 17 | ### Transaction common field 18 | 19 | Add the following field as one of transaction common fields. 20 | This field is an optional field and be available for any transaction. 21 | 22 | | Field Name | Required? | JSON Type | Internal Type | 23 | | ---------- | :-------: | :-------: | :-----------: | 24 | | `Charge` | | `object` | `Object` | 25 | 26 | ## `Charge` Field 27 | 28 | | Field Name | Required? | JSON Type | Internal Type | 29 | | ---------------- | :-------: | :------------------: | :-----------: | 30 | | `Amount` | ✅ | `object` or `string` | `Amount` | 31 | | `Destination` | ✅ | `string` | `Account` | 32 | | `DestinationTag` | | `number` | `UInt16` | 33 | 34 | #### `Amount` Field 35 | 36 | Like the `Amount` field used in other transaction types, it represents a native token when specified as a string, or an issued token when specified as an object. 37 | 38 | For tokens for which a TransferRate has been specified by the issuer, this field represents the amount of tokens sent by the sender of the transaction and doesn't guarantee the amount of tokens the destination account will receive. 39 | 40 | ### `Destination` Field 41 | 42 | The account to which the fee will be sent. 43 | 44 | It is not possible to specify the account from which the transaction originates or an account that has not been activated. 45 | 46 | # Matters to Consider 47 | 48 | These are not included in the above specifications, but are subject to discussion. 49 | 50 | ## `SendMax` Field 51 | 52 | We could add `SendMax` to specify exactly how much the sender pays and how much the receiver receives, but that would be more complicated. 53 | 54 | ## CrossCurrency 55 | 56 | It is possible to make cross-currency payments by specifying different currencies in the `SendMax` and `Amount` fields, but this will increase transaction load. 57 | 58 | The `Path` field cannot specified and the default Path is used. 59 | e.g. 60 | 61 | - AAA/BBB without XLS-60d 62 | - AAA/XRP, BBB/XRP, AAA/BBB with XLS-60d 63 | 64 | ## Multiple charges 65 | 66 | Multiple currencies can be set as fees by specifying multiple `Charge` fields as an array in the `Charges` field, but this increases the transaction load (especially if cross-currency is also available). 67 | -------------------------------------------------------------------------------- /XLS-0018-bootstrapping/README.md: -------------------------------------------------------------------------------- 1 |
 2 |     xls: 18
 3 |     title: Standard For Bootstrapping XRPLD Networks
 4 |     description: An experimental procedure to bootstrapping XRP Ledger Network
 5 |     author: Richard Holland (@RichardAH)
 6 |     created: 2021-03-25
 7 |     status: Stagnant
 8 |     category: Ecosystem
 9 | 
10 | 11 | This procedure is experimental and additions amendments or recommendations based on experience to this standard draft are welcome. 12 | 13 | ### Problem Definition 14 | 15 | `Xrpld` / `rippled`[[1]](https://github.com/ripple/rippled) is the software that processes, achieves consensus upon and serves the decentralised public XRP Ledger, as well as numerous other public and private ledgers. 16 | 17 | From time to time a decentralised network may stall, fork, or otherwise fail to achieve consensus because of a bug, network instability, unreliable nodes or operators, a deliberate attack, or any combination of these. In this event the bootstrapping procedure to return the network to normal operation is important and should be followed correctly to avoid (further) forks and halts. 18 | 19 | ### Terminology 20 | 21 | `Validator` refers to a rippled instance configured to publish validations of ledgers. 22 | `UNL` refers to a list of validator public keys that the network has collectively agreed are allowed to validate ledgers. 23 | `Node` refers to a validator currently listed in the network's `UNL`. 24 | 25 | ### Procedure — Cold Start 26 | 27 | Cold start describes a situation in which _none_ of the validators in a network are currently running. This situation can arise because they crashed, were terminated automatically or were terminated manually (or any combination of these.) 28 | 29 | 1. Select a node from your UNL to be the `leader`. The remaining nodes will be referred to as `followers`. 30 | 2. On the `leader` run rippled from your terminal in the foreground with the following flags: 31 | `./rippled --valid --quorum 1 --load` 32 | 3. On each follower run rippled from your terminal in the foreground with the following flags: 33 | `./rippled --net --quorum 1` 34 | 4. In a seperate terminal, on each of the `followers` and on the `leader` you may monitor whether or not ledgers are closing correctly using: 35 | `./rippled ledger closed` 36 | In particular ensure the ledger index is incrementing and the ledgers are validated. 37 | 5. On the `leader` terminate the foreground rippled process. (Ctrl + C) 38 | 6. On the `leader` run rippled normally as a daemon. 39 | 7. On the `leader` in the second terminal continue to monitor the last closed ledger until validation is consistently achieved, as per 4. 40 | 8. Select one `follower` node. 41 | 9. Terminate the foreground process on the selected `follower` and start rippled normally. 42 | 10. Monitor for LCL validation as per 4. 43 | 11. Repeat from step 8 until all nodes are running normally. 44 | 45 | ### Troubleshooting 46 | 47 | Small networks may require the `quorum` size to be permanently overridden for network stability. To do this modify the control script that runs rippled to include `--quorum X` where X is the number of nodes in your UNL less 1. 48 | 49 | ### Other cases TBD 50 | -------------------------------------------------------------------------------- /XLS-0071-initial-owner-reserve-exemptio/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 71
 3 |   title: Initial Owner Reserve Exemption
 4 |   description: The first two account `objects` that are counted towards the `OwnerCount` shall not increase the `Owner Reserve`
 5 |   author: Vet (@xVet)
 6 |   created: 2024-07-01
 7 |   status: Stagnant
 8 |   category: Amendment
 9 | 
10 | 11 | ## Terminology 12 | 13 | The reserve requirement has two parts: 14 | 15 | The `Base Reserve` is a minimum amount of XRP that is required for each address in the ledger. 16 | The `Owner Reserve` is an increase to the reserve requirement for each object that the address owns in the ledger. The cost per item is also called the incremental reserve. 17 | 18 | ## Abstract 19 | 20 | This proposal introduces the general initial owner reserve exemption. 21 | 22 | The first two account `objects` that are counted towards the `OwnerCount` shall not increase the `Owner Reserve`. We enforce the increase of the `Owner Reserve` , if `OwnerCount > 2` , else zero (Free) - For all objects that can be owned by an account e.g Offers, DID, NFTs, Trustlines, Oracles etc. 23 | 24 | ## Motivation 25 | 26 | 1. We do this to allow new created accounts by users / enterprises to be immediately able to use the XRP Ledger without the need initial XRP for reserves to accept an NFT, Loyalty points, Stablecoin, a RWA etc thus signficantly reduces the initial pain point of using the XRP Ledger without compromising the logic behind reserves, if accounts wish to own more objects then the XRPL will enforce just normally the reserve requierments. 27 | 28 | Owning an object on the XRP Ledger is a powerful feature that should be allowed up to the threshold of 2, to be free for any account. 29 | 30 | 2. In order to reduce the `base` and `owner` reserve long term, this step is a good middle ground. Typically, the concern around `account deletion fee` is associated with this topic. With this proposal we can keep this fee stable, as it is the owner reserve, reduce the base reserve long term but also get around the initial pain point of owner reserve funding 31 | 32 | 3. Xahau has the import feature, whereby if you import an activated XRPL account to Xahau, you get also 5 object slots for free. This seems to work well, where the `account deletion fee` on the XRPL is enough friction to prevent spam. 33 | 34 | 4. We already have this owner reserve exemption in a single case, for trustlines or more specific the `SetTrust` transaction, which helped trustlines to be a very popular and poweful feature on the XRPL for any new user. 35 | 36 | ## Implementation example 37 | 38 | XRPAmount const reserveCreate( 39 | (uOwnerCount < 2) ? XRPAmount(beast::zero) 40 | : view().fees().accountReserve(uOwnerCount + 1)); 41 | 42 | Enforcing increase of owner reserve if OwnerCount > 2 in the `SetTrust`transaction 43 | 44 | ## FAQ 45 | 46 | ### A: Can this allow spam attacks ? 47 | 48 | I don't see any attack vector that scales due to only the first two account object threshold, evidence in regards to Trustlines suggests that this is not a concern yet. Worst case, validators increase owner reserves in case spamming is observed, but the account deletion fee should be enough to prevent this. 49 | 50 | ### A: How to roll this exemption out and which transactors should have it ? 51 | 52 | It makes sense to include this exemption over time to as many object creation transaction as possible in case of adoption, while closely monitoring the network. 53 | -------------------------------------------------------------------------------- /XLS-0002-destination-information/reference.js: -------------------------------------------------------------------------------- 1 | function xls2d(uri) { 2 | const cleaned_uri = uri 3 | .replace(/^(.*:.*)?\?/gm, "") 4 | .replace(/\?/gim, "&") 5 | .replace(/^.*?:\/\//, "") 6 | .replace(/^ripple:/gim, ""); 7 | 8 | function clean() { 9 | return cleaned_uri; 10 | } 11 | 12 | function to() { 13 | //NB: this regex is case sensitive to assist in correctly matching XRP ledger addresses 14 | var match = 15 | /(?:(?:^|&)(?:to|TO|tO|To)=|^)([rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,55})/gm.exec( 16 | cleaned_uri, 17 | ); 18 | return match == null ? false : match[1]; 19 | } 20 | 21 | function dt() { 22 | var match = /(?:^|&)dt=([0-9]+)|:([0-9]+)$/gim.exec(cleaned_uri); 23 | if (match != null) return match[1] ? match[1] : match[2]; 24 | return false; 25 | } 26 | 27 | function amount() { 28 | var match = /(?:^|&)am(?:oun)?t=([0-9\.]+)/gim.exec(cleaned_uri); 29 | return match == null ? false : match[1]; 30 | } 31 | 32 | function currency() { 33 | var match = 34 | /(?:^|&)cur(?:rency)?=(?:([rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,55}):)?([A-Z]{3}|[A-Fa-f]{40})/gim.exec( 35 | cleaned_uri, 36 | ); 37 | return match == null 38 | ? false 39 | : { issuer: match[1] ? match[1] : false, currency: match[2] }; 40 | } 41 | 42 | function invoiceid() { 43 | var match = /(?:^|&)inv(?:oice)?(?:id)?=([a-f]{64})/gim.exec(cleaned_uri); 44 | return match == null ? false : match[1]; 45 | } 46 | 47 | return { 48 | uri: uri, 49 | clean: clean(), 50 | to: to(), 51 | dt: dt(), 52 | amount: amount(), 53 | currency: currency(), 54 | invoiceid: invoiceid(), 55 | }; 56 | } 57 | 58 | var examples = [ 59 | "rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY?dt=123", 60 | "rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY:123", 61 | "https://ripple.com//send?to=rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY&amount=30&dt=123", 62 | "https://sub.domain.site.edu.au//send?to=rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY&amount=30&dt=123", 63 | "https://someapp.com/sendXrp?to=rRippleBlah&dt=4&invoiceid=abcdef", 64 | "deposit-xrp?to=blah", 65 | "?rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY:123", 66 | "rAhDr1qUEG4gHXt6m6zyRE4oogDDWmYvcgotCqyyEpArk8", 67 | "to=rAhDr1qUEG4gHXt6m6zyRE4oogDDWmXFdzQdZdH9SJzcNJ", 68 | "to=rAhDr1qUEG4gHXt6m6zyRE4oogDDWmXFdzQdZdH9SJzcNJ¤cy=rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B:USD", 69 | "to=rAhDr1qUEG4gHXt6m6zyRE4oogDDWmXFdzQdZdH9SJzcNJ¤cy=USD", 70 | "to=rAhDr1qUEG4gHXt6m6zyRE4oogDDWmXFdzQdZdH9SJzcNJ¤cy=rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B:USD&invid=DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", 71 | "scheme://uri/folders?rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY:123", 72 | "scheme://uri/folders?rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY&amount=4:123", // this one a bit iffy 73 | "XVLhHMPHU98es4dbozjVtdWzVrDjtV8xvjGQTYPiAx6gwDC", 74 | "to=XVLhHMPHU98es4dbozjVtdWzVrDjtV8xvjGQTYPiAx6gwDC", 75 | "ripple:XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u", 76 | "ripple:XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u:58321", 77 | "ripple:XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u:58321¤cy=rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B:USD", 78 | "ripple:XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u:58321¤cy=XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u:ABC", 79 | "xrpl://XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u", 80 | "xrp://XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u", 81 | "f3w54ygsdfgfserga", 82 | ]; 83 | 84 | for (var i in examples) console.log(xls2d(examples[i])); 85 | -------------------------------------------------------------------------------- /XLS-0055-remit/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 55
 3 |   title: Remit
 4 |   description: Atomic Multi-Asset Payments for XRPL Protocol Chains
 5 |   author: Richard Holland (@RichardAH)
 6 |   created: 2023-12-03
 7 |   status: Final
 8 |   category: Amendment
 9 | 
10 | 11 | ## Introduction 12 | 13 | **_Remit_** is a new payment transactor designed for XRPL Protocol Chains, which allows a sender to send multiple currencies and tokens atomically to a specified destination. It is a push payment that delivers "no matter what" and is designed for retail and Hooks use-cases. 14 | 15 | ## Constraints 16 | 17 | Using _Remit_ the sender may send: 18 | 19 | - one or more Issued Currencies, and/or, 20 | - one or more pre-existing URITokens owned by the sender and/or, 21 | - one new URIToken created in-line as part of the transaction. 22 | 23 | The transactor has the following behaviours: 24 | 25 | - If the destination does not exist, the sender pays to create it. 26 | - If the destination does not have the required trust-lines, the sender pays to create these. 27 | - The exact amounts and tokens are always delivered as specified in the transaction, if validated with code tesSUCCESS. 28 | - The sender pays all transfer fees on currencies, and the fees are not subtracted from the sent amount. 29 | - Where the sender pays to create objects, this is a separate amount not taken from the amounts specified in the transaction. 30 | - The transaction is atomic, either all amounts and tokens are delivered in the exact specified amount or none of them are. 31 | - The transactor does no conversion or pathing, the sender must already have the balances and tokens they wish to send. 32 | - When no amounts or tokens are specified, the transaction may still succeed. This can be used to create an account or ensure an account already exists. 33 | 34 | ## Specification 35 | 36 | A _Remit_ transaction contains the following fields: 37 | Field | Required | Type | Description 38 | -|-|-|- 39 | sfAccount | ✅ |AccountID | The sender 40 | sfDestination | ✅ |AccountID | The recipient. Does not need to exist. Will be created if doesn't exist. 41 | sfDestinationTag | ❌ |UInt32 | May be used by the destination to differentiate sub-accounts. 42 | sfAmounts | ❌|Array of Amounts | Each of the currencies (if any) to send to the destination. Sender must have funded trustlines for each. Destination does not need trustlines. 43 | sfURITokenIDs | ❌| Array of URITokenIDs | Each of the URITokens (if any) to send to the destination. 44 | sfMintURIToken | ❌| URIToken | If included, an inline URIToken to be created and delivered to the destination, for example a receipt. 45 | sfBlob | ❌ | Blob | A hex blob up to 128 kib to supply to a receiving Hook at the destination. 46 | sfInform | ❌ | AccountID | A third party. If supplied, their Hooks will be weakly executed (but the sender will pay for that execution). 47 | sfInvoiceID | ❌ | UInt256 | An arbitrary identifier for this remittance. 48 | 49 | ## Example Transaction 50 | 51 | ``` 52 | { 53 | "TransactionType" : "Remit", 54 | "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 55 | "Destination" : "raod38dcxe6AGWfqxtZjPUfUdyrkWDe7d", 56 | "Amounts" : [ 57 | { 58 | "AmountEntry" : { 59 | "Amount" : "123" 60 | } 61 | }, 62 | { 63 | "AmountEntry" : { 64 | "Amount" : { 65 | "currency" : "USD", 66 | "issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 67 | "value" : "10" 68 | } 69 | } 70 | }, 71 | { 72 | "AmountEntry" : { 73 | "Amount" : { 74 | "currency" : "ABC", 75 | "issuer" : "rpfZurEGaJvpioTaxYpjh1EAtMXFN1HdtB", 76 | "value" : "12" 77 | } 78 | } 79 | } 80 | ], 81 | "URITokenIDs" : [ 82 | "C24DAF43927556F379F7B8616176E57ACEFF1B5D016DC896222603A6DD11CE05", 83 | "5E69C2D692E12D615B5FAC4A41989DA1EA5FD36E8F869B9ECA1121F561E33D2A" 84 | ], 85 | "MintURIToken" : { 86 | "URI" : "68747470733A2F2F736F6D652E757269" 87 | } 88 | } 89 | ``` 90 | 91 | ## Unwanted Remits 92 | 93 | Users who do not want to receive _Remit_ transactions can either set the _asfDisallowIncomingRemit_ on their accounts or install a Hook that will regulate incoming Remits. Alternatively they can simply burn unwanted URITokens or return unwanted Issued Currencies in order to claim the reserve that was paid by the sender. 94 | 95 | ## Chain Requirements 96 | 97 | XLS-55 depends on the adoption of XLS-35 on the applicable chain. 98 | -------------------------------------------------------------------------------- /XLS-0012-secret-numbers/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 12
  3 |   title: Secret Numbers
  4 |   description: Derive XRPL account keypairs based on 8x 6 digits for user-friendly, language-agnostic account secrets
  5 |   author: Wietse Wind , Nik Bougalis (@nbougalis)
  6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/15
  7 |   status: Final
  8 |   category: Ecosystem
  9 |   created: 2020-05-13
 10 | 
11 | 12 | # XLS-12: Secret Numbers 13 | 14 | ### Derive XRPL account keypairs based on 8x 6 digits 15 | 16 | ##### Abstract 17 | 18 | Existing XRPL account secrets are prone to typo's and not that user friendly. Using numbers means the secrets will be language (spoken, written) agnostic. Existing secrets (family seed, mnemonic) may be intimidating for the public that's relatively new to cryptocurrencies / blockchain technology & very prone to user error (eg. when writing down or reading). 19 | 20 | ## Background 21 | 22 | The common formats for XRPL account secrets are (at the time of writing the first (TypeScript) [Secret Numbers implementation](https://github.com/WietseWind/xrpl-secret-numbers), July 2019): 23 | 24 | - Family Seed, eg. `sh1HiK7SwjS1VxFdXi7qeMHRedrYX` 25 | - Mnemonic, eg. `car banana apple road ...` 26 | 27 | Both secrets contain characters that can be easily confuesed or misread. A range of numbers (0-9) is easier to write down (alphabet of just 10 defferent chars (numbers)). 28 | 29 | The Secret Numbers encoding method can be used on HEX private keys as well. As HEX private keys are hardly being used & for the sake of offering backwards compatibility for generated Secret Numbers, I propose this to be used with the entropy that can be used for ripple-keypairs as well. 30 | 31 | ## Secret Numbers 32 | 33 | A secret based on the Secret Numbers standard contains 8 blocks of 6 digits: the first five digits are int representations of the entropy, the 6th digit is a checksum based on the five preceding digits + the block number. 34 | 35 | A secret now looks like: 36 | 37 | ``` 38 | 554872 394230 209376 323698 140250 387423 652803 258676 39 | ``` 40 | 41 | A block indicator can be added to make it easier for users to enter the right block: 42 | 43 | ``` 44 | A. 554872 45 | B. 394230 46 | ... 47 | H. 258676 48 | ``` 49 | 50 | The first five digits are the decimal representation of a chunk of the entropy buffer, and will be in the range of `0 - 65535` (`0000 - FFFF`). 51 | 52 | ## Compatibility 53 | 54 | Secret Numbers can be used as entropy-param for eg. the `generateSeed` method of [ripple-keypairs](https://github.com/ripple/ripple-keypairs). This means a secret based on the Secret Numbers standard can always be turned in a family seed for backwards compatibility with existing XRPL clients. 55 | 56 | ## Encoding / Decoding 57 | 58 | - Secret Numbers contain 6 digits in the range of 0-9 per position. 59 | - Secret numbers contain 5 digits (int) entropy and a 6th checksum. 60 | - The checksum digit is based on the 5 digits entropy and the block position. This way a block of 6 digits entered at the wrong position (A-H) can be detected as being invalid. 61 | - A "Secret Number"-chunk should be represented as a string containing six digits, as there can be leading zeroes. 62 | 63 | ### Calculating 64 | 65 | Position is the block number starting at zero (0 - 7). The position then multiplied & incremented (`* 2 + 1`) to make sure it results in an odd number. 66 | 67 | ``` 68 | calculateChecksum(position: number, value: number): number { 69 | return value * (position * 2 + 1) % 9 70 | } 71 | ``` 72 | 73 | ##### Samples 74 | 75 | | HEX | Decimal | Block # | Calculation | Checksum | Result | 76 | | ---- | ------- | ------- | ------------------------- | -------- | ------ | 77 | | AF71 | 44913 | 0 | `44913 * (0 * 2 + 1) % 9` | 3 | 449133 | 78 | | 0000 | 0 | 2 | ` 0 * (2 * 2 + 1) % 9` | 0 | 000000 | 79 | | FFFF | 65535 | 3 | `65535 * (3 * 2 + 1) % 9` | 6 | 655356 | 80 | | FFFF | 65535 | 4 | `65535 * (4 * 2 + 1) % 9` | 0 | 655350 | 81 | | CD91 | 52625 | 7 | `52625 * (7 * 2 + 1) % 9` | 3 | 526253 | 82 | 83 | ## Implementations 84 | 85 | - TypeScript [![npm version](https://badge.fury.io/js/xrpl-secret-numbers.svg)](https://www.npmjs.com/xrpl-secret-numbers) - Github: https://github.com/WietseWind/xrpl-secret-numbers 86 | 87 | ## Representations 88 | 89 | #### String 90 | 91 | ``` 92 | 554872 394230 <...> 258676 93 | ``` 94 | 95 | #### Human Readable & entry 96 | 97 | ``` 98 | A. 554872 99 | B. 394230 100 | ... 101 | H. 258676 102 | ``` 103 | 104 | #### QR Codes 105 | 106 | ``` 107 | xrplsn:554872394230<...>258676 108 | ``` 109 | -------------------------------------------------------------------------------- /XLS-0037-concise-transaction-identifier-ctid/ctid.php: -------------------------------------------------------------------------------- 1 | 0xFFFFFFF || $ledger_seq < 0) 8 | throw new Exception("ledger_seq must not be greater than 268435455 or less than 0."); 9 | 10 | if (!is_numeric($txn_index)) 11 | throw new Exception("txn_index must be a number."); 12 | if ($txn_index > 0xFFFF || $txn_index < 0) 13 | throw new Exception("txn_index must not be greater than 65535 or less than 0."); 14 | 15 | if (!is_numeric($network_id)) 16 | throw new Exception("network_id must be a number."); 17 | if ($network_id > 0xFFFF || $network_id < 0) 18 | throw new Exception("network_id must not be greater than 65535 or less than 0."); 19 | 20 | $ledger_part = dechex($ledger_seq); 21 | $txn_part = dechex($txn_index); 22 | $network_part = dechex($network_id); 23 | 24 | if (strlen($ledger_part) < 7) 25 | $ledger_part = str_repeat("0", 7 - strlen($ledger_part)) . $ledger_part; 26 | if (strlen($txn_part) < 4) 27 | $txn_part = str_repeat("0", 4 - strlen($txn_part)) . $txn_part; 28 | if (strlen($network_part) < 4) 29 | $network_part = str_repeat("0", 4 - strlen($network_part)) . $network_part; 30 | 31 | return strtoupper("C" . $ledger_part . $txn_part . $network_part); 32 | } 33 | 34 | function decodeCTID($ctid) 35 | { 36 | if (is_string($ctid)) 37 | { 38 | if (!ctype_xdigit($ctid)) 39 | throw new Exception("ctid must be a hexadecimal string"); 40 | if (strlen($ctid) !== 16) 41 | throw new Exception("ctid must be exactly 16 nibbles and start with a C"); 42 | } else 43 | throw new Exception("ctid must be a hexadecimal string"); 44 | 45 | if (substr($ctid, 0, 1) !== 'C') 46 | throw new Exception("ctid must be exactly 16 nibbles and start with a C"); 47 | 48 | $ledger_seq = substr($ctid, 1, 7); 49 | $txn_index = substr($ctid, 8, 4); 50 | $network_id = substr($ctid, 12, 4); 51 | return array( 52 | "ledger_seq" => hexdec($ledger_seq), 53 | "txn_index" => hexdec($txn_index), 54 | "network_id" => hexdec($network_id) 55 | ); 56 | } 57 | 58 | // NOTE TO DEVELOPER: 59 | // you only need the two functions above, below are test cases, if you want them. 60 | 61 | print("Running tests...\n"); 62 | 63 | function assert_test($x) 64 | { 65 | if (!$x) 66 | echo "test failed!\n"; 67 | else 68 | echo "test passed\n"; 69 | } 70 | 71 | // Test case 1: Valid input values 72 | assert_test(encodeCTID(0xFFFFFFF, 0xFFFF, 0xFFFF) == "CFFFFFFFFFFFFFFF"); 73 | assert_test(encodeCTID(0, 0, 0) == "C000000000000000"); 74 | assert_test(encodeCTID(1, 2, 3) == "C000000100020003"); 75 | assert_test(encodeCTID(13249191, 12911, 49221) == "C0CA2AA7326FC045"); 76 | 77 | // Test case 2: ledger_seq greater than 0xFFFFFFF 78 | try { 79 | encodeCTID(0x10000000, 0xFFFF, 0xFFFF); 80 | assert_test(false); 81 | } catch (Exception $e) { 82 | assert_test(strcmp($e->getMessage(), "ledger_seq must not be greater than 268435455 or less than 0.") == 0); 83 | } 84 | try { 85 | encodeCTID(-1, 0xFFFF, 0xFFFF); 86 | assert_test(false); 87 | } catch (Exception $e) { 88 | assert_test(strcmp($e->getMessage(), "ledger_seq must not be greater than 268435455 or less than 0.") == 0); 89 | } 90 | 91 | // Test case 3: txn_index greater than 0xFFFF 92 | try { 93 | encodeCTID(0xFFFFFFF, 0x10000, 0xFFFF); 94 | assert_test(false); 95 | } catch (Exception $e) { 96 | assert_test(strcmp($e->getMessage(), "txn_index must not be greater than 65535 or less than 0.") == 0); 97 | } 98 | try { 99 | encodeCTID(0xFFFFFFF, -1, 0xFFFF); 100 | assert_test(false); 101 | } catch (Exception $e) { 102 | assert_test(strcmp($e->getMessage(), "txn_index must not be greater than 65535 or less than 0.") == 0); 103 | } 104 | 105 | // Test case 4: network_id greater than 0xFFFF 106 | try { 107 | encodeCTID(0xFFFFFFF, 0xFFFF, 0x10000); 108 | assert_test(false); 109 | } catch (Exception $e) { 110 | assert_test(strcmp($e->getMessage(), "network_id must not be greater than 65535 or less than 0.") == 0); 111 | } 112 | try { 113 | encodeCTID(0xFFFFFFF, 0xFFFF, -1); 114 | assert_test(false); 115 | } catch (Exception $e) { 116 | assert_test(strcmp($e->getMessage(), "network_id must not be greater than 65535 or less than 0.") == 0); 117 | } 118 | 119 | // Test case 5: Valid input values 120 | assert_test(decodeCTID("CFFFFFFFFFFFFFFF") == array("ledger_seq" => 0xFFFFFFF, "txn_index" => 0xFFFF, "network_id" => 0xFFFF)); 121 | assert_test(decodeCTID("C000000000000000") == array("ledger_seq" => 0, "txn_index" => 0, "network_id" => 0)); 122 | assert_test(decodeCTID("C000000100020003") == array("ledger_seq" =>1, "txn_index" => 2, "network_id" => 3)); 123 | assert_test(decodeCTID("C0CA2AA7326FC045") == array("ledger_seq" =>13249191, "txn_index" => 12911, "network_id" => 49221)); 124 | 125 | 126 | print("Done!\n"); 127 | 128 | ?> 129 | -------------------------------------------------------------------------------- /XLS-0046-dynamic-non-fungible-tokens/README.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: 46
 3 |   title: Dynamic Non Fungible Tokens (dNFTs)
 4 |   description: Support for XLS-20 NFTs to modify and upgrade token properties as mutable NFTs
 5 |   author: Vet (@xVet), Mayukha Vadari (@mvadari), TeQu (@tequdev)
 6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/130
 7 |   status: Final
 8 |   category: Ecosystem
 9 |   requires: [XLS-20](../XLS-0020-non-fungible-tokens/README.md)
10 |   created: 2023-08-18
11 | 
12 | 13 | ## Abstract 14 | 15 | This proposal aims to provide support for XLS-20 NFTs to modify/upgrade token properties. 16 | 17 | [XLS-20](link to spec) provides [Non-Fungible Token](link to docs) support, these tokens are immutable and don’t allow any changes. Currently NFTs can’t be modified, resulting often in new mints, which leads to ledger bloat that eat valuable resources as well as experimental approaches to use website endpoints to mimic dynamic abilities. 18 | 19 | Apart from the use of immutable NFTs, there is a wide range of use cases around dNFTs (Dynamic Non-Fungible Tokens) which are considered mutable NFTs. The goal is to provide both options to developers and users to cover all aspects of non fungibility, while choosing the least invasive approach to achieve this functionality. 20 | 21 | ## Motivation 22 | 23 | Usually, NFTs are typically static in nature. A static NFT refers to an NFT that possesses unchanging and immutable characteristics stored on the blockchain, rendering them unmodifiable. These static NFTs encompass various forms like images, videos, GIF files, music, and unlockable components. For instance, an illustration of a basketball player making a shot into a hoop serves as an example of a static NFT. 24 | 25 | On the other hand, `dynamic NFTs`, often abbreviated as dNFTs, represent the next phase in the evolution of the NFT landscape. These `dynamic NFTs` seamlessly integrate the inherent uniqueness of NFTs with the inclusion of dynamic data inputs. These inputs can arise from calculations conducted either on the blockchain or off-chain. 26 | 27 | Oracles could supply dynamic real-world data to NFTs. To illustrate, a `dynamic NFT` might showcase real-time updates of a basketball player's performance statistics as they actively play. 28 | 29 | ## Specification 30 | 31 | ### 3. New Transactors and Flags 32 | 33 | ### We will specify the following: 34 | 35 | New Transactor 36 | - NFTokenModify 37 | 38 | New Flags 39 | - tfMutable 40 | 41 | ### 3.1 tfMutable 42 | 43 | New flags for `NFTokenMint`: 44 | 45 | | Flag Name | Flag Value | Description | 46 | | ----------- | :----------: | :---------------------------------------------------------------------: | 47 | | `tfMutable` | `0x00000010` | `Allow issuer (or an entity authorized by the issuer) to modify “URI”.` | 48 | 49 | ### 3.2 NFTokenModify 50 | 51 | `NFTokenModify` is used to modify the URI property of a NFT: 52 | 53 | Transaction-specific Fields 54 | 55 | | Field Name | Required? | JSON Type | Internal Type | 56 | | ----------------- | :-------: | :-------: | :-----------: | 57 | | `TransactionType` | `✔️` | `string` | `UINT16` | 58 | 59 | Indicates the `account` which is owning the NFT, in case of `Owner` not specified, it's implied that the submitting `account` is also the `Owner` of the NFT. 60 | 61 | | Field Name | Required? | JSON Type | Internal Type | 62 | | ---------- | :-------: | :-------: | :-----------: | 63 | | `Owner` | | `string` | `ACCOUNT ID` | 64 | 65 | Indicates the `NFToken` object to be modified. 66 | 67 | | Field Name | Required? | JSON Type | Internal Type | 68 | | ----------- | :-------: | :-------: | :-----------: | 69 | | `NFTokenID` | `✔️` | `string` | `UINT256` | 70 | 71 | The new `URI` that points to data and/or metadata associated with the NFT. 72 | If a `URI` is omitted then the corresponding `URI` record in the XRP ledger, if present, is removed. 73 | 74 | | Field Name | Required? | JSON Type | Internal Type | 75 | | ---------- | :-------: | :-------: | :-----------: | 76 | | `URI` | | `string` | `BLOB` | 77 | 78 | Example (modify URI): 79 | 80 | { 81 | "TransactionType": "NFTokenModify", 82 | "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 83 | "Owner": "rogue5HnPRSszD9CWGSUz8UGHMVwSSKF6", 84 | "Fee": "10", 85 | "Sequence": 33, 86 | “NFTokenID”: “0008C350C182B4F213B82CCFA4C6F59AD76F0AFCFBDF04D5A048C0A300000007", 87 | "URI": "697066733A2F2F62616679626569636D6E73347A736F6C686C6976346C746D6E356B697062776373637134616C70736D6C6179696970666B73746B736D3472746B652F5665742E706E67", 88 | 89 | ... 90 | 91 | } 92 | 93 | If `tfMutable` is not set, executing NFTokenModify should fail! 94 | 95 | If `tfMutable` is set, executing NFTokenModify should fail when neither `Issuer` or an `account` authorized via `NFTokenMinter`, according to the specific flag, is executing the transaction. 96 | 97 | This approach takes into consideration that `NFToken Flags` are part of the `NFTokenID`, mutating anything that is part of the `NFTokenID` must be avoided. 98 | -------------------------------------------------------------------------------- /XLS_TEMPLATE.md: -------------------------------------------------------------------------------- 1 |
 2 |   xls: [XLS number]
 3 |   title: [The title is a few words, not a complete sentence]
 4 |   description: [Description is one full (short) sentence]
 5 |   implementation: [optional - link to rippled PR for Amendment/System XLSes]
 6 |   author: [a comma separated list of the author(s) with email addresses]
 7 |   category: [Amendment | System | Ecosystem | Meta]
 8 |   status: [Draft | Final | Living | Deprecated | Stagnant | Withdrawn]
 9 |   proposal-from: [link to XRPL-Standards Proposal discussion where this XLS was proposed]
10 |   requires: [optional - XLS number(s) if this depends on other features]
11 |   created: YYYY-MM-DD
12 |   updated: YYYY-MM-DD
13 | 
14 | 15 | > **Note:** This is the suggested template for new XLS specifications. After you have filled in the requisite fields, please delete the guidance text (italicized bracketed instructions). 16 | 17 | _[The requirements to sections depend on the type of proposal. For example, amendments require some information that may not be relevant for other kinds of proposals. Please adapt the template as appropriate.]_ 18 | 19 | _[The title should be 44 characters or less. The title should NOT include "XLS" prefix or the XLS number.]_ 20 | 21 | _[For Proposals (pre-Draft stage), the title must include the category prefix (e.g., "Meta XLS: XLS Process and Guidelines").]_ 22 | 23 | # [Title] 24 | 25 | ## 1. Abstract 26 | 27 | _[Abstract is a multi-sentence (short paragraph) technical summary. This should be a very terse and human-readable version of the specification section. Someone should be able to read only the abstract to get the gist of what this specification does.]_ 28 | 29 | ## 2. Motivation _(Optional)_ 30 | 31 | _[A motivation section is critical for XLSes that want to change the protocol. It should clearly explain why the existing protocol specification is inadequate to address the problem that the XLS solves. This section may be omitted if the motivation is evident.]_ 32 | 33 | ## 3. Specification 34 | 35 | _[The technical specification should describe the syntax and semantics of any new feature. The specification should be detailed enough to allow competing, interoperable implementations.]_ 36 | 37 | > **Note:** It is recommended to follow RFC 2119 and RFC 8174. Do not remove the key word definitions if RFC 2119 and RFC 8174 are followed. 38 | 39 | _[For Amendment XLSes, use the [AMENDMENT_TEMPLATE.md](AMENDMENT_TEMPLATE.md) to structure this section with detailed subsections for Serialized Types, Ledger Entries, Transactions, Permissions, and RPCs as needed.]_ 40 | 41 | _[For other XLS types, provide a clear technical specification.]_ 42 | 43 | ## 4. Rationale 44 | 45 | _[The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. The rationale should discuss important objections or concerns raised during discussion around the XLS.]_ 46 | 47 | ## 5. Backwards Compatibility _(Optional)_ 48 | 49 | _[All XLSes that introduce backwards incompatibilities must include a section describing these incompatibilities and their consequences. The XLS must explain how the author proposes to deal with these incompatibilities. This section may be omitted if the proposal does not introduce any backwards incompatibilities, but this section must be included if backward incompatibilities exist.]_ 50 | 51 | ## 6. Test Plan _(Optional)_ 52 | 53 | _[A description of the process to test the feature the authors are proposing. The test plan may be either inlined in the XLS file or included in the `../xls-###/` directory, where `###` refers to the XLS number. An implementation test plan is mandatory for XLSes that affect rippled. This section may be omitted for Ecosystem and Meta proposals.]_ 54 | 55 | ## 7. Reference Implementation _(Optional)_ 56 | 57 | _[An optional section that contains a reference/example implementation that people can use to assist in understanding or implementing this specification. For an Amendment/System XLS, a `Final` XLS must include a link to the rippled PR(s)/commit(s).]_ 58 | 59 | ## 8. Security Considerations 60 | 61 | _[All XLSes must contain a section that discusses the security implications/considerations relevant to the proposed change. Include information that might be important for security discussions, surfaces risks, and can be used throughout the life-cycle of the proposal. E.g. include security-relevant design decisions, concerns, important discussions, implementation-specific guidance and pitfalls, an outline of threats and risks and how they are being addressed. XLS submissions missing the `Security Considerations` section will be rejected. An XLS cannot proceed to status `Final` without a `Security Considerations` discussion deemed sufficient by the reviewers.]_ 62 | 63 | # Appendix _(Optional)_ 64 | 65 | ## Appendix A: FAQ _(Optional)_ 66 | 67 | _[A list of questions the author expects to be asked about the spec, and their answers. It is highly recommended but not required to include this section, to make it easier for spec readers to understand it.]_ 68 | 69 | ### A.1: [Question] 70 | 71 | _[Answer to the question]_ 72 | -------------------------------------------------------------------------------- /XLS-0061-cross-currency-nftokenacceptof/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 61
  3 |   title: CrossCurrency NFTokenAcceptOffer
  4 |   description: Allow cross-currency NFToken transactions using multiple currencies
  5 |   author: tequ (@tequdev)
  6 |   created: 2024-02-26
  7 |   status: Stagnant
  8 |   category: Amendment
  9 | 
10 | 11 | # Abstract 12 | 13 | XRPL's NFToken functionality, currently implemented as XLS-20, only allows the use of a single currency for transactions. 14 | 15 | This proposal allows cross-currency NFToken transactions using multiple currencies. 16 | 17 | Creators and marketplaces will be able to sell NFTs without being tied to a currency, and users will be able to buy NFTs without being tied to a currency. 18 | 19 | This will increase revenue opportunities for creators and marketplaces and create significant tokenomics. 20 | 21 | # Specification 22 | 23 | ## `NFTokenAcceptOffer` Transaction 24 | 25 | Add the following field: 26 | 27 | | Field Name | Required? | JSON Type | Internal Type | 28 | | ---------- | :-------: | :------------------: | :-----------: | 29 | | `Amount` | | `object` or `string` | `AMOUNT` | 30 | 31 | ### `Amount` Field 32 | 33 | When accepting an NFTokenOffer with the tfSellOffer flag set, the `Amount` field acts like the `SendMax` field, and when accepting an NFTokenOffer without tfSellOffer set, the `DeliverMin` field acts like the `Amount` field. 34 | 35 | It doesn't have to match the `Amount` of the Offeror specified in `NFTokenSellOffer` or `NFTokenBuyOffer`. 36 | 37 | In broker mode, the `Amount` field must not be present. 38 | 39 | ### `NFTokenBrokerFee` Field 40 | 41 | The existing `NFTokenBrokerFee` field can be any currency amount. 42 | 43 | ### CrossCurrency Accept 44 | 45 | The currency of `Amount` can be different from the currency of the BuyOffer / SellOffer. 46 | The currency of `NFTokenBrokerFee` can be different from the currency of the BuyOffer / SellOffer. 47 | 48 | Buyer must specify a sufficient amount that can be transferred to Seller, nftoken issuer(nftoken transfer fee), broker(broker fee), token issuer(token transfer fee). 49 | 50 | NFToken transfer fees are sent in the buyer's currency. 51 | NFToken with the tfOnlyXRP flag set will be paid in XRP. 52 | 53 | If there was insufficient amount or liquidity, the transaction will fail. 54 | 55 | ## `NFTokenCreateOffer` Transaction 56 | 57 | No change in fields. 58 | 59 | Change to only check if the NFToken issuer has a trustline for the currenfy of `Amount` field when creating a BuyOffer for an NFToken with royalties. 60 | 61 | ## The `NFTokenPage` Object 62 | 63 | No changes. 64 | 65 | ## The `NFTokenOffer` Object 66 | 67 | No changes. 68 | 69 | # Cases 70 | 71 | ## `Amount` Field not specified in `NFTokenAcceptOffer` Transaction 72 | 73 | According to XLS-20 specifications. Except for NFTs with royalties, issuer's trust line [must be checked](https://github.com/XRPLF/rippled/issues/4925). 74 | 75 | ## `Amount` Field specified and equal to `Amount` in `NFTokenOffer` 76 | 77 | DEX liquidity will not be used and existing transfer processing will be used. 78 | If the quantity in the `Amount` cannot be sent to the seller, the transaction will fail. 79 | 80 | ## `Amount` Field specified and not same as `Amount` in `NFTokenOffer` 81 | 82 | DEX liquidity will be used and other transfer processing (used in Payment/CheckCash) will be used. 83 | If the quantity in `Amount` cannot be sent to the seller, the transaction will fail. 84 | 85 | If the buyer's `Amount` can send more than the specified amount of the seller's `Amount`, the amount will be sent up to the maximum amount of the buyer's `Amount`. 86 | 87 | ## `NFTokenBrokerFee` Field specified 88 | 89 | Convert and send the amount from the buyer's `Amount` to satisfy the `NFTokenBrokerFee`. 90 | 91 | ## Accept NFToken with TransferFee 92 | 93 | Paid in the same currency as the buyer's `Amount`. 94 | If no trustline is set, the transaction will fail. 95 | 96 | ## Accept NFToken with TransferFee, `tfOnlyXRP`, `Amount` Field specified, `NFTokenBrokerFee` Field specified 97 | 98 | Up to 3 times currency will be converted and up to 3 times token transfer fees will be charged. 99 | 100 | 1. From Buyer to Broker (`NFTokenBrokerFee` currency, Buyer's `Amount` currency fee) 101 | 1. From Buyer to Issuer (`TransferFee` currency or XRP, Buyer's `Amount` currency fee) 102 | 1. From Buyer to Seller (`Amount` currency, Buyer's `Amount` currency fee) 103 | 104 | # Concern 105 | 106 | ## Transaction load 107 | 108 | Processing load is expected to increase due to up to three cross-currency remittance processes (flows) within a single transaction. 109 | 110 | However, instead of merely being concerned about the increased load of a single transaction, it should be noted that in the current, similar processing would have to be spread over two or more transactions, NFTokenAccept and CrossCurrency Payment. 111 | 112 | ## Royalty for NFToken with tfOnlyFlag 113 | 114 | The royalty is sent by converting a portion of the buyer's Amount (TransferFee %) into XRP, so if there is not enough liquidity, the royalty might be almost zero. 115 | 116 | # Note 117 | 118 | The process used by Payment is used for currency conversion and remittance processing. 119 | Path cannot be specified, and only direct AAA->BBB pairs are used at this time. 120 | 121 | If auto-bridge is enabled by default by XLS-60d, the auto-bridge path will also be used for the currency conversion process in this proposal. 122 | -------------------------------------------------------------------------------- /XLS-0041-xpop/README.md: -------------------------------------------------------------------------------- 1 |
  2 |     xls: 41
  3 |     title: XRPL Proof of Payment Standard (XPOP)
  4 |     author: Richard Holland (@RichardAH)
  5 | 	description: An offline non-interactive cryptographic proof that a transaction was successfully submitted to the XRP Ledger and what its impact (metadata) was
  6 |     created: 2023-05-04
  7 |     status: Final
  8 |     category: Ecosystem
  9 | 
10 | 11 | # XLS-41d 12 | 13 | # Abstract 14 | 15 | An XRPL Proof of Payment (XPOP) is an offline non-interactive cryptographic proof that a transaction was successfully submitted to the XRP Ledger and what its impact (metadata) was. 16 | 17 | # Background 18 | 19 | The XRPL is comprised of a chain of blocks (Ledgers) co-operatively and deterministically computed, shared and subsequently signed (validated) by a quorum of rippled nodes (validators) operating in a socially trusted group known as a Unique Node List (UNL). The UNL is typically published by a trusted third party in a format known as a Validator List (VL). (Examples: https://vl.xrplf.com, https://vl.ripple.com). Each VL is cryptographically signed by a master publishing key. Users of the network ultimately trust this publisher (key) to choose appropriate validators for the UNL that will co-operate to make forward progress and not conspire to defraud them. 20 | 21 | # Proof 22 | 23 | Each VL contains a list of validators signed for by the VL publisher key. 24 | 25 | Each validator is a key that signs validation messages over each Ledger header. 26 | 27 | Each Ledger header contains the root hash of two patricia merkle tries: 28 | 29 | - the current (”account”) state for the ledger, and 30 | - the transactions and their metadata in that Ledger. 31 | 32 | A quorum (from a given VL) of signed validation messages proves a Ledger was correctly closed and became part of the block chain. 33 | 34 | Thus if one trusts the VL publisher key, then one can form a complete chain of validation from the VL key down to a transaction and its meta data without connectivity to the internet. This is an XPOP. 35 | 36 | # Format 37 | 38 | XPOPs are a JSON object with the following schema: 39 | 40 | ``` 41 | { 42 | "ledger": { 43 | "acroot": merkle root of account state map | hex string, 44 | "txroot": merkle root of transaction map | hex string , 45 | "close": close time | int, 46 | "coins": total drops | int or string, 47 | "cres": close time resolution | int, 48 | "flags": ledger flags | int, 49 | "pclose": parent close time | int, 50 | "phash": parent hash | hex string, 51 | }, 52 | "transaction": { 53 | "blob": serialized transaction | hex string, 54 | "meta": serialized metadata | hex string, 55 | "proof": 56 | }, 57 | "validation": { 58 | "data": { 59 | validator pub key or master key | base58 string : serialized validation message | hex string 60 | ... for each validation message 61 | }, 62 | "unl": { 63 | "blob": base64 encoded VL blob as appearing on vl site | base64 string, 64 | ... remaining fields from vl site 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | The `proof` key inside `transaction` section has one of two possible forms: 71 | 72 | 1. List form: 73 | 74 | In this form the merkle proof is a list of lists (and strings) containing the minimum number of entries to form the merkle proof. Each list contains 16 entries (branches `0` through `F`). For each branch either the the root hash of that branch is provided as a 64 hex nibble string, or a further list of 16 entries is provided, until the transaction that the proof proves is reached. If a list contains fewer than 16 entries then the verifier should infer that the remaining entries are all null entries (hash strings that are sequences of 0s). 75 | 76 | ``` 77 | # list form 78 | "proof": [ 79 | hex string for branch '0', 80 | ... 81 | hex string for branch 'A', 82 | # branch 'B' is a list 83 | [ 84 | hex string for branch 'B0', 85 | ... , 86 | hex string for branch 'BE', 87 | # branch 'BF' is a list 88 | [ 89 | hex string for branch 'BF0', 90 | ... 91 | ] 92 | ], 93 | hex string for branch 'C', 94 | ... 95 | ] 96 | 97 | ``` 98 | 99 | 1. Tree form: 100 | 101 | In this form the merkle proof is an object of objects containing the entire transaction map for the ledger. This form is useful if many XPOPs must be generated for the same ledger and the size of each individual XPOP is less relevant than the amount of work to make and store the XPOPs. Each object contains three keys: `children`, `hash`, `key`. 102 | - The `children` key is always either an empty object or is keyed with only the branches which actually exist there, each as a single hex nibble `0` - `F`. 103 | - The `hash` key is always a 64 nibble hex string: either the hash over the children (with appropriate namespace) or, if a leaf node, the hash over the node (with appropriate namespace). 104 | - The `key` key is always a 64 nibble hex string: the keylet (index) of the object at this location. 105 | 106 | ``` 107 | # tree form 108 | "proof": { 109 | "hash" : hex string, 110 | "key" : hex string, 111 | "children" : { 112 | "0": 113 | { 114 | "children": {}, 115 | "hash": hex string, 116 | "key": hex string 117 | }, 118 | ... 119 | "F": 120 | { ...} 121 | } 122 | } 123 | ``` 124 | 125 | # Verifying 126 | 127 | See reference implementation at: [xpop-verifier-py](https://github.com/RichardAH/xpop-verifier-py/blob/main/verify.py) 128 | -------------------------------------------------------------------------------- /XLS-0051-nftoken-escrow/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 51d
  3 |   title: NFToken Escrows
  4 |   author: Mayukha Vadari (@mvadari)
  5 |   created: 2023-11-17
  6 |   status: Stagnant
  7 |   category: Amendment
  8 | 
9 | 10 | # NFToken Escrows 11 | 12 | ## Abstract 13 | 14 | The XRP Ledger currently only supports escrows for one type of token: XRP. Now that XLS-20 is live on the network and there are almost 4 million NFTs on the ledger, users may want to also be able to put their NFTs in an escrow, just as they would with their funds. 15 | 16 | ## 1. Overview 17 | 18 | This spec proposes modifications to one existing on-ledger object and one existing transaction: 19 | 20 | - `Escrow` ledger object 21 | - `EscrowCreate` transaction 22 | 23 | This change will require an amendment, tentatively called `featureNFTokenEscrow`. 24 | 25 | ## 2. On-Ledger Object: `Escrow` 26 | 27 | The [`Escrow` object](https://xrpl.org/escrow-object.html) already exists on the XRPL. We propose a slight modification to support NFT escrows. 28 | 29 | ### 2.1. Fields 30 | 31 | As a reference, these are the existing escrow object fields. 32 | 33 | | Field Name | Required? | JSON Type | Internal Type | 34 | | ------------------------ | --------- | --------- | ------------- | 35 | | `LedgerIndex` | ✔️ | `string` | `HASH256` | 36 | | `LedgerEntryType` | ✔️ | `string` | `UINT16` | 37 | | `Owner` | ✔️ | `string` | `AccountID` | 38 | | `OwnerNode` | ✔️ | `string` | `UInt32` | 39 | | `Amount` | ✔️ | `Amount` | `Amount` | 40 | | `Condition` | | `string` | `Blob` | 41 | | `CancelAfter` | | `number` | `UInt32` | 42 | | `FinishAfter` | | `number` | `UInt32` | 43 | | `Destination` | ✔️ | `string` | `AccountID` | 44 | | `DestinationNode` | ✔️ | `string` | `UInt32` | 45 | | `DestinationTag` | | `number` | `UInt32` | 46 | | `PreviousTxnId` | ✔️ | `string` | `HASH256` | 47 | | `PreviousLedgerSequence` | ✔️ | `number` | `UInt32` | 48 | | `SourceTag` | | `number` | `UInt32` | 49 | 50 | We propose these modifications: 51 | 52 | | Field Name | Required? | JSON Type | Internal Type | 53 | | ---------- | --------- | --------- | ------------- | 54 | | `Amount` | | `Amount` | `Amount` | 55 | | `NFTokens` | | `array` | `STArray` | 56 | 57 | #### 2.1.1. `Amount` 58 | 59 | The `Amount` field is still used as it currently is, but it is now optional, to support escrows that only hold NFTs. An `Escrow` object must have an `Amount` field and/or an `NFTokens` field. 60 | 61 | #### 2.1.2. `NFTokens` 62 | 63 | The NFTs that are in the escrow. One escrow can hold up to 32 NFTs, equivalent to one `NFTokenPage`. 64 | 65 | NFTs stored in an escrow cannot be burned (and cannot be modified either, if [XLS-46](https://github.com/XRPLF/XRPL-Standards/discussions/130) is enabled). 66 | 67 | ## 3. Transaction: `EscrowCreate` 68 | 69 | The [`EscrowCreate` transaction](https://xrpl.org/escrowcreate.html) already exists on the XRPL. We propose a slight modification to support NFT escrows. 70 | 71 | ### 3.1. Fields 72 | 73 | As a reference, these are the existing `EscrowCreate` fields: 74 | 75 | | Field Name | Required? | JSON Type | Internal Type | 76 | | ----------------- | --------- | --------- | ------------- | 77 | | `TransactionType` | ✔️ | `string` | `UInt16` | 78 | | `Account` | ✔️ | `string` | `AccountID` | 79 | | `Amount` | ✔️ | `Amount` | `Amount` | 80 | | `Destination` | ✔️ | `string` | `AccountID` | 81 | | `Condition` | | `string` | `Blob` | 82 | | `CancelAfter` | | `number` | `UInt32` | 83 | | `FinishAfter` | | `number` | `UInt32` | 84 | | `SourceTag` | | `number` | `UInt32` | 85 | | `DestinationTag` | | `number` | `UInt32` | 86 | 87 | We propose these modifications: 88 | | Field Name | Required? | JSON Type | Internal Type | 89 | |------------|-----------|-----------|---------------| 90 | |`Amount`| |`Amount`|`Amount`| 91 | |`NFTokenIDs`| |`array`|`STArray`| 92 | 93 | #### 3.1.1. `Amount` 94 | 95 | The `Amount` field is still used as it currently is, but it is now optional, to support escrows that only hold NFTs. An `EscrowCreate` transaction must have an `Amount` field and/or an `NFTokens` field. 96 | 97 | #### 3.1.2. `NFTokenIDs` 98 | 99 | This field contains the `NFTokenID`s of the `NFToken`s that the user wants to put in an escrow. There can be up to 32 NFTs in an escrow. 100 | 101 | # Appendix 102 | 103 | ## Appendix A: FAQ 104 | 105 | ### A.1: Why not use a separate `NFTokenEscrow` object? 106 | 107 | That felt like an unnecessary complication and would involve a lot of repeated code. 108 | 109 | [//]: # "Also, I didn't want to rewrite out every part of the current escrow implementation in the spec" 110 | 111 | ### A.2: Can I put XRP and NFTs in the same escrow? 112 | 113 | Yes. 114 | 115 | ### A.3: Why is this so much simpler than for issued currency escrows? 116 | 117 | The complication with issued currencies is that they must be held by accounts in [trustlines](https://xrpl.org/trust-lines-and-issuing.html). There is currently no way for a ledger object (such as an escrow) to own another ledger object (such as a trustline). 118 | 119 | NFTs have no such requirement - all that defines an NFT is the `NFTokenID` and the `URI`, which can easily be held by an object instead of an account. 120 | -------------------------------------------------------------------------------- /XLS-0037-concise-transaction-identifier-ctid/ctid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | std::optional 13 | encodeCTID(uint32_t ledger_seq, uint16_t txn_index, uint16_t network_id) noexcept 14 | { 15 | if (ledger_seq > 0xFFFFFFF) 16 | return {}; 17 | 18 | uint64_t ctidValue = 19 | ((0xC0000000ULL + static_cast(ledger_seq)) << 32) + 20 | (static_cast(txn_index) << 16) + network_id; 21 | 22 | std::stringstream buffer; 23 | buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16) 24 | << ctidValue; 25 | return {buffer.str()}; 26 | } 27 | 28 | template 29 | std::optional> 30 | decodeCTID(const T ctid) noexcept 31 | { 32 | uint64_t ctidValue {0}; 33 | if constexpr (std::is_same_v || std::is_same_v || 34 | std::is_same_v || 35 | std::is_same_v) 36 | { 37 | const std::string ctidString(ctid); 38 | 39 | if (ctidString.length() != 16) 40 | return {}; 41 | 42 | if (!std::regex_match(ctidString, std::regex("^[0-9A-F]+$"))) 43 | return {}; 44 | 45 | ctidValue = std::stoull(ctidString, nullptr, 16); 46 | } else if constexpr (std::is_integral_v) 47 | ctidValue = ctid; 48 | else 49 | return {}; 50 | 51 | if (ctidValue > 0xFFFFFFFFFFFFFFFFULL || 52 | (ctidValue & 0xF000000000000000ULL) != 0xC000000000000000ULL) 53 | return {}; 54 | 55 | uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFFFFFUL; 56 | uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU; 57 | uint16_t network_id = ctidValue & 0xFFFFU; 58 | return {{ledger_seq, txn_index, network_id}}; 59 | } 60 | 61 | // NOTE TO DEVELOPER: 62 | // you only need the two functions above, below are test cases, if 63 | // you want them. 64 | 65 | int main() { 66 | std::cout << "Running test cases..." << std::endl; 67 | // Test case 1: Valid input values 68 | assert(encodeCTID(0xFFFFFFFUL, 0xFFFFU, 0xFFFFU) == 69 | std::optional("CFFFFFFFFFFFFFFF")); 70 | assert(encodeCTID(0, 0, 0) == std::optional("C000000000000000")); 71 | assert(encodeCTID(1U, 2U, 3U) == 72 | std::optional("C000000100020003")); 73 | assert(encodeCTID(13249191UL, 12911U, 49221U) == 74 | std::optional("C0CA2AA7326FC045")); 75 | 76 | // Test case 2: ledger_seq greater than 0xFFFFFFF 77 | assert(!encodeCTID(0x10000000UL, 0xFFFFU, 0xFFFFU)); 78 | 79 | // Test case 3: txn_index greater than 0xFFFF 80 | // this test case is impossible in c++ due to the type, left in for 81 | // completeness assert(!encodeCTID(0xFFFFFFF, 0x10000, 0xFFFF)); 82 | 83 | // Test case 4: network_id greater than 0xFFFF 84 | // this test case is impossible in c++ due to the type, left in for 85 | // completeness assert(!encodeCTID(0xFFFFFFFUL, 0xFFFFU, 0x10000U)); 86 | 87 | // Test case 5: Valid input values 88 | assert((decodeCTID("CFFFFFFFFFFFFFFF") == 89 | std::optional>( 90 | std::make_tuple(0xFFFFFFFULL, 0xFFFFU, 0xFFFFU)))); 91 | assert((decodeCTID("C000000000000000") == 92 | std::optional>( 93 | std::make_tuple(0, 0, 0)))); 94 | assert((decodeCTID("C000000100020003") == 95 | std::optional>( 96 | std::make_tuple(1U, 2U, 3U)))); 97 | assert((decodeCTID("C0CA2AA7326FC045") == 98 | std::optional>( 99 | std::make_tuple(13249191UL, 12911U, 49221U)))); 100 | 101 | // Test case 6: ctid not a string or big int 102 | assert(!decodeCTID(0xCFF)); 103 | 104 | // Test case 7: ctid not a hexadecimal string 105 | assert(!decodeCTID("C003FFFFFFFFFFFG")); 106 | 107 | // Test case 8: ctid not exactly 16 nibbles 108 | assert(!decodeCTID("C003FFFFFFFFFFF")); 109 | 110 | // Test case 9: ctid too large to be a valid CTID value 111 | assert(!decodeCTID("CFFFFFFFFFFFFFFFF")); 112 | 113 | // Test case 10: ctid doesn't start with a C nibble 114 | assert(!decodeCTID("FFFFFFFFFFFFFFFF")); 115 | 116 | // Test case 11: Valid input values 117 | assert((decodeCTID(0xCFFFFFFFFFFFFFFFULL) == 118 | std::optional>( 119 | std::make_tuple(0xFFFFFFFUL, 0xFFFFU, 0xFFFFU)))); 120 | assert((decodeCTID(0xC000000000000000ULL) == 121 | std::optional>( 122 | std::make_tuple(0, 0, 0)))); 123 | assert((decodeCTID(0xC000000100020003ULL) == 124 | std::optional>( 125 | std::make_tuple(1U, 2U, 3U)))); 126 | assert((decodeCTID(0xC0CA2AA7326FC045ULL) == 127 | std::optional>( 128 | std::make_tuple(13249191UL, 12911U, 49221U)))); 129 | 130 | // Test case 12: ctid not exactly 16 nibbles 131 | assert(!decodeCTID(0xC003FFFFFFFFFFF)); 132 | 133 | // Test case 13: ctid too large to be a valid CTID value 134 | // this test case is not possible in c++ because it would overflow the type, 135 | // left in for completeness assert(!decodeCTID(0xCFFFFFFFFFFFFFFFFULL)); 136 | 137 | // Test case 14: ctid doesn't start with a C nibble 138 | assert(!decodeCTID(0xFFFFFFFFFFFFFFFFULL)); 139 | 140 | std::cout << "Done!" << std::endl; 141 | return 0; 142 | } 143 | -------------------------------------------------------------------------------- /XLS-0010-non-transferable-tokens/README.md: -------------------------------------------------------------------------------- 1 |
 2 |     xls: 10
 3 |     title: Non-Transferable Token (NTT) standard
 4 |     author: RichardAH (@RichardAH)
 5 |     created: 2020-04-05
 6 |     status: Stagnant
 7 |     category: Ecosystem
 8 | 
9 | 10 | ## Changelog 11 | 12 | 28-7-21: Name of this standard was changed from `Issuer Controlled Token` to `Non-Transferable Token` to reflect industry norms 13 | 14 | ## 1. Introduction 15 | 16 | The XRPL supports 160bit currency codes as discussed here: [https://xrpl.org/currency-formats.html#nonstandard-currency-codes](https://xrpl.org/currency-formats.html#nonstandard-currency-codes) which include as a subset the standard three letter ISO 4217 codes the reader is probably familiar with (e.g. “USD”, “CAD”, etc.) 17 | 18 | The intention of currency codes on the XRPL is to provide fungible IOUs that can be “rippled” between accounts across trusted issuers, facilitating end to end value exchange across multiple unrelated parties on the ledger. 19 | 20 | However in recent years, particularly with the rise in popularity of programmable money platforms such as Ethereum, it has become apparent that tokens outside the typical fungible form might also fill important use-cases. 21 | 22 | ## 2. User Controlled Tokens 23 | 24 | The standard IOU on the XRPL exists as a relationship between two parties: a _user_ and an _issuer_. In order to use the issuer’s token, the user must first add a trustline to their XRPL account for the specified currency code and issuer. Once the trustline exists the issuer (or potentially another user) may send IOUs to that trustline balance. More background here: [https://xrpl.org/trust-lines-and-issuing.html](https://xrpl.org/trust-lines-and-issuing.html) 25 | 26 | ## 3. Non-transferable Tokens (aka Issuer Controlled Tokens) 27 | 28 | This standard proposes a second way to use the trustline mechanism on the XRPL: An issuer controlled token. 29 | 30 | In this model the user no longer is required to add a trustline to the issuer. In fact the user need not do anything. Instead the issuer creates a trustline from the user to itself with a nominal unity balance for a 160 bit “custom” currency code and with rippling disabled. This currency code contains the token information, the trustline itself is just a storage mechanism. 31 | 32 | Any third party looking at the user’s XRPL account will see this “token” attached to the user’s account. 33 | 34 | Issuer Controlled Tokens have the following properties: 35 | 36 | 1. The token is completely controlled by the issuer. The issuer may at any time close the trustline unilaterally, erasing the token from the ledger. 37 | 2. The token is inherently non-fungible (although a fungibility layer could be added through smart contacts.) 38 | 3. The issuer can revoke and re-issue the token at any time with an updated currency code. 39 | 4. The token’s currency encodes 160 bits of arbitrary data (152 bits if excluding the first 8 bits as a type code). 40 | 5. The token is publicly visible to anyone who looks up the user’s account on the XRPL. 41 | 42 | Possible uses for such a token include black and whitelisting / stamps, smart contract per-account state storage and payment and informational pointers expanding on the purpose, behaviour and/or ownership of an XRPL account. 43 | 44 | ## 4. Namespace 45 | 46 | Much like addresses in Internet Protocol space, 160bit currency codes are at risk of nonsensical and overlapping allocation without some sort of standardised allocation scheme. 47 | 48 | By convention the first byte of the 160bit currency code determines the currency code “type”. Byte 0x00 (XRP) cannot be used, and 0x01 has been used previously for demurrage. No complete database of known currency types exists. Some adhoc defacto standards have emerged such as “if the currency code decodes to valid ASCII then display the said ASCII string” but these are not widespread. 49 | 50 | In order to preserve top level “type” codes this standard proposes to reserve 0xFF as the type code for Issuer Controlled Tokens. 51 | 52 | ## 5. Token Specification 53 | 54 | [ Type = 0xFF ] [ Sub type = 0x0000-0xFFFF ] [ Payload = 136b ] 55 | 56 | Byte 0 – Type code: must be 0xFF meaning Issuer Controlled Token 57 | 58 | Byte 1-2 — Subtype code: Unsigned big endian 16 bit integer. Allocated according to a standards database such as this one. This number 0-65,535 tells wallets how to interpret this token. 59 | 60 | Byte 3-19 – Payload bytes: 136 bits of arbitrary data the token encodes. 61 | 62 | ## 6. Spam Prevention 63 | 64 | Since it is possible for anyone to be an issuer they may produce a token that appears on any user’s account (provided they are willing to lock up 5 XRP to do so). This produces something of a problem for wallet software: which tokens are legitimate and which are spam? 65 | 66 | Wallets should give the user the option to display all Issuer Controlled Tokens or to display only tokens from a whitelist pre-populated by the wallet software. That whitelist should include as a subset a community controlled whitelist described below. The wallet should also allow the end user to update and add entries to the whitelist. 67 | 68 | ## 7. Allocation and Community Bulletin Board 69 | 70 | It is proposed that a community “bulletin board” account be created on the XRPL that has no known private key, I.e. a blackhole account. To this account issuer controlled tokens will be assigned from a community XRPL account controlled collectively by key members of the XRPL community through multi-signing. 71 | 72 | In order to whitelist an issuer of a token, the issuer’s 20 byte account ID is truncated at the end by five bytes. Five bytes are then prepended to the start of the account ID in comprising: 0xFF FF FF, followed by the sub-type of the white listed token. 73 | 74 | An alternative way to do this would have been to open a trustline to the issuer from the community account, however the above has an additional advantage: by changing the sub-type of the entry to 0xFF FF F**E** we can signal that the token is actually blacklisted rather than whitelisted. Thus the concept of a bulletin board. 75 | 76 | Further information relevant to all wallets can be stored on the community bulletin board in this way, simply by allocating subtype numbers for those notice types. For example known scam accounts can be placed on the bulletin board, or URL pointers to lists of known scam accounts. 77 | -------------------------------------------------------------------------------- /XLS-0037-concise-transaction-identifier-ctid/ctid.py: -------------------------------------------------------------------------------- 1 | def encodeCTID(ledger_seq, txn_index, network_id): 2 | if not isinstance(ledger_seq, int): 3 | raise ValueError("ledger_seq must be a number.") 4 | if ledger_seq > 0xFFFFFFF or ledger_seq < 0: 5 | raise ValueError("ledger_seq must not be greater than 268435455 or less than 0.") 6 | 7 | if not isinstance(txn_index, int): 8 | raise ValueError("txn_index must be a number.") 9 | if txn_index > 0xFFFF or txn_index < 0: 10 | raise ValueError("txn_index must not be greater than 65535 or less than 0.") 11 | 12 | if not isinstance(network_id, int): 13 | raise ValueError("network_id must be a number.") 14 | if network_id > 0xFFFF or network_id < 0: 15 | raise ValueError("network_id must not be greater than 65535 or less than 0.") 16 | 17 | ctid_value = ((0xC0000000 + ledger_seq) << 32) + (txn_index << 16) + network_id 18 | return format(ctid_value, 'x').upper() 19 | 20 | def decodeCTID(ctid): 21 | if isinstance(ctid, str): 22 | if not ctid.isalnum(): 23 | raise ValueError("ctid must be a hexadecimal string or BigInt") 24 | 25 | if len(ctid) != 16: 26 | raise ValueError("ctid must be exactly 16 nibbles and start with a C") 27 | 28 | ctid_value = int(ctid, 16) 29 | elif isinstance(ctid, int): 30 | ctid_value = ctid 31 | else: 32 | raise ValueError("ctid must be a hexadecimal string or BigInt") 33 | 34 | if ctid_value > 0xFFFFFFFFFFFFFFFF or ctid_value & 0xF000000000000000 != 0xC000000000000000: 35 | raise ValueError("ctid must be exactly 16 nibbles and start with a C") 36 | 37 | ledger_seq = (ctid_value >> 32) & 0xFFFFFFF 38 | txn_index = (ctid_value >> 16) & 0xFFFF 39 | network_id = ctid_value & 0xFFFF 40 | return { 41 | 'ledger_seq': ledger_seq, 42 | 'txn_index': txn_index, 43 | 'network_id': network_id 44 | } 45 | 46 | // NOTE TO DEVELOPER: 47 | // you only need the two functions above, below are test cases, if you want them. 48 | 49 | import unittest 50 | 51 | class TestEncodeAndDecodeCTID(unittest.TestCase): 52 | def test(self): 53 | # Test case 1: Valid input values 54 | self.assertEqual(encodeCTID(0xFFFFFFF, 0xFFFF, 0xFFFF), "CFFFFFFFFFFFFFFF") 55 | self.assertEqual(encodeCTID(0, 0, 0), "C000000000000000") 56 | self.assertEqual(encodeCTID(1, 2, 3), "C000000100020003") 57 | self.assertEqual(encodeCTID(13249191, 12911, 49221), "C0CA2AA7326FC045") 58 | 59 | # Test case 2: ledger_seq greater than 0xFFFFFFF or less than 0 60 | with self.assertRaises(ValueError, msg="ledger_seq must not be greater than 268435455 or less than 0."): 61 | encodeCTID(0x10000000, 0xFFFF, 0xFFFF) 62 | encodeCTID(-1, 0xFFFF, 0xFFFF) 63 | 64 | # Test case 3: txn_index greater than 0xFFFF or less than 0 65 | with self.assertRaises(ValueError, msg="txn_index must not be greater than 65535 or less than 0."): 66 | encodeCTID(0xFFFFFFF, 0x10000, 0xFFFF) 67 | encodeCTID(0xFFFFFFF, -1, 0xFFFF) 68 | 69 | # Test case 4: network_id greater than 0xFFFF or less than 0 70 | with self.assertRaises(ValueError, msg="network_id must not be greater than 65535 or less than 0."): 71 | encodeCTID(0xFFFFFFF, 0xFFFF, -1) 72 | 73 | # Test case 5: Valid input values 74 | self.assertDictEqual(decodeCTID("CFFFFFFFFFFFFFFF"), {'ledger_seq': 0xFFFFFFF, 'txn_index': 0xFFFF, 'network_id': 0xFFFF}) 75 | self.assertDictEqual(decodeCTID("C000000000000000"), {'ledger_seq': 0, 'txn_index': 0, 'network_id': 0}) 76 | self.assertDictEqual(decodeCTID("C000000100020003"), {'ledger_seq': 1, 'txn_index': 2, 'network_id': 3}) 77 | self.assertDictEqual(decodeCTID("C0CA2AA7326FC045"), {'ledger_seq': 13249191, 'txn_index': 12911, 'network_id': 49221}) 78 | 79 | # Test case 6: ctid not a string or big int 80 | with self.assertRaises(ValueError, msg="ctid must be a hexadecimal string or BigInt"): 81 | decodeCTID(0xCFF) 82 | 83 | # Test case 7: ctid not a hexadecimal string 84 | with self.assertRaises(ValueError, msg="ctid must be a hexadecimal string or BigInt"): 85 | decodeCTID("C003FFFFFFFFFFFG") 86 | 87 | # Test case 8: ctid not exactly 16 nibbles 88 | with self.assertRaises(ValueError, msg="ctid must be exactly 16 nibbles and start with a C"): 89 | decodeCTID("C003FFFFFFFFFFF") 90 | 91 | # Test case 9: ctid too large to be a valid CTID value 92 | with self.assertRaises(ValueError, msg="ctid must be exactly 16 nibbles and start with a C"): 93 | decodeCTID("CFFFFFFFFFFFFFFFF") 94 | 95 | # Test case 10: ctid doesn't start with a C nibble 96 | with self.assertRaises(ValueError, msg="ctid must be exactly 16 nibbles and start with a C"): 97 | decodeCTID("FFFFFFFFFFFFFFFF") 98 | 99 | # the same tests again but using bigint instead of string 100 | # 101 | 102 | # Test case 11: Valid input values 103 | self.assertDictEqual(decodeCTID(0xCFFFFFFFFFFFFFFF), {'ledger_seq': 0xFFFFFFF, 'txn_index': 0xFFFF, 'network_id': 0xFFFF}) 104 | self.assertDictEqual(decodeCTID(0xC000000000000000), {'ledger_seq': 0, 'txn_index': 0, 'network_id': 0}) 105 | self.assertDictEqual(decodeCTID(0xC000000100020003), {'ledger_seq': 1, 'txn_index': 2, 'network_id': 3}) 106 | self.assertDictEqual(decodeCTID(0xC0CA2AA7326FC045), {'ledger_seq': 13249191, 'txn_index': 12911, 'network_id': 49221}) 107 | 108 | # Test case 12: ctid not exactly 16 nibbles 109 | with self.assertRaises(ValueError, msg="ctid must be exactly 16 nibbles and start with a C"): 110 | decodeCTID(0xC003FFFFFFFFFFF) 111 | 112 | # Test case 13: ctid too large to be a valid CTID value 113 | with self.assertRaises(ValueError, msg="ctid must be exactly 16 nibbles and start with a C"): 114 | decodeCTID(0xCFFFFFFFFFFFFFFFF) 115 | 116 | # Test case 14: ctid doesn't start with a C nibble 117 | with self.assertRaises(ValueError, msg="ctid must be exactly 16 nibbles and start with a C"): 118 | decodeCTID(0xFFFFFFFFFFFFFFFF) 119 | 120 | 121 | if __name__ == '__main__': 122 | (TestEncodeAndDecodeCTID()).test() 123 | -------------------------------------------------------------------------------- /XLS-0021-asset-code-prefixes/README.md: -------------------------------------------------------------------------------- 1 |
 2 |     xls: 21
 3 |     title: 21: Allocating Asset Code Prefixes
 4 |     description: This proposal defines a mechanism for setting aside prefixes for specific formats and publishing a list of formats potentially in use.
 5 |     author: Rome Reginelli (@mDuo13)
 6 |     created: 2021-07-28
 7 |     status: Stagnant
 8 |     category: Ecosystem
 9 | 
10 | 11 | # Proposal for Allocating Asset Code Prefixes 12 | 13 | Asset codes in the XRP Ledger protocol are natively 160 bits; the reference implementation of the core server defines a shortcut to display 3-character "ISO 4217-like" currency codes using ASCII. These [standard-currency codes](https://xrpl.org/currency-formats.html#standard-currency-codes) use the prefix `0x00` to distinguish them from other asset codes and to prevent haphazard and inconsistent decoding and display of asset codes. (Note: XRP does not usually use a currency code, but when it does, it has an all-zeroes value, which can be considered a special case of the Standard Currency Codes format.) 14 | 15 | Various other formats have been proposed and even implemented, but until now there has been no organization around the reserving of different prefixes. This proposal defines a mechanism for setting aside prefixes for specific formats and publishing a list of formats potentially in use. 16 | 17 | ## Canonical List 18 | 19 | A table of asset code prefixes would be added to xrpl.org and maintained by the XRPL.org contributors. A starting point for the table might be something like this: 20 | 21 | | Prefix | Status | Name | Standard | 22 | | ------------- | ------ | ------------------------------------------- | ------------------------------------------------------------------ | 23 | | `0x00` | ✅ | Standard Currency Codes | https://xrpl.org/currency-formats.html#standard-currency-codes | 24 | | `0x01` | ❌ | Interest-Bearing (Demurrage) Currency Codes | https://xrpl.org/demurrage.html | 25 | | `0x02` | ❌ | XLS-14d Non-Fungible Tokens | https://github.com/XRPLF/XRPL-Standards/discussions/30 | 26 | | `0x03` | 📄 | XLS-30d AMM LP Tokens | https://github.com/XRPLF/XRPL-Standards/discussions/78 | 27 | | `0x20`—`0x7E` | ⚠️ | Full ASCII codes | | 28 | | `0xEC` | 📄 | Extended Prefixes. | Reserved for additional prefixes. [See below](#extended-prefixes). | 29 | | `0xFF` | 📄 | Non-Transferrable Tokens | https://github.com/XRPLF/XRPL-Standards/discussions/20 | 30 | 31 | Any prefixes not listed in the table are considered available (🆓). 32 | 33 | Explanation of status labels: 34 | 35 | - ✅ Accepted. This prefix is used for a specific format which has been accepted an XRP Ledger Standard. 36 | - 📄 Proposed. This prefix has been set aside for use with a proposed or in-development standard. 37 | - ❌ Deprecated. This prefix was previously used, but the associated format is not currently recommended for implementation or use. 38 | - ⚠️ Reserved. This prefix does not have a proper standard, but is discouraged to prevent overlap with emergent usage or for other reasons. 39 | - 🆓 Available. This prefix has not yet been used by any standard format. 40 | 41 | ### Reserving Prefixes 42 | 43 | To reserve a prefix, one would create an [XRPL standard draft](https://github.com/XRPLF/XRPL-Standards) per the standard procedure, and note in the draft the request to receive either a specific unused prefix or the next available prefix. A maintainer for XRPL.org can check the proposal and, judging that it is made in good faith, update the list on XRPL.org to add the new prefix as "Reserved" with a link to the draft discussion. 44 | 45 | Proposals that reserve multiple prefixes are discouraged, but may be granted if after additional scrutiny it is justified to set aside multiple top-level prefixes for the proposal. In most cases, sub-types or variations on the same format can be defined in the asset code's payload data, such as the next 8 bits following the prefix. 46 | 47 | When the corresponding XRPL Standards Draft is accepted as a full standard, the prefix changes from the "Reserved" to "Accepted" status. A maintainer of XRPL.org can update the list to track the new status of the code, and update the link to point to the full standard. Similarly, a standards proposal can move an Accepted or Reserved prefix to Deprecated. 48 | 49 | If a Standards Draft is withdrawn, any prefix(es) reserved for that draft can be made available again or moved to Deprecated, based on the best judgment of maintainers as to whether the reuse of a prefix is likely to lead to confusion, incompatibilities, or other problems. 50 | 51 | ## Extended Prefixes 52 | 53 | Since it seems possible that, eventually, there may be more than 256 different formats, the prefix `0xEC` is reserved for additional prefixes. For any asset code starting in `0xEC`, the next 8 bits indicate the rest of the currency code. For example, `0xEC00` would be the first extended prefix, then `0xEC01`, and so on, with `0xECEC` being reserved for further-extended prefixes, recursively. 54 | 55 | Extended prefixes are less desirable than regular prefixes since, by the nature of the asset code's fixed size, extended prefixes have fewer remaining bits to work with. With a normal currency code, the prefix occupies 8 bits, leaving 152 bits for the rest of the asset code. With singly-extended prefixes, the prefix is 16 bits, leaving 144 bits of payload. With doubly-extended prefixes, it drops to 132 bits of payload, and so on. Still, this is a decent amount of room to work with, especially since each format can define a potentially large number of unique asset codes. 56 | 57 | ## ASCII Codes 58 | 59 | It seems that some XRPL users may already be using a de-facto standard of using the entire 160-bit asset code value to represent a text code / name for the asset. We can avoid overlapping with this usage by avoiding any codes that start with printable ASCII characters, which have codes from `0x20` through `0x7E`. 60 | 61 | There's probably not much we can do for asset codes in the wild that use extended ASCII or UTF-8. A subsequent proposal could set aside a prefix for a more standardized and flexible format. (For example, you may want to set aside a few bytes for a currency symbol e.g. $, €, ¥, or 💩.) 62 | -------------------------------------------------------------------------------- /XLS-0037-concise-transaction-identifier-ctid/ctid.ts: -------------------------------------------------------------------------------- 1 | const encodeCTID = ( 2 | ledger_seq: number, 3 | txn_index: number, 4 | network_id: number, 5 | ): string => { 6 | if (ledger_seq > 0xfffffff || ledger_seq < 0) 7 | throw new Error( 8 | "ledger_seq must not be greater than 268435455 or less than 0.", 9 | ); 10 | 11 | if (txn_index > 0xffff || txn_index < 0) 12 | throw new Error("txn_index must not be greater than 65535 or less than 0."); 13 | 14 | if (network_id > 0xffff || network_id < 0) 15 | throw new Error( 16 | "network_id must not be greater than 65535 or less than 0.", 17 | ); 18 | 19 | return ( 20 | ((BigInt(0xc0000000) + BigInt(ledger_seq)) << 32n) + 21 | (BigInt(txn_index) << 16n) + 22 | BigInt(network_id) 23 | ) 24 | .toString(16) 25 | .toUpperCase(); 26 | }; 27 | 28 | const decodeCTID = ( 29 | ctid: string | bigint, 30 | ): { ledger_seq: number; txn_index: number; network_id: number } => { 31 | let ctidValue: bigint; 32 | if (typeof ctid === "string") { 33 | if (!/^[0-9A-F]+$/.test(ctid)) 34 | throw new Error("ctid must be a hexadecimal string or BigInt"); 35 | if (ctid.length !== 16) 36 | throw new Error("ctid must be exactly 16 nibbles and start with a C"); 37 | 38 | ctidValue = BigInt(`0x${ctid}`); 39 | } else ctidValue = ctid; 40 | 41 | if ( 42 | ctidValue > 0xffffffffffffffffn || 43 | (ctidValue & 0xf000000000000000n) !== 0xc000000000000000n 44 | ) 45 | throw new Error("ctid must be exactly 16 nibbles and start with a C"); 46 | 47 | const ledger_seq = Number((ctidValue >> 32n) & 0xfffffffn); 48 | const txn_index = Number((ctidValue >> 16n) & 0xffffn); 49 | const network_id = Number(ctidValue & 0xffffn); 50 | return { ledger_seq, txn_index, network_id }; 51 | }; 52 | 53 | // NOTE TO DEVELOPER: 54 | // you only need the two functions above, below are test cases, if you want them. 55 | import { strict as assert } from "assert"; 56 | const tests = (): void => { 57 | console.log("Running test cases..."); 58 | // Test cases For encodeCTID 59 | 60 | // Test case 1: Valid input values 61 | assert.equal(encodeCTID(0xfffffff, 0xffff, 0xffff), "CFFFFFFFFFFFFFFF"); 62 | assert.equal(encodeCTID(0, 0, 0), "C000000000000000"); 63 | assert.equal(encodeCTID(1, 2, 3), "C000000100020003"); 64 | assert.equal(encodeCTID(13249191, 12911, 49221), "C0CA2AA7326FC045"); 65 | 66 | // Test case 2: ledger_seq greater than 0xFFFFFFF 67 | assert.throws( 68 | () => encodeCTID(0x10000000, 0xffff, 0xffff), 69 | /ledger_seq must not be greater than 268435455 or less than 0./, 70 | ); 71 | assert.throws( 72 | () => encodeCTID(-1, 0xffff, 0xffff), 73 | /ledger_seq must not be greater than 268435455 or less than 0./, 74 | ); 75 | 76 | // Test case 3: txn_index greater than 0xFFFF 77 | assert.throws( 78 | () => encodeCTID(0xfffffff, 0x10000, 0xffff), 79 | /txn_index must not be greater than 65535 or less than 0./, 80 | ); 81 | assert.throws( 82 | () => encodeCTID(0xfffffff, -1, 0xffff), 83 | /txn_index must not be greater than 65535 or less than 0./, 84 | ); 85 | 86 | // Test case 4: network_id greater than 0xFFFF 87 | assert.throws( 88 | () => encodeCTID(0xfffffff, 0xffff, 0x10000), 89 | /network_id must not be greater than 65535 or less than 0./, 90 | ); 91 | assert.throws( 92 | () => encodeCTID(0xfffffff, 0xffff, -1), 93 | /network_id must not be greater than 65535 or less than 0./, 94 | ); 95 | 96 | // Test cases For decodeCTID 97 | 98 | // Test case 5: Valid input values 99 | assert.deepEqual(decodeCTID("CFFFFFFFFFFFFFFF"), { 100 | ledger_seq: 0xfffffff, 101 | txn_index: 0xffff, 102 | network_id: 0xffff, 103 | }); 104 | assert.deepEqual(decodeCTID("C000000000000000"), { 105 | ledger_seq: 0, 106 | txn_index: 0, 107 | network_id: 0, 108 | }); 109 | assert.deepEqual(decodeCTID("C000000100020003"), { 110 | ledger_seq: 1, 111 | txn_index: 2, 112 | network_id: 3, 113 | }); 114 | assert.deepEqual(decodeCTID("C0CA2AA7326FC045"), { 115 | ledger_seq: 13249191, 116 | txn_index: 12911, 117 | network_id: 49221, 118 | }); 119 | 120 | // Test case 6: ctid not a string or big int 121 | // impossible in typescript, left commented for completeness 122 | //assert.throws(() => decodeCTID(0xCFF), /ctid must be a hexadecimal string or BigInt/); 123 | 124 | // Test case 7: ctid not a hexadecimal string 125 | assert.throws( 126 | () => decodeCTID("C003FFFFFFFFFFFG"), 127 | /ctid must be a hexadecimal string or BigInt/, 128 | ); 129 | 130 | // Test case 8: ctid not exactly 16 nibbles 131 | assert.throws( 132 | () => decodeCTID("C003FFFFFFFFFFF"), 133 | /ctid must be exactly 16 nibbles and start with a C/, 134 | ); 135 | 136 | // Test case 9: ctid too large to be a valid CTID value 137 | assert.throws( 138 | () => decodeCTID("CFFFFFFFFFFFFFFFF"), 139 | /ctid must be exactly 16 nibbles and start with a C/, 140 | ); 141 | 142 | // Test case 10: ctid doesn't start with a C nibble 143 | assert.throws( 144 | () => decodeCTID("FFFFFFFFFFFFFFFF"), 145 | /ctid must be exactly 16 nibbles and start with a C/, 146 | ); 147 | 148 | // Test case 11: Valid input values 149 | assert.deepEqual(decodeCTID(0xcfffffffffffffffn), { 150 | ledger_seq: 0xfffffff, 151 | txn_index: 0xffff, 152 | network_id: 0xffff, 153 | }); 154 | assert.deepEqual(decodeCTID(0xc000000000000000n), { 155 | ledger_seq: 0, 156 | txn_index: 0, 157 | network_id: 0, 158 | }); 159 | assert.deepEqual(decodeCTID(0xc000000100020003n), { 160 | ledger_seq: 1, 161 | txn_index: 2, 162 | network_id: 3, 163 | }); 164 | assert.deepEqual(decodeCTID(0xc0ca2aa7326fc045n), { 165 | ledger_seq: 13249191, 166 | txn_index: 12911, 167 | network_id: 49221, 168 | }); 169 | 170 | // Test case 12: ctid not exactly 16 nibbles 171 | assert.throws( 172 | () => decodeCTID(0xc003fffffffffffn), 173 | /ctid must be exactly 16 nibbles and start with a C/, 174 | ); 175 | 176 | // Test case 13: ctid too large to be a valid CTID value 177 | assert.throws( 178 | () => decodeCTID(0xcffffffffffffffffn), 179 | /ctid must be exactly 16 nibbles and start with a C/, 180 | ); 181 | 182 | // Test case 14: ctid doesn't start with a C nibble 183 | assert.throws( 184 | () => decodeCTID(0xffffffffffffffffn), 185 | /ctid must be exactly 16 nibbles and start with a C/, 186 | ); 187 | 188 | console.log("Done."); 189 | }; 190 | 191 | tests(); 192 | -------------------------------------------------------------------------------- /XLS-0017-xfl/README.md: -------------------------------------------------------------------------------- 1 |
  2 |     xls: 17
  3 |     title: XFL Developer-friendly representation of XRPL balances
  4 |     descrption: Introduces developer-friendly representation of XRPL balances.
  5 |     author: RichardAH (@RichardAH)
  6 |     created: 2021-03-19
  7 |     status: Final
  8 |     category: System
  9 | 
10 | 11 | # Background 12 | 13 | The XRP ledger allows for two types of balances on accounts: 14 | 15 | - Native `xrp` balances 16 | - IOU/Token `trustline` balances 17 | 18 | Native balances are encoded and processed as signed 63bit integer values with an implicit decimal point at the 6th place from the right. A single unit is referred to as a drop. Thus the smallest possible value is `1 drop` represented logically as: `0.000001 xrp`. 19 | 20 | Trustline balances are encoded and processed as a 63 bit decimal floating point number in the following format: 21 | {`sign bit` `8 bits of exponent` `54 bits of mantissa`} 22 | Note: This is not the IEEE floating point format (which is base 2.) 23 | 24 | - The exponent is biased by 97. Thus an encoded exponent of `0b00000000` is `10^(-97)`. 25 | - The mantissa is normalised between `10^15` and `10^16 - 1` 26 | - The canonical zero of this format is all bits 0. 27 | 28 | A 64th disambiguation bit is prepended (most significant bit) to both datatypes, which is `0` in the case of a native balance and `1` in the case of a trustline balance. [[1]](https://xrpl.org/serialization.html#amount-fields) 29 | 30 | # Problem Definition 31 | 32 | Performing computations between these two number formats can be difficult and error-prone for third party developers. 33 | 34 | One existing partial solution is passing these amounts around as human-readable decimal numbers encoded as strings. This is computationally intensive and still does not allow numbers of one type to be mathematically operated on with numbers of the other type in a consistent and predictable way. 35 | 36 | Internally the XRPL requires two independent types, however any xrp balance less than `10B` will translate without loss of precision into the trustline balance type. For amounts of xrp above `10B` (but less than `100B`) only 1 significant figure is lost from the least significant side. Thus in the worst possible case (moving >= `10B` XRP) `10` drops may be lost from an amount. 37 | 38 | The benefits of representing xrp balances in the trustline balance format are: 39 | 40 | - Much simpler developer experience (less mentally demanding, lower barrier to entry) 41 | - Can compute exchange rates between trustline balances and xrp 42 | - Can store all balance types in a single data type 43 | - A unified singular set of safe math routines which are much less likely to be used wrongly by developers 44 | 45 | # XFL Format 46 | 47 | The XFL format is designed for ease of use and maximum compatibility across a wide variety of processors and platforms. 48 | 49 | For maximum ease of passing and returning from (pure) functions all XFL numbers are encoded into an `enclosing number` which is always a signed 64 bit integer, as follows: 50 | 51 | Note: bit 63 is the most significant bit 52 | 53 | - bit 63: `enclosing sign bit` always 0 for a valid XFL 54 | - bit 62: `internal sign bit` 0 = negative 1 = positive. note: this is not the int64_t's sign bit. 55 | - bit 61 - 53: `exponent` biased such that `0b000000000` = -97 56 | - bit 53 - 0: `mantissa` between `10^15` and `10^16 - 1` 57 | 58 | Special case: 59 | 60 | - Canonical zero: enclosing number = 0 61 | 62 | Any XFL with a negative enclosing sign bit is `invalid`. This _DOES NOT_ refer to the internal sign bit inside the XFL format. It is definitely possible to have a negative value represented in an XFL, however these always exist with a _POSITIVE_ enclosing sign bit. 63 | 64 | Invalid (negative enclosing sign bit) XFL values are reserved for propagation of error codes. If an invalid XFL is passed to an XFL processing function (for example `float_multiply`) it too should return an invalid XFL. 65 | 66 | # Examples 67 | 68 | | Number | Enclosing | To String | 69 | | ------ | ------------------- | ----------------------------- | 70 | | -1 | 1478180677777522688 | -1000000000000000 \* 10^(-15) | 71 | | 0 | 0000000000000000000 | [canonical zero] | 72 | | +1 | 6089866696204910592 | +1000000000000000 \* 10^(-15) | 73 | | +PI | 6092008288858500385 | +3141592653589793 \* 10^(-15) | 74 | | -PI | 1480322270431112481 | -3141592653589793 \* 10^(-15) | 75 | 76 | # Reference Implementations 77 | 78 | Javascript: 79 | 80 | ```js 81 | const minMantissa = 1000000000000000n; 82 | const maxMantissa = 9999999999999999n; 83 | const minExponent = -96; 84 | const maxExponent = 80; 85 | 86 | function make_xfl(exponent, mantissa) { 87 | // convert types as needed 88 | if (typeof exponent != "bigint") exponent = BigInt(exponent); 89 | 90 | if (typeof mantissa != "bigint") mantissa = BigInt(mantissa); 91 | 92 | // canonical zero 93 | if (mantissa == 0n) return 0n; 94 | 95 | // normalize 96 | let is_negative = mantissa < 0; 97 | if (is_negative) mantissa *= -1n; 98 | 99 | while (mantissa > maxMantissa) { 100 | mantissa /= 10n; 101 | exponent++; 102 | } 103 | while (mantissa < minMantissa) { 104 | mantissa *= 10n; 105 | exponent--; 106 | } 107 | 108 | // canonical zero on mantissa underflow 109 | if (mantissa == 0) return 0n; 110 | 111 | // under and overflows 112 | if (exponent > maxExponent || exponent < minExponent) return -1; // note this is an "invalid" XFL used to propagate errors 113 | 114 | exponent += 97n; 115 | 116 | let xfl = !is_negative ? 1n : 0n; 117 | xfl <<= 8n; 118 | xfl |= BigInt(exponent); 119 | xfl <<= 54n; 120 | xfl |= BigInt(mantissa); 121 | 122 | return xfl; 123 | } 124 | 125 | function get_exponent(xfl) { 126 | if (xfl < 0n) throw "Invalid XFL"; 127 | if (xfl == 0n) return 0n; 128 | return ((xfl >> 54n) & 0xffn) - 97n; 129 | } 130 | 131 | function get_mantissa(xfl) { 132 | if (xfl < 0n) throw "Invalid XFL"; 133 | if (xfl == 0n) return 0n; 134 | return xfl - ((xfl >> 54n) << 54n); 135 | } 136 | 137 | function is_negative(xfl) { 138 | if (xfl < 0n) throw "Invalid XFL"; 139 | if (xfl == 0n) return false; 140 | return ((xfl >> 62n) & 1n) == 0n; 141 | } 142 | 143 | function to_string(xfl) { 144 | if (xfl < 0n) throw "Invalid XFL"; 145 | if (xfl == 0n) return ""; 146 | return ( 147 | (is_negative(xfl) ? "-" : "+") + 148 | get_mantissa(xfl) + 149 | " * 10^(" + 150 | get_exponent(xfl) + 151 | ")" 152 | ); 153 | } 154 | ``` 155 | 156 | C: 157 | 158 | - See implementation in Hooks: [here](https://github.com/RichardAH/rippled-hooks/blob/6b132d6d1382e3ee61e6759cecad36f08b9e665f/src/ripple/app/tx/impl/applyHook.cpp#L86) 159 | -------------------------------------------------------------------------------- /XLS-0037-concise-transaction-identifier-ctid/ctid.js: -------------------------------------------------------------------------------- 1 | const encodeCTID = (ledger_seq, txn_index, network_id) => { 2 | if (typeof ledger_seq != "number") 3 | throw new Error("ledger_seq must be a number."); 4 | if (ledger_seq > 0xfffffff || ledger_seq < 0) 5 | throw new Error( 6 | "ledger_seq must not be greater than 268435455 or less than 0.", 7 | ); 8 | 9 | if (typeof txn_index != "number") 10 | throw new Error("txn_index must be a number."); 11 | if (txn_index > 0xffff || txn_index < 0) 12 | throw new Error("txn_index must not be greater than 65535 or less than 0."); 13 | 14 | if (typeof network_id != "number") 15 | throw new Error("network_id must be a number."); 16 | if (network_id > 0xffff || network_id < 0) 17 | throw new Error( 18 | "network_id must not be greater than 65535 or less than 0.", 19 | ); 20 | 21 | return ( 22 | ((BigInt(0xc0000000) + BigInt(ledger_seq)) << 32n) + 23 | (BigInt(txn_index) << 16n) + 24 | BigInt(network_id) 25 | ) 26 | .toString(16) 27 | .toUpperCase(); 28 | }; 29 | 30 | const decodeCTID = (ctid) => { 31 | let ctidValue; 32 | if (typeof ctid === "string") { 33 | if (!/^[0-9A-F]+$/.test(ctid)) 34 | throw new Error("ctid must be a hexadecimal string or BigInt"); 35 | 36 | if (ctid.length !== 16) 37 | throw new Error("ctid must be exactly 16 nibbles and start with a C"); 38 | 39 | ctidValue = BigInt("0x" + ctid); 40 | } else if (typeof ctid === "bigint") ctidValue = ctid; 41 | else throw new Error("ctid must be a hexadecimal string or BigInt"); 42 | 43 | if ( 44 | ctidValue > 0xffffffffffffffffn || 45 | (ctidValue & 0xf000000000000000n) != 0xc000000000000000n 46 | ) 47 | throw new Error("ctid must be exactly 16 nibbles and start with a C"); 48 | 49 | const ledger_seq = Number((ctidValue >> 32n) & 0xfffffffn); 50 | const txn_index = Number((ctidValue >> 16n) & 0xffffn); 51 | const network_id = Number(ctidValue & 0xffffn); 52 | return { 53 | ledger_seq, 54 | txn_index, 55 | network_id, 56 | }; 57 | }; 58 | 59 | // NOTE TO DEVELOPER: 60 | // you only need the two functions above, below are test cases for nodejs, if you want them. 61 | if (typeof window === "undefined" && typeof process === "object") { 62 | console.log("Running test cases..."); 63 | // Test cases For encodeCTID 64 | const assert = require("assert"); 65 | 66 | // Test case 1: Valid input values 67 | assert.equal(encodeCTID(0xfffffff, 0xffff, 0xffff), "CFFFFFFFFFFFFFFF"); 68 | assert.equal(encodeCTID(0, 0, 0), "C000000000000000"); 69 | assert.equal(encodeCTID(1, 2, 3), "C000000100020003"); 70 | assert.equal(encodeCTID(13249191, 12911, 49221), "C0CA2AA7326FC045"); 71 | 72 | // Test case 2: ledger_seq greater than 0xFFFFFFF 73 | assert.throws( 74 | () => encodeCTID(0x10000000, 0xffff, 0xffff), 75 | /ledger_seq must not be greater than 268435455 or less than 0./, 76 | ); 77 | assert.throws( 78 | () => encodeCTID(-1, 0xffff, 0xffff), 79 | /ledger_seq must not be greater than 268435455 or less than 0./, 80 | ); 81 | 82 | // Test case 3: txn_index greater than 0xFFFF 83 | assert.throws( 84 | () => encodeCTID(0xfffffff, 0x10000, 0xffff), 85 | /txn_index must not be greater than 65535 or less than 0./, 86 | ); 87 | assert.throws( 88 | () => encodeCTID(0xfffffff, -1, 0xffff), 89 | /txn_index must not be greater than 65535 or less than 0./, 90 | ); 91 | 92 | // Test case 4: network_id greater than 0xFFFF 93 | assert.throws( 94 | () => encodeCTID(0xfffffff, 0xffff, 0x10000), 95 | /network_id must not be greater than 65535 or less than 0./, 96 | ); 97 | assert.throws( 98 | () => encodeCTID(0xfffffff, 0xffff, -1), 99 | /network_id must not be greater than 65535 or less than 0./, 100 | ); 101 | 102 | // Test cases For decodeCTID 103 | 104 | // Test case 5: Valid input values 105 | assert.deepEqual(decodeCTID("CFFFFFFFFFFFFFFF"), { 106 | ledger_seq: 0xfffffff, 107 | txn_index: 0xffff, 108 | network_id: 0xffff, 109 | }); 110 | assert.deepEqual(decodeCTID("C000000000000000"), { 111 | ledger_seq: 0, 112 | txn_index: 0, 113 | network_id: 0, 114 | }); 115 | assert.deepEqual(decodeCTID("C000000100020003"), { 116 | ledger_seq: 1, 117 | txn_index: 2, 118 | network_id: 3, 119 | }); 120 | assert.deepEqual(decodeCTID("C0CA2AA7326FC045"), { 121 | ledger_seq: 13249191, 122 | txn_index: 12911, 123 | network_id: 49221, 124 | }); 125 | 126 | // Test case 6: ctid not a string or big int 127 | assert.throws( 128 | () => decodeCTID(0xcff), 129 | /ctid must be a hexadecimal string or BigInt/, 130 | ); 131 | 132 | // Test case 7: ctid not a hexadecimal string 133 | assert.throws( 134 | () => decodeCTID("C003FFFFFFFFFFFG"), 135 | /ctid must be a hexadecimal string or BigInt/, 136 | ); 137 | 138 | // Test case 8: ctid not exactly 16 nibbles 139 | assert.throws( 140 | () => decodeCTID("C003FFFFFFFFFFF"), 141 | /ctid must be exactly 16 nibbles and start with a C/, 142 | ); 143 | 144 | // Test case 9: ctid too large to be a valid CTID value 145 | assert.throws( 146 | () => decodeCTID("CFFFFFFFFFFFFFFFF"), 147 | /ctid must be exactly 16 nibbles and start with a C/, 148 | ); 149 | 150 | // Test case 10: ctid doesn't start with a C nibble 151 | assert.throws( 152 | () => decodeCTID("FFFFFFFFFFFFFFFF"), 153 | /ctid must be exactly 16 nibbles and start with a C/, 154 | ); 155 | 156 | // the same tests again but using bigint instead of string 157 | // 158 | 159 | // Test case 11: Valid input values 160 | assert.deepEqual(decodeCTID(0xcfffffffffffffffn), { 161 | ledger_seq: 0xfffffff, 162 | txn_index: 0xffff, 163 | network_id: 0xffff, 164 | }); 165 | assert.deepEqual(decodeCTID(0xc000000000000000n), { 166 | ledger_seq: 0, 167 | txn_index: 0, 168 | network_id: 0, 169 | }); 170 | assert.deepEqual(decodeCTID(0xc000000100020003n), { 171 | ledger_seq: 1, 172 | txn_index: 2, 173 | network_id: 3, 174 | }); 175 | assert.deepEqual(decodeCTID(0xc0ca2aa7326fc045n), { 176 | ledger_seq: 13249191, 177 | txn_index: 12911, 178 | network_id: 49221, 179 | }); 180 | 181 | // Test case 12: ctid not exactly 16 nibbles 182 | assert.throws( 183 | () => decodeCTID(0xc003fffffffffffn), 184 | /ctid must be exactly 16 nibbles and start with a C/, 185 | ); 186 | 187 | // Test case 13: ctid too large to be a valid CTID value 188 | assert.throws( 189 | () => decodeCTID(0xcffffffffffffffffn), 190 | /ctid must be exactly 16 nibbles and start with a C/, 191 | ); 192 | 193 | // Test case 14: ctid doesn't start with a C nibble 194 | assert.throws( 195 | () => decodeCTID(0xffffffffffffffffn), 196 | /ctid must be exactly 16 nibbles and start with a C/, 197 | ); 198 | 199 | console.log("Done."); 200 | } 201 | -------------------------------------------------------------------------------- /XLS-0050-validator-toml-infra-details/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 50
  3 |   title: Aiming for a healthy distribution of (validator) infrastructure
  4 |   description: Best practices for the geographical & provider distribution of validators
  5 |   author: Wietse Wind (@WietseWind)
  6 |   created: 2023-11-14
  7 |   status: Final
  8 |   category: Ecosystem
  9 | 
10 | 11 | # Abstract 12 | 13 | The network is at risk of centralised consensus failure when too many validators (especially dUNL validators) are running on the same cloud provider/network. While negative [UNL helps](.), the network is still at the risk of a halt when a large infra/cloud provider has an outage. 14 | 15 | This proposal introduces best practices for the geographical & provider distribution of validators, something those composing dUNL contents & those adding trusted validators to their `rippled` & `xahaud` config manually. 16 | 17 | ℹ️ Note: this proposal is only focussing at **validators**, as their (instant lack of) availability could harm network forward progress. All arguments (motivation) apply to RPC nodes, hubs, as well, but e.g. RPC nodes could benefit from live HTTP failover availability & them being offline doesn't directly harm the network's forward progress. 18 | 19 | # Motivation 20 | 21 | With high performing cloud servers (VPS, dedicated) are more and more common, and with cloud providers often beating convenience and price of self hosting / colocation, several validator operators are running their validators at the same cloud providers. Independently executed network crawls show large clusters of validators at only a handful cloud providers. Most common providers are: 22 | 23 | - Google (Google Cloud Platform) 24 | - Amazon AWS 25 | - DigitalOcean 26 | - Hetzner 27 | 28 | When looking at traceroutes, things look even worse when taking the datacenter/POP of these cloud providers into account, as cloud providers, through availability and pricing, route customers to specific preferred datacenter locations (and thus: routes). 29 | 30 | As a result of a significant number of validators running on cloud infra at a small number of cloud providers, an outage at one cloud provider (datacenter, power, infra, routing, BGP, ...) can potentially drop the network below the consensus threshold. With Negative UNL taking 256 ledgers to kick in, this is unwanted. 31 | 32 | # Proposal 33 | 34 | Things to take into account picking a suitable provider (cloud, infrastructure): 35 | 36 | - Significant distribution across ASN (networks) as per validator IP address 37 | - Significant distribution across physical location (datacenters, POPs) 38 | 39 | ### 1. Validator operator resource location selection 40 | 41 | Validator operators (even when not on the UNL, if they prefer to be on dUNL / in each other's validator lists) should always: 42 | 43 | - Take the above into account 44 | - Preferably prevent running their validator on a cloud provider 45 | - If running on a cloud provider: preferably pick the least used cloud provider & cloud provider datacenter location (POP) by other validator operators 46 | 47 | ### 2. TOML contents 48 | 49 | A typical `validator` section in the `xrp-ledger.toml` and `xahau.toml` preferably contains a `server_country` property: 50 | 51 | ```toml 52 | [[VALIDATORS]] 53 | ... 54 | server_country = "??" 55 | ``` 56 | 57 | The following properties **must** be added: 58 | 59 | - `network_asn` (integer) containing the ASN (autonomous system number) of the IP address the validator uses to connect out (see Appendix A & Appendix B) 60 | 61 | #### ⚖️ **CONSIDERATION: publishing the ASN could be considered an attack vector, as it would expose the provider to DDoS to take down connectivity to certain validators. However, if the network is sufficiently decentralised/distributed from a infrastructure point of view, taking down one or to routes wouldn't harm the network.** 62 | 63 | The following properties **should** be added: 64 | 65 | - `server_location` (string) containing a written explanation of provider provided location details (see Appendix A) 66 | - `server_cloud` (boolean) if the server is running at a cloud provider (e.g. VPS / cloud dedicated: **a server you will never physically see, won't know how to find is a cloud server.**, so VPS / dedicated rented machine: cloud = true. Your own bare metal, colocated: cloud = false) 67 | 68 | #### ⚠️ **NEVER PUBLISH YOUR VALIDATOR IP ADDRESS IN YOUR TOML!** 69 | 70 | ### 3. UNL publishers & validator operators 71 | 72 | UNL publishers & validator operators should start to obtain ASN & geographical location from trusted validator operators, and take them into account when composing the UNL list contents. 73 | 74 | UNL publishers should not add validators to the UNL without the above TOML properties. 75 | 76 | # Call for immediate action 77 | 78 | ### Validator operators 79 | 80 | - Update your TOML with the above information 81 | - Check your manual validator list additions for the above properties 82 | 83 | ### UNL publishers 84 | 85 | - Check the validators on the published UNL for the above TOML properties 86 | - Call for adding the above TOML properties 87 | - Communicate a "Grace Period" for those not disclosing this info 88 | - (If communication is possible) work with operators of reliable validators if they are running on cloud/infra hotspots, ask them to migrate 89 | - (If communication is not possible) over time, replace validator operators on cloud/infra hotspots, for reliable validators providing more provider & geographical redundancy. 90 | 91 | # Appendix A - sample TOML values 92 | 93 | ### Example: Cloud: VPS / Rented dedicated 94 | 95 | ```toml 96 | server_country = "NL" 97 | server_location = "DigitalOcean AMS3 (Amsterdam, NL)" 98 | server_cloud = true 99 | network_asn = 14061 100 | ``` 101 | 102 | ### Example: Non-Cloud: Self hosted or own hardware, colocated 103 | 104 | ```toml 105 | server_country = "NL" 106 | server_location = "Private datacenter (Utrecht area, NL)" 107 | server_cloud = false 108 | network_asn = 16089 109 | ``` 110 | 111 | # Appendix B - obtaining the ASN (autonomous system number) 112 | 113 | To obtain the ASN for your IP address (usually your provider or provider's upstream provider), you will need the IP address used by your validator to connect out to others. Providing there are no proxy settings & the machine has native IP connectivity to the outside world, you can find/confirm your outgoing IP address by executing (bash): 114 | 115 | ```bash 116 | curl --silent https://myip.wtf/text 117 | ``` 118 | 119 | You can find the ASN for the IP range this IP is allocated from using: 120 | 121 | ```bash 122 | whois -h whois.cymru.com {ip} 123 | ``` 124 | 125 | A one-line command to obtain IP and obtain ASN: 126 | 127 | ```bash 128 | whois -h whois.cymru.com $(curl --silent https://myip.wtf/text) 129 | ``` 130 | 131 | Sample output: 132 | 133 | ``` 134 | AS | IP | AS Name 135 | 14061 | 31.239.49.82 | MyBackYardServerRack LTD USA 136 | ``` 137 | 138 | In this case `14061` is the integer value for your `network_asn` property in your TOML file. 139 | 140 | #### ⚠️ **Warning! The AS name may contain a location identifier. This usually refers to the original place the IP range was allocated / the headquarters of the owner of the IP range. This is in NO WAY an indication of the physical location of your machine.** 141 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | > [!IMPORTANT] 4 | > This process is in a state of flux right now, and this document is still referring to the old process. Please refer to https://github.com/XRPLF/XRPL-Standards/discussions/340 instead. 5 | 6 | The work of the [XRP Ledger](https://xrpl.org) community is open, collaborative, and welcoming of all contributors participating in good faith. Part of that effort involves standardization, and this document outlines how anyone can contribute to that process. 7 | 8 | ## Licensing 9 | 10 | Any XRPL Standards document can be referred to interchangeably as an "XLS", "XRPL Standard", or "document". Copyright on all content is subject to the terms of this [license](LICENSE), and all contributors grant everyone a royalty-free license to use their contributions, including the following grants: 11 | 12 | - Copyright: a royalty-free license to anyone to use any contributions submitted to this repository. 13 | - Patent: a commitment to license on a royalty-free basis any essential patent claims relating to any contributions in this repository. 14 | 15 | ## Specification Process 16 | 17 | ### 1. Start a Discussion & Gather Feedback 18 | 19 | Before opening a PR with any kind of formal proposal, please first gather community input by starting a [Discussion](https://github.com/XRPLF/XRPL-Standards/discussions). Discussions are suitable for early work-in-progress: ask, suggest, add, and make sweeping changes. Collecting such feedback helps to refine your concept, and is required in order to move forward in the specification process. 20 | 21 | #### Discussion Title 22 | 23 | When creating a new discussion for your idea, the discussion title should follow the naming convention `XLS-{0000}d {Title}`, where `{0000}` is a unique number for the XLS, `d` indicates that the document is a Draft (i.e., work in progress), and `{Title}` is a descriptive title for the proposed document. 24 | 25 | #### Specification Number 26 | 27 | Use the next number that has not yet been used. If a conflict occurs, it will be fixed by a maintainer or editor. Maintainers or editors also reserve the right to remove or re-number proposals as necessary. The number is important, as it will be used to reference features and ideas throughout the community. 28 | 29 | ### 2. Closing a Discussion 30 | 31 | When a discussion has produced a well-refined standard, authors should post a comment to the discussion noting that the discussion will be closed in a few days. This allows time (for those engaged with the discussion) to submit final commentary. 32 | 33 | Once this waiting period has elapsed, the standard's author may close the discussion from further comment, and then move the discussion to a [**specification pull request**](#3-specification-pull-requests) to add the standard into the repository as a markdown file (see below for specification formats). 34 | 35 | Next, the discussion author should edit the discussion to include a link to the PR. The last comment on the discussion should also be a link to the PR. 36 | 37 | The intention of this workflow is that the discussion be closed from further comments, with further comments instead being posted on the PR for fine-tuning and alignment with implementation or adoption, as appropriate. 38 | 39 | ### 3. Specification Pull Requests 40 | 41 | When opening a specification PR, there are two document types: _Drafts_ and _Candidate Specifications_. The type and status of any particular document has no bearing on the priority of that document. Typically, the further along in the process, the more likely it is that any particular XLS will be implemented or adopted. 42 | 43 | #### Drafts 44 | 45 | A _Draft_ is a document that proposes some new feature, protocol or idea related to the XRP Ledger. The criteria for getting the document merged is minimal: project maintainers will review the document to ensure style conformance and applicability, but will otherwise not require editorial fixes before allowing it to be published. 46 | 47 | A document does not need to have an implementation in order to become a Draft. A Draft may or may not have implementation(s) available and no code is required prior to the Draft being published. 48 | 49 | A Draft is often stable enough for people to experiment with, but has not necessarily been endorsed by the entire community. When there are competing Drafts that address the same problem in different ways, all such Drafts can continue to be refined, promoted, and used independently, without any blocking. Implementors can create software to implement this type of standard into their codebase to explore how it works in a real world setting. 50 | 51 | Any, or all, competing Drafts _may_ graduate into a Candidate Specification. 52 | 53 | Notice that a Draft is not a [rubber-stamp](https://idioms.thefreedictionary.com/rubber-stamp) of the content in any particular document. Documents can still undergo significant changes and potentially be discarded all together. 54 | 55 | ##### Publishing a Draft 56 | 57 | To publish a new Draft, submit a Pull Request to this repo with a new folder and a new Markdown file. The folder MUST follow the naming convention `XLS-{0000}d-{title}` where `{0000}` is the unique number referencing the XLS, `d` indicates that the document is a Draft, and `{title}` is a lower case title with spaces replaced by hyphens (`-`). The submission should have front-matter (similar to GitHub pages rendered from Markdown) specifying at least a `title` and `type`. The `type` MUST have the value `draft`. 58 | 59 | An example draft name is: `XLS-20d-non-fungible-token-support-native` 60 | 61 | Use the following template when creating the Markdown file: [xls-template.md](./xls-template.md) 62 | 63 | Assuming there is consensus to publish, one of the project maintainers will review the submission and confirm the document's XLS number, often making a follow-up commit to the PR which renames the file as appropriate. 64 | 65 | #### Candidate Specifications 66 | 67 | A _Candidate Specification_ is a document that was previously a Draft, but is considered stable enough by the community such that no further changes are required. Once an XLS becomes a Candidate Specification, no further substantive changes are allowed under the same XLS number. 68 | 69 | Refinements in detail are still allowed and recommended. For example, you can clarify exact error cases and define the error codes and transaction result codes that occur in those cases. 70 | 71 | ##### Publishing a Candidate Specification 72 | 73 | When a Draft is considered stable, there is a call for review from the community to publish the document as a Candidate Specification by making a PR to remove the `d` from the document folder name and update the `type` to `candidate-specification`. 74 | 75 | Once published as a Candidate Specification, no further substantive changes are allowed under the same XLS number. 76 | 77 | For Specifications that require changes or implementation in the XRP Ledger server software and protocol, the Candidate Specification cannot be published until the relevant change has been merged into [the software's `master` branch](https://github.com/XRPLF/rippled/tree/master). 78 | 79 | #### Errata 80 | 81 | The community may discover errors in a Candidate Specification. In these circumstances, it is possible to update the document to fix typos or clarify the original meaning of the document. 82 | 83 | ### Deprecated or Rejected XLSs 84 | 85 | An XLS document may be rejected after public discussion has settled and comments have been made summarizing the rationale for rejection. Similarly, a document may be deprecated when its use should be discouraged. A member of the core maintainer team will move rejected and deprecated proposals to the `/rejected` folder in this repo. 86 | -------------------------------------------------------------------------------- /XLS-0022-api-versioning/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 22
  3 |   title: rippled API Versioning
  4 |   description: The API version number allows for evolving the `rippled` API while maintaining backward compatibility
  5 |   author: Elliot Lee (@intelliot), Peng Wang (@pwang200)
  6 |   status: Final
  7 |   category: System
  8 |   created: 2021-08-11
  9 | 
10 | 11 | # rippled API Versioning 12 | 13 | rippled offers an API (application programming interface) that apps use to integrate with the XRP Ledger. In order to evolve and improve this API over time, while providing consistency and ensuring backward compatibility for clients, the API shall be versioned with an `API version number`. 14 | 15 | For JSON-RPC and WebSocket (the most commonly-used interfaces), the API version number field is called `"api_version"`. 16 | 17 | ### JSON-RPC 18 | 19 | ```json 20 | { 21 | "method": "...", 22 | "params": [ 23 | { 24 | "api_version": 1, 25 | "...": "..." 26 | } 27 | ] 28 | } 29 | ``` 30 | 31 | Notice that "api_version" is [not a top-level field](https://github.com/ripple/rippled/issues/3065). 32 | 33 | ### WebSocket 34 | 35 | ```json 36 | { 37 | "api_version": 1, 38 | "id": 4, 39 | "command": "...", 40 | "...": "..." 41 | } 42 | ``` 43 | 44 | Following the [WebSocket request format](https://xrpl.org/request-formatting.html), it is placed at the top level. 45 | 46 | ### rippled command line 47 | 48 | The RPC command and its parameters are parsed first. Then, a JSON-RPC request is created. rippled shall not support multiple versions of the command-parameter parsers, so the JSON-RPC requests will have one API version number. The latest API version number shall be used. Under the hood, rippled shall insert the `"api_version"` field internally. 49 | 50 | ### gRPC 51 | 52 | The version number for gRPC is specified as part of the package name of the gRPC service definition (in .proto files). The .proto files of different versions shall be placed in version-specific folders. 53 | 54 | Multiple versions of the API are created by constructing multiple services, located in different namespaces. From gRPC's perspective, they are simply different services. All of the services can be registered to a single gRPC server. 55 | 56 | ## Specification 57 | 58 | **The API version number shall be a single unsigned integer.** This is simpler than the major.minor.patch format, which is already used for versions of rippled itself. When it comes to API versions, there is no need for additional complexity beyond a single integer. 59 | 60 | **The API version number shall be increased if, and only if, we introduce one or more breaking changes to the API.** When the version number is increased, it is increased by one. 61 | 62 | **The lowest valid version number shall be 1.** The version number 0 is invalid. 63 | 64 | **rippled shall support a range of versions with consecutive version numbers.** At the moment, the range is [1, 1]. 65 | 66 | **Client requests targeting an API version that is out of the supported version range shall be rejected.** For safety and ease of understanding, we do not allow "forward compatibility" where, if a version number in the request is higher than the supported range, we would lower its version number to the highest supported version number. Most clients know in advance which rippled servers they are connecting to (indeed, this is required for the XRPL security model). So it is unlikely that they will "mistakenly" make a request to a server that does not support the API version that they require. And if they do, it is better to respond with an error: the client can then choose to handle the error in a way that is appropriate for its use case. 67 | 68 | **Client targeting APIs with versions 2 and above shall specify the version number in their requests.** The WebSocket and JSON-RPC (HTTP) requests shall include the version number in the request payloads. 69 | 70 | **Requests without a version number shall be handled as version 1 requests.** This provides backward compatibility to the time when API version numbers did not yet exist. 71 | 72 | **Responses do not need to include the API version number.** This saves some bandwidth and encourages the "best practice" of having the client track its requests with the `id` field. 73 | 74 | **When a client sends multiple requests over a persistent connection (e.g. WebSockets), each request may use a different API version number.** This gives clients flexibility, with the ability to "progressively" update to new API versions in specific requests, without changing their entire integration. 75 | 76 | **When using the rippled command line interface, the latest API version shall be used.** The command line interface is typically used for development, debugging, and one-off requests. For simplicity and to ensure that developers always pay attention to the evolution of the API, these requests shall use the latest API version. 77 | 78 | **An API version that available in a stable release of rippled is considered "ready".** From the client's perspective, alpha/beta API versions generally should not exist. 79 | 80 | ## Implementation Details 81 | 82 | There are four (4) RPC interfaces: 83 | 84 | 1. JSON-RPC (HTTP) 85 | 2. WebSocket 86 | 3. rippled command line 87 | 4. gRPC 88 | 89 | The API version number is a single 32-bit unsigned integer. A range of consecutive API versions are supported by rippled. The lower and upper bounds of the range shall be hardcoded. Different rippled software versions may support different API version ranges. 90 | 91 | For JSON-RPC and WebSocket, when a client requests an API version that is out of the supported range, the returned error message shall include the string "Unsupported API version", the version number targeted by the request, and the lower and upper bounds of the supported range. 92 | 93 | ## What is considered a breaking change? 94 | 95 | - Deleting (removing), renaming, changing the type of, or changing the meaning of a field of a request or a response. 96 | - Changing the order of position-based parameters, or inserting a new field in front of existing position-based parameters. 97 | - Deleting or renaming an API method (function). 98 | - Changing the behavior of an API method in a way that is visible to existing clients. 99 | - Changing HTTP status codes[^1]. 100 | 101 | gRPC only: 102 | 103 | - Changing a proto field number. 104 | - Deleting or renaming an enum or enum value. 105 | - Moving fields into or out of a oneof, split, or merge oneof. 106 | - Changing the label of a message field, i.e. optional, repeated, required. 107 | - Changing the stream value of a method request or response. 108 | - Deleting or renaming a package or service. 109 | 110 | ## What is not a breaking change? 111 | 112 | - Adding a new field to a request or response message, if it is not a position-based parameter. 113 | - Adding a new API method (function). 114 | 115 | ## References 116 | 117 | - [Documentation: API Versioning](https://xrpl.org/request-formatting.html#api-versioning) 118 | - [API versioning #3155 (rippled PR)](https://github.com/ripple/rippled/pull/3155): Merged and released in [rippled v1.5.0](https://github.com/ripple/rippled/releases/tag/1.5.0) 119 | - [Original Requirements](https://github.com/pwang200/RippledRPCDesign/blob/API_versioning/requirement/requirements.md) 120 | - [Original Design Document](https://github.com/pwang200/RippledRPCDesign/blob/API_versioning/design/design.md) 121 | 122 | [^1]: 123 | Changes to response status codes. For example, this is a breaking change: 124 | **Before:** POST /values returns 400 for missing property 125 | **After:** POST /values returns 409 for missing property 126 | [Reference](https://community.blackbaud.com/blogs/69/3219) 127 | -------------------------------------------------------------------------------- /site/build_site.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Build script for XLS Standards static site generator. 4 | Converts markdown XLS files to HTML and creates an index page. 5 | """ 6 | 7 | import os 8 | import shutil 9 | from pathlib import Path 10 | 11 | import markdown 12 | from jinja2 import Environment, FileSystemLoader 13 | 14 | from xls_parser import find_xls_documents 15 | from collections import Counter 16 | 17 | 18 | def convert_markdown_to_html(content: str) -> str: 19 | """Convert markdown content to HTML.""" 20 | md = markdown.Markdown( 21 | extensions=["extra", "codehilite", "toc", "tables"], 22 | extension_configs={ 23 | "codehilite": {"css_class": "highlight"}, 24 | "toc": {"permalink": True}, 25 | }, 26 | ) 27 | return md.convert(content) 28 | 29 | 30 | def build_site(): 31 | """Main function to build the static site.""" 32 | 33 | # Setup directories 34 | source_dir = Path(__file__).parent.resolve() 35 | root_dir = source_dir.parent 36 | site_dir = source_dir / "_site" 37 | template_dir = source_dir / "templates" 38 | assets_dir = source_dir / "assets" 39 | 40 | # Set base URL for GitHub Pages (can be overridden with env var) 41 | base_url = os.environ.get("GITHUB_PAGES_BASE_URL", "/XRPL-Standards") if "GITHUB_REPOSITORY" in os.environ else os.environ.get("GITHUB_PAGES_BASE_URL", ".") 42 | 43 | # Clean and create site directory 44 | if site_dir.exists(): 45 | shutil.rmtree(site_dir) 46 | site_dir.mkdir() 47 | 48 | # Create subdirectories 49 | (site_dir / "xls").mkdir() 50 | (site_dir / "category").mkdir() # New directory for category pages 51 | (site_dir / "assets").mkdir() 52 | 53 | # Setup Jinja2 environment 54 | if not template_dir.exists(): 55 | raise FileNotFoundError(f"Templates directory not found: {template_dir}") 56 | 57 | env = Environment(loader=FileSystemLoader(template_dir)) 58 | 59 | # Find and parse all XLS documents using the parser module 60 | xls_docs = find_xls_documents(root_dir) 61 | 62 | # Generate HTML for each document 63 | for doc in xls_docs: 64 | folder = root_dir / doc.folder 65 | readme_path = folder / "README.md" 66 | 67 | try: 68 | with open(readme_path, "r", encoding="utf-8") as f: 69 | content = f.read() 70 | 71 | # Convert to HTML 72 | html_content = convert_markdown_to_html(content) 73 | 74 | # Render XLS page 75 | xls_template = env.get_template("xls.html") 76 | rendered_html = xls_template.render( 77 | doc=doc, 78 | content=html_content, 79 | title=f"XLS-{doc.number}: {doc.title}", 80 | base_url=".." if base_url == "." else base_url, 81 | ) 82 | 83 | # Write XLS HTML file 84 | output_path = site_dir / "xls" / f"{doc.folder}.html" 85 | with open(output_path, "w", encoding="utf-8") as f: 86 | f.write(rendered_html) 87 | 88 | print(f"Generated: {output_path}") 89 | 90 | except Exception as e: 91 | print(f"Error processing {doc.folder}: {e}") 92 | raise 93 | 94 | # Sort documents by number in reverse order (later ones more relevant) 95 | xls_docs.sort(key=lambda x: int(x.number), reverse=True) 96 | 97 | # Group documents by category for category pages and navigation 98 | categories = {} 99 | for doc in xls_docs: 100 | category = doc.category 101 | if category not in categories: 102 | categories[category] = [] 103 | categories[category].append(doc) 104 | 105 | # Generate category pages 106 | category_template = env.get_template("category.html") 107 | all_categories = [(cat, len(docs)) for cat, docs in sorted(categories.items())] 108 | 109 | for category, category_docs in categories.items(): 110 | # Sort category documents by number in reverse order 111 | category_docs.sort(key=lambda x: int(x.number), reverse=True) 112 | 113 | category_html = category_template.render( 114 | title=f"{category} XLS Standards", 115 | category=category, 116 | category_docs=category_docs, 117 | all_categories=all_categories, 118 | total_count=len(xls_docs), 119 | base_url=".." if base_url == "." else base_url, 120 | ) 121 | 122 | # Write category HTML file 123 | category_file = site_dir / "category" / f"{category.lower()}.html" 124 | with open(category_file, "w", encoding="utf-8") as f: 125 | f.write(category_html) 126 | 127 | print(f"Generated category page: {category_file}") 128 | 129 | # Generate index page with category navigation 130 | index_template = env.get_template("index.html") 131 | index_html = index_template.render( 132 | title="XRP Ledger Standards (XLS)", 133 | total_count=len(xls_docs), 134 | xls_docs=xls_docs, 135 | all_categories=all_categories, 136 | base_url=base_url, 137 | ) 138 | 139 | # Write index file 140 | with open(site_dir / "index.html", "w", encoding="utf-8") as f: 141 | f.write(index_html) 142 | 143 | # Generate contribute page from CONTRIBUTING.md 144 | contributing_path = root_dir / "CONTRIBUTING.md" 145 | if contributing_path.exists(): 146 | try: 147 | with open(contributing_path, "r", encoding="utf-8") as f: 148 | contributing_content = f.read() 149 | 150 | # Convert markdown to HTML 151 | contributing_html_content = convert_markdown_to_html(contributing_content) 152 | 153 | # Render contribute page 154 | contribute_template = env.get_template("contribute.html") 155 | contribute_html = contribute_template.render( 156 | title="Contributing to XLS Standards", 157 | content=contributing_html_content, 158 | base_url=base_url, 159 | ) 160 | 161 | # Write contribute file 162 | with open(site_dir / "contribute.html", "w", encoding="utf-8") as f: 163 | f.write(contribute_html) 164 | 165 | print(f"Generated contribute page from CONTRIBUTING.md") 166 | 167 | except Exception as e: 168 | print(f"Error generating contribute page: {e}") 169 | else: 170 | print("Warning: CONTRIBUTING.md not found") 171 | 172 | # Copy CSS file 173 | css_source = assets_dir / "style.css" 174 | css_dest = site_dir / "assets" / "style.css" 175 | if css_source.exists(): 176 | shutil.copy2(css_source, css_dest) 177 | else: 178 | raise FileNotFoundError(f"CSS file not found: {css_source}") 179 | 180 | # Copy favicon 181 | favicon_source = assets_dir / "favicon.ico" 182 | favicon_dest = site_dir / "assets" / "favicon.ico" 183 | if favicon_source.exists(): 184 | shutil.copy2(favicon_source, favicon_dest) 185 | else: 186 | print(f"Warning: Favicon not found: {favicon_source}") 187 | 188 | print(f"Site built successfully! Generated {len(xls_docs)} XLS documents.") 189 | 190 | # Count by status for reporting 191 | # Count documents by status (case-insensitive, no hardcoding) 192 | status_counts = Counter(getattr(doc, "status", "").strip().lower() or "unknown" for doc in xls_docs) 193 | 194 | for status, count in status_counts.items(): 195 | print(f"- {status.capitalize()}: {count}") 196 | 197 | 198 | if __name__ == "__main__": 199 | build_site() 200 | -------------------------------------------------------------------------------- /XLS-0064-pseudo-account/README.md: -------------------------------------------------------------------------------- 1 |
  2 | xls: 64
  3 | title: Pseudo-Account
  4 | description: A standard for a "pseudo-account" AccountRoot object to be associated with one or more ledger entries.
  5 | author: Vito Tumas (@Tapanito)
  6 | status: Draft
  7 | category: Amendment
  8 | created: 2025-03-04
  9 | updated: 2025-08-29
 10 | 
11 | 12 | ### Abstract 13 | 14 | This document proposes a standard for a _pseudo-account_, an `AccountRoot` ledger entry that can be associated with one or more other ledger entries. A pseudo-account is designed to hold and/or issue assets on behalf of its associated entries, enabling protocol-level functionality that requires an on-ledger entity to manage funds. 15 | 16 | ### Motivation 17 | 18 | The XRP Ledger is an account-based system where assets (XRP, IOUs, etc.) can only be held by an `AccountRoot` entry. However, several advanced protocols, such as Automated Market Makers (AMMs), lending pools, and vaults, require a ledger _object_ itself to hold and manage assets. 19 | 20 | The [XLS-30 (AMM)](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0030-automated-market-maker#readme) specification pioneered this concept by introducing a pseudo-account linked to each `AMM` instance. This allows the AMM to track its token balances and issue Liquidity Provider Tokens (`LPTokens`). 21 | 22 | This specification formalizes and standardizes the requirements for an `AccountRoot` when it functions as a pseudo-account, ensuring a consistent and secure implementation across different protocols. It defines mandatory flags, a naming convention for linking fields, and the core invariants that any protocol using a pseudo-account must enforce. 23 | 24 | ### Specification 25 | 26 | #### Ledger Entries 27 | 28 | This specification defines a set of mandatory properties and fields for an `AccountRoot` ledger entry when it is used as a pseudo-account. 29 | 30 | ##### **`AccountRoot`** 31 | 32 | ###### **Object Identifier** 33 | 34 | The address of the pseudo-account's `AccountRoot` must be derived deterministically and be difficult to predict before creation. This prevents malicious actors from front-running the creation transaction by pre-funding the address. The protocol creating the `AccountRoot` must ensure the derived address is unoccupied. 35 | 36 | A nonce-based approach is used to generate the unique `AccountRoot` ID: 37 | 38 | 1. Initialize a nonce, $i$, to $0$. 39 | 2. Compute a candidate ID: `AccountID` = `SHA512-Half`($i$ || `ParentLedgerHash` || ``). 40 | 3. Check if an `AccountRoot` with this `AccountID` already exists on the ledger. 41 | 4. If it exists, increment the nonce $i$ and repeat from step 2. 42 | 5. If it does not exist, the computed `AccountID` is used for the new pseudo-account. 43 | 44 | ###### **Fields** 45 | 46 | | Field Name | Constant | Required | Internal Type | Default Value | Description | 47 | | :----------- | :------: | :------: | :-----------: | :-----------: | :--------------------------------------------------------------------------------- | 48 | | `ID` | Yes | Yes | `HASH256` | N/A | The unique identifier of the ledger object this pseudo-account is associated with. | 49 | | `Flags` | Yes | Yes | `UINT32` | N/A | A set of flags that must be set for a pseudo-account. | 50 | | `Sequence` | Yes | Yes | `UINT32` | `0` | The sequence number, which must be `0`. | 51 | | `RegularKey` | Yes | No | `ACCOUNT` | N/A | A regular key, which must not be set for a pseudo-account. | 52 | 53 | A detailed description of these fields follows: 54 | 55 | **`ID`** 56 | 57 | This field links the pseudo-account to its parent ledger object. Any protocol introducing a pseudo-account must define a new, optional field on the `AccountRoot` object to store this ID. The field name must follow this convention: 58 | 59 | - `` is the name of the associated ledger object (e.g., `AMM`, `Vault`). Names that are acronyms should be fully capitalized (`AMMID`). Otherwise, use PascalCase (`VaultID`). 60 | - The suffix `ID` must always be appended. 61 | 62 | **`Flags`** 63 | 64 | The following flags must be set on a pseudo-account's `AccountRoot` and must be immutable: 65 | 66 | | Flag Name | Hex Value | Description | 67 | | :----------------- | :----------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 68 | | `lsfDisableMaster` | `0x00040000` | Disables the master key pair, ensuring no entity can sign transactions directly for this account. Control is ceded entirely to protocol rules. | 69 | | `lsfDepositAuth` | `0x01000000` | Requires authorization for deposits, typically meaning that funds can only be sent to this account via specific protocol transactions rather than standard `Payment` transactions. | 70 | 71 | **`Sequence`** 72 | 73 | The `Sequence` number of a pseudo-account must be initialized to `0` and must not be changed. This, combined with the disabled master key, prevents the account from ever submitting a transaction on its own behalf. 74 | 75 | **`RegularKey`** 76 | 77 | A `RegularKey` must not be set on a pseudo-account. 78 | 79 | ###### **Reserves** 80 | 81 | The cost of creating a pseudo-account depends on whether it is owned and controlled by another account. 82 | 83 | - **Owned Pseudo-Accounts:** For objects like a `Vault` where a single account owns and controls the associated pseudo-account, the transaction must increase the owner's XRP reserve by one increment. This is in addition to any other reserve requirements of the transaction (e.g., for the `Vault` object itself). The transaction fee is the standard network fee. 84 | 85 | - **Unowned Pseudo-Accounts:** For objects like an `AMM` that are not owned by any account, the creation transaction must charge a special, higher-than-normal transaction fee. This fee must be at least the value of one incremental owner reserve (currently **2 XRP**, subject to change via Fee Voting). This amount is burned, compensating for the permanent ledger space without tying the reserve to a specific owner. 86 | 87 | ###### **Deletion** 88 | 89 | A pseudo-account must be deleted together with the associated object. 90 | 91 | ###### **Invariants** 92 | 93 | The following invariants must hold true for any `AccountRoot` entry functioning as a pseudo-account: 94 | 95 | - The ledger object identified by the `ID` field must exist. 96 | - Exactly one `ID` field must be present on the `AccountRoot` (e.g., an account cannot be linked to both an `AMMID` and a `VaultID`). 97 | - The `lsfDisableMaster` and `lsfDepositAuth` flags must always be set. 98 | - The `Sequence` number must always be `0`, and must never change. 99 | - AMM pseudo-accounts created under old rules will have a sequence number set to the index of the ledger they were created in. They still must never change. 100 | - A `RegularKey` must not be set. 101 | 102 | ### Security Considerations 103 | 104 | The design of pseudo-accounts includes several critical security features: 105 | 106 | - **No Direct Control:** The mandatory `lsfDisableMaster` flag and the absence of a `RegularKey` ensure that no user can directly control the pseudo-account or its assets. All fund movements are governed exclusively by the rules of the associated protocol. 107 | - **Transaction Prevention:** A `Sequence` of `0` makes it impossible for the account to submit transactions, preventing any misuse of the account itself. 108 | - **Address Front-running Prevention:** The deterministic but unpredictable method for generating the account address prevents attackers from guessing the address and sending funds to it before it is officially created by the protocol. 109 | - **Controlled Deposits:** The `lsfDepositAuth` flag prevents arbitrary `Payment` transactions from being sent to the account, ensuring that its balances can only be modified through legitimate protocol transactions. 110 | -------------------------------------------------------------------------------- /XLS-0025-enhanced-secret-numbers/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 25
  3 |   title: Enhanced Secret Numbers
  4 |   description: Enhances XLS-12 secret number format by introducing an additional block for encoding ancillary information, and supporingt for longer secrets.
  5 |   author: Nik Bougalis 
  6 |   status: Final
  7 |   category: Ecosystem
  8 |   created: 2021-12-10
  9 | 
10 | 11 | # Enhanced Secret Numbers 12 | 13 | ## Abstract 14 | 15 | The Secret Numbers proposal **XLS-12** introduces a new format for secrets - based on blocks of numbers/digits (0-9) with block-level checksums - to generate XRP Ledger account keys and address. This draft proposes augmenting and enhancing **XLS-12** to make the format more flexible and more robust. 16 | 17 | ### Motivation 18 | 19 | The XRP Ledger typically uses 128-bit seeds as the cryptographic material from which cryptographic keys for accounts are derived. Such seeds are generally too long for humans to remember and the existing formats are hard to transcribe. Prior to XLS-12, there were 4 canonical encoding for the seed data: 20 | 21 | 1. A Base58-based encoding scheme beginning with the letter `s` (in this format, the result is often, but _incorrectly_, referred to as a private key); or 22 | 2. A word-based "mnemonic", encoded using a variant of RFC 1751; or 23 | 3. A raw hex string, directly representing the 128-bit seed; or 24 | 4. A passphrase, an arbitrary string of words which is hashed to produce a seed. 25 | 26 | By way of example, let's use the well-known [genesis account](https://xrpl.org/accounts.html#special-addresses), `rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh`, which has an associated **`secp256k`** key: 27 | 28 | 1. The seed for the account is `snoPBrXtMeMyMHUVTgbuqAfg1SUTb`; 29 | 2. The mnemonic is `I IRE BOND BOW TRIO LAID SEAT GOAL HEN IBIS IBIS DARE`; 30 | 3. The raw hex is `DEDCE9CE67B451D852FD4E846FCDE31C`; and 31 | 4. The corresponding passphrase is `masterpassphrase`. 32 | 33 | The **XLS-12** standard introduced a new format, secret mumbers, which consists of 8 blocks of 6 digits, where the first five digits correspoding to a 16-bit number (from 0 to 65535) and the 6th digit is a checksum based on the five preceding digits and the index of the block. The **XLS-12** secret numbers for the genesis account are: `570521-598543-265488-209520-212450-201006-286214-581400`. 34 | 35 | ### Issues with XLS-12 36 | 37 | The **XLS-12** specification, while convenient and easier to transcribe, has several drawbacks: 38 | 39 | First, it encodes no ancillary information about the information encoded (e.g. whether it is meant to derive a **secp256k1** or an **Ed25519** key) and is limited to only encoding seeds, from which keys are then derived. Second, the block-level checksum is inefficient and detects only a small percentage of errors. 40 | 41 | Lastly, it's unclear whether the secret numbers should be used to create a **secp256k1** or an **Ed25519** key. For example, given the above `570521-598543-265488-209520-212450-201006-286214-581400` secret numbers, an implementation could derive an **Ed25519** and, therefore, an account that is not the well-known genesis account. Tools that support secret numbers typically resort to detecting the type of account, usually in a semi- automated fashion, but on occasion by resorting to asking the user to confirm their intended public address. 42 | 43 | ### Enhancements to XLS-12 44 | 45 | This proposal aims to address these problems by proposing extensions to **XLS-12** which retaining backwards compatibility. There are three primary changes: 46 | 47 | 1. Using a simplified but more powerful per-block checksum; 48 | 2. Introducing an additional block, which is used to encode ancillary information; and 49 | 3. Support for longer blocks, allowing encoding of 128-bit and 256-bit secrets. 50 | 51 | Secret numbers compatible with this specification will always be composed of an odd number of blocks; secret numbers compatible with the **XLS-12** specification will always be composed of an even number of blocks—or more specifically, precisely 8 blocks. This makes it possible to seamlessly detect which standard was used and allows for backwards compatibility. 52 | 53 | #### Per-block checksusm 54 | 55 | Under **XLS-12** the Nth block consists of 16 bits of raw key material, and an additional checksum digit. Assuming the keying material has value `X` the Nth block would be: `(X * 10) + ((X * (N * 2 + 1)) % 9)`. 56 | 57 | In theory, the checksum digit can take any value from [0,...,9] but, by construction, not all checksum digits are equally likely across all blocks, reducing their usefulness. For blocks at positions 2 and 8, the only valid checksum digits are `0`, `3` and `6`; blocks at position 5 are worse, with `0` being the _only_ valid checksum digit. 58 | 59 | This proposal simplifies the checksum function for key blocks by reducing it to a simple multiplication by 13, a prime number. That is, the Nth key block simply becomes: `X * 13`. Under this scheme, the possible values are multiple of 13 and and the range is from [0,...,851955]. This scheme avoids the position-dependent behavior observed with the existing scheme and allows for more efficient error detection. 60 | 61 | #### Information Block 62 | 63 | This proposal _prepends_ an additional 6-digit block, composed of two 16-bit values, that encodes ancillary information about the content and a checksum of the blocks that can be used to detect whole-block transposition, or incorrect blocks. 64 | 65 | First, calculate the checksum of all blocks as `(∑(𝑥[i] * π[i]>)) mod 256`, where `𝑥[i]` is the ith 16-bit block of data being encoded and `π[i]` is the ith prime number. The result is an 8-bit value, `C`. 66 | 67 | ##### Flags 68 | 69 | The proposal also defines an 8-bit field, that can be used to specify details or ancillary information about the key. This proposal defines this as a bitfield named `F`. The following flags are defined: 70 | 71 | | Value | Meaning | 72 | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | 73 | | `0x01` | The data block represents a raw 128-bit _seed_, from which private keys can be derived using the derivation algorithm defined in `rippled`. | 74 | | `0x02` | The private key calculated or derived from this block of data is used with the **secp256k1** algorithm. | 75 | | `0x04` | The private key calculated or derived from this block of data is used with the **Ed25519** algorithm. | 76 | | `0x08` | The data block represents an **Ed25519** validator master key. This flag cannot be combined with any other flags. | 77 | | `0x10` | The data block represents the fulfillment of a `PREIMAGE-SHA256` cryptocondition. This flag cannot be combined with any other flags. | 78 | 79 | ##### Using Flags 80 | 81 | The different flags used with extended keys make it possible to directly encode different types of keys as well as allow for 128-bit and 256-bit keys. 82 | 83 | For example: 84 | 85 | - A 256-bit **`secp256k`** (or **`Ed25519`**, if the `0x08` flag is included) key can be directly encoded as 17 groups, if they information block does not include the `0x01` flag. 86 | - A 256-bit preimage for a cryptocondition can be encoded as 17 groups, if the information block includes the `0x10` flag. 87 | 88 | Other flags may be added in the future. A compliant implementation of this proposal _SHOULD_ fail if any flags other than the ones it understands are used. 89 | 90 | ##### Information Block Checksum 91 | 92 | By construction, no valid XLS12 block can have a value greater than 655356 (i.e. 65535 \* 10 + (65535 % 9)). This means that any block that begins with a 7, 8 or 9 cannot be a valid XLS12 block. This means that we can construct the first block in such a way so as to allow for automatic detection of "classic" XLS-12 keys and "extended" keys as defined in this proposal. 93 | 94 | Again, assume that C is the checksum described above and F is the flag bitfield, then the information block checksum `X` is defined as: 95 | 96 | X = (F ^ C) % 3 97 | 98 | The information block itself is then calculated as: 99 | 100 | A = ((X + 7) * 100000) + (C * 256 + F) 101 | 102 | The end result is an A block that begins with 7, 8 or 9, which means it cannot be a valid XLS12 block, while still retaining a checksum in the A block. 103 | -------------------------------------------------------------------------------- /XLS-0016-nft-metadata/README.md: -------------------------------------------------------------------------------- 1 |
  2 |     xls: 16
  3 |     title: NFT Metadata
  4 |     author: Hubert Getrouw (@HubertG97)
  5 |     created: 2021-03-17
  6 |     status: Stagnant
  7 |     category: Ecosystem
  8 | 
9 | 10 | In addition to @WietseWind 's [XLS-14d](https://github.com/XRPLF/XRPL-Standards/discussions/30) and @RichardAH 's proposal [XLS-15d](https://github.com/XRPLF/XRPL-Standards/discussions/34) here is a proposal to create a standard for the creation of metadata for the tokens created with a CTI in the currency code. 11 | 12 | When issuing an indivisible token on the XRPL the only data given is the currency code. For optimal usage, there has to be more metadata for an NFT. For example a description and a URI to an IPFS file. 13 | Using the Concise Transaction Identifier, a prior transaction can be used to mark the metadata contained in the memo's field for the use of the NFT. 14 | 15 | The [Memos Field](https://xrpl.org/transaction-common-fields.html#memos-field) in an XRPL transaction is presented as an array of objects which contains one memo field per object. This field consists of the following fields: 16 | 17 | - MemoData 18 | - MemoFormat 19 | - MemoType 20 | 21 | The MemoData field can be used for the metadata itself and the MemoFormat indicates the nature of the given data inside the MemoData field (MIME type). To create a certain hierarchy for covering failure of the URI specified, the MemoType field contains the numbering of the data named as shown below followed by the MIME type: 22 | 23 | - NFT/0 - _Description_ - `text/plain` 24 | - NFT/1 - _Author_ - `text/plain` 25 | - NFT/2 - _Primary URI_ - `text/uri` 26 | - NFT/3 - _Back-up URI_ - `text/uri` 27 | - NFT/4 - _Reduced image Data URI as last back-up_ - `text/uri` 28 | 29 | The usage of a back-up URI and Data URI can be seen as optional and can be replaced with other kinds of data that have the preference of the issuer of the NFT for example contact information. 30 | The limit of storage is 1kb of data in the memo's field in total. Multiple memos can be used to give as much information as fits to the 1kb of data. 31 | 32 | If there is only one memo on the CTI referenced transaction and the memo data contains a URI of any sort then this is deemed to be the NFT content. The multiple memo's structure will be the advanced method to issue NFTs. The standard will also be compatible with previously created NFTs referred to as the simple method. 33 | 34 | --- 35 | 36 | **Issuing** 37 | 38 | For the metadata, there has to be created a transaction from the same address as the issuer of the NFT to for example a hot wallet. This transaction of 1 drop contains the description and URIs needed for the NFT. 39 | 40 | The currency code for an NFT consists of 3 parts: 41 | 42 | - Prefix 02 for HEX currency code 43 | - [CTI](https://github.com/XRPLF/XRPL-Standards/discussions/34) (Concise Transaction Identifier) 44 | - Short name converted to HEX for the NFT to a maximum of 12 characters or less (filled up with 0's if it's less) 45 | 46 | After this, a Trust line can be set up using the above currency code and the NFTs being transferred from the issuing address to the hot wallet. 47 | 48 | --- 49 | 50 | ### Advanced method 51 | 52 | _For example_ 53 | 54 | **Issuer:** `rBzoA1EXxE2FeGV4Z57pMGRuzd3dfKxVUt` 55 | **Hot wallet:** `rp9d3gds8bY7hkP8FmNqJZ1meMtYLtyPoz` 56 | 57 | The JSON for the metadata transaction would look like this: 58 | 59 | ``` 60 | { 61 | "Account": "rBzoA1EXxE2FeGV4Z57pMGRuzd3dfKxVUt", 62 | "TransactionType": "Payment", 63 | "Amount": "1", 64 | "Destination": "rp9d3gds8bY7hkP8FmNqJZ1meMtYLtyPoz", 65 | "Fee": "100000", 66 | "Memos": [{ 67 | "Memo": { 68 | "MemoData": "546861742773206F6E6520736D616C6C20696D616765206F66206D6F6F6E2C206F6E65206769616E74206C65617020666F72204E4654206F6E20746865205852504C", 69 | "MemoFormat": "746578742F706C61696E", 70 | "MemoType": "6E66742F30" 71 | } 72 | }, 73 | { 74 | "Memo": { 75 | "MemoData": "48756265727420476574726F7577", 76 | "MemoFormat": "746578742F706C61696E", 77 | "MemoType": "6E66742F31" 78 | } 79 | }, 80 | { 81 | "Memo": { 82 | "MemoData": "697066733A2F2F62616679626569686561786B696A3276656D6B7337726B716E6F67367933367579793337626B33346D697533776F72636A756F6833747532773279", 83 | "MemoFormat": "746578742F757269", 84 | "MemoType": "6E66742F32" 85 | } 86 | }, 87 | 88 | { 89 | "Memo": { 90 | "MemoData": "68747470733A2F2F676574726F75772E636F6D2F696D672F707572706C656D6F6F6E2E706E67", 91 | "MemoFormat": "746578742F757269", 92 | "MemoType": "6E66742F33" 93 | } 94 | }, 95 | { 96 | "Memo": { 97 | "MemoData": "646174613A696D6167652F6769663B6261736536342C52306C474F446C684641415541505141414141414142415145434167494441774D4542415146425155474267594842776348392F66342B506A352B666E362B7672372B2F76382F507A392F66332B2F76377741414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414143483542414541414241414C4141414141415541425141414157524943534F5A476D65614B7175624F74437A664D7551564D456937674D41494D554D684942554F4D784941454141544A736942674F4A674267654267456A476E413063673945524446464342364E4D6248305945684543454579784844414354316571383352486C69654D73474147414641414949436A495051776F4F66675A4A41544A5A5932414C4255634B535A516A684552424A41384355774F6750414B6649326445547156554A6A774166364345435352694F436F4D42416F6A6A61675149514137", 98 | "MemoFormat": "746578742F757269", 99 | "MemoType": "6E66742F34" 100 | } 101 | }] 102 | 103 | 104 | } 105 | ``` 106 | 107 | Converted to human-readable output's [this](https://xrpcharts.ripple.com/#/transactions/24ADC5D0EF72DDA45A7464A7F74B762801FA12C26F6BFEEFC61CF72140623F27) transaction 108 | 109 | Using this transaction's txn hash, txn_index, ledger_hash, and ledger_index creates a CTI of `23080995397183855` 110 | Converted to HEX it will be `52000B03B6296F` 111 | 112 | The name of the NFT will be '_Purple moon_'. 113 | After conversing this to HEX the complete currency code looks like this `0252000B03B6296F507572706C65206D6F6F6E00` 114 | 115 | **02** - _XRPL NFT identifier_ 116 | **52000B03B6296F** - _CTI_ 117 | **507572706C65206D6F6F6E00** - _NFT name_ 118 | 119 | When Issuing a token the same address has to be used as the sender of the aforementioned transaction. 120 | As explained in this [blogpost](https://coil.com/p/Huub/Introduction-to-NFT-on-the-XRP-Ledger/4ee41zWW-) a Trust line has to be created between the Issuer and the hot wallet. 121 | 122 | Make sure the issuer address has an `AccountSet` of `SetFlag` to `8` 123 | 124 | ``` 125 | { 126 | "TransactionType": "TrustSet", 127 | "Account": "rp9d3gds8bY7hkP8FmNqJZ1meMtYLtyPoz", 128 | "Fee": "12", 129 | "Flags" : 131072, 130 | "LimitAmount": { 131 | "currency": "0252000B03B6296F507572706C65206D6F6F6E00", 132 | "issuer": "rBzoA1EXxE2FeGV4Z57pMGRuzd3dfKxVUt", 133 | "value": "1000000000000000e-95" 134 | } 135 | } 136 | ``` 137 | 138 | In the currency field the HEX converted currency code is used. 139 | The value is set to `1000000000000000e-95` which will result in 10 NFTs. 140 | More explanation about this can be found in @WietseWind's proposal [XLS-14d](https://github.com/XRPLF/XRPL-Standards/discussions/30) 141 | 142 | Last step is to send the tokens from the issuer to the hot wallet. 143 | 144 | ``` 145 | { 146 | "TransactionType": "Payment", 147 | "Account": "rBzoA1EXxE2FeGV4Z57pMGRuzd3dfKxVUt", 148 | "Fee": "12", 149 | "Destination" : "rp9d3gds8bY7hkP8FmNqJZ1meMtYLtyPoz", 150 | "Amount": { 151 | "currency": "0252000B03B6296F507572706C65206D6F6F6E00", 152 | "issuer": "rBzoA1EXxE2FeGV4Z57pMGRuzd3dfKxVUt", 153 | "value": "1000000000000000e-95" 154 | } 155 | } 156 | ``` 157 | 158 | Now there are 10 Purple moon NFTs on address `rp9d3gds8bY7hkP8FmNqJZ1meMtYLtyPoz` 159 | 160 | ### Simple Method 161 | 162 | The JSON for the metadata transaction would look like this: 163 | 164 | ``` 165 | { 166 | "Account": "rBzoA1EXxE2FeGV4Z57pMGRuzd3dfKxVUt", 167 | "TransactionType": "Payment", 168 | "Amount": "1", 169 | "Destination": "rp9d3gds8bY7hkP8FmNqJZ1meMtYLtyPoz", 170 | "Fee": "100000", 171 | "Memos": [{ 172 | "Memo": { 173 | "MemoData": "697066733A2F2F62616679626569666C6A667870786F7A6E6A703273357266697270666A756E7876706B71737863727133766C626F6C346536717A376F7972693571", 174 | "MemoFormat": "746578742F757269", 175 | "MemoType": "7872706C2F6E6674" 176 | } 177 | }] 178 | } 179 | ``` 180 | 181 | Converted to human-readable output's [this](https://xrpcharts.ripple.com/#/transactions/7DFCD417FCEE35F7BB3ABECD05C27BA71F1E845BFD29C19AF3CF5E55B44EA55C) transaction 182 | 183 | After that, a trust set and sending the tokens is the same as the advanced method 184 | -------------------------------------------------------------------------------- /XLS-0023-lite-accounts/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 23
  3 |   title: Lite Accounts
  4 |   description: A proposal for lite accounts with reduced account reserves but limited features
  5 |   author: Wietse Wind (@WietseWind)
  6 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/56
  7 |   status: Stagnant
  8 |   category: Amendment
  9 |   created: 2021-08-27
 10 | 
11 | 12 | # LiteAccounts Amendment 13 | 14 | By @RichardAH, @WietseWind 15 | 16 | ## Introduction 17 | 18 | The XRP Ledger is a fast consensus-based blockchain which, unlike competing chains, maintains its user's balances as persistent state rather than as a graph of unspent transaction outputs. This imposes unique per-account storage costs on the network. These costs are passed on to the user. 19 | 20 | The cost of creating an account on the ledger is, at time of writing, `5 XRP` with an additional lockup of `15 XRP` (which is redeemable if the account is later deleted). Collectively this is known as the Account Reserve. It has been argued the Account Reserve acts as a barrier to entry for new users and remains a major impedement to the expansion of the ecosystem. 21 | 22 | The `AccountRoot` ledger object represents the authoritative state of an XRPL account. 23 | 24 | It contains the following fields: 25 | 26 | | Field | Type | Bytes | Required | Lite Account | 27 | | ------------------- | --------- | ----------- | -------- | ------------ | 28 | | sfLedgerEntryType | UInt16 | 3 | ✅ | ✅  | 29 | | sfFlags | UInt32 | 5 | ✅ | ✅  | 30 | | sfAccount | AccountID | 22 | ✅ | ✅  | 31 | | sfSequence | UInt32 | 5 | ✅ | ✅  | 32 | | sfBalance | Amount | 9 | ✅ | ✅  | 33 | | sfOwnerCount | UInt32 | 5 | ✅ | ✅  | 34 | | sfPreviousTxnID | UInt256 | 33 | ✅ | ✅  | 35 | | sfPreviousTxnLgrSeq | UInt32 | 5 | ✅ | ✅  | 36 | | sfAccountTxnID | UInt256 | 33 |   |   | 37 | | sfRegularKey | AccountID | 22 |   |   | 38 | | sfEmailHash | UInt128 | 17 |   |   | 39 | | sfWalletLocator | UInt256 | 33 |   |   | 40 | | sfWalletSize | UInt32 | 5 |   |   | 41 | | sfMessageKey | Blob | 33 |   |   | 42 | | sfTransferRate | UInt32 | 5 |   |   | 43 | | sfDomain | Blob | at most 256 |   |   | 44 | | sfTickSize | UInt8 | 4 |   |   | 45 | | sfTicketCount | UInt32 | 6 |   |   | 46 | | sfSponsor^^ | AccountID | 22 |   | ✅  | 47 | 48 | ^^ Part of this proposal 49 | 50 | An XRPL Account which owns no on-ledger objects (i.e: does not have any trustlines, escrows, offers, etc.), does not specify a `regular key` and does not set any of its other optional fields has a serialized size of `87 bytes` (excluding storage overhead.) 51 | 52 | We propose that any such _minimal_ account root imposes such a small burden on the ledger that it should be able to be treated differently to _full_ account roots. 53 | 54 | ## Lite Accounts 55 | 56 | We propose that a **lite account** is an XRPL Account with: 57 | 58 | - a _minimal_ account root 59 | - the _new_ `asfLiteAccount` account flag set, and, 60 | - (optionally) the _new_ `sfSponsor` field set. 61 | 62 | #### Lite accounts have an Account Reserve of `1/5th` the ledger's object reserve (i.e. `1 XRP` at time of writing.) 63 | 64 | ## ⚠️ Restrictions 65 | 66 | A lite account **cannot**: 67 | 68 | - Own on-ledger objects, and thus cannot: 69 | - Hold or create Trustline balances 70 | - Have non-default limits on incoming trustlines 71 | - Create Offers 72 | - Create Escrows 73 | - Create Checks 74 | - Create Payment Channels 75 | - Create or install Hooks (pending amendment) 76 | - Set or use a Signer List 77 | - Create Tickets 78 | - Require or use Deposit Authorizations 79 | - Set or use a Regular Key 80 | - Become a sponsor for another lite account 81 | - Perform `AccountSet` on any fields except `sfSponsor` and `sfFlags` 82 | 83 | ## New Fields and Flags 84 | 85 | The following changes are made to fields and flags. 86 | 87 | 1. `sfSponsor` — a new optional _AccountID_ field on the acount root which indicates that another account owns the reserve for the account root. 88 | 2. `tfSponsor` — a new transaction flag that indicates the intent of the sender to create and sponsor a new lite account. 89 | 3. `asfLiteAccount` — a new account flag that can be set and unset subject to certain conditions, which indicates that the account is a lite account and subject to the restrictions of a lite account. 90 | 91 | These are explained in further detail below. 92 | 93 | ## Sponsorship 94 | 95 | Lite accounts may be _sponsored_. Sponsorship provides that another account pays the account reserve and, subject to conditions, is entitled to recover the entire account reserve should the lite account be later deleted. _Full_ accounts cannot be sponsored, only accounts with `asfLiteAccount` flag can be sponsored. 96 | 97 | To sponsor a new account creation: 98 | A `ttPAYMENT` transaction is created and successfully submitted, which: 99 | 100 | 1. Specifies an unfunded destination account, 101 | 2. Specifies the (new) `tfSponsor` flag, and 102 | 3. Sends at least as much `XRP` as the lite account reserve. 103 | 104 | There is no way for an already established XRPL account (full or lite) to subsequently become a sponsored account. Sponsorship can only occur through account creation. 105 | 106 | ## Reclamation 107 | 108 | A **sponsor** is entitled to _reclaim_ the Account Reserve on a lite account (for which they are the sponsor) subject to certain conditions: 109 | 110 | ### Scenario 1: AccountDelete 111 | 112 | If a sponsored lite account: 113 | 114 | - contains less than two times the lite account reserve, and 115 | - does not sign and submit any transaction that results in a `tesSUCCESS` on a validated ledger for more than `1 million` ledgers, 116 | 117 | Then the account's sponsor may recover the Account Reserve via an `AccountDelete` transaction. The spsonsor also receives the remaining balance in the lite account. 118 | 119 | The `AccountDelete` transaction specifies the lite account as the `sfAccount` field but is signed by the sponsor. 120 | 121 | ### Scenario 2: AccountSet 122 | 123 | If a sponsored lite account, at any time: 124 | 125 | - contains at least two times the lite account reserve 126 | 127 | Then the account's `sponsor` may recover the Account Reserve via an `AccountSet` transaction that _clears_ the `sfSponsor` field. 128 | 129 | The `AccountSet` transaction specifies the lite account as the `sfAccount` field but is signed by the sponsor and can only be used to delete the `sfSponsor` field. 130 | 131 | Doing so results in a balance mutation on both the lite account and the sponsor account to reflect the return of the lite Account Reserve. 132 | 133 | ## Upgrading 134 | 135 | The owner of a lite account can upgrade their account twice: 136 | 137 | ### Scenario 1: Removal of Sponsor 138 | 139 | The owner of a lite account may unilaterally unsponsor his or her own account by: 140 | 141 | - ensuring the account contains at least twice the lite Account Reserve, and 142 | - creating and successfully submitting an `AccountSet` transaction that _clears_ the `sfSponsor` field. 143 | 144 | Doing so results in a balance mutation on both the lite account and the sponsor account to reflect the return of the lite Account Reserve. 145 | 146 | ### Scenario 2: Full Account 147 | 148 | The owner of an unsponsored lite account may upgrade the account to a _full_ account by: 149 | 150 | - ensuring the account contains at least the Full Account Reserve (at time of writing `20 XRP`) 151 | - creating and successfully submitting an `AccountSet` transaction that _clears_ the `asfLiteAccount` flag. 152 | 153 | ## Downgrading 154 | 155 | The owner of a _full_ account may opt to downgrade their account to a lite account by: 156 | 157 | - creating and successfully submitting an `AccountSet` transaction that _sets_ the `asfLiteAccount` flag. This action frees up the difference between the full Account Reserve and the lite Account Reserve. 158 | 159 | ## Deletion of Lite Accounts 160 | 161 | The owner of a lite account may delete their account subject to certain conditions: 162 | 163 | ### Scenario 1: Send all to Sponsor 164 | 165 | If the lite account is sponsored then the owner of the lite account may: 166 | 167 | - create and successfully submit an `AccountDelete` transaction, which 168 | - specifies the account `sponsor` as the `Destination` field 169 | 170 | ### Scenario 2: Upgrade and Delete 171 | 172 | If the lite account is sponsored then the owner of the lite account may upgrade their account to an unsponsored account. 173 | Once the lite account is unspsonsored, the user may proceed with a normal `AccountDelete` operation (with the proceeds going to any desired `Destination`). 174 | -------------------------------------------------------------------------------- /XLS-0095-rename-rippled-to-xrpld/README.md: -------------------------------------------------------------------------------- 1 |
  2 |   xls: 95
  3 |   title: Rename ripple(d) to xrpl(d)
  4 |   description: Renames references of "ripple(d)" to "xrpl(d)" in the documentation and codebase, and other downstream changes.
  5 |   discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/384
  6 |   author: Bart Thomee (@bthomee)
  7 |   status: Draft
  8 |   category: System
  9 |   created: 2025-10-22
 10 | 
11 | 12 | # System XLS: Renaming ripple(d) to xrpl(d) 13 | 14 | ## 1 Abstract 15 | 16 | This document describes a process to rename references of "ripple(d)" to "xrpl(d)" in the documentation and codebase of the XRPL open source project. This includes renaming the GitHub repository, the binary, configuration files, C++ namespaces, include guards, CMake files, code comments, artifacts, copyright notices, and the Ripple epoch. 17 | 18 | ## 2 Motivation 19 | 20 | In the initial phases of development of the XRPL, the open source codebase was called “rippled” and it remains with that name even today. Today, over 1000 nodes run the application, and code contributions have been submitted by developers located around the world. The XRPL community is larger than ever. We at Ripple are proposing a few changes to the project in light of the decentralized and diversified nature of XRPL. 21 | 22 | Some of what we describe here was floated [previously](https://github.com/XRPLF/XRPL-Standards/discussions/121) (see items 7 and 8), but ultimately was not followed up on. We are now taking this up again, because we deem it still to be the right thing to do. 23 | 24 | ## 3 Specification 25 | 26 | ### 3.1 Overview 27 | 28 | We aim to make the following changes. 29 | 30 | #### 3.1.1 Repository 31 | 32 | We will rename the [rippled](https://github.com/XRPLF/rippled) GitHub repository to [xrpld](https://github.com/XRPLF/xrpld). After renaming, the former URL will forward all requests to the latter URL, so anyone with the old link will still be able to find the codebase. 33 | 34 | #### 3.1.2 Binary 35 | 36 | We will rename the `rippled` binary to `xrpld`, and add a symlink named `rippled` that points to `xrpld`, so that node operators do not have to change anything on their end initially. We will further remove this symlink six months later to complete the switchover. 37 | 38 | #### 3.1.3 Config 39 | 40 | We will rename the `rippled.cfg` configuration file to `xrpld.cfg`, and to modify the code to accept both files. As above, we will further remove the old configuration file six months later to complete the switchover. 41 | 42 | #### 3.1.4 Namespace 43 | 44 | We will rename the `ripple` C++ namespace to `xrpl`. This will require other projects, such as `clio` to make a one-time update to their codebase as well, which we will help them with. 45 | 46 | #### 3.1.5 Include guards 47 | 48 | C++ include guards are used to prevent the contents of a header file from being included multiple times in a single compilation unit. These include guards currently start with `RIPPLE_` or `RIPPLED_`. We will rename both prefixes to `XRPL_`. 49 | 50 | #### 3.1.6 CMake 51 | 52 | We will rename the compiler files from `RippleXXX.cmake` or `RippledXXX.cmake` to `XrplXXX.cmake`, and any references to `ripple` and `rippled` (with or without capital letters) to `xrpl` and `xrpld`, respectively. 53 | 54 | #### 3.1.7 Code and comments 55 | 56 | We will rename references to `ripple` and `rippled` (with or without capital letters) to `xrpl` and `xrpld`, respectively. We will leave flags such as `tfNoRippleDirect` and `ltRIPPLE_STATE` untouched, as those do not refer to the company but to the concept of [rippling](https://xrpl.org/docs/concepts/tokens/fungible-tokens/rippling). 57 | 58 | #### 3.1.8 Artifacts 59 | 60 | The .rpm and .deb packages, and the [Docker image](https://hub.docker.com/r/rippleci/rippled) will be renamed too. As above, we will maintain the current artifacts for a period of six months, after which they will no longer be updated. 61 | 62 | #### 3.1.9 Copyright notices 63 | 64 | The root of the repository contains a [LICENSE.md](http://license.md/) file that lists the original contributors for the year of 2011, and the XRP Ledger developers from 2012 onwards, which is as intended. However, this is sometimes inconsistent with the individual copyright notices scattered throughout the codebase, which refer to XRP Ledger developers in different ways. 65 | 66 | Going forward, unless the copyright notices in individual files reference public code used from external projects (e.g. Bitcoin), such notices will be removed and the license file in the root of the repo will consistently apply throughout. 67 | 68 | #### 3.1.10 Ripple epoch 69 | 70 | We will rename the Ripple epoch, which refers to the timestamp of 01/01/2001 at 0:00 UTC, to XRPL epoch. 71 | 72 | These changes should also be made in the various xrpl libraries, where functions such as `xrpl.utils.datetime_to_ripple_time` will be maintained for six months. During that time period they will forward calls to the renamed function and will print a warning to the log about the deprecation. 73 | 74 | #### 3.1.11 Other 75 | 76 | After making the changes described above, there will invariably be a few references that we somehow missed, or newly appeared after an open PR gets merged. We will update these on a case-by-case basis. 77 | 78 | #### 3.1.12 Documentation 79 | 80 | Once all references have been updated throughout the XRPLF codebases, we will also update the docs, code, samples, and tutorials hosted at [xrpl.org](https://xrpl.org/). 81 | 82 | ### 3.2 Implementation 83 | 84 | To minimize disruption to contributors, operators, and others, we aim to implement the changes as follows. 85 | 86 | #### 3.2.1 Execution 87 | 88 | We will first make the changes that are purely cosmetic and do not require any action by the community: 89 | 90 | - Include guards 91 | - CMake 92 | - Code and comments 93 | - Copyright notices 94 | 95 | We will next make the changes that should also be safe but carry some risk of unexpected behavior: 96 | 97 | - Config 98 | - Binary 99 | 100 | We will finally make the changes that may require action from some community members and that carry more risk: 101 | 102 | - Repository 103 | - Namespace 104 | - Ripple epoch 105 | - Artifacts 106 | 107 | Each of the changes listed above will be made separately. To avoid contributors having to rebase their PRs many times, we will perform the changes in the first group quickly. As the third group of changes is the most impactful and may need extensive collaboration with the community, we will roll them out slowly. 108 | 109 | #### 3.2.2 Script 110 | 111 | To assist developers in making the same changes to their forks or open PRs, we will add a script to the repository that will apply all changes to their local branch. This script will be updated each time we make a change. 112 | 113 | #### 3.2.3 Support 114 | 115 | Contributors, operators, and others may need to take some actions as part of this proposal. For instance, build failures are expected in PRs after we rename the namespace from `ripple` to `xrpl`. We will work with each of them to ensure the changes occur as smoothly as possible. 116 | 117 | ## 4 Rationale 118 | 119 | Rolling out of the changes in stages as described above minimizes disruption to contributors, operators, and others. Instead of making the changes one by one, we can also combine groups of changes into a chain of PRs, which could reduce effort by contributors at the expense of more complex coordination of these in-flight PRs and a higher burden for reviewers. We opted for the former approach to keep things simple, but may reconsider if needed. 120 | 121 | ## 5 Security Considerations 122 | 123 | The changes primarily involve renaming references in the codebase and documentation, and do not affect the functioning of the application. This notwithstanding, thorough testing will be conducted to verify that all changes are correctly implemented and do not introduce any regressions. 124 | 125 | ## 6 Appendix 126 | 127 | ### 6.1 FAQ 128 | 129 | **Q: Why is this change necessary?** 130 | A: The change is intended to better reflect the decentralized and diversified nature of the XRPL community. 131 | 132 | **Q: I'm a node operator. What do I need to do?** 133 | A: Initially, nothing. The `rippled` binary (symlink) and `rippled.cfg` configuration file will continue to work as before. However, within six months after the changes have been made, you will need to switch to using the `xrpld` binary and `xrpld.cfg` configuration file. This may involve updating scripts, automation, and/or monitoring that you use to manage your node. 134 | 135 | **Q: I'm a developer with an open PR. What do I need to do?** 136 | A: After the changes have been made, you will need to rebase your PR onto the updated `develop` branch. To assist you with this, we will provide a script that applies all changes to your local branch. Please reach out to us if you need help with this. 137 | 138 | **Q: I'm a developer with a fork of the codebase. What do I need to do?** 139 | A: Similar to the previous question, you will need to update your fork to reflect the changes made in the `develop` branch. 140 | 141 | **Q: I'm a user of the XRPL. What do I need to do?** 142 | A: Nothing will change for you. The changes are internal to the codebase and documentation, and do not affect the user experience. 143 | 144 | **Q: Will there be any changes to the functionality of rippled?** 145 | A: Similar to the previous question, no, the changes are purely cosmetic and do not affect the functionality of the application. 146 | -------------------------------------------------------------------------------- /site/xls_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | XLS Standards Parser - Extracts metadata from XLS markdown documents. 4 | 5 | This module provides functionality to parse XLS (XRPL Standards) documents 6 | and extract their metadata for use in documentation systems and validation. 7 | """ 8 | 9 | import re 10 | from dataclasses import asdict, dataclass 11 | from pathlib import Path 12 | from typing import List, Optional, Tuple 13 | import sys 14 | 15 | from bs4 import BeautifulSoup 16 | 17 | 18 | @dataclass 19 | class XLSDocument: 20 | """Represents an XLS document with metadata.""" 21 | 22 | number: str 23 | title: str 24 | description: str 25 | authors: List[Tuple[str, str]] # Tuple of (author_name, author_link) 26 | folder: str 27 | filename: str 28 | status: str # draft, final, stagnant, withdrawn, etc. 29 | category: str # amendment, ecosystem, system, etc. 30 | created: str # YYYY-MM-DD format 31 | 32 | def to_dict(self): 33 | return asdict(self) 34 | 35 | 36 | def extract_xls_metadata(content: str, folder_name: str) -> Optional[XLSDocument]: 37 | """Extract metadata from XLS markdown content. 38 | 39 | Args: 40 | content: The raw markdown content of the XLS document 41 | folder_name: Name of the folder containing the XLS document 42 | 43 | Returns: 44 | XLSDocument instance with extracted metadata, or None if parsing fails 45 | """ 46 | 47 | # Initialize metadata with defaults 48 | metadata = {} 49 | 50 | # Parse HTML pre block for metadata 51 | pre_regex = r"
(.*?)
" 52 | match = re.search(pre_regex, content, re.DOTALL) 53 | if match: 54 | pre_text = match.group(1) 55 | else: 56 | print("ERROR: No
 block found in content")
 57 |         sys.exit(1)
 58 | 
 59 |     # Extract metadata using standardized patterns (headers are now enforced by CI)
 60 |     patterns = {
 61 |         "title": r"[tT]itle:\s*(.*?)(?:\n|$)",
 62 |         "description": r"[dD]escription:\s*(.*?)(?:\n|$)",
 63 |         "authors": r"[aA]uthors?:\s*(.*?)(?:\n|$)",
 64 |         "status": r"[sS]tatus:\s*(.*?)(?:\n|$)",
 65 |         "category": r"[cC]ategory:\s*(.*?)(?:\n|$)",
 66 |         "created": r"[cC]reated:\s*(.*?)(?:\n|$)",
 67 |     }
 68 | 
 69 |     def format_author(author):
 70 |         """Format author information into name and link tuple."""
 71 |         author = author.strip()
 72 |         # Email address
 73 |         email_match = re.match(r"^(.*?)\s*<\s*([^>]+)\s*>$", author)
 74 |         if email_match:
 75 |             name = email_match.group(1).strip()
 76 |             email = email_match.group(2).strip()
 77 |             return name, f'mailto:{email}'
 78 |         # GitHub username in parentheses
 79 |         gh_match = re.match(r"^(.*?)\s*\(@([^)]+)\)$", author)
 80 |         if gh_match:
 81 |             name = gh_match.group(1).strip()
 82 |             gh_user = gh_match.group(2).strip()
 83 |             return name, f'https://github.com/{gh_user}'
 84 |         # Just a name
 85 |         return author, ""
 86 | 
 87 |     for key, pattern in patterns.items():
 88 |         match = re.search(pattern, pre_text, re.IGNORECASE | re.DOTALL)
 89 |         if match:
 90 |             value = match.group(1).strip()
 91 |             # Clean HTML tags from value and process based on field type
 92 |             if key == "authors":
 93 |                 # Process comma-separated authors
 94 |                 value = [
 95 |                     format_author(author)
 96 |                     for author in value.split(",")
 97 |                 ]
 98 |             else:
 99 |                 # Clean HTML tags for other fields
100 |                 value = BeautifulSoup(value, "html.parser").get_text().strip()
101 |             metadata[key] = value
102 | 
103 |     # Extract XLS number from folder name
104 |     xls_match = re.match(r"XLS-(\d+)([d]?)", folder_name)
105 |     if xls_match:
106 |         number = xls_match.group(1)
107 |     else:
108 |         number = "000"
109 | 
110 |     return XLSDocument(
111 |         number=number,
112 |         title=metadata.get("title", "Unknown Title"),
113 |         description=metadata.get("description", "No description available"),
114 |         authors=metadata.get("authors", [("Unknown Author", "")]),
115 |         folder=folder_name,
116 |         filename="README.md",
117 |         status=metadata.get("status", "Unknown"),
118 |         category=metadata.get("category", "Unknown"),
119 |         created=metadata.get("created", "Unknown"),
120 |     )
121 | 
122 | 
123 | def find_xls_documents(root_dir: Path) -> List[XLSDocument]:
124 |     """Find and parse all XLS documents in the given directory.
125 | 
126 |     Args:
127 |         root_dir: Root directory to search for XLS folders
128 | 
129 |     Returns:
130 |         List of XLSDocument instances for all found documents
131 | 
132 |     Raises:
133 |         Exception: If parsing fails for any document
134 |     """
135 |     xls_docs = []
136 |     xls_folders = [
137 |         d for d in root_dir.iterdir() if d.is_dir() and d.name.startswith("XLS-")
138 |     ]
139 | 
140 |     for folder in xls_folders:
141 |         readme_path = folder / "README.md"
142 |         if readme_path.exists():
143 |             try:
144 |                 with open(readme_path, "r", encoding="utf-8") as f:
145 |                     content = f.read()
146 | 
147 |                 doc = extract_xls_metadata(content, folder.name)
148 |                 if doc:
149 |                     xls_docs.append(doc)
150 |                     print(f"Parsed: {folder.name} - {doc.title}")
151 |                 else:
152 |                     raise Exception(f"Failed to parse metadata from {folder.name}")
153 | 
154 |             except Exception as e:
155 |                 print(f"Error processing {folder.name}: {e}")
156 |                 raise
157 | 
158 |     return xls_docs
159 | 
160 | 
161 | def validate_xls_documents(root_dir: Path) -> bool:
162 |     """Validate that all XLS documents can be parsed correctly.
163 | 
164 |     Args:
165 |         root_dir: Root directory containing XLS folders
166 | 
167 |     Returns:
168 |         True if all documents parse successfully, False otherwise
169 |     """
170 |     try:
171 |         docs = find_xls_documents(root_dir)
172 | 
173 |         # Basic validation checks
174 |         if not docs:
175 |             print("Warning: No XLS documents found")
176 |             return False
177 | 
178 |         # Check for duplicate numbers
179 |         numbers = [doc.number for doc in docs]
180 |         if len(numbers) != len(set(numbers)):
181 |             duplicates = [num for num in set(numbers) if numbers.count(num) > 1]
182 |             print(f"Error: Duplicate XLS numbers found: {duplicates}")
183 |             return False
184 | 
185 |         # Check for required fields
186 |         validation_errors = []
187 |         for doc in docs:
188 |             if not doc.title or doc.title == "Unknown Title":
189 |                 validation_errors.append(
190 |                     f"Error: {doc.folder} is missing required title metadata"
191 |                 )
192 |             if not doc.authors or doc.authors == [("Unknown Author", "")]:
193 |                 validation_errors.append(
194 |                     f"Error: {doc.folder} is missing required authors metadata"
195 |                 )
196 |             elif any(not name for name, _ in doc.authors):
197 |                 validation_errors.append(
198 |                     f"Error: {doc.folder} has an author with missing name"
199 |                 )
200 |             elif any(link == "" for _, link in doc.authors):
201 |                 validation_errors.append(
202 |                     f"Error: {doc.folder} has an author with missing link"
203 |                 )
204 | 
205 |             if not doc.status or doc.status == "Unknown":
206 |                 validation_errors.append(
207 |                     f"Error: {doc.folder} is missing required status metadata"
208 |                 )
209 | 
210 |             if not doc.category or doc.category == "Unknown":
211 |                 validation_errors.append(
212 |                     f"Error: {doc.folder} is missing required category metadata"
213 |                 )
214 |             elif doc.category not in ["Amendment", "Ecosystem", "System", "Meta"]:
215 |                 validation_errors.append(
216 |                     f"Error: {doc.folder} has an invalid category: {doc.category}"
217 |                 )
218 | 
219 |             if not doc.created or doc.created == "Unknown":
220 |                 validation_errors.append(
221 |                     f"Error: {doc.folder} is missing required created metadata"
222 |                 )
223 | 
224 |         if validation_errors:
225 |             print("\n")
226 |             for error in validation_errors:
227 |                 print(error)
228 |             print(
229 |                 f"Validation failed: {len(validation_errors)} document(s) missing required metadata"
230 |             )
231 |             return False
232 | 
233 |         print(f"\nSuccessfully validated {len(docs)} XLS documents")
234 |         return True
235 | 
236 |     except Exception as e:
237 |         print(f"Validation failed: {e}")
238 |         return False
239 | 
240 | 
241 | if __name__ == "__main__":
242 |     """Run validation when script is executed directly."""
243 |     import sys
244 |     from pathlib import Path
245 | 
246 |     root_dir = Path(".")
247 |     success = validate_xls_documents(root_dir)
248 |     sys.exit(0 if success else 1)
249 | 


--------------------------------------------------------------------------------
/XLS-0054-nftokenoffer-destination-tag/README.md:
--------------------------------------------------------------------------------
  1 | 
  2 |   xls: 54
  3 |   title: NFTokenOffer Destination Tag
  4 |   description: Add DestinationTag to NFTokenCreateOffer transaction and NFTokenOffer object
  5 |   author: Florent (@florent-uzio)
  6 |   created: 2023-11-27
  7 |   status: Stagnant
  8 |   category: Amendment
  9 | 
10 | 11 | # Challenge Overview 12 | 13 | My team is working with multiple partners who build on the XRP Ledger. 14 | The majority of those partners mention the cost associated with the XRP reserves (base + owner) which can quickly become high. 15 | Some projects use NFTs and they need to create hundred, thousand... of accounts. Those projects custody the key for their end users. 16 | 17 | As an example, in 2023 an NFT project needed to create 4000 accounts requiring approximately 60,000 XRP for it (10 base + ~5 to cover transaction fees and owner reserves). 18 | 19 | # 1. Proposed solution 20 | 21 | To streamline this process and minimize costs, we propose a technical solution by modifying one ledger object and one transaction: 22 | 23 | 1. [NFTokenOffer](https://xrpl.org/nftokenoffer.html#nftokenoffer) ledger object 24 | 2. [NFTokenCreateOffer](https://xrpl.org/nftokencreateoffer.html#nftokencreateoffer) transaction 25 | 26 | Those changes will require an amendment. 27 | 28 | # 2. `NFTokenOffer` ledger object 29 | 30 | One new field is added to the object: `DestinationTag`. 31 | 32 | ## 2.1. Fields 33 | 34 | As a reference, the `NFTokenOffer` object has the current fields (in addition to the [common fields](https://xrpl.org/transaction-common-fields.html)): 35 | 36 | | Name | JSON Type | Internal Type | Required | 37 | | ----------------- | --------------- | ------------- | -------- | 38 | | Amount | Currency Amount | AMOUNT | Yes | 39 | | Destination | string | AccountID | No | 40 | | Expiration | number | UInt32 | No | 41 | | LedgerEntryType | string | UInt16 | Yes | 42 | | NFTokenID | string | Hash256 | Yes | 43 | | NFTokenOfferNode | string | UInt64 | No | 44 | | Owner | string | AccountID | Yes | 45 | | OwnerNode | string | UInt64 | No | 46 | | PreviousTxnID | string | Hash256 | Yes | 47 | | PreviousTxnLgrSeq | number | UInt32 | Yes | 48 | 49 | `DestinationTag` would be added to that object with the following specifications: 50 | 51 | | Name | JSON Type | Internal Type | Required | 52 | | -------------- | --------- | ------------- | -------- | 53 | | DestinationTag | Number | UInt32 | No | 54 | 55 | # 3. `NFTokenCreateOffer` transaction 56 | 57 | This transaction already exists and one field would be added to it: `DestinationTag`. 58 | 59 | ## 3.1. Fields 60 | 61 | The current fields of that transaction are: 62 | 63 | | Field | JSON Type | Internal Type | Required | 64 | | ----------- | --------------- | ------------- | ------------------ | 65 | | Owner | String | AccountID | Yes if `buy` offer | 66 | | NFTokenID | String | Hash256 | Yes | 67 | | Amount | Currency Amount | Amount | Yes | 68 | | Expiration | Number | UInt32 | No | 69 | | Destination | String | AccountID | No | 70 | 71 | `DestinationTag` would be added to that transaction with the following specifications: 72 | 73 | | Name | JSON Type | Internal Type | Required | 74 | | -------------- | --------- | ------------- | -------- | 75 | | DestinationTag | Number | UInt32 | No | 76 | 77 | ## 3.2. Usage 78 | 79 | Once the transaction is submitted and validated by the XRPL, the `DestinationTag` is stored in the `NFTokenOffer` object and will then be used by the NFT project backend to give ownership of a specific NFT to one of their user. 80 | 81 | ## 4. Flow, how would you use such `DestinationTag` for NFTs? 82 | 83 | Let’s start this explanation by analyzing first a payment flow with an exchange like Coinbase for example. 84 | 85 | We will then see how it can be expanded to NFTs. 86 | 87 | Let’s say we have three people: 88 | 89 | - Alice has a 2,000 XRP balance with Coinbase (so Alice has a destination tag), 90 | - Bob has a 1,000 XRP balance with Coinbase (so Bob also has a destination tag). 91 | - Doug has his own regular XRP address with a 1,500 XRP balance (no destination tag required for him). 92 | 93 | ![image](https://github.com/XRPLF/XRPL-Standards/assets/36513774/7901d35f-4899-4eb2-b805-acb35e4309dd) 94 | 95 | If Alice wants to send Doug 500 XRP, Alice logs on to Coinbase, enters Doug's `r...` address & the amount, and clicks send. Coinbase's balance decreases, Doug's increases. A Payment transaction is sent on the XRPL. No problem. 96 | 97 | If Doug wants to send Bob 500 XRP, first Doug needs to find Bob's destination tag for Coinbase. Bob logs on, finds the number, gives it to Doug, and Doug sends a transaction where the destination is Coinbase's r... address, and the destination tag is the number Bob gave Doug. 98 | Doug's balance decreases, and Coinbase's balance increases. But there's an extra step - Coinbase has to examine that transaction, extract the destination tag, look it up in their own database, and then credit Bob with the XRP. 99 | 100 | Now what if Bob wants to send Alice 750 XRP? If Coinbase submits a payment transaction from their own r... address with Bob's SenderTag to their own r... address with Alice's DestinationTag, the transaction will fail as malformed (`temREDUNDANT`)! 101 | 102 | There's no point in having such a transaction on the ledger. Coinbase simply needs to update their own records to move the balance from Bob to Alice - there's no point in involving the XRPL. 103 | 104 | **Now let's expand this example to include NFTs.** 105 | 106 | An NFT project, such as the one mentioned at the top of the page that wants to create 4,000 wallets, might envision, for each NFT, to do the following: 107 | 108 | 1. Fund a "custodied" wallet with 15 XRP (Payment). 109 | 2. Mint the NFT ([NFTokenMint](https://xrpl.org/nftokenmint.html)). 110 | 3. Offer the NFT to the "custodied" account (create a sell [NFTokenCreateOffer](https://xrpl.org/nftokencreateoffer.html#nftokencreateoffer) with amount of 0 (the amount can vary, it’s just an example here) and `destination` set to the new wallet. 111 | 4. Have the new "custodied" wallet accept the NFT offer ([NFTokenAcceptOffer](https://xrpl.org/nftokenacceptoffer.html)) 112 | 113 | Step 4, though, implies that they control the secret key of the "custodied" wallet and submit transactions on its behalf. 114 | Technically the NFT project buys and sells NFTs among their customers 115 | 116 | **Now they're starting to sound like Coinbase.** 117 | 118 | Using the exchange-like approach, the process for each NFT would be: 119 | 120 | 1. Mint the NFT ([NFTokenMint](https://xrpl.org/nftokenmint.html)) 121 | 2. Internally assign ownership of the NFT to a customer, 122 | 123 | Now, whenever one of their internal customers wants to buy or sell the NFT to another internal customer, there is no point in involving the XRPL, just like Bob paying Alice on Coinbase. 124 | 125 | If an external wallet wants to buy one of the NFTs, they create an offer for it. They don't even necessarily have to know the destination tag of who owns it - but the NFT project doesn't submit an automated [NFTokenAcceptOffer](https://xrpl.org/nftokenacceptoffer.html) transaction until the current internal owner accepts it manually. 126 | 127 | If one of the NFT Project users wants to buy an external NFT, _that_ is when `DestinationTag` comes in. 128 | 129 | The NFT project submits a buy [NFTokenCreateOffer](https://xrpl.org/nftokencreateoffer.html#nftokencreateoffer) which includes the user's destination tag. 130 | When the offer is accepted, the NFT project looks at the `NFTokenAcceptOffer` transaction metadata, which includes the offer being deleted (in the `DeletedNode` object), gets the `DestinationTag` from there, and credits ownership of the NFT to that account. 131 | 132 | Now if an external wallet would like to sell an NFT to a specific user of that NFT project, that specific user will give his destination tag to the seller. 133 | The seller submits a sell [NFTokenCreateOffer](https://xrpl.org/nftokencreateoffer.html#nftokencreateoffer) with the NFT project’s `r…` address as the destination, and the user's destination tag. Similarly as the buy scenario above, once the sell [NFTokenCreateOffer](https://xrpl.org/nftokencreateoffer.html#nftokencreateoffer) is accepted, the NFT project will credit ownership of the NFT to that specific user. 134 | 135 | ## 5. Additional Notes 136 | 137 | 1. The destination tag is NOT added to the [NFToken](https://xrpl.org/nftoken.html#nftoken) object itself. There's no need for it. It's the project's responsibility to internally update ownership of an NFT just like it's Coinbase's responsibility to update their customer's account balance when receiving a payment. 138 | 139 | 2. This proposal would interact with the `lsfRequireDestTag` flag on an `AccountRoot`. The `NFTokenCreateOffer` and `NFTokenAcceptOffer` transactors should enforce the behavior of that flag. 140 | 141 | ## 6. Benefits 142 | 143 | - Cost Reduction: Minimizes XRP reserve requirements, lowering the financial burden on NFT projects. With `DestinationTag`, the NFT Project mentioned at the top of the page would only need one account (~200 XRP including base + [NFT reserves](https://xrpl.org/nft-reserve-requirements.html#nft-reserve-requirements)) instead of 4000 accounts (~60,000 XRP). 144 | - Efficiency: Simplifies the process by internalizing ownership transactions, reducing the need for extensive ledger involvement. 145 | - Enhanced User Experience: Streamlines the NFT creation and exchange process, making it more user-friendly for both project creators and consumers. 146 | --------------------------------------------------------------------------------