mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-03-24 21:42:30 +00:00
Compare commits
20 Commits
release-3.
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed490da0b | ||
|
|
5fe416654f | ||
|
|
e0aeab7157 | ||
|
|
84987aa2df | ||
|
|
175e0a96eb | ||
|
|
d4be722dbc | ||
|
|
3cb15342a7 | ||
|
|
4346cf9a94 | ||
|
|
476ab17cc5 | ||
|
|
7d0b02bae9 | ||
|
|
7ab575804a | ||
|
|
2801055b14 | ||
|
|
1d52bd8eaa | ||
|
|
d54684f45a | ||
|
|
86618b900f | ||
|
|
b634b6902e | ||
|
|
b20b963c8c | ||
|
|
91380d73e1 | ||
|
|
1ba708467b | ||
|
|
d1adbd575a |
@@ -1,7 +1,5 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
For translated verisons of this document, see the [@l10n folder](@l10n/).
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of, but not limited to characteristics like age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
3
_code-samples/delete-account/README.md
Normal file
3
_code-samples/delete-account/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Delete Account
|
||||
|
||||
Delete an account from the XRP Ledger, removing its data and sending its XRP to another account.
|
||||
4
_code-samples/delete-account/js/.env
Normal file
4
_code-samples/delete-account/js/.env
Normal file
@@ -0,0 +1,4 @@
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=s████████████████████████████
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
45
_code-samples/delete-account/js/README.md
Normal file
45
_code-samples/delete-account/js/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Delete Account (JavaScript)
|
||||
|
||||
JavaScript sample code showing how to delete an account from the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
|
||||
|
||||
```sh
|
||||
$ node delete-account.js
|
||||
Got new account from faucet:
|
||||
Address: rsuTU7xBF1u8jxKMw5UHvbKkLmvix7zQoe
|
||||
Seed: sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
|
||||
|
||||
Edit the .env file to add this seed, then wait until the account can be deleted.
|
||||
Account is too new to be deleted.
|
||||
Account sequence + 255: 15226794
|
||||
Validated ledger index: 15226538
|
||||
(Sequence + 255 must be less than or equal to the ledger index)
|
||||
Estimate: 15 minutes until account can be deleted
|
||||
OK: Account owner count (0) is low enough.
|
||||
OK: Account balance (100000000 drops) is high enough.
|
||||
A total of 1 problem(s) prevent the account from being deleted.
|
||||
```
|
||||
|
||||
Edit the `.env` file to add the seed of the account to delete. For example:
|
||||
|
||||
```ini
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
```
|
||||
|
||||
Then run the script again:
|
||||
|
||||
```sh
|
||||
node delete-account.js
|
||||
```
|
||||
205
_code-samples/delete-account/js/delete-account.js
Normal file
205
_code-samples/delete-account/js/delete-account.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { Client, Wallet, getBalanceChanges, validate } from 'xrpl'
|
||||
import 'dotenv/config'
|
||||
|
||||
const client = new Client('wss://s.altnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// Where to send the deleted account's remaining XRP:
|
||||
const DESTINATION_ACCOUNT = 'rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo' // Testnet faucet
|
||||
|
||||
// Load the account to delete from .env file -----------------------------------
|
||||
// If the seed value is still the default, get a new account from the faucet.
|
||||
// It won't be deletable immediately.
|
||||
let wallet
|
||||
if (!process.env.ACCOUNT_SEED || process.env.ACCOUNT_SEED === 's████████████████████████████') {
|
||||
console.log("Couldn't load seed from .env; getting account from the faucet.")
|
||||
wallet = (await client.fundWallet()).wallet
|
||||
console.log(`Got new account from faucet:
|
||||
Address: ${wallet.address}
|
||||
Seed: ${wallet.seed}
|
||||
`)
|
||||
|
||||
console.log('Edit the .env file to add this seed, then wait until the account can be deleted.')
|
||||
} else {
|
||||
wallet = Wallet.fromSeed(process.env.ACCOUNT_SEED, { algorithm: process.env.ACCOUNT_ALGORITHM })
|
||||
console.log(`Loaded account: ${wallet.address}`)
|
||||
}
|
||||
|
||||
// Check account info to see if account can be deleted -------------------------
|
||||
let acctInfoResp
|
||||
try {
|
||||
acctInfoResp = await client.request({
|
||||
command: 'account_info',
|
||||
account: wallet.address,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('account_info failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let numProblems = 0
|
||||
|
||||
// Check if sequence number is too high
|
||||
const acctSeq = acctInfoResp.result.account_data.Sequence
|
||||
const lastValidatedLedgerIndex = acctInfoResp.result.ledger_index
|
||||
if (acctSeq + 255 > lastValidatedLedgerIndex) {
|
||||
console.error(`Account is too new to be deleted.
|
||||
Account sequence + 255: ${acctSeq + 255}
|
||||
Validated ledger index: ${lastValidatedLedgerIndex}
|
||||
(Sequence + 255 must be less than or equal to the ledger index)`)
|
||||
|
||||
// Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
const estWaitTimeS = (acctSeq + 255 - lastValidatedLedgerIndex) * 3.5
|
||||
if (estWaitTimeS < 120) {
|
||||
console.log(`Estimate: ${estWaitTimeS} seconds until account can be deleted`)
|
||||
} else {
|
||||
const estWaitTimeM = Math.round(estWaitTimeS / 60, 0)
|
||||
console.log(`Estimate: ${estWaitTimeM} minutes until account can be deleted`)
|
||||
}
|
||||
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account sequence number (${acctSeq}) is low enough.`)
|
||||
}
|
||||
|
||||
// Check if owner count is too high
|
||||
const ownerCount = acctInfoResp.result.account_data.OwnerCount
|
||||
if (ownerCount > 1000) {
|
||||
console.error(`Account owns too many objects in the ledger.
|
||||
Owner count: ${ownerCount}
|
||||
(Must be 1000 or less)`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account owner count (${ownerCount}) is low enough.`)
|
||||
}
|
||||
|
||||
// Check if XRP balance is high enough
|
||||
// Look up current incremental owner reserve to compare vs account's XRP balance
|
||||
// using server_state so that both are in drops
|
||||
let serverStateResp
|
||||
try {
|
||||
serverStateResp = await client.request({
|
||||
command: 'server_state'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('server_state failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
const deletionCost = serverStateResp.result.state.validated_ledger?.reserve_inc
|
||||
if (!deletionCost) {
|
||||
console.error("Couldn't get reserve values from server. " +
|
||||
"Maybe it's not synced to the network?")
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const acctBalance = acctInfoResp.result.account_data.Balance
|
||||
if (acctBalance < deletionCost) {
|
||||
console.error(`Account does not have enough XRP to pay the cost of deletion.
|
||||
Balance: ${acctBalance}
|
||||
Cost of account deletion: ${deletionCost}`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account balance (${acctBalance} drops) is high enough.`)
|
||||
}
|
||||
|
||||
// Check if FirstNFTSequence is too high
|
||||
const firstNFTSeq = acctInfoResp.result.account_data.FirstNFTokenSequence || 0
|
||||
const mintedNFTs = acctInfoResp.result.account_data.MintedNFTokens || 0
|
||||
if (firstNFTSeq + mintedNFTs + 255 > lastValidatedLedgerIndex) {
|
||||
console.error(`Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
|
||||
Current total: ${firstNFTSeq + mintedNFTs + 255}
|
||||
Validated ledger index: ${lastValidatedLedgerIndex}
|
||||
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the ledger index)`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log('OK: FirstNFTokenSequence + MintedNFTokens is low enough.')
|
||||
}
|
||||
|
||||
// Check that all issued NFTs have been burned
|
||||
const burnedNFTs = acctInfoResp.result.account_data.BurnedNFTokens || 0
|
||||
if (mintedNFTs > burnedNFTs) {
|
||||
console.error(`Account has issued NFTs outstanding.
|
||||
Number of NFTs minted: ${mintedNFTs}
|
||||
Number of NFTs burned: ${burnedNFTs}`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log('OK: No outstanding, un-burned NFTs')
|
||||
}
|
||||
|
||||
// Stop if any problems were found
|
||||
if (numProblems) {
|
||||
console.error(`A total of ${numProblems} problem(s) prevent the account from being deleted.`)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Check for deletion blockers -------------------------------------------------
|
||||
const blockers = []
|
||||
let marker
|
||||
const ledger_index = 'validated'
|
||||
while (true) {
|
||||
let accountObjResp
|
||||
try {
|
||||
accountObjResp = await client.request({
|
||||
command: 'account_objects',
|
||||
account: wallet.address,
|
||||
deletion_blockers_only: true,
|
||||
ledger_index,
|
||||
marker
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('account_objects failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
for (const obj of accountObjResp.result.account_objects) {
|
||||
blockers.push(obj)
|
||||
}
|
||||
if (accountObjResp.result.marker) {
|
||||
marker = accountObjResp.result.marker
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!blockers.length) {
|
||||
console.log('OK: Account has no deletion blockers.')
|
||||
} else {
|
||||
console.log(`Account cannot be deleted until ${blockers.length} blocker(s) are removed:`)
|
||||
for (const blocker of blockers) {
|
||||
console.log(JSON.stringify(blocker, null, 2))
|
||||
}
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Delete the account ----------------------------------------------------------
|
||||
const accountDeleteTx = {
|
||||
TransactionType: 'AccountDelete',
|
||||
Account: wallet.address,
|
||||
Destination: DESTINATION_ACCOUNT
|
||||
}
|
||||
validate(accountDeleteTx)
|
||||
|
||||
console.log('Signing and submitting the AccountDelete transaction:',
|
||||
JSON.stringify(accountDeleteTx, null, 2))
|
||||
const deleteTxResponse = await client.submitAndWait(accountDeleteTx, { wallet, autofill: true, failHard: true })
|
||||
|
||||
// Check result of the AccountDelete transaction -------------------------------
|
||||
console.log(JSON.stringify(deleteTxResponse.result, null, 2))
|
||||
const resultCode = deleteTxResponse.result.meta.TransactionResult
|
||||
if (resultCode !== 'tesSUCCESS') {
|
||||
console.error(`AccountDelete failed with code ${resultCode}.`)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('Account deleted successfully.')
|
||||
const balanceChanges = getBalanceChanges(deleteTxResponse.result.meta)
|
||||
console.log('Balance changes:', JSON.stringify(balanceChanges, null, 2))
|
||||
|
||||
client.disconnect()
|
||||
10
_code-samples/delete-account/js/package.json
Normal file
10
_code-samples/delete-account/js/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "delete-account",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.3.1",
|
||||
"xrpl": "^4.6.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
4
_code-samples/delete-account/py/.env
Normal file
4
_code-samples/delete-account/py/.env
Normal file
@@ -0,0 +1,4 @@
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=s████████████████████████████
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
47
_code-samples/delete-account/py/README.md
Normal file
47
_code-samples/delete-account/py/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Delete Account (Python)
|
||||
|
||||
Python sample code showing how to delete an account from the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
|
||||
|
||||
```sh
|
||||
$ python delete-account.py
|
||||
Got new account from faucet:
|
||||
Address: rNqLzC9pVbphwwpTBNPjpx14QSauHH3kzv
|
||||
Seed: sEdTNEJgK3cVshBEakfVic4MMtWCETY
|
||||
|
||||
Edit the .env file to add this seed, then wait until the account can be deleted.
|
||||
Account is too new to be deleted.
|
||||
Account sequence + 255: 15226905
|
||||
Validated ledger index: 15226649
|
||||
(Sequence + 255 must be less than or equal to the ledger index)
|
||||
Estimate: 15 minutes until account can be deleted
|
||||
OK: Account owner count (0) is low enough.
|
||||
OK: Account balance (100000000 drops) is high enough.
|
||||
A total of 1 problem(s) prevent the account from being deleted.
|
||||
```
|
||||
|
||||
Edit the `.env` file to add the seed of the account to delete. For example:
|
||||
|
||||
```ini
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=sEdTNEJgK3cVshBEakfVic4MMtWCETY
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
```
|
||||
|
||||
Then run the script again:
|
||||
|
||||
```sh
|
||||
python delete-account.py
|
||||
```
|
||||
199
_code-samples/delete-account/py/delete-account.py
Normal file
199
_code-samples/delete-account/py/delete-account.py
Normal file
@@ -0,0 +1,199 @@
|
||||
import os
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet, generate_faucet_wallet
|
||||
from xrpl.models.requests import AccountInfo, ServerState, AccountObjects
|
||||
from xrpl.models.transactions import AccountDelete
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.utils import get_balance_changes
|
||||
|
||||
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
|
||||
|
||||
# Where to send the deleted account's remaining XRP:
|
||||
DESTINATION_ACCOUNT = "rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo" # Testnet faucet
|
||||
|
||||
# Load the account to delete from .env file -----------------------------------
|
||||
# If the seed value is still the default, get a new account from the faucet.
|
||||
# It won't be deletable immediately.
|
||||
load_dotenv()
|
||||
account_seed = os.getenv("ACCOUNT_SEED")
|
||||
account_algorithm = os.getenv("ACCOUNT_ALGORITHM", "ed25519")
|
||||
|
||||
if account_seed == "s████████████████████████████" or not account_seed:
|
||||
print("Couldn't load seed from .env; getting account from the faucet.")
|
||||
wallet = generate_faucet_wallet(client)
|
||||
print(
|
||||
f"Got new account from faucet:\n"
|
||||
f" Address: {wallet.address}\n"
|
||||
f" Seed: {wallet.seed}\n"
|
||||
)
|
||||
|
||||
print(
|
||||
"Edit the .env file to add this seed, then wait until the account can be deleted."
|
||||
)
|
||||
else:
|
||||
wallet = Wallet.from_seed(account_seed, algorithm=account_algorithm)
|
||||
print(f"Loaded account: {wallet.address}")
|
||||
|
||||
# Check account info to see if account can be deleted -------------------------
|
||||
try:
|
||||
acct_info_resp = client.request(
|
||||
AccountInfo(account=wallet.address, ledger_index="validated")
|
||||
)
|
||||
except Exception as err:
|
||||
print(f"account_info failed with error: {err}")
|
||||
exit(1)
|
||||
|
||||
acct_info_result = acct_info_resp.result
|
||||
num_problems = 0
|
||||
|
||||
# Check if sequence number is too high
|
||||
acct_seq = acct_info_result["account_data"]["Sequence"]
|
||||
last_validated_ledger_index = acct_info_result["ledger_index"]
|
||||
|
||||
if acct_seq + 255 > last_validated_ledger_index:
|
||||
print(
|
||||
f"Account is too new to be deleted.\n"
|
||||
f" Account sequence + 255: {acct_seq + 255}\n"
|
||||
f" Validated ledger index: {last_validated_ledger_index}\n"
|
||||
f" (Sequence + 255 must be less than or equal to the ledger index)"
|
||||
)
|
||||
|
||||
# Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
est_wait_time_s = (acct_seq + 255 - last_validated_ledger_index) * 3.5
|
||||
if est_wait_time_s < 120:
|
||||
print(f"Estimate: {est_wait_time_s} seconds until account can be deleted")
|
||||
else:
|
||||
est_wait_time_m = round(est_wait_time_s / 60)
|
||||
print(f"Estimate: {est_wait_time_m} minutes until account can be deleted")
|
||||
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account sequence number ({acct_seq}) is low enough.")
|
||||
|
||||
# Check if owner count is too high
|
||||
owner_count = acct_info_result["account_data"]["OwnerCount"]
|
||||
if owner_count > 1000:
|
||||
print(
|
||||
f"Account owns too many objects in the ledger.\n"
|
||||
f" Owner count: {owner_count}\n"
|
||||
f" (Must be 1000 or less)"
|
||||
)
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account owner count ({owner_count}) is low enough.")
|
||||
|
||||
# Check if XRP balance is high enough
|
||||
# Look up current incremental owner reserve to compare vs account's XRP balance
|
||||
# using server_state so that both are in drops
|
||||
try:
|
||||
server_state_resp = client.request(ServerState())
|
||||
except Exception as err:
|
||||
print("server_state failed with error:", err)
|
||||
exit(1)
|
||||
|
||||
validated_ledger = server_state_resp.result["state"].get("validated_ledger", {})
|
||||
deletion_cost = validated_ledger.get("reserve_inc")
|
||||
|
||||
if not deletion_cost:
|
||||
print(
|
||||
"Couldn't get reserve values from server. Maybe it's not synced to the network?"
|
||||
)
|
||||
print(json.dumps(server_state_resp.result, indent=2))
|
||||
exit(1)
|
||||
|
||||
acct_balance = int(acct_info_result["account_data"]["Balance"])
|
||||
if acct_balance < deletion_cost:
|
||||
print(
|
||||
f"Account does not have enough XRP to pay the cost of deletion.\n"
|
||||
f" Balance: {acct_balance}\n"
|
||||
f" Cost of account deletion: {deletion_cost}"
|
||||
)
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account balance ({acct_balance} drops) is high enough.")
|
||||
|
||||
# Check if FirstNFTSequence is too high
|
||||
first_nfq_seq = acct_info_result["account_data"].get("FirstNFTokenSequence", 0)
|
||||
minted_nfts = acct_info_result["account_data"].get("MintedNFTokens", 0)
|
||||
if first_nfq_seq + minted_nfts + 255 > last_validated_ledger_index:
|
||||
print(f"""Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
|
||||
Current total: {first_nfq_seq + minted_nfts + 255}
|
||||
Validated ledger index: {last_validated_ledger_index}
|
||||
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the the ledger index)""")
|
||||
num_problems += 1
|
||||
else:
|
||||
print("OK: FirstNFTokenSequence + MintedNFTokens is low enough.")
|
||||
|
||||
# Check that all issued NFTs have been burned
|
||||
burned_nfts = acct_info_result["account_data"].get("BurnedNFTokens", 0)
|
||||
if minted_nfts > burned_nfts:
|
||||
print(f"""Account has NFTs outstanding.
|
||||
Number of NFTs minted: {minted_nfts}
|
||||
Number of NFTs burned: {burned_nfts}""")
|
||||
num_problems += 1
|
||||
else:
|
||||
print("OK: No outstanding, un-burned NFTs")
|
||||
|
||||
# Stop if any problems were found
|
||||
if num_problems:
|
||||
print(
|
||||
f"A total of {num_problems} problem(s) prevent the account from being deleted."
|
||||
)
|
||||
exit(1)
|
||||
|
||||
# Check for deletion blockers -------------------------------------------------
|
||||
blockers = []
|
||||
marker = None
|
||||
ledger_index = "validated"
|
||||
|
||||
while True:
|
||||
try:
|
||||
account_obj_resp = client.request(
|
||||
AccountObjects(
|
||||
account=wallet.address,
|
||||
deletion_blockers_only=True,
|
||||
ledger_index=ledger_index,
|
||||
marker=marker,
|
||||
)
|
||||
)
|
||||
except Exception as err:
|
||||
print(f"account_objects failed with error: {err}")
|
||||
exit(1)
|
||||
|
||||
blockers.extend(account_obj_resp.result["account_objects"])
|
||||
|
||||
marker = account_obj_resp.result.get("marker")
|
||||
if not marker:
|
||||
break
|
||||
|
||||
if not blockers:
|
||||
print("OK: Account has no deletion blockers.")
|
||||
else:
|
||||
print(f"Account cannot be deleted until {len(blockers)} blocker(s) are removed:")
|
||||
for blocker in blockers:
|
||||
print(json.dumps(blocker, indent=2))
|
||||
exit(1)
|
||||
|
||||
# Delete the account ----------------------------------------------------------
|
||||
account_delete_tx = AccountDelete(
|
||||
account=wallet.address, destination=DESTINATION_ACCOUNT
|
||||
)
|
||||
|
||||
print("Signing and submitting the AccountDelete transaction:")
|
||||
print(json.dumps(account_delete_tx.to_xrpl(), indent=2))
|
||||
delete_tx_response = submit_and_wait(account_delete_tx, client, wallet, fail_hard=True)
|
||||
|
||||
# Check result of the AccountDelete transaction -------------------------------
|
||||
print(json.dumps(delete_tx_response.result, indent=2))
|
||||
result_code = delete_tx_response.result["meta"]["TransactionResult"]
|
||||
|
||||
if result_code != "tesSUCCESS":
|
||||
print(f"AccountDelete failed with code {result_code}.")
|
||||
exit(1)
|
||||
|
||||
print("Account deleted successfully.")
|
||||
balance_changes = get_balance_changes(delete_tx_response.result["meta"])
|
||||
print("Balance changes:", json.dumps(balance_changes, indent=2))
|
||||
2
_code-samples/delete-account/py/requirements.txt
Normal file
2
_code-samples/delete-account/py/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
xrpl-py==4.5.0
|
||||
python-dotenv==1.2.1
|
||||
101
blog/2026/vulnerabilitydisclosurereport-bug-mar2026.md
Normal file
101
blog/2026/vulnerabilitydisclosurereport-bug-mar2026.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
category: 2026
|
||||
date: "2026-03-23"
|
||||
template: '../../@theme/templates/blogpost'
|
||||
seo:
|
||||
description: This vulnerability disclosure report contains technical details of the XRP Ledger bug reported on June 9, 2025.
|
||||
labels:
|
||||
- Advisories
|
||||
markdown:
|
||||
editPage:
|
||||
hide: true
|
||||
---
|
||||
# Vulnerability Disclosure Report: Transaction Set Handling
|
||||
|
||||
This vulnerability disclosure report contains technical details of the XRP Ledger bug reported on June 9, 2025.
|
||||
|
||||
**Date Reported:** June 9, 2025
|
||||
|
||||
**Affected Version(s):** rippled up to 2.6.2
|
||||
|
||||
## Summary of Vulnerability
|
||||
|
||||
Two vulnerabilities that affected XRPL's liveness were discovered by **Common Prefix**, which could have prevented the network from making forward progress. A UNL validator would need to have been compromised in order to exploit the bugs. Fixes for both vulnerabilities were released as part of version 3.0.0 of rippled.
|
||||
|
||||
## Impact
|
||||
|
||||
If a UNL validator had been compromised before these vulnerabilities were fixed, a modified version of rippled could have been deployed that exploited the bugs. In particular, by manipulating the transaction data in transaction sets, the compromised validator could then cause a crash in all other validators that directly received the modified message. Crashes could then be triggered repeatedly until removal of the compromised validator from the UNL.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Discovery
|
||||
|
||||
**Nikolaos Kamarinakis** from **Common Prefix** reported the vulnerabilities via a responsible disclosure report. Ripple engineering teams validated the report with an independent proof-of-concept that reproduced both bugs in a separate network.
|
||||
|
||||
### Root Cause
|
||||
|
||||
A consensus round between validators is an attempt to agree on a set of transactions so they can be processed and included in the ledger. Each validator proposes a set of unprocessed transactions that they know about, and exchanges messages with other validators to establish a final set of transactions that a supermajority of them can agree on. The two vulnerabilities arose from how the transaction sets disputes were handled in our code.
|
||||
|
||||
Both vulnerabilities required one of the ~35 UNL validators to have been compromised, which would open the door to sending a maliciously crafted message that in turn would trigger any receiving node to crash. Even though compromising a UNL validator is challenging since they typically hide behind proxy nodes and only communicate with those nodes, it is not impossible.
|
||||
|
||||
### Vulnerability 1: comparing transactions
|
||||
|
||||
When a validator receives a transaction set from another validator, it compares that transaction set with its own and identifies the transactions that are different ("disputed"). This is where the first vulnerability came into play, as a compromised validator was able to claim that a certain transaction could be found in a node in the SHAMap where it actually was not located. Any validator who received the malicious transaction set would then crash the moment it tried to look up the transaction ID using the invalid node ID.
|
||||
|
||||
### Vulnerability 2: relaying transactions
|
||||
|
||||
When a validator has come across disputed transactions, it will relay them to its peers. The second vulnerability leveraged this functionality, whereby a compromised validator sent a transaction set in which the transaction data was an arbitrary hash. Any validator who received the malicious transaction set would identify this as a disputed transaction and would want to relay it to its peers.
|
||||
|
||||
Before relaying a transaction, a validator first checks if it is a pseudo transaction, as those should not be relayed. In this case, when a validator would try to perform this inspection, the invalid data in the transaction set would cause it to crash.
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
### Vulnerability 1: comparing transactions
|
||||
|
||||
1. A compromised validator constructs a `TxSet` where the `TxId` is not in the correct node.
|
||||
2. The validator sends an accompanying proposal to its peers.
|
||||
3. Peer validators receive the position, acquire the `TxSet`, and attempt to create disputes. This results in a crash when the node ID is used to look up the transaction ID.
|
||||
|
||||
### Vulnerability 2: relaying transactions
|
||||
|
||||
1. A compromised validator constructs a `TxSet` containing a `SHAMapItem` with data that cannot be read by the `STTx` constructor.
|
||||
2. The validator sends an accompanying proposal to its peers.
|
||||
3. Peer validators receive the position, acquire the `TxSet`, create disputes and attempt to relay them. This results in a crash when the pseudo transaction check is performed.
|
||||
|
||||
## Remediation
|
||||
|
||||
- To protect against the first vulnerability we added an extra check to confirm that a transaction can really be found in the node where the proposal said it would be.
|
||||
- To protect against the second vulnerability we added a try-catch to handle the exception that the code will throw when a malicious transaction is inspected.
|
||||
- To replicate the findings of Common Prefix, we deployed a modified version of rippled in our testing platform to simulate a compromised UNL validator. Without the first fix, the first attack would indeed crash all nodes that received a malicious message sent by the compromised validator. The same thing happened without the second fix when the second attack was launched. After applying both fixes, nodes that received the manipulated messages would no longer crash.
|
||||
|
||||
## Security enhancements roadmap
|
||||
|
||||
We continue to strengthen XRPL’s security posture through multiple initiatives:
|
||||
|
||||
- Expanded security audits to preemptively find issues in unreleased code.
|
||||
- AI-assisted code reviews to identify security issues.
|
||||
- Hackathons and increased bug bounty incentives.
|
||||
|
||||
## Fixes / Patches Available
|
||||
|
||||
The fixes were released as part of [**rippled 3.0.0**](../2025/rippled-3.0.0.md).
|
||||
|
||||
## Acknowledgements
|
||||
We extend our deepest gratitude to **Common Prefix** for responsibly disclosing both vulnerabilities and for giving us time and assistance to fix them.
|
||||
|
||||
## Contact
|
||||
For more information or to report further issues, please see the [Bug Bounty Program](https://ripple.com/legal/bug-bounty) page.
|
||||
|
||||
## Incident Response Timeline
|
||||
|
||||
| Key Action | Date | Description |
|
||||
|---|---|---|
|
||||
| Initial discovery | 9 June 2025 | Nikolaos Kamarinakis from Common Prefix submits a bug report. |
|
||||
| Test bed deployed | 10 July 2025 | Testing set up in place. |
|
||||
| Bug reproduction | 6 August 2025 | First vulnerability reproduced. |
|
||||
| Bug reproduction | 11 August 2025 | Second vulnerability reproduced. |
|
||||
| Fixes created | 19 August 2025 | Fixes created in a private repository and in review. |
|
||||
| Fixes tested | 10 October 2025 | Common Prefix tests the fixes. |
|
||||
| Fixes approved | 16 October 2025 | Common Prefix approves the fixes. |
|
||||
| Fixes released | 9 December 2025 | Fixes included in 3.0.0 release. |
|
||||
| Report published | 23 March 2026 | Public vulnerability disclosure report published. |
|
||||
@@ -10,6 +10,7 @@
|
||||
- group: '2026'
|
||||
expanded: false
|
||||
items:
|
||||
- page: 2026/vulnerabilitydisclosurereport-bug-mar2026.md
|
||||
- page: 2026/rippled-3.1.2.md
|
||||
- page: 2026/vulnerabilitydisclosurereport-bug-feb2026.md
|
||||
- page: 2026/rippled-3.1.1.md
|
||||
|
||||
4
context7.json
Normal file
4
context7.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"url": "https://context7.com/xrplf/xrpl-dev-portal",
|
||||
"public_key": "pk_dOzui5DRlEAqAN0pGtYMi"
|
||||
}
|
||||
@@ -273,6 +273,8 @@
|
||||
[XChainAddClaimAttestation transaction]: /docs/references/protocol/transactions/types/xchainaddclaimattestation.md
|
||||
[XChainAddClaimAttestation transactions]: /docs/references/protocol/transactions/types/xchainaddclaimattestation.md
|
||||
[XChainBridge amendment]: /resources/known-amendments.md#xchainbridge
|
||||
[XChainClaim transaction]: /docs/references/protocol/transactions/types/xchainclaim.md
|
||||
[XChainClaim transactions]: /docs/references/protocol/transactions/types/xchainclaim.md
|
||||
[XChainCreateBridge transaction]: /docs/references/protocol/transactions/types/xchaincreatebridge.md
|
||||
[XChainCreateBridge transactions]: /docs/references/protocol/transactions/types/xchaincreatebridge.md
|
||||
[XChainCreateBridge]: /docs/references/protocol/transactions/types/xchaincreatebridge.md
|
||||
|
||||
@@ -6,7 +6,7 @@ labels:
|
||||
---
|
||||
# Deleting Accounts
|
||||
|
||||
The owner of an account can send an [AccountDelete transaction][] to deletes the account and related entries from the ledger, sending most of the account's remaining XRP balance to another account. To discourage wasteful creation and deletion of accounts, deleting an account requires burning a higher than usual amount of XRP as the [transaction cost](../transactions/transaction-cost.md).
|
||||
The owner of an account can send an [AccountDelete transaction][] to delete the account and related entries from the ledger, sending most of the account's remaining XRP balance to another account. To discourage wasteful creation and deletion of accounts, deleting an account requires burning a higher than usual amount of XRP as the [transaction cost](../transactions/transaction-cost.md).
|
||||
|
||||
Some types of associated ledger entries block an account from being deleted. For example, the issuer of a fungible token can't be deleted while anyone holds a nonzero balance of that token.
|
||||
|
||||
@@ -16,22 +16,32 @@ After an account has been deleted, it can be re-created in the ledger through th
|
||||
|
||||
To be deleted, an account must meet the following requirements:
|
||||
|
||||
- The account's `Sequence` number plus 256 must be less than the current [Ledger Index][].
|
||||
- The account must not have any "deletion blockers" in its owner directory. This includes cases where the account is a sender _or_ receiver of funds. See below for a full list of deletion blockers.
|
||||
- The account must own fewer than 1000 objects in the ledger.
|
||||
- The account's `Sequence` number plus 255 must be less than or equal to the current [Ledger Index][]. This is to protect against replaying old transactions.
|
||||
- The account must not have any "deletion blockers" in its owner directory. Deletion blockers are generally ledger entries that represent assets, obligations, or transfers of funds. See below for a full list of deletion blockers.
|
||||
- The account must own 1000 or fewer objects in the ledger.
|
||||
- The transaction must pay a special [transaction cost][] equal to at least the [owner reserve](reserves.md) for one item (currently {% $env.PUBLIC_OWNER_RESERVE %}).
|
||||
- If the account has issued any [NFTs](../tokens/nfts/index.md), they must all have been burned. Additionally, the account's `FirstNFTSequence` number plus `MintedNFTokens` number plus 255 must be less than or equal to the current ledger index. This is to protect against reusing `NFTokenID` values. {% amendment-disclaimer name="fixNFTokenRemint" /%}
|
||||
|
||||
### Deletion Blockers
|
||||
## Deletion Blockers
|
||||
|
||||
The following [ledger entry types](../../references/protocol/ledger-data/ledger-entry-types/index.md) are deletion blockers:
|
||||
The following table shows which [ledger entry types](../../references/protocol/ledger-data/ledger-entry-types/index.md) are deletion blockers. Any other types of ledger entries that an account owns are automatically deleted along with the account.
|
||||
|
||||
- `Escrow`
|
||||
- `PayChannel`
|
||||
- `RippleState` (trust line)
|
||||
- `Check`
|
||||
- `PermissionedDomain` {% amendment-disclaimer name="PermissionedDomains" /%}
|
||||
Some deletion blockers cannot be removed unilaterally. For example, if you have issued a token to someone else, you cannot delete your account as long as they hold your token. In other cases, you can remove the deletion blocker by sending a transaction that causes the entry to be removed from the ledger.
|
||||
|
||||
Any other types of ledger entries that an account owns are automatically deleted along with the account.
|
||||
| Entry Type | Related Amendment | How to remove |
|
||||
|---|---|---|
|
||||
| [Bridge][Bridge entry] | {% amendment-disclaimer name="XChainBridge" compact=true /%} | Cannot be removed. |
|
||||
| [Check][Check entry] | {% amendment-disclaimer name="Checks" compact=true /%} | Send a [CheckCancel transaction][] to cancel the check. |
|
||||
| [Escrow][Escrow entry] | (Core protocol) | Send an [EscrowCancel transaction][] to cancel the transaction if it has expired; send [EscrowFinish transaction][] to finish it if you can satisfy the timed and/or conditional requirements of the escrow. Otherwise, you can't remove the entry. |
|
||||
| [PayChannel][PayChannel entry] | (Core protocol) | Send a [PaymentChannelClaim transaction][] with the `tfClose` flag to request closing the channel; after the settle delay has passed, send another [PaymentChannelClaim transaction][] to fully close and remove the channel. |
|
||||
| [PermissionedDomain][PermissionedDomain entry] | {% amendment-disclaimer name="PermissionedDomains" compact=true /%} | Send a [PermissionedDomainDelete transaction][] to delete the domain. |
|
||||
| [RippleState][RippleState entry] | (Core protocol) | You can only remove the entry if the counterparty's settings are entirely default. If they are, you can remove it by getting the balance to 0 and setting your own settings to the default with a [TrustSet transaction][]. In the common case, the holder can remove the entry but the issuer cannot. |
|
||||
| [MPToken][MPToken entry] | {% amendment-disclaimer name="MPTokensV1" compact=true /%} | If you are the holder of the MPT, reduce your balance to 0 (for example, using a payment), then send an [MPTokenAuthorize transaction][] with the `tfMPTUnauthorize` flag. If you are the issuer of the MPT, you can't remove the entry. |
|
||||
| [MPTokenIssuance][MPTokenIssuance entry] | {% amendment-disclaimer name="MPTokensV1" compact=true /%} | Send an [MPTokenIssuanceDestroy transaction][]. You can only do this if there are no holders of the MPT. |
|
||||
| [NFTokenPage][NFTokenPage entry] | {% amendment-disclaimer name="NonFungibleTokensV1_1" compact=true /%} | Send [NFTokenBurn transactions][] to burn, or [NFTokenCreateOffer transactions][] to sell or transfer, each NFT you hold. |
|
||||
| [Vault][Vault entry] | {% amendment-disclaimer name="SingleAssetVault" compact=true /%} | Send a [VaultDelete transaction][] to delete the vault. You can only do this if the vault is empty. |
|
||||
| [XChainOwnedClaimID][XChainOwnedClaimID entry] | {% amendment-disclaimer name="XChainBridge" compact=true /%} | Send an [XChainClaim transaction][] to complete the cross-chain transfer. |
|
||||
| [XChainOwned<br>CreateAccountClaimID][XChainOwnedCreateAccountClaimID entry] | {% amendment-disclaimer name="XChainBridge" compact=true /%} | Send enough attestations ([XChainAddAccountCreateAttestation transactions][]) to create the new account. |
|
||||
|
||||
## Cost of Deleting
|
||||
|
||||
|
||||
@@ -45,9 +45,8 @@ Clawback is disabled by default. To use clawback, you must send an [AccountSet t
|
||||
|
||||
| Field | JSON Type | [Internal Type][] | Required | Description |
|
||||
|:----------|:---------------------|:------------------|:---------|:------------------|
|
||||
| `Account` | String - [Address][] | AccountID | Yes | The issuer of the asset being clawed back. Only the issuer can submit this transaction. |
|
||||
| `Asset` | Object | Issue | Yes | Specifies the asset that the issuer wants to claw back from the AMM pool. The asset can be XRP, a token, or an MPT (see: [Specifying Without Amounts][]). The `issuer` field must match with `Account`. |
|
||||
| `Asset2` | Object | Issue | Yes | Specifies the other asset in the AMM's pool. The asset can be XRP, a token, or an MPT (see: [Specifying Without Amounts][]). |
|
||||
| `Asset` | Object | Issue | Yes | The asset to claw back, which can be a trust line token or MPT (see: [Specifying Without Amounts][]). The issuer must be the sender of this transaction. |
|
||||
| `Asset2` | Object | Issue | Yes | The other asset of the AMM pool to claw back from. The asset can be XRP, a trust line token, or an MPT (see: [Specifying Without Amounts][]). |
|
||||
| `Amount` | [Currency Amount][] | Amount | No | The maximum amount to claw back from the AMM account. The `currency` and `issuer` subfields should match the `Asset` subfields. If this field isn't specified, or the `value` subfield exceeds the holder's available tokens in the AMM, all of the holder's tokens are clawed back. |
|
||||
| `Holder` | String - [Address][] | AccountID | Yes | The account holding the asset to be clawed back. |
|
||||
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
---
|
||||
seo:
|
||||
description: Delete an account, sending its remaining XRP to another account.
|
||||
labels:
|
||||
- Accounts
|
||||
---
|
||||
# Delete an Account
|
||||
|
||||
This tutorial shows how to delete an [account](../../../concepts/accounts/index.md) from the XRP Ledger, including checking that it meets the [requirements for deletion](../../../concepts/accounts/deleting-accounts.md).
|
||||
|
||||
## Goals
|
||||
|
||||
By following this tutorial, you should learn how to:
|
||||
|
||||
- Check if an account can be deleted.
|
||||
- Delete an account.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger [client library](../../../references/client-libraries.md), such as **xrpl.js**, installed.
|
||||
- Have an XRP Ledger Testnet account to delete. If you create a new account as part of the tutorial, you must wait about 15 minutes for it to become eligible for deletion.
|
||||
- Know an address where you want to send the deleted account's remaining XRP. For this tutorial, you can use the address `rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo` to return funds to the Testnet faucet.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Connect and get accounts
|
||||
|
||||
To get started, import the client library and instantiate an API client. To delete an account, you need the address of an account to receive the deleted account's remaining XRP.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
The sample code also imports `dotenv` so that it can load environment variables from a `.env` file.
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" before="// Load the account to delete" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
The sample code also imports `python-dotenv` so that it can load environment variables from a `.env` file.
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" before="# Load the account to delete" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
You need to instantiate a wallet instance for the account you want to delete. Since you can only delete an account that is at least ~15 minutes old, the sample code loads a seed value from a `.env` file. If you don't have an account seed defined in the `.env` file, you can get a new account from the faucet, but it won't be possible to delete it right away.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Load the account to delete" before="// Check account info" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Load the account to delete" before="# Check account info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check to see if the account can be deleted
|
||||
|
||||
Before deleting an account, you should check that it meets the requirements for deletion.
|
||||
|
||||
#### 3.1. Get account info
|
||||
|
||||
The first step to checking if an account can be deleted is to get its account info as of the latest validated ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check account info" before="// Check if sequence number is too high" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check account info" before="# Check if sequence number is too high" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.2. Check sequence number
|
||||
|
||||
Check that the account's current sequence number, in the `Sequence` field of the account data, is low enough compared with the latest validated ledger index. For the account to be deletable, its sequence number plus 255 must be lower than or equal to the ledger index.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if sequence number is too high" before="// Check if owner count is too high" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if sequence number is too high" before="# Check if owner count is too high" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.3. Check owner count
|
||||
|
||||
Check the `OwnerCount` field of the account data to see if the account owns too many other ledger entries. For an account to be deletable, it must own 1000 or fewer entries (of any type) in the ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if owner count is too high" before="// Check if XRP balance is high enough" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if owner count is too high" before="# Check if XRP balance is high enough" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.4. Check XRP balance
|
||||
|
||||
Deleting an account requires a special [transaction cost][] equal to the incremental owner reserve, so an account can't be deleted if its current XRP balance is less than that. To check if an account has enough XRP, use the [server_state method][] to look up the current incremental reserve and compare with the account's XRP balance in the `Balance` field of the account data.
|
||||
|
||||
{% admonition type="warning" name="Caution" %}The [server_info method][] returns reserve values as decimal XRP, whereas [server_state][server_state method] returns drops of XRP. An account's `Balance` field is in drops of XRP. Be sure to compare equivalent units! (1 XRP = 1 million drops){% /admonition %}
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if XRP balance is high enough" before="// Check if FirstNFTSequence is too high" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if XRP balance is high enough" before="# Check if FirstNFTSequence is too high" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.5. Check NFT sequence number
|
||||
|
||||
Check the `FirstNFTokenSequence` and `MintedNFTokens` fields of the account. (If either field is absent, you can treat its value as `0` for this purpose.) For the account to be deletable, the sum of these two numbers plus 255 must be lower than or equal to the current ledger index.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if FirstNFTSequence is too high" before="// Check that all issued NFTs have been burned" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if FirstNFTSequence is too high" before="# Check that all issued NFTs have been burned" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.6. Check that all issued NFTs have been burned
|
||||
|
||||
If the account has issued any NFTs that are still present in the ledger, the account cannot be deleted. You can check to see if any exist by comparing the `MintedNFTokens` field and `BurnedNFTokens` fields of the account. (In both cases, if the field is omitted, treat its value as `0`.) If the `MintedNFTokens` value is larger, the account has issued at least one NFT that has not been burned yet.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check that all issued NFTs have been burned" before="// Stop if any problems were found" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check that all issued NFTs have been burned" before="# Stop if any problems were found" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.7. Stop if the account can't be deleted
|
||||
|
||||
If any of the previous checks failed, you can't delete the account. Resolving the problems varies by type:
|
||||
|
||||
- If the account sequence or NFT sequence number is too low, wait for the ledger index to advance automatically and try again later. About 15 minutes should be enough.
|
||||
- If the account owns too many objects, or has issued NFTs outstanding, remove the offending objects.
|
||||
- If the account does not have enough XRP, you can send XRP to it so that it can pay the deletion cost, but of course in this case the account does not have enough XRP for you to recover any by deleting it. You can also wait and try again if the network [votes to lower the reserve requirements](../../../concepts/consensus-protocol/fee-voting.md), which would also lower the cost to delete an account.
|
||||
|
||||
The sample code does not try to handle these problems, and quits if any problems were found:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Stop if any problems were found" before="// Check for deletion blockers" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Stop if any problems were found" before="# Check for deletion blockers" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Check for deletion blockers and remove them if possible
|
||||
|
||||
Some types of ledger entries can block an account from being deleted. You can check for these types of entries using the [account_objects method][] with the `"deletion_blockers_only": true` parameter.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check for deletion blockers" before="// Delete the account" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check for deletion blockers" before="# Delete the account" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the account has deletion blockers, you may or may not be able to remove them by sending other transactions, depending on the ledger entry. For example, if one of the blockers is a `RippleState` entry, you may be able to remove it by reducing your balance to zero through payments or offers and using a [TrustSet transaction][] to return your settings to the default state. Since there are many possibilities, the sample code does not show how to remove deletion blockers.
|
||||
|
||||
### 5. Delete the account
|
||||
|
||||
If all the checks passed, send an [AccountDelete transaction][] to delete the account. Since this transaction type requires a much higher [transaction cost][] than normal, it's a good idea to submit the transaction with the "fail hard" setting enabled. This can save you from paying the transaction cost if the transaction was going to fail with a [`tec` result code](../../../references/protocol/transactions/transaction-results/tec-codes.md). (Fail hard stops the server from relaying the transaction to the network if the transaction provisionally fails, which catches common errors before the transaction can achieve a consensus.)
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Delete the account" before="// Check result of the AccountDelete transaction" /%}
|
||||
|
||||
If the transaction is successful, you can use [getBalanceChanges(...)](https://js.xrpl.org/functions/getBalanceChanges.html) to check the metadata and see how much XRP was delivered from the deleted account to the destination account.
|
||||
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check result of the AccountDelete transaction" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Delete the account" before="# Check result of the AccountDelete transaction" /%}
|
||||
|
||||
If the transaction is successful, you can use [get_balance_changes(...)](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.utils.html#xrpl.utils.get_balance_changes) to check the metadata and see how much XRP was delivered from the deleted account to the destination account.
|
||||
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check result of the AccountDelete transaction" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Deleting Accounts](../../../concepts/accounts/deleting-accounts.md)
|
||||
- [Transaction Cost](../../../concepts/transactions/transaction-cost.md)
|
||||
- **References:**
|
||||
- [AccountDelete transaction][]
|
||||
- [account_info method][]
|
||||
- [account_objects method][]
|
||||
- [server_state method][]
|
||||
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -12,6 +12,7 @@ If you don't [run your own `rippled` server](../infrastructure/installation/inde
|
||||
## Non-Commercial
|
||||
| Operator | [Network][] | JSON-RPC URL | WebSocket URL | Notes |
|
||||
|:----------|:------------|:-------------|:--------------|:---------------------|
|
||||
| Honeycluster | **Mainnet** | `https://honeycluster.io/` | `wss://honeycluster.io/` | Full history server cluster with Clio |
|
||||
| InFTF | **Mainnet** | `https://xrplcluster.com/` <br> `https://xrpl.ws/` [²][] | `wss://xrplcluster.com/` <br> `wss://xrpl.ws/` [²][] | Full history server cluster with CORS support. |
|
||||
| Ripple[¹][] | **Mainnet** | `https://s1.ripple.com:51234/` | `wss://s1.ripple.com/` | General purpose server cluster |
|
||||
| Ripple[¹][] | **Mainnet** | `https://s2.ripple.com:51234/` | `wss://s2.ripple.com/` | [Full-history server](../concepts/networks-and-servers/ledger-history.md#full-history) cluster |
|
||||
@@ -28,10 +29,12 @@ If you don't [run your own `rippled` server](../infrastructure/installation/inde
|
||||
| Operator | [Network][] | JSON-RPC URL | WebSocket URL | Notes |
|
||||
|:----------|:------------|:-------------|:--------------|:---------------------|
|
||||
| Ripple[¹][] | Testnet | `https://s.altnet.rippletest.net:51234/` | `wss://s.altnet.rippletest.net:51233/` | Testnet public server |
|
||||
| Honeycluster | Testnet | `https://testnet.honeycluster.io/` | `wss://testnet.honeycluster.io/` | Testnet public server |
|
||||
| XRPL Labs | Testnet | `https://testnet.xrpl-labs.com/` | `wss://testnet.xrpl-labs.com/` | Testnet public server with CORS support |
|
||||
| Ripple[¹][] | Testnet (Clio) | `https://clio.altnet.rippletest.net:51234/` | `wss://clio.altnet.rippletest.net:51233/` | Testnet public server with Clio |
|
||||
| Ripple[¹][] | Devnet | `https://s.devnet.rippletest.net:51234/` | `wss://s.devnet.rippletest.net:51233/` | Devnet public server |
|
||||
| Ripple[¹][] | Devnet (Clio) | `https://clio.devnet.rippletest.net:51234/` | `wss://clio.devnet.rippletest.net:51233/` | Devnet public server with Clio |
|
||||
| Honeycluster | Devnet | `https://devnet.honeycluster.io/` | `wss://devnet.honeycluster.io/` | Devnet public server |
|
||||
| XRPL Labs | Xahau Testnet | `https://xahau-test.net/` | `wss://xahau-test.net/` | [Hooks-enabled](https://hooks.xrpl.org/) Xahau Testnet |
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ ignore:
|
||||
- _code-samples/create-amm/ts/tsconfig.json
|
||||
- resources/contribute-blog/_blog-template.md
|
||||
- resources/contribute-documentation/_tutorial-template.md
|
||||
- CODE-OF-CONDUCT.md
|
||||
l10n:
|
||||
defaultLocale: en-US
|
||||
locales:
|
||||
|
||||
@@ -326,7 +326,8 @@
|
||||
- page: docs/tutorials/best-practices/key-management/remove-a-regular-key-pair.md
|
||||
- page: docs/tutorials/best-practices/key-management/offline-account-setup.md
|
||||
- page: docs/tutorials/best-practices/key-management/set-up-multi-signing.md
|
||||
- page: docs/tutorials/best-practices/key-management/send-a-multi-signed-transaction.md
|
||||
- page: docs/tutorials/best-practices/key-management/send-a-multi-signed-transaction.md
|
||||
- page: docs/tutorials/best-practices/key-management/delete-an-account.md
|
||||
- group: Advanced Developer Topics
|
||||
groupTranslationKey: sidebar.docs.tutorials.advancedDeveloperTopics
|
||||
expanded: false
|
||||
|
||||
Reference in New Issue
Block a user