Files
xrpl-dev-portal/_code-samples/delete-account/py/delete-account.py
2026-03-17 16:01:37 -07:00

200 lines
7.0 KiB
Python

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))