Skip to content

Commit b65fde9

Browse files
authored
Merge pull request #2911 from XRPLF/amm-clob-demo
Add AMM Demo Video
2 parents 788cbd8 + 524e63c commit b65fde9

File tree

4 files changed

+383
-0
lines changed

4 files changed

+383
-0
lines changed

_code-samples/amm-clob/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# AMM CLOB Demo
2+
3+
Simulate how offers interact with each other and AMMs on the XRPL DEX.

_code-samples/amm-clob/js/amm-clob.js

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
if (typeof module !== "undefined") {
2+
// Use var here because const/let are block-scoped to the if statement.
3+
var xrpl = require('xrpl')
4+
}
5+
6+
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233");
7+
client.connect()
8+
9+
let aliceWallet = null
10+
let bobWallet = null
11+
let issuerWallet = null
12+
13+
let aliceWalletBalance = null
14+
let bobWalletBalance = null
15+
16+
// Add an event listener to the startButton
17+
document.addEventListener("DOMContentLoaded", function() {
18+
startButton.addEventListener("click", start)
19+
aCreateOfferButton.addEventListener("click", aliceCreateOffer)
20+
bCreateOfferButton.addEventListener("click", bobCreateOffer)
21+
});
22+
23+
// Function to get Alice and Bob balances
24+
25+
async function getBalances() {
26+
aliceWalletBalance = await client.getBalances(aliceWallet.address)
27+
bobWalletBalance = await client.getBalances(bobWallet.address)
28+
29+
aliceWalletField.value = `${aliceWalletBalance[0].value} XRP / ${aliceWalletBalance[1].value} USD`
30+
bobWalletField.value = `${bobWalletBalance[0].value} XRP / ${bobWalletBalance[1].value} USD`
31+
}
32+
33+
// Function to update AMM
34+
35+
async function ammInfoUpdate() {
36+
const ammInfo = await client.request({
37+
"command": "amm_info",
38+
"asset": {
39+
"currency": "XRP"
40+
},
41+
"asset2": {
42+
"currency": "USD",
43+
"issuer": issuerWallet.address
44+
},
45+
"ledger_index": "validated"
46+
})
47+
48+
ammInfoField.value = JSON.stringify(ammInfo.result.amm, null, 2)
49+
}
50+
51+
// Function to update Alice and Bobs offers
52+
53+
async function updateOffers() {
54+
const aliceOffers = await client.request({
55+
"command": "account_offers",
56+
"account": aliceWallet.address
57+
})
58+
59+
if ( aliceOffers.result.offers == "" ) {
60+
aliceOffersField.value = `No offers.`
61+
} else {
62+
aliceOffersField.value = `${JSON.stringify(aliceOffers.result.offers, null, 2)}`
63+
}
64+
65+
const bobOffers = await client.request({
66+
"command": "account_offers",
67+
"account": bobWallet.address
68+
})
69+
70+
if ( bobOffers.result.offers == "" ) {
71+
bobOffersField.value = `No offers.`
72+
} else {
73+
bobOffersField.value = `${JSON.stringify(bobOffers.result.offers, null, 2)}`
74+
}
75+
}
76+
77+
// Function to set up test harness
78+
async function start() {
79+
80+
// Fund wallets and wait for each to complete
81+
startButton.textContent = "Loading wallets...";
82+
83+
const issuerStart = client.fundWallet()
84+
const ammStart = client.fundWallet()
85+
const aliceStart = client.fundWallet()
86+
const bobStart = client.fundWallet()
87+
88+
const [issuerResult, ammResult, aliceResult, bobResult] = await Promise.all([issuerStart, ammStart, aliceStart, bobStart])
89+
90+
issuerWallet = issuerResult.wallet
91+
const ammWallet = ammResult.wallet
92+
aliceWallet = aliceResult.wallet
93+
bobWallet = bobResult.wallet
94+
95+
// Set up account settings
96+
startButton.textContent = "Setting up account settings...";
97+
98+
const issuerSetRipple = client.submitAndWait({
99+
"TransactionType": "AccountSet",
100+
"Account": issuerWallet.address,
101+
"SetFlag": xrpl.AccountSetAsfFlags.asfDefaultRipple
102+
}, {autofill: true, wallet: issuerWallet})
103+
104+
const ammSetTrust = client.submitAndWait({
105+
"TransactionType": "TrustSet",
106+
"Account": ammWallet.address,
107+
"LimitAmount": {
108+
"currency": "USD",
109+
"issuer": issuerWallet.address,
110+
"value": "10000"
111+
}
112+
}, {autofill: true, wallet: ammWallet})
113+
114+
const aliceSetTrust = client.submitAndWait({
115+
"TransactionType": "TrustSet",
116+
"Account": aliceWallet.address,
117+
"LimitAmount": {
118+
"currency": "USD",
119+
"issuer": issuerWallet.address,
120+
"value": "10000"
121+
}
122+
}, {autofill: true, wallet: aliceWallet})
123+
124+
const bobSetTrust = client.submitAndWait({
125+
"TransactionType": "TrustSet",
126+
"Account": bobWallet.address,
127+
"LimitAmount": {
128+
"currency": "USD",
129+
"issuer": issuerWallet.address,
130+
"value": "10000"
131+
}
132+
}, {autofill: true, wallet: bobWallet})
133+
134+
await Promise.all([issuerSetRipple, ammSetTrust, aliceSetTrust, bobSetTrust])
135+
136+
// Send USD token
137+
startButton.textContent = "Sending USD...";
138+
139+
const issuerAccountInfo = await client.request({
140+
"command": "account_info",
141+
"account": issuerWallet.address
142+
})
143+
144+
let sequence = issuerAccountInfo.result.account_data.Sequence
145+
146+
const ammUSD = client.submitAndWait({
147+
"TransactionType": "Payment",
148+
"Account": issuerWallet.address,
149+
"Amount": {
150+
"currency": "USD",
151+
"value": "1000",
152+
"issuer": issuerWallet.address
153+
},
154+
"Destination": ammWallet.address,
155+
"Sequence": sequence ++
156+
}, {autofill: true, wallet: issuerWallet})
157+
158+
const aliceUSD = client.submitAndWait({
159+
"TransactionType": "Payment",
160+
"Account": issuerWallet.address,
161+
"Amount": {
162+
"currency": "USD",
163+
"value": "1000",
164+
"issuer": issuerWallet.address
165+
},
166+
"Destination": aliceWallet.address,
167+
"Sequence": sequence ++
168+
}, {autofill: true, wallet: issuerWallet})
169+
170+
const bobUSD = client.submitAndWait({
171+
"TransactionType": "Payment",
172+
"Account": issuerWallet.address,
173+
"Amount": {
174+
"currency": "USD",
175+
"value": "1000",
176+
"issuer": issuerWallet.address
177+
},
178+
"Destination": bobWallet.address,
179+
"Sequence": sequence ++
180+
}, {autofill: true, wallet: issuerWallet})
181+
182+
await Promise.all([ammUSD, aliceUSD, bobUSD])
183+
184+
// Update Alice and Bob's XRP and USD balances
185+
186+
getBalances()
187+
188+
// Set up AMM
189+
startButton.textContent = "Creating AMM...";
190+
191+
await client.submitAndWait({
192+
"TransactionType": "AMMCreate",
193+
"Account": ammWallet.address,
194+
"Amount": "50000000", // XRP as drops
195+
"Amount2": {
196+
"currency": "USD",
197+
"issuer": issuerWallet.address,
198+
"value": "500"
199+
},
200+
"TradingFee": 500 // 0.5%
201+
}, {autofill: true, wallet: ammWallet})
202+
203+
// Update AMM
204+
ammInfoUpdate()
205+
206+
startButton.textContent = "Ready (Click to Restart)";
207+
208+
}
209+
210+
211+
// Submit Alice Offers
212+
async function aliceCreateOffer() {
213+
214+
aCreateOfferButton.textContent = "Creating Offer..."
215+
216+
try {
217+
let aliceTakerGets = null
218+
let aliceTakerPays = null
219+
220+
if ( aliceTakerGetsCurrency.value == 'XRP' ) {
221+
aliceTakerGets = xrpl.xrpToDrops(aliceTakerGetsAmount.value)
222+
} else {
223+
aliceTakerGets = {
224+
"currency": "USD",
225+
"issuer": issuerWallet.address,
226+
"value": aliceTakerGetsAmount.value
227+
}
228+
}
229+
230+
if ( aliceTakerPaysCurrency.value == 'XRP' ) {
231+
aliceTakerPays = xrpl.xrpToDrops(aliceTakerPaysAmount.value)
232+
} else {
233+
aliceTakerPays = {
234+
"currency": "USD",
235+
"issuer": issuerWallet.address,
236+
"value": aliceTakerPaysAmount.value
237+
}
238+
}
239+
240+
await client.submitAndWait({
241+
"TransactionType": "OfferCreate",
242+
"Account": aliceWallet.address,
243+
"TakerGets": aliceTakerGets,
244+
"TakerPays": aliceTakerPays
245+
}, {autofill: true, wallet: aliceWallet})
246+
247+
updateOffers()
248+
getBalances()
249+
ammInfoUpdate()
250+
251+
} catch (error) {
252+
aliceOffersField.value = `${error.message}`
253+
}
254+
255+
aCreateOfferButton.textContent = "Create Another Offer"
256+
}
257+
258+
// Submit Bob Offers
259+
async function bobCreateOffer() {
260+
261+
bCreateOfferButton.textContent = "Creating Offer..."
262+
263+
try {
264+
let bobTakerGets = null
265+
let bobTakerPays = null
266+
267+
if ( bobTakerGetsCurrency.value == 'XRP' ) {
268+
bobTakerGets = xrpl.xrpToDrops(bobTakerGetsAmount.value)
269+
} else {
270+
bobTakerGets = {
271+
"currency": "USD",
272+
"issuer": issuerWallet.address,
273+
"value": bobTakerGetsAmount.value
274+
}
275+
}
276+
277+
if ( bobTakerPaysCurrency.value == 'XRP' ) {
278+
bobTakerPays = xrpl.xrpToDrops(bobTakerPaysAmount.value)
279+
} else {
280+
bobTakerPays = {
281+
"currency": "USD",
282+
"issuer": issuerWallet.address,
283+
"value": bobTakerPaysAmount.value
284+
}
285+
}
286+
287+
await client.submitAndWait({
288+
"TransactionType": "OfferCreate",
289+
"Account": bobWallet.address,
290+
"TakerGets": bobTakerGets,
291+
"TakerPays": bobTakerPays
292+
}, {autofill: true, wallet: bobWallet})
293+
294+
updateOffers()
295+
getBalances()
296+
ammInfoUpdate()
297+
298+
} catch (error) {
299+
bobOffersField.value = `${error.message}`
300+
}
301+
302+
bCreateOfferButton.textContent = "Create Another Offer"
303+
}

