How do I issue a custom token on XRPL?
Last updated:
Issuing a custom token (issued currency) on the XRP Ledger is straightforward and doesn't require deploying smart contracts. XRPL has native support for issued currencies, allowing any account to create and distribute tokens representing any asset. This guide provides a complete tutorial for token issuance.
Understanding XRPL Token Model
XRPL uses a trust line model for issued currencies. Unlike Ethereum ERC-20 tokens, XRPL tokens don't have a fixed supply stored in a contract. Instead, they represent IOUs (I Owe You) between accounts. When you issue a token, you're essentially creating a liability - you owe that token value to the holder.
Key concepts: The Issuer is the account creating and distributing the token. Holders are accounts that trust the issuer and hold the token. Trust Lines are bidirectional accounting relationships between issuer and holder. Currency Code is a 3-character code (like "USD") or 40-character hex string.
Step-by-Step Token Issuance
Step 1: Create Issuer Account
First, create and fund an issuer account on testnet for testing:
```javascript const xrpl = require('xrpl');
async function createIssuer() { const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233'); await client.connect(); // Create and fund issuer account const issuer = (await client.fundWallet()).wallet; console.log('Issuer Address:', issuer.address); console.log('Issuer Seed:', issuer.seed); await client.disconnect(); return issuer; }
createIssuer(); ```
Step 2: Configure Issuer Account
Set proper flags on the issuer account. The most important is DefaultRipple, which allows your token to be freely traded:
```javascript async function configureIssuer(issuer) { const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233'); await client.connect(); // Set Default Ripple flag (required for DEX trading) const accountSet = { TransactionType: 'AccountSet', Account: issuer.address, SetFlag: 8 // asfDefaultRipple }; const prepared = await client.autofill(accountSet); const signed = issuer.sign(prepared); const result = await client.submitAndWait(signed.tx_blob); if (result.result.meta.TransactionResult === 'tesSUCCESS') { console.log('Issuer configured successfully'); } await client.disconnect(); } ```
Step 3: Holder Creates Trust Line
Before the issuer can send tokens, the holder must create a trust line, indicating they trust the issuer for up to a specified amount:
```javascript async function createTrustLine(holder, issuerAddress, currencyCode, limit) { const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233'); await client.connect(); const trustSet = { TransactionType: 'TrustSet', Account: holder.address, LimitAmount: { currency: currencyCode, issuer: issuerAddress, value: limit.toString() } }; const prepared = await client.autofill(trustSet); const signed = holder.sign(prepared); const result = await client.submitAndWait(signed.tx_blob); if (result.result.meta.TransactionResult === 'tesSUCCESS') { console.log('Trust line created successfully'); console.log('Transaction:', result.result.hash); } await client.disconnect(); }
// Usage const holder = (await client.fundWallet()).wallet; await createTrustLine(holder, issuer.address, 'USD', '1000'); ```
Step 4: Issue Tokens
Once the trust line exists, the issuer can send tokens:
```javascript async function issueToken(issuer, holderAddress, currencyCode, amount) { const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233'); await client.connect(); const payment = { TransactionType: 'Payment', Account: issuer.address, Destination: holderAddress, Amount: { currency: currencyCode, value: amount.toString(), issuer: issuer.address } }; const prepared = await client.autofill(payment); const signed = issuer.sign(prepared); const result = await client.submitAndWait(signed.tx_blob); if (result.result.meta.TransactionResult === 'tesSUCCESS') { console.log('Tokens issued successfully'); console.log('Amount:', amount, currencyCode); console.log('Transaction:', result.result.hash); } await client.disconnect(); }
// Issue 100 USD tokens await issueToken(issuer, holder.address, 'USD', '100'); ```
Complete Python Example
```python from xrpl.clients import JsonRpcClient from xrpl.wallet import generate_faucet_wallet from xrpl.models.transactions import TrustSet, Payment, AccountSet from xrpl.models.amounts import IssuedCurrencyAmount from xrpl.transaction import submit_and_wait from xrpl.models.requests import AccountLines
# Connect to testnet client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
# Step 1: Create issuer issuer = generate_faucet_wallet(client) print(f"Issuer: {issuer.address}")
# Step 2: Configure issuer (set DefaultRipple) account_set = AccountSet( account=issuer.address, set_flag=8 # asfDefaultRipple ) submit_and_wait(account_set, client, issuer) print("Issuer configured")
# Step 3: Create holder and trust line holder = generate_faucet_wallet(client) print(f"Holder: {holder.address}")
trust_set = TrustSet( account=holder.address, limit_amount=IssuedCurrencyAmount( currency="USD", issuer=issuer.address, value="10000" ) ) submit_and_wait(trust_set, client, holder) print("Trust line created")
# Step 4: Issue tokens payment = Payment( account=issuer.address, destination=holder.address, amount=IssuedCurrencyAmount( currency="USD", issuer=issuer.address, value="1000" ) ) response = submit_and_wait(payment, client, issuer) print(f"Tokens issued: {response.result['hash']}")
# Verify token balance account_lines = client.request(AccountLines( account=holder.address )) for line in account_lines.result['lines']: if line['currency'] == 'USD': print(f"Holder balance: {line['balance']} USD") ```
Token Configuration Options
Transfer Fee
Charge a fee on token transfers (0-50%):
```javascript const accountSet = { TransactionType: 'AccountSet', Account: issuer.address, TransferRate: 1002000000 // 0.2% fee (1000000000 = 0%) }; ```
Freeze Functionality
Prevent token trading (for regulatory compliance):
```javascript // Global freeze (all tokens) const globalFreeze = { TransactionType: 'AccountSet', Account: issuer.address, SetFlag: 7 // asfGlobalFreeze };
// Individual freeze (specific trust line) const trustSet = { TransactionType: 'TrustSet', Account: issuer.address, LimitAmount: { currency: 'USD', issuer: holderAddress, value: '0' }, Flags: 0x00040000 // tfSetFreeze }; ```
Currency Codes
Standard (3 characters): "USD", "EUR", "BTC", "ETH" Hex (40 characters): For longer names or special characters
```javascript // Convert string to hex currency code function stringToHex(str) { return Buffer.from(str, 'utf8').toString('hex').toUpperCase().padEnd(40, '0'); }
const currencyCode = stringToHex('MYTOKEN'); // "4D59544F4B454E0000000000000000000000000000" ```
Checking Token Supply
```javascript async function getTokenSupply(issuerAddress, currencyCode) { const client = new xrpl.Client('wss://xrplcluster.com'); await client.connect(); const lines = await client.request({ command: 'gateway_balances', account: issuerAddress, ledger_index: 'validated' }); let total = 0; const obligations = lines.result.obligations; if (obligations && obligations[currencyCode]) { total = parseFloat(obligations[currencyCode]); } console.log(`Total ${currencyCode} issued: ${total}`); await client.disconnect(); return total; } ```
Best Practices
1. Use a dedicated issuer account (cold wallet for security) 2. Set DefaultRipple flag for DEX trading 3. Consider No Ripple flag for certain use cases 4. Document token purpose and terms 5. Test thoroughly on testnet first 6. Set appropriate trust line limits 7. Monitor issued supply and holder count 8. Consider implementing transfer fees for sustainability 9. Use freeze capabilities responsibly (regulatory compliance only) 10. Secure issuer private keys with hardware wallets
Common Errors
tefNO_LINE: Holder hasn't created trust line yet tecPATH_DRY: Trust line limit reached tecUNFUNDED_PAYMENT: Issuer has insufficient XRP for fees tecNO_PERMISSION: Account flags not set correctly
Token Metadata
Store token metadata in account domain:
```javascript const accountSet = { TransactionType: 'AccountSet', Account: issuer.address, Domain: Buffer.from('https://mytoken.com').toString('hex') }; ```
Issuing custom tokens on XRPL is powerful, cost-effective (no gas fees), and doesn't require smart contract development, making it ideal for stablecoins, loyalty points, tokenized assets, and more.