Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add interactive tutorial #2851

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,85 @@ The diagram below illustrates how an offer interacts with other offers and AMM l

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

You can also use the test harness below to experiment with offers and AMM interactions.

<!-- Interactive Tutorial -->

<script src="https://unpkg.com/[email protected]/build/xrpl-latest.js"></script>
<script type="application/javascript" src="/js/tutorials/amm-clob-interactive.js"></script>

<div style="padding: 5px">
<div id="header">
<button id="startButton" type="button" class="btn btn-primary" style="margin-bottom: 5px">Start Demo</button>
</div>
<div id="amm-box" style="border: 1px solid black; height: 250px; margin-bottom: 5px; padding: 5px; background-color: #f0f0f0">
<p style="font-weight: bold">XRP/TST AMM</p>
<textarea id="ammInfoField" style="height: 80%; width: 100%; resize: none"></textarea>
</div>
<div id="offers-box" style="display:flex; gap: 5px">
<div id="alice-box" style="border: 1px solid black; width: 50%; background-color: #f0f0f0; padding: 5px">
<p style="font-weight: bold">Alice's Wallet</p>
<textarea readonly id="aliceWalletField" style="height: 30px; width: 200px; resize: none"></textarea>
<div style="display: flex; gap: 10px">
<label style="display: flex; align-items: center; gap: 10px">Taker Gets:
<textarea id="aliceTakerGetsAmount" style="height: 30px; width: 100px; resize: none"></textarea>
</label>
<label style="display: flex; align-items: center; gap: 10px">Currency:
<select id="aliceTakerGetsCurrency" style="height: 30px">
<option>XRP</option>
<option>USD</option>
</select>
</label>
</div>
<div style="display:flex; gap: 10px">
<label style="display: flex; align-items: center; gap: 10px">Taker Pays:
<textarea id="aliceTakerPaysAmount" style="height: 30px; width: 100px; resize: none"></textarea>
</label>
<label style="display: flex; align-items: center; gap: 10px">Currency:
<select id="aliceTakerPaysCurrency" style="height: 30px">
<option>USD</option>
<option>XRP</option>
</select>
</label>
</div>
<button id= "aCreateOfferButton" type="button" class="btn btn-primary" style="margin-bottom: 5px">Create Offer</button>
<p style="font-weight: bold">Alice's Offers</p>
<textarea id="aliceOffersField" style="height: 210px; width: 100%; resize: none"></textarea>
</div>
<div id="bob-box" style="border: 1px solid black; width: 50%; background-color: #f0f0f0; padding: 5px">
<p style="font-weight: bold">Bob's Wallet</p>
<textarea readonly id="bobWalletField" style="height: 30px; width: 200px; resize: none"></textarea>
<div style="display: flex; gap: 10px">
<label style="display: flex; align-items: center; gap: 10px">Taker Gets:
<textarea id="bobTakerGetsAmount" style="height: 30px; width: 100px; resize: none"></textarea>
</label>
<label style="display: flex; align-items: center; gap: 10px">Currency:
<select id="bobTakerGetsCurrency" style="height: 30px">
<option>XRP</option>
<option>USD</option>
</select>
</label>
</div>
<div style="display:flex; gap: 10px">
<label style="display: flex; align-items: center; gap: 10px">Taker Pays:
<textarea id="bobTakerPaysAmount" style="height: 30px; width: 100px; resize: none"></textarea>
</label>
<label style="display: flex; align-items: center; gap: 10px">Currency:
<select id="bobTakerPaysCurrency" style="height: 30px">
<option>USD</option>
<option>XRP</option>
</select>
</label>
</div>
<button id="bCreateOfferButton" type="button" class="btn btn-primary" style="margin-bottom: 5px">Create Offer</button>
<p style="font-weight: bold">Bob's Offers</p>
<textarea id="bobOffersField" style="height: 210px; width: 100%; resize: none"></textarea>
</div>
</div>
</div>

<!-- Interactive Tutorial -->

