The Native DEX - Orders and Offers
Learning Objectives
Create limit orders using OfferCreate transactions
Understand order matching and how offers are consumed
Query the order book to display market depth
Cancel and manage open offers
Implement auto-bridging concepts for optimal execution
Most DEXs require smart contracts: Uniswap on Ethereum, Serum on Solana. These add complexity, gas costs, and potential vulnerabilities.
- **Native to the protocol** - no contract deployment
- **Order book model** - traditional limit orders, not AMM curves
- **Minimal fees** - standard transaction fee (~0.00001 XRP)
- **Auto-bridging** - automatically routes through XRP for better prices
- **Atomic settlement** - trades settle in 3-5 seconds with finality
If you need a decentralized exchange for issued currencies, XRPL's native DEX is remarkably capable.
An offer says: "I will give X in exchange for Y."
// Offer structure
{
// What you're willing to give up (sell)
TakerGets: {
currency: 'USD',
issuer: 'rUSDIssuer...',
value: '100'
},
// What you want to receive (buy)
TakerPays: {
currency: 'EUR',
issuer: 'rEURIssuer...',
value: '90'
}
}
// Translation: "I'll sell 100 USD for 90 EUR"
// Exchange rate: 0.9 EUR per USD (or 1.11 USD per EUR)
```
- `TakerGets` = What the taker gets = What you're selling
- `TakerPays` = What the taker pays = What you're buying
This is from the perspective of someone taking your offer.
When offers overlap in price, they match:
Order Book:
SELL side (TakerGets USD, TakerPays EUR):
Offer A: Sell 100 USD @ 0.90 EUR/USD
Offer B: Sell 50 USD @ 0.92 EUR/USD
Offer C: Sell 200 USD @ 0.95 EUR/USD
BUY side (TakerGets EUR, TakerPays USD):
Offer D: Buy 100 USD @ 0.88 EUR/USD
Offer E: Buy 75 USD @ 0.85 EUR/USD
- Matches Offer A (best price: 0.90)
- Fills 80 USD from Offer A
- Offer A now has 20 USD remaining
- New buy order fully consumed (no remainder on book)
XRP amounts are always in drops (strings):
// Selling XRP for USD
{
TakerGets: '100000000', // 100 XRP in drops (string)
TakerPays: {
currency: 'USD',
issuer: 'rIssuer...',
value: '50'
}
}
// "I'll sell 100 XRP for 50 USD" = 0.50 USD/XRP
// Buying XRP with EUR
{
TakerGets: {
currency: 'EUR',
issuer: 'rIssuer...',
value: '100'
},
TakerPays: '200000000' // 200 XRP in drops
}
// "I'll sell 100 EUR for 200 XRP" = 0.50 EUR/XRP
// src/dex/create-offer.js
const xrpl = require('xrpl');
async function createLimitOrder(
wallet,
takerGets, // What you're selling
takerPays, // What you're buying
options = {}
) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
try {
const offer = {
TransactionType: 'OfferCreate',
Account: wallet.address,
TakerGets: takerGets,
TakerPays: takerPays
};
// Optional: Set expiration
if (options.expiration) {
// Expiration is in Ripple epoch time
offer.Expiration = dateToRippleTime(options.expiration);
}
// Optional: Offer flags
if (options.passive) {
offer.Flags = 0x00010000; // tfPassive
}
if (options.immediateOrCancel) {
offer.Flags = (offer.Flags || 0) | 0x00020000; // tfImmediateOrCancel
}
if (options.fillOrKill) {
offer.Flags = (offer.Flags || 0) | 0x00040000; // tfFillOrKill
}
console.log('Creating offer:');
console.log( Selling: ${formatAmount(takerGets)});
console.log( Buying: ${formatAmount(takerPays)});
const prepared = await client.autofill(offer);
const signed = wallet.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
const txResult = result.result.meta.TransactionResult;
if (txResult === 'tesSUCCESS') {
// Check if offer was placed or fully matched
const offerCreated = result.result.meta.AffectedNodes.find(
node => node.CreatedNode?.LedgerEntryType === 'Offer'
);
if (offerCreated) {
console.log('✓ Offer placed on order book');
return {
success: true,
status: 'placed',
offerSequence: prepared.Sequence,
hash: signed.hash
};
} else {
console.log('✓ Offer fully matched immediately');
return {
success: true,
status: 'filled',
hash: signed.hash
};
}
} else {
console.log('✗ Offer failed:', txResult);
return {
success: false,
resultCode: txResult
};
}
} finally {
await client.disconnect();
}
}
function formatAmount(amount) {
if (typeof amount === 'string') {
return ${Number(amount) / 1_000_000} XRP;
}
return ${amount.value} ${amount.currency};
}
function dateToRippleTime(date) {
// Ripple epoch: January 1, 2000
const rippleEpoch = new Date('2000-01-01T00:00:00Z').getTime() / 1000;
return Math.floor(date.getTime() / 1000) - rippleEpoch;
}
module.exports = { createLimitOrder, formatAmount, dateToRippleTime };
```
// Offer Flags
const tfPassive = 0x00010000; // 65536
// Don't match existing offers on the book
// Only place on book, let others match you
// Useful for market making
const tfImmediateOrCancel = 0x00020000; // 131072
// Only match existing offers, don't leave remainder on book
// Take what's available now, cancel the rest
// Useful for taking without placing
const tfFillOrKill = 0x00040000; // 262144
// Either fill the ENTIRE order, or don't execute at all
// No partial fills allowed
// Useful for all-or-nothing trades
const tfSell = 0x00080000; // 524288
// Treat offer as sell order (affects partial fill calculations)
// Less commonly used
// Example: Create a passive offer
async function createPassiveOffer(wallet, gets, pays) {
return createLimitOrder(wallet, gets, pays, { passive: true });
}
// Example: Take liquidity without placing an order
async function takeOnlyOrder(wallet, gets, pays) {
return createLimitOrder(wallet, gets, pays, { immediateOrCancel: true });
}
```
// src/dex/trading-example.js
const xrpl = require('xrpl');
async function tradingDemo() {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
// Create two traders
const alice = xrpl.Wallet.generate();
const bob = xrpl.Wallet.generate();
const issuer = xrpl.Wallet.generate();
// Fund accounts
await client.fundWallet(alice);
await client.fundWallet(bob);
await client.fundWallet(issuer);
console.log('Alice:', alice.address);
console.log('Bob:', bob.address);
console.log('Issuer:', issuer.address);
// Set up issuer
await enableDefaultRipple(client, issuer);
// Create trust lines and issue tokens
await createTrustLine(client, alice, issuer.address, 'USD', '10000');
await createTrustLine(client, bob, issuer.address, 'USD', '10000');
await issueTokens(client, issuer, alice.address, 'USD', '1000');
console.log('\n--- Alice creates offer: Sell 100 USD for 50 XRP ---');
// Alice: Sell 100 USD for 50 XRP
const aliceOffer = {
TransactionType: 'OfferCreate',
Account: alice.address,
TakerGets: {
currency: 'USD',
issuer: issuer.address,
value: '100'
},
TakerPays: xrpl.xrpToDrops('50')
};
const alicePrepared = await client.autofill(aliceOffer);
const aliceSigned = alice.sign(alicePrepared);
await client.submitAndWait(aliceSigned.tx_blob);
console.log("Alice's offer placed");
// Check order book
const book = await client.request({
command: 'book_offers',
taker_gets: {
currency: 'USD',
issuer: issuer.address
},
taker_pays: {
currency: 'XRP'
}
});
console.log('\nOrder book (USD/XRP):');
for (const offer of book.result.offers) {
console.log( ${offer.Account}: ${formatAmount(offer.TakerGets)} for ${formatAmount(offer.TakerPays)});
}
console.log('\n--- Bob takes offer: Buy 100 USD for 50 XRP ---');
// Bob: Buy 100 USD for 50 XRP (matches Alice's offer)
const bobOffer = {
TransactionType: 'OfferCreate',
Account: bob.address,
TakerGets: xrpl.xrpToDrops('50'),
TakerPays: {
currency: 'USD',
issuer: issuer.address,
value: '100'
}
};
const bobPrepared = await client.autofill(bobOffer);
const bobSigned = bob.sign(bobPrepared);
const result = await client.submitAndWait(bobSigned.tx_blob);
console.log('Trade result:', result.result.meta.TransactionResult);
// Check final balances
console.log('\nFinal balances:');
const aliceInfo = await client.request({
command: 'account_info',
account: alice.address
});
const aliceLines = await client.request({
command: 'account_lines',
account: alice.address
});
console.log(Alice: ${xrpl.dropsToXrp(aliceInfo.result.account_data.Balance)} XRP, ${aliceLines.result.lines[0]?.balance || 0} USD);
const bobInfo = await client.request({
command: 'account_info',
account: bob.address
});
const bobLines = await client.request({
command: 'account_lines',
account: bob.address
});
console.log(Bob: ${xrpl.dropsToXrp(bobInfo.result.account_data.Balance)} XRP, ${bobLines.result.lines[0]?.balance || 0} USD);
await client.disconnect();
}
// Helper functions
async function enableDefaultRipple(client, wallet) {
const tx = {
TransactionType: 'AccountSet',
Account: wallet.address,
SetFlag: 8 // asfDefaultRipple
};
const prepared = await client.autofill(tx);
const signed = wallet.sign(prepared);
await client.submitAndWait(signed.tx_blob);
}
async function createTrustLine(client, wallet, issuer, currency, limit) {
const tx = {
TransactionType: 'TrustSet',
Account: wallet.address,
LimitAmount: { currency, issuer, value: limit }
};
const prepared = await client.autofill(tx);
const signed = wallet.sign(prepared);
await client.submitAndWait(signed.tx_blob);
}
async function issueTokens(client, issuer, recipient, currency, amount) {
const tx = {
TransactionType: 'Payment',
Account: issuer.address,
Destination: recipient,
Amount: { currency, issuer: issuer.address, value: amount }
};
const prepared = await client.autofill(tx);
const signed = issuer.sign(prepared);
await client.submitAndWait(signed.tx_blob);
}
function formatAmount(amount) {
if (typeof amount === 'string') {
return ${Number(amount) / 1_000_000} XRP;
}
return ${amount.value} ${amount.currency};
}
tradingDemo().catch(console.error);
```
// src/dex/order-book.js
const xrpl = require('xrpl');
async function getOrderBook(baseCurrency, quoteCurrency, limit = 10) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
try {
// Get asks (selling base for quote)
const asks = await client.request({
command: 'book_offers',
taker_gets: baseCurrency, // Base currency (being sold)
taker_pays: quoteCurrency, // Quote currency (being bought)
limit: limit
});
// Get bids (buying base with quote)
const bids = await client.request({
command: 'book_offers',
taker_gets: quoteCurrency, // Quote currency (being sold)
taker_pays: baseCurrency, // Base currency (being bought)
limit: limit
});
return {
pair: ${baseCurrency.currency}/${quoteCurrency.currency},
asks: asks.result.offers.map(o => formatOffer(o, 'ask')),
bids: bids.result.offers.map(o => formatOffer(o, 'bid'))
};
} finally {
await client.disconnect();
}
}
function formatOffer(offer, side) {
const gets = parseAmount(offer.TakerGets);
const pays = parseAmount(offer.TakerPays);
// Calculate price
let price;
if (side === 'ask') {
// Selling base for quote: price = quote/base
price = pays / gets;
} else {
// Buying base with quote: price = gets/pays
price = gets / pays;
}
return {
account: offer.Account,
price: price,
amount: gets,
total: pays,
sequence: offer.Sequence
};
}
function parseAmount(amount) {
if (typeof amount === 'string') {
return Number(amount) / 1_000_000;
}
return Number(amount.value);
}
// Display formatted order book
async function displayOrderBook(base, quote) {
const book = await getOrderBook(base, quote, 5);
console.log(\n=== ${book.pair} Order Book ===\n);
console.log('ASKS (Sell orders):');
console.log('Price\t\tAmount\t\tTotal');
console.log('-'.repeat(40));
for (const ask of book.asks) {
console.log(${ask.price.toFixed(4)}\t\t${ask.amount.toFixed(2)}\t\t${ask.total.toFixed(2)});
}
console.log('\nBIDS (Buy orders):');
console.log('Price\t\tAmount\t\tTotal');
console.log('-'.repeat(40));
for (const bid of book.bids) {
console.log(${bid.price.toFixed(4)}\t\t${bid.amount.toFixed(2)}\t\t${bid.total.toFixed(2)});
}
if (book.asks.length > 0 && book.bids.length > 0) {
const spread = book.asks[0].price - book.bids[0].price;
const spreadPercent = (spread / book.asks[0].price) * 100;
console.log(\nSpread: ${spread.toFixed(4)} (${spreadPercent.toFixed(2)}%));
}
}
module.exports = { getOrderBook, displayOrderBook };
```
// src/dex/my-offers.js
const xrpl = require('xrpl');
async function getMyOffers(address) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
try {
const response = await client.request({
command: 'account_offers',
account: address
});
return response.result.offers.map(offer => ({
sequence: offer.seq,
selling: formatAmount(offer.taker_gets),
buying: formatAmount(offer.taker_pays),
quality: offer.quality, // Exchange rate
expiration: offer.expiration
? new Date((offer.expiration + 946684800) * 1000)
: null
}));
} finally {
await client.disconnect();
}
}
function formatAmount(amount) {
if (typeof amount === 'string') {
return { currency: 'XRP', value: Number(amount) / 1_000_000 };
}
return {
currency: amount.currency,
issuer: amount.issuer,
value: Number(amount.value)
};
}
module.exports = { getMyOffers };
```
// src/dex/cancel-offer.js
const xrpl = require('xrpl');
async function cancelOffer(wallet, offerSequence) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
try {
const cancel = {
TransactionType: 'OfferCancel',
Account: wallet.address,
OfferSequence: offerSequence
};
const prepared = await client.autofill(cancel);
const signed = wallet.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log('Cancel result:', result.result.meta.TransactionResult);
return {
success: result.result.meta.TransactionResult === 'tesSUCCESS',
hash: signed.hash
};
} finally {
await client.disconnect();
}
}
async function cancelAllOffers(wallet) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
try {
// Get all open offers
const offers = await client.request({
command: 'account_offers',
account: wallet.address
});
console.log(Found ${offers.result.offers.length} open offers);
const results = [];
for (const offer of offers.result.offers) {
const cancel = {
TransactionType: 'OfferCancel',
Account: wallet.address,
OfferSequence: offer.seq
};
const prepared = await client.autofill(cancel);
const signed = wallet.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
results.push({
sequence: offer.seq,
success: result.result.meta.TransactionResult === 'tesSUCCESS'
});
}
return results;
} finally {
await client.disconnect();
}
}
module.exports = { cancelOffer, cancelAllOffers };
```
// Replace offer atomically using OfferSequence in new offer
async function replaceOffer(wallet, oldSequence, newTakerGets, newTakerPays) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
try {
// New offer with OfferSequence cancels the old one
const offer = {
TransactionType: 'OfferCreate',
Account: wallet.address,
TakerGets: newTakerGets,
TakerPays: newTakerPays,
OfferSequence: oldSequence // This cancels the old offer
};
const prepared = await client.autofill(offer);
const signed = wallet.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
return {
success: result.result.meta.TransactionResult === 'tesSUCCESS',
newSequence: prepared.Sequence,
hash: signed.hash
};
} finally {
await client.disconnect();
}
}
```
XRPL automatically routes trades through XRP when it offers a better price:
Direct path: USD → EUR
Available rate: 1 USD = 0.85 EUR
Bridged path: USD → XRP → EUR
USD → XRP rate: 1 USD = 2 XRP
XRP → EUR rate: 1 XRP = 0.45 EUR
Combined: 1 USD = 0.90 EUR (better!)
XRPL automatically uses the bridged path.
// Auto-bridging happens automatically in:
// 1. Cross-currency payments (paths include XRP)
// 2. OfferCreate (matches through XRP order books)
// When you create an offer for USD/EUR:
// - XRPL checks USD/EUR direct order book
// - XRPL checks USD/XRP and XRP/EUR books
// - Best rate wins
// You don't need to do anything special
// Just create your offer normally
const offer = {
TransactionType: 'OfferCreate',
Account: wallet.address,
TakerGets: { currency: 'USD', issuer: usdIssuer, value: '100' },
TakerPays: { currency: 'EUR', issuer: eurIssuer, value: '85' }
};
// If there's a better rate through XRP, it's used automatically
```
// src/dex/check-bridging.js
async function findBestRate(baseCurrency, quoteCurrency) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');
await client.connect();
try {
// Direct rate
const directBook = await client.request({
command: 'book_offers',
taker_gets: baseCurrency,
taker_pays: quoteCurrency,
limit: 1
});
let directRate = null;
if (directBook.result.offers.length > 0) {
const offer = directBook.result.offers[0];
directRate = parseAmount(offer.TakerPays) / parseAmount(offer.TakerGets);
}
// Bridged rate through XRP
const baseToXrp = await client.request({
command: 'book_offers',
taker_gets: baseCurrency,
taker_pays: { currency: 'XRP' },
limit: 1
});
const xrpToQuote = await client.request({
command: 'book_offers',
taker_gets: { currency: 'XRP' },
taker_pays: quoteCurrency,
limit: 1
});
let bridgedRate = null;
if (baseToXrp.result.offers.length > 0 && xrpToQuote.result.offers.length > 0) {
const leg1 = baseToXrp.result.offers[0];
const leg2 = xrpToQuote.result.offers[0];
const rate1 = parseAmount(leg1.TakerPays) / parseAmount(leg1.TakerGets);
const rate2 = parseAmount(leg2.TakerPays) / parseAmount(leg2.TakerGets);
bridgedRate = rate1 * rate2;
}
console.log(Direct rate: ${directRate || 'no liquidity'});
console.log(Bridged rate: ${bridgedRate || 'no liquidity'});
const bestRate = directRate && bridgedRate
? Math.max(directRate, bridgedRate)
: directRate || bridgedRate;
return {
direct: directRate,
bridged: bridgedRate,
best: bestRate,
useBridge: bridgedRate > directRate
};
} finally {
await client.disconnect();
}
}
function parseAmount(amount) {
if (typeof amount === 'string') {
return Number(amount) / 1_000_000;
}
return Number(amount.value);
}
```
// src/dex/simple-bot.js
const xrpl = require('xrpl');
class SimpleTradingBot {
constructor(wallet, client) {
this.wallet = wallet;
this.client = client;
this.openOrders = new Map(); // sequence -> order info
}
// Place a limit order
async placeLimitOrder(side, baseAmount, price, baseCurrency, quoteCurrency) {
let takerGets, takerPays;
if (side === 'sell') {
// Selling base for quote
takerGets = baseCurrency.currency === 'XRP'
? xrpl.xrpToDrops(baseAmount)
: { ...baseCurrency, value: baseAmount.toString() };
const quoteAmount = baseAmount * price;
takerPays = quoteCurrency.currency === 'XRP'
? xrpl.xrpToDrops(quoteAmount)
: { ...quoteCurrency, value: quoteAmount.toString() };
} else {
// Buying base with quote
const quoteAmount = baseAmount * price;
takerGets = quoteCurrency.currency === 'XRP'
? xrpl.xrpToDrops(quoteAmount)
: { ...quoteCurrency, value: quoteAmount.toString() };
takerPays = baseCurrency.currency === 'XRP'
? xrpl.xrpToDrops(baseAmount)
: { ...baseCurrency, value: baseAmount.toString() };
}
const offer = {
TransactionType: 'OfferCreate',
Account: this.wallet.address,
TakerGets: takerGets,
TakerPays: takerPays
};
const prepared = await this.client.autofill(offer);
const signed = this.wallet.sign(prepared);
const result = await this.client.submitAndWait(signed.tx_blob);
if (result.result.meta.TransactionResult === 'tesSUCCESS') {
this.openOrders.set(prepared.Sequence, {
side,
baseAmount,
price,
sequence: prepared.Sequence
});
return {
success: true,
sequence: prepared.Sequence,
hash: signed.hash
};
}
return { success: false };
}
// Cancel a specific order
async cancelOrder(sequence) {
const cancel = {
TransactionType: 'OfferCancel',
Account: this.wallet.address,
OfferSequence: sequence
};
const prepared = await this.client.autofill(cancel);
const signed = this.wallet.sign(prepared);
await this.client.submitAndWait(signed.tx_blob);
this.openOrders.delete(sequence);
}
// Cancel all orders
async cancelAllOrders() {
for (const sequence of this.openOrders.keys()) {
await this.cancelOrder(sequence);
}
}
// Get current mid-market price
async getMidPrice(baseCurrency, quoteCurrency) {
const asks = await this.client.request({
command: 'book_offers',
taker_gets: baseCurrency,
taker_pays: quoteCurrency,
limit: 1
});
const bids = await this.client.request({
command: 'book_offers',
taker_gets: quoteCurrency,
taker_pays: baseCurrency,
limit: 1
});
let askPrice = null, bidPrice = null;
if (asks.result.offers.length > 0) {
const offer = asks.result.offers[0];
askPrice = parseAmount(offer.TakerPays) / parseAmount(offer.TakerGets);
}
if (bids.result.offers.length > 0) {
const offer = bids.result.offers[0];
bidPrice = parseAmount(offer.TakerGets) / parseAmount(offer.TakerPays);
}
if (askPrice && bidPrice) {
return (askPrice + bidPrice) / 2;
}
return askPrice || bidPrice;
}
}
function parseAmount(amount) {
if (typeof amount === 'string') {
return Number(amount) / 1_000_000;
}
return Number(amount.value);
}
module.exports = { SimpleTradingBot };
```
XRPL's native DEX is remarkably capable for basic trading needs—no smart contract risks, minimal fees, fast settlement. However, liquidity is thinner than centralized exchanges, and sophisticated trading strategies need careful implementation. For simple exchanges and payment corridor liquidity, it excels. For high-frequency trading, look elsewhere.
Assignment: Build a functional trading bot that can place, manage, and cancel orders on the native DEX.
Requirements:
Place limit buy and sell orders
Track open orders with sequence numbers
Cancel individual and all orders
Replace orders (cancel + new atomically)
Display order book for a currency pair
Calculate mid-market price
Show spread and depth
Detect auto-bridging opportunities
Implement basic market making (place orders on both sides)
Implement simple taker (take best available price)
Add stop-loss simulation (cancel on price move)
Display current positions
Track filled amounts from order matches
Calculate P&L on trades
Order management works correctly (30%)
Market data accurate and useful (25%)
Trading strategies implemented (25%)
Monitoring comprehensive (20%)
Time investment: 4-5 hours
Value: Foundation for any trading application on XRPL
1. Offer Structure Question:
You want to buy 100 XRP with USD at $0.50 per XRP. What should TakerGets and TakerPays be?
A) TakerGets: 100 XRP, TakerPays: 50 USD
B) TakerGets: 50 USD, TakerPays: 100 XRP
C) TakerGets: 50 USD, TakerPays: "100000000" (drops)
D) Either B or C depending on amount formatting
Correct Answer: C
Explanation: You want to buy XRP, so you're selling USD. TakerGets is what you're selling (50 USD), TakerPays is what you're buying (100 XRP = 100,000,000 drops as a string). XRP amounts must be in drops as strings, while issued currency amounts are objects with currency/issuer/value.
2. Offer Flag Question:
You want to take liquidity from the order book without leaving a resting order if your full size isn't available. Which flag should you use?
A) tfPassive
B) tfImmediateOrCancel
C) tfFillOrKill
D) No flag needed
Correct Answer: B
Explanation: tfImmediateOrCancel matches against existing offers and cancels any unfilled portion. tfFillOrKill would fail entirely if the full amount isn't available. tfPassive does the opposite—only places on book without matching. No flag would potentially leave a resting order.
3. Auto-Bridging Question:
You create an offer to trade EUR for USD. The best direct EUR/USD rate is 0.90, but EUR/XRP gives 0.95 effectively. What happens?
A) Your offer uses the direct rate of 0.90
B) Your offer automatically routes through XRP at 0.95
C) The offer fails due to conflicting rates
D) You must manually specify XRP bridging
Correct Answer: B
Explanation: XRPL automatically checks both direct order books and bridged paths through XRP. If the bridged path offers a better rate, it's used automatically. You don't need to specify anything—auto-bridging is a native protocol feature. This is one of XRPL's key advantages for cross-currency trading.
4. Order Cancellation Question:
You placed an offer with sequence 15, which was partially filled. What happens when you cancel it?
A) Nothing—partially filled offers can't be canceled
B) The entire original order is refunded
C) The unfilled portion is removed from the book
D) You must specify exactly how much to cancel
Correct Answer: C
Explanation: Canceling an offer removes the remaining unfilled portion from the order book. Whatever was already filled is already settled and can't be reversed. You cancel by OfferSequence, and whatever remains of that offer is removed. You don't specify amounts—the entire remaining offer is canceled.
5. Order Book Question:
You query book_offers with taker_gets: USD, taker_pays: XRP. What kind of orders are you seeing?
A) Buy orders for XRP (selling USD)
B) Sell orders for XRP (buying USD)
C) Both buy and sell orders
D) Market orders only
Correct Answer: A
Explanation: book_offers returns offers where someone is selling taker_gets and buying taker_pays. So taker_gets: USD means they're selling USD, and taker_pays: XRP means they want XRP in return. These are effectively "buy XRP with USD" orders. To see the other side (sell XRP for USD), you'd query with taker_gets: XRP, taker_pays: USD.
- Offers: https://xrpl.org/offers.html
- OfferCreate: https://xrpl.org/offercreate.html
- OfferCancel: https://xrpl.org/offercancel.html
- Decentralized Exchange: https://xrpl.org/decentralized-exchange.html
- Auto-bridging: https://xrpl.org/autobridging.html
- book_offers: https://xrpl.org/book_offers.html
- account_offers: https://xrpl.org/account_offers.html
For Next Lesson:
Now you understand order books. Lesson 9 introduces AMMs on XRPL—a different model for providing liquidity that complements the order book DEX.
End of Lesson 8
Total words: ~5,500
Estimated completion time: 60 minutes reading + 4-5 hours for deliverable
Key Takeaways
TakerGets is what you sell, TakerPays is what you buy
: Named from the perspective of someone taking your offer.
Offers can fill immediately, partially, or become resting orders
: Check the transaction result to know what happened.
Auto-bridging through XRP is automatic
: XRPL finds the best rate including through XRP; you don't need to specify.
Track your offer sequences
: You need the sequence number to cancel or replace orders.
Use offer flags appropriately
: tfPassive for market making, tfImmediateOrCancel for taking, tfFillOrKill for all-or-nothing. ---