mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-20 03:35:51 +00:00
Merge pull request #2113 from XRPLF/readd_create_amm_tutorial
Tutorial: Create an Automated Market Maker
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
"pbkdf2-hmac": "^1.1.0",
|
||||
"prompt": "^1.3.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.3",
|
||||
"xrpl": "^2.7.0-beta.2"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ripple-lib": "^1.0.0-beta.1",
|
||||
"xrpl": "^2.8.0-beta.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
3
content/_code-samples/create-amm/README.md
Normal file
3
content/_code-samples/create-amm/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Create AMM
|
||||
|
||||
Code samples for the [Create an Automated Market Maker tutorial](TODO), showing how to make set up a new AMM.
|
||||
18
content/_code-samples/create-amm/js/connect.js
Normal file
18
content/_code-samples/create-amm/js/connect.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// In browsers, use a <script> tag. In Node.js, uncomment the following line:
|
||||
// const xrpl = require('xrpl')
|
||||
|
||||
const WS_URL = 'wss://s.devnet.rippletest.net:51233/'
|
||||
const EXPLORER = 'devnet.xrpl.org' // Optional, for linking
|
||||
|
||||
async function main() {
|
||||
// Define the network client
|
||||
const client = new xrpl.Client(WS_URL)
|
||||
await client.connect()
|
||||
|
||||
// ... custom code goes here
|
||||
|
||||
// Disconnect when done (If you omit this, Node.js won't end the process)
|
||||
await client.disconnect()
|
||||
}
|
||||
|
||||
main()
|
||||
231
content/_code-samples/create-amm/js/create-amm.js
Normal file
231
content/_code-samples/create-amm/js/create-amm.js
Normal file
@@ -0,0 +1,231 @@
|
||||
'use strict'
|
||||
// Code to set up an AMM instance between
|
||||
// "TST.rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" and novel "FOO" tokens.
|
||||
|
||||
// Dependencies for Node.js; this if statement lets the code run unmodified
|
||||
// in browsers, as long as you provide a <script> tag (see example demo.html),
|
||||
// as well as in Node.js.
|
||||
if (typeof module !== "undefined") {
|
||||
// Use var here because const/let are block-scoped to the if statement.
|
||||
var xrpl = require('xrpl')
|
||||
// Configure console.log to print deeper into nested objects so you can
|
||||
// better see properties of the AMM:
|
||||
require('util').inspect.defaultOptions.depth = 5
|
||||
}
|
||||
|
||||
// Connect to the network -----------------------------------------------------
|
||||
const WS_URL = 'wss://s.devnet.rippletest.net:51233'
|
||||
const EXPLORER = 'https://devnet.xrpl.org'
|
||||
|
||||
async function main() {
|
||||
const client = new xrpl.Client(WS_URL);
|
||||
await client.connect()
|
||||
|
||||
// Get credentials from the Faucet -------------------------------------------
|
||||
console.log("Requesting address from the faucet...")
|
||||
const wallet = (await client.fundWallet()).wallet
|
||||
|
||||
// To use an existing account, use code such as the following:
|
||||
// const wallet = xrpl.Wallet.fromSeed(process.env['USE_SEED'])
|
||||
|
||||
// Acquire tokens ------------------------------------------------------------
|
||||
const offer_result = await client.submitAndWait({
|
||||
"TransactionType": "OfferCreate",
|
||||
"Account": wallet.address,
|
||||
"TakerPays": {
|
||||
currency: "TST",
|
||||
issuer: "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd",
|
||||
value: "25"
|
||||
},
|
||||
"TakerGets": xrpl.xrpToDrops(25*10*1.16)
|
||||
}, {autofill: true, wallet: wallet})
|
||||
if (offer_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
console.log(`TST offer placed: ${EXPLORER}/transactions/${offer_result.result.hash}`)
|
||||
const balance_changes = xrpl.getBalanceChanges(offer_result.result.meta)
|
||||
for (const bc of balance_changes) {
|
||||
if (bc.account != wallet.address) {continue}
|
||||
for (const bal of bc.balances) {
|
||||
if (bal.currency == "TST") {
|
||||
console.log(`Got ${bal.value} ${bal.currency}.${bal.issuer}.`)
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
} else {
|
||||
throw `Error sending transaction: ${offer_result}`
|
||||
}
|
||||
// Successfully placing the offer doesn't necessarily mean that you have TST,
|
||||
// but for now, let's assume it matched existing Offers on ledger so you do.
|
||||
|
||||
// Call helper function to set up a new "FOO" issuer, create a trust line
|
||||
// to them, and receive 1000 FOO from them.
|
||||
const foo_amount = await get_new_token(client, wallet, "FOO", "1000")
|
||||
|
||||
// Check if AMM already exists ----------------------------------------------
|
||||
const amm_info_request = {
|
||||
"command": "amm_info",
|
||||
"asset": {
|
||||
"currency": "TST",
|
||||
"issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd",
|
||||
},
|
||||
"asset2": {
|
||||
"currency": foo_amount.currency,
|
||||
"issuer": foo_amount.issuer
|
||||
},
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
try {
|
||||
const amm_info_result = await client.request(amm_info_request)
|
||||
console.log(amm_info_result)
|
||||
} catch(err) {
|
||||
if (err.data.error === 'actNotFound') {
|
||||
console.log(`No AMM exists yet for the pair
|
||||
${foo_amount.currency}.${foo_amount.issuer} /
|
||||
TST.rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd.
|
||||
(This is probably as expected.)`)
|
||||
} else {
|
||||
throw(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Look up AMM transaction cost ---------------------------------------------
|
||||
// AMMCreate requires burning one owner reserve. We can look up that amount
|
||||
// (in drops) on the current network using server_state:
|
||||
const ss = await client.request({"command": "server_state"})
|
||||
const amm_fee_drops = ss.result.state.validated_ledger.reserve_inc.toString()
|
||||
console.log(`Current AMMCreate transaction cost:
|
||||
${xrpl.dropsToXrp(amm_fee_drops)} XRP`)
|
||||
|
||||
|
||||
// Create AMM ---------------------------------------------------------------
|
||||
// This example assumes that 15 TST ≈ 100 FOO in value.
|
||||
const ammcreate_result = await client.submitAndWait({
|
||||
"TransactionType": "AMMCreate",
|
||||
"Account": wallet.address,
|
||||
"Amount": {
|
||||
currency: "TST",
|
||||
issuer: "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd",
|
||||
value: "15"
|
||||
},
|
||||
"Amount2": {
|
||||
"currency": foo_amount.currency,
|
||||
"issuer": foo_amount.issuer,
|
||||
"value": "100"
|
||||
},
|
||||
"TradingFee": 500, // 0.5%
|
||||
"Fee": amm_fee_drops
|
||||
}, {autofill: true, wallet: wallet, fail_hard: true})
|
||||
// Use fail_hard so you don't waste the tx cost if you mess up
|
||||
if (ammcreate_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
console.log(`AMM created: ${EXPLORER}/transactions/${ammcreate_result.result.hash}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${ammcreate_result}`
|
||||
}
|
||||
|
||||
// Confirm that AMM exists --------------------------------------------------
|
||||
// Make the same amm_info request as earlier, but this time it should succeed
|
||||
const amm_info_result2 = await client.request(amm_info_request)
|
||||
console.log(amm_info_result2)
|
||||
const lp_token = amm_info_result2.result.amm.lp_token
|
||||
const amount = amm_info_result2.result.amm.amount
|
||||
const amount2 = amm_info_result2.result.amm.amount2
|
||||
console.log(`The AMM account ${lp_token.issuer} has ${lp_token.value} total
|
||||
LP tokens outstanding, and uses the currency code ${lp_token.currency}.`)
|
||||
console.log(`In its pool, the AMM holds ${amount.value} ${amount.currency}.${amount.issuer}
|
||||
and ${amount2.value} ${amount2.currency}.${amount2.issuer}`)
|
||||
|
||||
// Check token balances
|
||||
const account_lines_result = await client.request({
|
||||
"command": "account_lines",
|
||||
"account": wallet.address,
|
||||
// Tip: To look up only the new AMM's LP Tokens, uncomment:
|
||||
// "peer": lp_token.issuer,
|
||||
"ledger_index": "validated"
|
||||
})
|
||||
console.log(account_lines_result)
|
||||
|
||||
// Disconnect when done -----------------------------------------------------
|
||||
await client.disconnect()
|
||||
}
|
||||
main()
|
||||
|
||||
|
||||
/* Issue tokens ---------------------------------------------------------------
|
||||
* Fund a new issuer using the faucet, and issue some fungible tokens
|
||||
* to the specified address. In production, you would not do this; instead,
|
||||
* you would acquire tokens from an existing issuer (for example, you might
|
||||
* buy them in the DEX, or make an off-ledger deposit at a stablecoin issuer).
|
||||
* For a more thorough explanation of this process, see
|
||||
* "Issue a Fungible Token": https://xrpl.org/issue-a-fungible-token.html
|
||||
* Params:
|
||||
* client: an xrpl.Client instance that is already connected to the network
|
||||
* wallet: an xrpl.Wallet instance that should hold the new tokens
|
||||
* currency_code: string currency code (3-char ISO-like or hex code)
|
||||
* issue_quantity: string number of tokens to issue. Arbitrarily capped
|
||||
* at "10000000000"
|
||||
* Resolves to: an "Amount"-type JSON object, such as:
|
||||
* {
|
||||
* "currency": "TST",
|
||||
* "issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd",
|
||||
* "value": "123.456"
|
||||
* }
|
||||
*/
|
||||
async function get_new_token(client, wallet, currency_code, issue_quantity) {
|
||||
// Get credentials from the Testnet Faucet -----------------------------------
|
||||
console.log("Funding an issuer address with the faucet...")
|
||||
const issuer = (await client.fundWallet()).wallet
|
||||
console.log(`Got issuer address ${issuer.address}.`)
|
||||
|
||||
// Enable issuer DefaultRipple ----------------------------------------------
|
||||
const issuer_setup_result = await client.submitAndWait({
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": issuer.address,
|
||||
"SetFlag": xrpl.AccountSetAsfFlags.asfDefaultRipple
|
||||
}, {autofill: true, wallet: issuer} )
|
||||
if (issuer_setup_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
console.log(`Issuer DefaultRipple enabled: ${EXPLORER}/transactions/${issuer_setup_result.result.hash}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${issuer_setup_result}`
|
||||
}
|
||||
|
||||
// Create trust line to issuer ----------------------------------------------
|
||||
const trust_result = await client.submitAndWait({
|
||||
"TransactionType": "TrustSet",
|
||||
"Account": wallet.address,
|
||||
"LimitAmount": {
|
||||
"currency": currency_code,
|
||||
"issuer": issuer.address,
|
||||
"value": "10000000000" // Large limit, arbitrarily chosen
|
||||
}
|
||||
}, {autofill: true, wallet: wallet})
|
||||
if (trust_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
console.log(`Trust line created: ${EXPLORER}/transactions/${trust_result.result.hash}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${trust_result}`
|
||||
}
|
||||
|
||||
// Issue tokens -------------------------------------------------------------
|
||||
const issue_result = await client.submitAndWait({
|
||||
"TransactionType": "Payment",
|
||||
"Account": issuer.address,
|
||||
"Amount": {
|
||||
"currency": currency_code,
|
||||
"value": issue_quantity,
|
||||
"issuer": issuer.address
|
||||
},
|
||||
"Destination": wallet.address
|
||||
}, {autofill: true, wallet: issuer})
|
||||
if (issue_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
console.log(`Tokens issued: ${EXPLORER}/transactions/${issue_result.result.hash}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${issue_result}`
|
||||
}
|
||||
|
||||
return {
|
||||
"currency": currency_code,
|
||||
"value": issue_quantity,
|
||||
"issuer": issuer.address
|
||||
}
|
||||
}
|
||||
5
content/_code-samples/create-amm/js/package.json
Normal file
5
content/_code-samples/create-amm/js/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "2.11.0"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"dependencies": {
|
||||
"five-bells-condition": "*",
|
||||
"ripple-lib": "^0.17.6",
|
||||
"xrpl": "^2.8.0-beta.0"
|
||||
"xrpl": "^2.11.0"
|
||||
},
|
||||
"//": "Intended for Node.js version ?.? and higher"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ async function main() {
|
||||
// ... custom code goes here
|
||||
|
||||
// Disconnect when done (If you omit this, Node.js won't end the process)
|
||||
client.disconnect()
|
||||
await client.disconnect()
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -32,7 +32,7 @@ async function main() {
|
||||
})
|
||||
|
||||
// Disconnect when done so Node.js can end the process
|
||||
client.disconnect()
|
||||
await client.disconnect()
|
||||
}
|
||||
|
||||
// call the async function
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -17,11 +17,11 @@ async function main() {
|
||||
function printLedgerResult(){
|
||||
console.log(ledger["result"])
|
||||
}
|
||||
|
||||
|
||||
// Execute function at least once before checking for markers.
|
||||
do {
|
||||
printLedgerResult()
|
||||
|
||||
|
||||
if (ledger["result"]["marker"] === undefined) {
|
||||
break
|
||||
}
|
||||
@@ -37,7 +37,7 @@ async function main() {
|
||||
} while (true)
|
||||
|
||||
// Disconnect when done. If you omit this, Node.js won't end the process.
|
||||
client.disconnect()
|
||||
await client.disconnect()
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"author": "",
|
||||
"license": "CC0-1.0",
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// In browsers, add the following <script> tags to the HTML to load dependencies
|
||||
// instead of using require():
|
||||
// <script src="https://unpkg.com/xrpl@2.2.0/build/xrpl-latest-min.js"></script>
|
||||
// <script src="https://unpkg.com/xrpl@2.11.0/build/xrpl-latest-min.js"></script>
|
||||
// <script src='https://cdn.jsdelivr.net/npm/bignumber.js@9.0.2/bignumber.min.js'></script>
|
||||
const xrpl = require('xrpl')
|
||||
const BigNumber = require('bignumber.js')
|
||||
@@ -15,7 +15,7 @@ async function main() {
|
||||
// ... custom code goes here
|
||||
|
||||
// Disconnect when done (If you omit this, Node.js won't end the process)
|
||||
client.disconnect()
|
||||
await client.disconnect()
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^2.1.0",
|
||||
"xrpl": "^2.11.0",
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.7",
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^2.0.0"
|
||||
"xrpl": "^2.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ async function main() {
|
||||
// Wait for Validation (again) -----------------------------------------------
|
||||
|
||||
// Disconnect when done (If you omit this, Node.js won't end the process)
|
||||
client.disconnect()
|
||||
await client.disconnect()
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user