### Restrictions on Assets

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:
Expand Down
298 changes: 298 additions & 0 deletions static/js/tutorials/amm-clob-interactive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
if (typeof module !== "undefined") {
// Use var here because const/let are block-scoped to the if statement.
var xrpl = require('xrpl')
}

const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233");
client.connect()

let aliceWallet = null
let bobWallet = null
let issuerWallet = null

let aliceWalletBalance = null
let bobWalletBalance = null

// Add an event listener to the startButton
document.addEventListener("DOMContentLoaded", function() {
startButton.addEventListener("click", start)
aCreateOfferButton.addEventListener("click", aliceCreateOffer)
bCreateOfferButton.addEventListener("click", bobCreateOffer)
});

// Function to get Alice and Bob balances

async function getBalances() {
aliceWalletBalance = await client.getBalances(aliceWallet.address)
bobWalletBalance = await client.getBalances(bobWallet.address)

aliceWalletField.value = `${aliceWalletBalance[0].value} XRP / ${aliceWalletBalance[1].value} USD`
bobWalletField.value = `${bobWalletBalance[0].value} XRP / ${bobWalletBalance[1].value} USD`
}

// Function to update AMM

async function ammInfoUpdate() {
const ammInfo = await client.request({
"command": "amm_info",
"asset": {
"currency": "XRP"
},
"asset2": {
"currency": "USD",
"issuer": issuerWallet.address
},
"ledger_index": "validated"
})

ammInfoField.value = JSON.stringify(ammInfo.result.amm, null, 2)
}

// Function to update Alice and Bobs offers

async function updateOffers() {
const aliceOffers = await client.request({
"command": "account_offers",
"account": aliceWallet.address
})

if ( aliceOffers.result.offers == "" ) {
aliceOffersField.value = `No offers.`
} else {
aliceOffersField.value = `${JSON.stringify(aliceOffers.result.offers, null, 2)}`
}

const bobOffers = await client.request({
"command": "account_offers",
"account": bobWallet.address
})

if ( bobOffers.result.offers == "" ) {
bobOffersField.value = `No offers.`
} else {
bobOffersField.value = `${JSON.stringify(bobOffers.result.offers, null, 2)}`
}
}

// Function to set up test harness
async function start() {

// Fund wallets and wait for each to complete
startButton.textContent = "Loading wallets...";

const issuerStart = client.fundWallet()
const ammStart = client.fundWallet()
const aliceStart = client.fundWallet()
const bobStart = client.fundWallet()

const [issuerResult, ammResult, aliceResult, bobResult] = await Promise.all([issuerStart, ammStart, aliceStart, bobStart])

issuerWallet = issuerResult.wallet
const ammWallet = ammResult.wallet
aliceWallet = aliceResult.wallet
bobWallet = bobResult.wallet

// Set up account settings
startButton.textContent = "Setting up account settings...";

const issuerSetRipple = client.submitAndWait({
"TransactionType": "AccountSet",
"Account": issuerWallet.address,
"SetFlag": xrpl.AccountSetAsfFlags.asfDefaultRipple
}, {autofill: true, wallet: issuerWallet})

const ammSetTrust = client.submitAndWait({
"TransactionType": "TrustSet",
"Account": ammWallet.address,
"Flags": 262144,
"LimitAmount": {
"currency": "USD",
"issuer": issuerWallet.address,
"value": "10000"
}
}, {autofill: true, wallet: ammWallet})

const aliceSetTrust = client.submitAndWait({
"TransactionType": "TrustSet",
"Account": aliceWallet.address,
"Flags": 262144,
"LimitAmount": {
"currency": "USD",
"issuer": issuerWallet.address,
"value": "10000"
}
}, {autofill: true, wallet: aliceWallet})

const bobSetTrust = client.submitAndWait({
"TransactionType": "TrustSet",
"Account": bobWallet.address,
"Flags": 262144,
"LimitAmount": {
"currency": "USD",
"issuer": issuerWallet.address,
"value": "10000"
}
}, {autofill: true, wallet: bobWallet})

await Promise.all([issuerSetRipple, ammSetTrust, aliceSetTrust, bobSetTrust])

// Send USD token
startButton.textContent = "Sending USD...";

const issuerAccountInfo = await client.request({
"command": "account_info",
"account": issuerWallet.address
})

let sequence = issuerAccountInfo.result.account_data.Sequence

const ammUSD = client.submitAndWait({
"TransactionType": "Payment",
"Account": issuerWallet.address,
"Amount": {
"currency": "USD",
"value": "1000",
"issuer": issuerWallet.address
},
"Destination": ammWallet.address,
"Sequence": sequence ++
}, {autofill: true, wallet: issuerWallet})

const aliceUSD = client.submitAndWait({
"TransactionType": "Payment",
"Account": issuerWallet.address,
"Amount": {
"currency": "USD",
"value": "1000",
"issuer": issuerWallet.address
},
"Destination": aliceWallet.address,
"Sequence": sequence ++
}, {autofill: true, wallet: issuerWallet})

const bobUSD = client.submitAndWait({
"TransactionType": "Payment",
"Account": issuerWallet.address,
"Amount": {
"currency": "USD",
"value": "1000",
"issuer": issuerWallet.address
},
"Destination": bobWallet.address,
"Sequence": sequence ++
}, {autofill: true, wallet: issuerWallet})

await Promise.all([ammUSD, aliceUSD, bobUSD])

// Update Alice and Bob's XRP and USD balances

getBalances()

// Set up AMM
startButton.textContent = "Creating AMM...";

await client.submitAndWait({
"TransactionType": "AMMCreate",
"Account": ammWallet.address,
"Amount": "50000000", // XRP as drops
"Amount2": {
"currency": "USD",
"issuer": issuerWallet.address,
"value": "500"
},
"TradingFee": 500 // 0.5%
}, {autofill: true, wallet: ammWallet})

// Update AMM
ammInfoUpdate()

startButton.textContent = "Ready (Click to Restart)";

}