_code-samples/amm-clob/js/demo.html

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<script src="https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js"></script>
2+
<script type="application/javascript" src="amm-clob.js"></script>
3+
4+
<div style="padding: 5px">
5+
<div id="header">
6+
<button id="startButton" type="button" class="btn btn-primary" style="margin-bottom: 5px">Start Demo</button>
7+
</div>
8+
<div id="amm-box" style="border: 1px solid black; height: 250px; margin-bottom: 5px; padding: 5px; background-color: #f0f0f0">
9+
<p style="font-weight: bold">XRP/USD AMM</p>
10+
<textarea id="ammInfoField" style="height: 80%; width: 100%; resize: none" disabled></textarea>
11+
</div>
12+
<div id="offers-box" style="display:flex; gap: 5px">
13+
<div id="alice-box" style="border: 1px solid black; width: 50%; background-color: #f0f0f0; padding: 5px">
14+
<p style="font-weight: bold">Alice's Wallet</p>
15+
<textarea readonly id="aliceWalletField" style="height: 30px; width: 225px; resize: none" disabled></textarea>
16+
<div style="display: flex; gap: 10px">
17+
<label style="display: flex; align-items: center; gap: 10px">Taker Gets:
18+
<textarea id="aliceTakerGetsAmount" style="height: 30px; width: 100px; resize: none"></textarea>
19+
</label>
20+
<label style="display: flex; align-items: center; gap: 10px">Currency:
21+
<select id="aliceTakerGetsCurrency" style="height: 30px">
22+
<option>XRP</option>
23+
<option>USD</option>
24+
</select>
25+
</label>
26+
</div>
27+
<div style="display:flex; gap: 10px">
28+
<label style="display: flex; align-items: center; gap: 10px">Taker Pays:
29+
<textarea id="aliceTakerPaysAmount" style="height: 30px; width: 100px; resize: none"></textarea>
30+
</label>
31+
<label style="display: flex; align-items: center; gap: 10px">Currency:
32+
<select id="aliceTakerPaysCurrency" style="height: 30px">
33+
<option>USD</option>
34+
<option>XRP</option>
35+
</select>
36+
</label>
37+
</div>
38+
<button id= "aCreateOfferButton" type="button" class="btn btn-primary" style="margin-bottom: 5px">Create Offer</button>
39+
<p style="font-weight: bold">Alice's Offers</p>
40+
<textarea id="aliceOffersField" style="height: 210px; width: 100%; resize: none" disabled></textarea>
41+
</div>
42+
<div id="bob-box" style="border: 1px solid black; width: 50%; background-color: #f0f0f0; padding: 5px">
43+
<p style="font-weight: bold">Bob's Wallet</p>
44+
<textarea readonly id="bobWalletField" style="height: 30px; width: 225px; resize: none" disabled></textarea>
45+
<div style="display: flex; gap: 10px">
46+
<label style="display: flex; align-items: center; gap: 10px">Taker Gets:
47+
<textarea id="bobTakerGetsAmount" style="height: 30px; width: 100px; resize: none"></textarea>
48+
</label>
49+
<label style="display: flex; align-items: center; gap: 10px">Currency:
50+
<select id="bobTakerGetsCurrency" style="height: 30px">
51+
<option>XRP</option>
52+
<option>USD</option>
53+
</select>
54+
</label>
55+
</div>
56+
<div style="display:flex; gap: 10px">
57+
<label style="display: flex; align-items: center; gap: 10px">Taker Pays:
58+
<textarea id="bobTakerPaysAmount" style="height: 30px; width: 100px; resize: none"></textarea>
59+
</label>
60+
<label style="display: flex; align-items: center; gap: 10px">Currency:
61+
<select id="bobTakerPaysCurrency" style="height: 30px">
62+
<option>USD</option>
63+
<option>XRP</option>
64+
</select>
65+
</label>
66+
</div>
67+
<button id="bCreateOfferButton" type="button" class="btn btn-primary" style="margin-bottom: 5px">Create Offer</button>
68+
<p style="font-weight: bold">Bob's Offers</p>
69+
<textarea id="bobOffersField" style="height: 210px; width: 100%; resize: none" disabled></textarea>
70+
</div>
71+
</div>
72+
</div>

docs/concepts/tokens/decentralized-exchange/automated-market-makers.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ The diagram below illustrates how an offer interacts with other offers and AMM l
6565

6666
![Offer path through DEX.](/docs/img/amm-clob-diagram.png)
6767

68+
<div align="center">
69+
<iframe width="560" height="315" src="https://www.youtube.com/embed/tJ1mQxYpt-A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
70+
</div>
71+
72+
6873
### Restrictions on Assets
6974

7075
To prevent misuse, some restrictions apply to the assets used in an AMM. If you try to create an AMM with an asset that does not meet these restrictions, the transaction fails. The rules are as follows:

0 commit comments

Comments
 (0)