// Submit Alice Offers
async function aliceCreateOffer() {

try {
let aliceTakerGets = null
let aliceTakerPays = null

if ( aliceTakerGetsCurrency.value == 'XRP' ) {
aliceTakerGets = xrpl.xrpToDrops(aliceTakerGetsAmount.value)
} else {
aliceTakerGets = {
"currency": "USD",
"issuer": issuerWallet.address,
"value": aliceTakerGetsAmount.value
}
}

if ( aliceTakerPaysCurrency.value == 'XRP' ) {
aliceTakerPays = xrpl.xrpToDrops(aliceTakerPaysAmount.value)
} else {
aliceTakerPays = {
"currency": "USD",
"issuer": issuerWallet.address,
"value": aliceTakerPaysAmount.value
}
}

await client.submitAndWait({
"TransactionType": "OfferCreate",
"Account": aliceWallet.address,
"TakerGets": aliceTakerGets,
"TakerPays": aliceTakerPays
}, {autofill: true, wallet: aliceWallet})

updateOffers()
getBalances()
ammInfoUpdate()

} catch (error) {
aliceOffersField.value = `${error.message}`
}
}

// Submit Bob Offers
async function bobCreateOffer() {

try {
let bobTakerGets = null
let bobTakerPays = null

if ( bobTakerGetsCurrency.value == 'XRP' ) {
bobTakerGets = xrpl.xrpToDrops(bobTakerGetsAmount.value)
} else {
bobTakerGets = {
"currency": "USD",
"issuer": issuerWallet.address,
"value": bobTakerGetsAmount.value
}
}

if ( bobTakerPaysCurrency.value == 'XRP' ) {
bobTakerPays = xrpl.xrpToDrops(bobTakerPaysAmount.value)
} else {
bobTakerPays = {
"currency": "USD",
"issuer": issuerWallet.address,
"value": bobTakerPaysAmount.value
}
}

await client.submitAndWait({
"TransactionType": "OfferCreate",
"Account": bobWallet.address,
"TakerGets": bobTakerGets,
"TakerPays": bobTakerPays
}, {autofill: true, wallet: bobWallet})

updateOffers()
getBalances()
ammInfoUpdate()

} catch (error) {
bobOffersField.value = `${error.message}`
}
}