mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-19 19:25:51 +00:00
Issue a token: working code
- Fix bugs in submit and verify helper function - Tweak tutorial and placement of helper function - Finish drafting sample code - Put sample code into tutorial
This commit is contained in:
12
content/_code-samples/issue-a-token/demo.html
Normal file
12
content/_code-samples/issue-a-token/demo.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Code Sample - Issue a Token</title>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://unpkg.com/ripple-lib@1.9.8/build/ripple-latest-min.js"></script>
|
||||||
|
<script type="application/javascript" src="../submit-and-verify/submit-and-verify.js"></script>
|
||||||
|
<script type="application/javascript" src="issue-a-token.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>Open your browser's console (F12) to see the logs.</body>
|
||||||
|
</html>
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
// Example credentials
|
|
||||||
let hot_address = "rMCcNuTcajgw7YTgBy1sys3b89QqjUrMpH"
|
|
||||||
let hot_secret = "sn3nxiW7v8KXzPzAqzyHXbSSKNuN9"
|
|
||||||
let cold_address = ""
|
|
||||||
let cold_secret = ""
|
|
||||||
|
|
||||||
// Connect ---------------------------------------------------------------------
|
// Connect ---------------------------------------------------------------------
|
||||||
// ripple = require('ripple-lib') // For Node.js. In browsers, use <script>.
|
// ripple = require('ripple-lib') // For Node.js. In browsers, use <script>.
|
||||||
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||||
|
console.log("Connecting to Testnet...")
|
||||||
api.connect()
|
api.connect()
|
||||||
api.on('connected', async () => {
|
api.on('connected', async () => {
|
||||||
|
|
||||||
@@ -30,13 +25,14 @@ api.on('connected', async () => {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Requesting addresses from the Testnet faucet...")
|
||||||
const hot_data = await get_faucet_address()
|
const hot_data = await get_faucet_address()
|
||||||
hot_address = hot_data.account.address
|
const hot_address = hot_data.account.address
|
||||||
hot_secret = hot_data.account.secret
|
const hot_secret = hot_data.account.secret
|
||||||
|
|
||||||
const cold_data = await get_faucet_address()
|
const cold_data = await get_faucet_address()
|
||||||
cold_address = cold_data.account.address
|
const cold_address = cold_data.account.address
|
||||||
cold_secret = cold_data.account.secret
|
const cold_secret = cold_data.account.secret
|
||||||
|
|
||||||
console.log("Waiting until we have a validated starting sequence number...")
|
console.log("Waiting until we have a validated starting sequence number...")
|
||||||
// If you go too soon, the funding transaction might slip back a ledger and
|
// If you go too soon, the funding transaction might slip back a ledger and
|
||||||
@@ -45,31 +41,120 @@ api.on('connected', async () => {
|
|||||||
// the faucet.
|
// the faucet.
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await api.request("account_info", {account: address, ledger_index: "validated"})
|
await api.request("account_info", {account: cold_address, ledger_index: "validated"})
|
||||||
|
await api.request("account_info", {account: hot_address, ledger_index: "validated"})
|
||||||
break
|
break
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
if (e.data.error != 'actNotFound') throw e
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(`Got hot address ${hot_address} and cold address ${cold_address}.`)
|
||||||
|
|
||||||
// Prepare AccountSet transaction for the issuer (cold address)
|
// Configure issuer (cold address) settings ----------------------------------
|
||||||
const cold_settings_tx = {
|
const cold_settings_tx = {
|
||||||
|
"TransactionType": "AccountSet",
|
||||||
"Account": cold_address,
|
"Account": cold_address,
|
||||||
"TransferFee": 0,
|
"TransferRate": 0,
|
||||||
"TickSize": 5,
|
"TickSize": 5,
|
||||||
"SetFlag": 8 // enable Default Ripple
|
"SetFlag": 8 // enable Default Ripple
|
||||||
//"Flags": (api.txFlags.AccountSet.DisallowXRP |
|
//"Flags": (api.txFlags.AccountSet.DisallowXRP |
|
||||||
// api.txFlags.AccountSet.RequireDestTag)
|
// api.txFlags.AccountSet.RequireDestTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
const prepared_cst = await api.prepareTransaction(cold_settings_tx, {maxLedgerVersionOffset: 10})
|
const cst_prepared = await api.prepareTransaction(cold_settings_tx, {maxLedgerVersionOffset: 10})
|
||||||
|
const cst_signed = api.sign(cst_prepared.txJSON, cold_secret)
|
||||||
|
// submit_and_verify helper function from:
|
||||||
|
// https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/submit-and-verify/
|
||||||
|
console.log("Sending cold address AccountSet transaction...")
|
||||||
|
const cst_result = await submit_and_verify(api, cst_signed.signedTransaction)
|
||||||
|
if (cst_result == "tesSUCCESS") {
|
||||||
|
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${cst_signed.id}`)
|
||||||
|
} else {
|
||||||
|
throw `Error sending transaction: ${cst_result}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Configure hot address settings --------------------------------------------
|
||||||
|
|
||||||
|
const hot_settings_tx = {
|
||||||
|
"TransactionType": "AccountSet",
|
||||||
|
"Account": hot_address,
|
||||||
|
"SetFlag": 2 // enable Require Auth so we can't use trust lines that users
|
||||||
|
// make to the hot address, even by accident.
|
||||||
|
//"Flags": (api.txFlags.AccountSet.DisallowXRP |
|
||||||
|
// api.txFlags.AccountSet.RequireDestTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hst_prepared = await api.prepareTransaction(hot_settings_tx, {maxLedgerVersionOffset: 10})
|
||||||
|
const hst_signed = api.sign(hst_prepared.txJSON, hot_secret)
|
||||||
|
console.log("Sending hot address AccountSet transaction...")
|
||||||
|
const hst_result = await submit_and_verify(api, hst_signed.signedTransaction)
|
||||||
|
if (hst_result == "tesSUCCESS") {
|
||||||
|
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${hst_signed.id}`)
|
||||||
|
} else {
|
||||||
|
throw `Error sending transaction: ${hst_result}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create trust line from hot to cold address --------------------------------
|
||||||
|
const currency_code = "FOO"
|
||||||
|
const trust_set_tx = {
|
||||||
|
"TransactionType": "TrustSet",
|
||||||
|
"Account": hot_address,
|
||||||
|
"LimitAmount": {
|
||||||
|
"currency": currency_code,
|
||||||
|
"issuer": cold_address,
|
||||||
|
"value": "10000000000" // Large limit, arbitrarily chosen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ts_prepared = await api.prepareTransaction(trust_set_tx, {maxLedgerVersionOffset: 10})
|
||||||
|
const ts_signed = api.sign(ts_prepared.txJSON, hot_secret)
|
||||||
|
console.log("Creating trust line from hot address to issuer...")
|
||||||
|
const ts_result = await submit_and_verify(api, ts_signed.signedTransaction)
|
||||||
|
if (ts_result == "tesSUCCESS") {
|
||||||
|
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${ts_signed.id}`)
|
||||||
|
} else {
|
||||||
|
throw `Error sending transaction: ${ts_result}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send token ----------------------------------------------------------------
|
||||||
|
const issue_quantity = "3840"
|
||||||
|
const send_token_tx = {
|
||||||
|
"TransactionType": "Payment",
|
||||||
|
"Account": cold_address,
|
||||||
|
"Amount": {
|
||||||
|
"currency": currency_code,
|
||||||
|
"value": issue_quantity,
|
||||||
|
"issuer": cold_address
|
||||||
|
},
|
||||||
|
"Destination": hot_address
|
||||||
|
}
|
||||||
|
|
||||||
|
const payment_prepared = await api.prepareTransaction(send_token_tx, {maxLedgerVersionOffset: 10})
|
||||||
|
const payment_signed = api.sign(payment_prepared.txJSON, cold_secret)
|
||||||
|
// submit_and_verify helper from _code-samples/submit-and-verify
|
||||||
|
console.log(`Sending ${issue_quantity} ${currency_code} to ${hot_address}...`)
|
||||||
|
const payment_result = await submit_and_verify(api, payment_signed.signedTransaction)
|
||||||
|
if (payment_result == "tesSUCCESS") {
|
||||||
|
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${payment_signed.id}`)
|
||||||
|
} else {
|
||||||
|
throw `Error sending transaction: ${payment_result}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check balances ------------------------------------------------------------
|
||||||
|
console.log("Getting hot address balances...")
|
||||||
|
const hot_balances = await api.request("account_lines", {account: hot_address, ledger_index: "validated"})
|
||||||
|
console.log(hot_balances)
|
||||||
|
|
||||||
|
console.log("Getting cold address balances...")
|
||||||
|
const cold_balances = await api.request("gateway_balances", {
|
||||||
|
account: cold_address,
|
||||||
|
ledger_index: "validated",
|
||||||
|
hotwallet: [hot_address]
|
||||||
|
})
|
||||||
|
console.log(cold_balances)
|
||||||
|
|
||||||
}) // End of api.on.('connected')
|
}) // End of api.on.('connected')
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
|
||||||
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
||||||
<script type="application/javascript" src="../rippleapi_quickstart/semi-reliable-submit.js"></script>
|
<script type="application/javascript" src="../submit-and-verify/submit-and-verify.js"></script>
|
||||||
<script type="application/javascript" src="require-destination-tags.js"></script>
|
<script type="application/javascript" src="require-destination-tags.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>Open your browser's console (F12) to see the logs.</body>
|
<body>Open your browser's console (F12) to see the logs.</body>
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
function lookup_tx_final(tx_id, max_ledger, min_ledger) {
|
|
||||||
if (typeof min_ledger == "undefined") {
|
|
||||||
min_ledger = -1
|
|
||||||
}
|
|
||||||
if (typeof max_ledger == "undefined") {
|
|
||||||
max_ledger = -1
|
|
||||||
}
|
|
||||||
if (min_ledger > max_ledger) {
|
|
||||||
// Assume the args were just passed backwards & swap them
|
|
||||||
[min_ledger, max_ledger] = [max_ledger, min_ledger]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to determine if we (should) know the transaction's final result yet.
|
|
||||||
// If the server has validated all ledgers the tx could possibly appear in,
|
|
||||||
// then we should know its final result.
|
|
||||||
async function server_has_ledger_range(min_ledger, max_ledger) {
|
|
||||||
const si = await api.request("server_info")
|
|
||||||
if (si.info.complete_ledgers == "empty") {
|
|
||||||
console.warn("Connected server is not synced.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// In case of a discontiguous set, use only the last set, since we need
|
|
||||||
// continuous history from submission to expiration to know that a
|
|
||||||
// transaction failed to achieve consensus.
|
|
||||||
const ledger_ranges = si.info.complete_ledgers.split(',')
|
|
||||||
// Note: last_range can be in the form 'x-y' or just 'y'
|
|
||||||
const last_range = ledger_ranges[ledger_ranges.length -1].split('-')
|
|
||||||
const lr_min = parseInt(last_range[0])
|
|
||||||
const lr_max = parseInt(last_range[last_range.length - 1])
|
|
||||||
const max_validated = Math.min(lr_max, lr_max)
|
|
||||||
if (lr_min <= min_ledger && max_validated >= max_ledger) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
api.on('ledger', async (ledger) => {
|
|
||||||
try {
|
|
||||||
tx_result = await api.request("tx", {
|
|
||||||
"transaction": tx_id,
|
|
||||||
"min_ledger": min_ledger,
|
|
||||||
"max_ledger": max_ledger
|
|
||||||
})
|
|
||||||
|
|
||||||
if (tx_result.validated) {
|
|
||||||
resolve(tx_result.meta.TransactionResult)
|
|
||||||
} else if (max_ledger > ledger.ledgerVersion) {
|
|
||||||
// Transaction found, not validated, but we should have a final result
|
|
||||||
// by now.
|
|
||||||
// Work around https://github.com/ripple/rippled/issues/3727
|
|
||||||
if (server_has_ledger_range(min_ledger, max_ledger)) {
|
|
||||||
// Transaction should have been validated by now.
|
|
||||||
reject(`Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this ledger is correct.`)
|
|
||||||
} else {
|
|
||||||
reject("Can't get final result. Check a full history server.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Transaction may still be validated later. Keep waiting.
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
if (e.data.error == "txnNotFound") {
|
|
||||||
if (e.data.searched_all) {
|
|
||||||
reject(`Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`)
|
|
||||||
} else {
|
|
||||||
if (max_ledger > ledger.ledgerVersion) {
|
|
||||||
// Transaction may yet be confirmed. This would not be a bad time
|
|
||||||
// to resubmit the transaction just in case.
|
|
||||||
} else {
|
|
||||||
// Work around https://github.com/ripple/rippled/issues/3750
|
|
||||||
if (server_has_ledger_range(min_ledger, max_ledger)) {
|
|
||||||
reject(`Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`)
|
|
||||||
} else {
|
|
||||||
reject("Can't get final result. Check a full history server.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown error; pass it back up
|
|
||||||
reject(`Unknown Error: ${e}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) // end ledger event handler
|
|
||||||
}) // end promise def
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
/* import RippleAPI and support libraries */
|
|
||||||
const RippleAPI = require('ripple-lib').RippleAPI;
|
|
||||||
|
|
||||||
/* Credentials of the account placing the order */
|
|
||||||
const myAddr = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn';
|
|
||||||
const mySecret = 's████████████████████████████';
|
|
||||||
|
|
||||||
/* Define the order to place here */
|
|
||||||
const myOrder = {
|
|
||||||
'direction': 'buy',
|
|
||||||
'quantity': {
|
|
||||||
'currency': 'FOO',
|
|
||||||
'counterparty': 'rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v',
|
|
||||||
'value': '100'
|
|
||||||
},
|
|
||||||
'totalPrice': {
|
|
||||||
'currency': 'XRP',
|
|
||||||
'value': '1000'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Milliseconds to wait between checks for a new ledger. */
|
|
||||||
const INTERVAL = 1000;
|
|
||||||
/* Instantiate RippleAPI. Uses s2 (full history server) */
|
|
||||||
const api = new RippleAPI({server: 'wss://s2.ripple.com'});
|
|
||||||
/* Number of ledgers to check for valid transaction before failing */
|
|
||||||
const ledgerOffset = 5;
|
|
||||||
const myInstructions = {maxLedgerVersionOffset: ledgerOffset};
|
|
||||||
|
|
||||||
|
|
||||||
/* Verify a transaction is in a validated XRP Ledger version */
|
|
||||||
function verifyTransaction(hash, options) {
|
|
||||||
console.log('Verifying Transaction');
|
|
||||||
return api.getTransaction(hash, options).then(data => {
|
|
||||||
console.log('Final Result: ', data.outcome.result);
|
|
||||||
console.log('Validated in Ledger: ', data.outcome.ledgerVersion);
|
|
||||||
console.log('Sequence: ', data.sequence);
|
|
||||||
return data.outcome.result === 'tesSUCCESS';
|
|
||||||
}).catch(error => {
|
|
||||||
/* If transaction not in latest validated ledger,
|
|
||||||
try again until max ledger hit */
|
|
||||||
if (error instanceof api.errors.PendingLedgerVersionError) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => verifyTransaction(hash, options)
|
|
||||||
.then(resolve, reject), INTERVAL);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Function to prepare, sign, and submit a transaction to the XRP Ledger. */
|
|
||||||
function submitTransaction(lastClosedLedgerVersion, prepared, secret) {
|
|
||||||
const signedData = api.sign(prepared.txJSON, secret);
|
|
||||||
return api.submit(signedData.signedTransaction).then(data => {
|
|
||||||
console.log('Tentative Result: ', data.resultCode);
|
|
||||||
console.log('Tentative Message: ', data.resultMessage);
|
|
||||||
/* The tentative result should be ignored. Transactions that succeed here can ultimately fail,
|
|
||||||
and transactions that fail here can ultimately succeed. */
|
|
||||||
|
|
||||||
/* Begin validation workflow */
|
|
||||||
const options = {
|
|
||||||
minLedgerVersion: lastClosedLedgerVersion,
|
|
||||||
maxLedgerVersion: prepared.instructions.maxLedgerVersion
|
|
||||||
};
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => verifyTransaction(signedData.id, options)
|
|
||||||
.then(resolve, reject), INTERVAL);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
api.connect().then(() => {
|
|
||||||
console.log('Connected');
|
|
||||||
return api.prepareOrder(myAddr, myOrder, myInstructions);
|
|
||||||
}).then(prepared => {
|
|
||||||
console.log('Order Prepared');
|
|
||||||
return api.getLedger().then(ledger => {
|
|
||||||
console.log('Current Ledger', ledger.ledgerVersion);
|
|
||||||
return submitTransaction(ledger.ledgerVersion, prepared, mySecret);
|
|
||||||
});
|
|
||||||
}).then(() => {
|
|
||||||
api.disconnect().then(() => {
|
|
||||||
console.log('api disconnected');
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
}).catch(console.error);
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<title>Code Sample - Send XRP</title>
|
<title>Code Sample - Send XRP</title>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
|
||||||
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
||||||
<script type="application/javascript" src="../rippleapi_quickstart/semi-reliable-submit.js"></script>
|
<script type="application/javascript" src="../submit-and-verify/submit-and-verify.js"></script>
|
||||||
<script type="application/javascript" src="send-xrp.js"></script>
|
<script type="application/javascript" src="send-xrp.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>Open your browser's console (F12) to see the logs.</body>
|
<body>Open your browser's console (F12) to see the logs.</body>
|
||||||
|
|||||||
32
content/_code-samples/submit-and-verify/README.md
Normal file
32
content/_code-samples/submit-and-verify/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Submit and Verify
|
||||||
|
|
||||||
|
Example JavaScript code using ripple-lib to submit a signed transaction blob and wait until it has a final result.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// example testnet creds. Don't use for real
|
||||||
|
const address = "raXDGCShEGqYz2d94qkv1UmAh2uJd3UTea"
|
||||||
|
const secret = "ssNBEKCkEY3W6YFfrjcSoNit91Vvj"
|
||||||
|
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||||
|
api.connect()
|
||||||
|
api.on('connected', async () => {
|
||||||
|
const prepared = await api.prepareTransaction({
|
||||||
|
"TransactionType": "AccountSet",
|
||||||
|
"Account": address
|
||||||
|
})
|
||||||
|
const signed = api.sign(prepared.txJSON, secret)
|
||||||
|
|
||||||
|
const result = await submit_and_verify(api, signed.signedTransaction)
|
||||||
|
|
||||||
|
if (result == "tesSUCCESS") {
|
||||||
|
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${signed.id}`)
|
||||||
|
} else if (result == "unknown") {
|
||||||
|
console.log(`Transaction status unknown. `)
|
||||||
|
} else if (result == "tefMAX_LEDGER") {
|
||||||
|
console.log(`Transaction failed to achieve a consensus.`)
|
||||||
|
} else {
|
||||||
|
console.log(`Transaction failed with code ${result}: https://testnet.xrpl.org/transactions/${signed.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
167
content/_code-samples/submit-and-verify/submit-and-verify.js
Normal file
167
content/_code-samples/submit-and-verify/submit-and-verify.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// Submit-and-verify XRPL transaction using ripple-lib (v1.x)
|
||||||
|
// Demonstrates how to submit a transaction and wait for validation.
|
||||||
|
// This is not true "robust" transaction submission because it does not protect
|
||||||
|
// against power outages or other sudden interruptions.
|
||||||
|
|
||||||
|
// Look up a transaction's result.
|
||||||
|
// Arguments:
|
||||||
|
// @param api object RippleAPI instance connected to the network where you
|
||||||
|
// submitted the transaction.
|
||||||
|
// @param tx_id string The identifying hash of the transaction.
|
||||||
|
// @param max_ledger int optional The highest ledger index where the
|
||||||
|
// transaction can be validated.
|
||||||
|
// @param min_ledger int optional The lowest ledger index where the
|
||||||
|
// transaction can be validated.
|
||||||
|
// Returns: Promise<object> -> result of the tx command with the transaction's
|
||||||
|
// validated transaction results.
|
||||||
|
// On failure, the reason is an object with two fields:
|
||||||
|
// - failure_final: if true, this transaction did not achieve consensus and
|
||||||
|
// it can never be validated in the future (assuming the
|
||||||
|
// min_ledger and max_ledger values provided were accurate).
|
||||||
|
// - msg: A human-readable message explaining what happened.
|
||||||
|
function lookup_tx_final(api, tx_id, max_ledger, min_ledger) {
|
||||||
|
if (typeof min_ledger == "undefined") {
|
||||||
|
min_ledger = -1
|
||||||
|
}
|
||||||
|
if (typeof max_ledger == "undefined") {
|
||||||
|
max_ledger = -1
|
||||||
|
}
|
||||||
|
if (min_ledger > max_ledger) {
|
||||||
|
// Assume the args were just passed backwards & swap them
|
||||||
|
[min_ledger, max_ledger] = [max_ledger, min_ledger]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to determine if we (should) know the transaction's final result yet.
|
||||||
|
// If the server has validated all ledgers the tx could possibly appear in,
|
||||||
|
// then we should know its final result.
|
||||||
|
async function server_has_ledger_range(min_ledger, max_ledger) {
|
||||||
|
const si = await api.request("server_info")
|
||||||
|
// console.log(`Server has ledger range: ${si.info.complete_ledgers}`)
|
||||||
|
if (si.info.complete_ledgers == "empty") {
|
||||||
|
console.warn("Connected server is not synced.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// In case of a discontiguous set, use only the last set, since we need
|
||||||
|
// continuous history from submission to expiration to know that a
|
||||||
|
// transaction failed to achieve consensus.
|
||||||
|
const ledger_ranges = si.info.complete_ledgers.split(',')
|
||||||
|
// Note: last_range can be in the form 'x-y' or just 'y'
|
||||||
|
const last_range = ledger_ranges[ledger_ranges.length -1].split('-')
|
||||||
|
const lr_min = parseInt(last_range[0])
|
||||||
|
const lr_max = parseInt(last_range[last_range.length - 1])
|
||||||
|
if (lr_min <= min_ledger && lr_max >= max_ledger) {
|
||||||
|
// Server has ledger range needed.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ledger_listener = async (ledger) => {
|
||||||
|
try {
|
||||||
|
tx_result = await api.request("tx", {
|
||||||
|
"transaction": tx_id,
|
||||||
|
"min_ledger": min_ledger,
|
||||||
|
"max_ledger": max_ledger
|
||||||
|
})
|
||||||
|
|
||||||
|
if (tx_result.validated) {
|
||||||
|
resolve(tx_result.meta.TransactionResult)
|
||||||
|
} else if (ledger.ledgerVersion >= max_ledger) {
|
||||||
|
api.off("ledger", ledger_listener)
|
||||||
|
// Transaction found, not validated, but we should have a final result
|
||||||
|
// by now.
|
||||||
|
// Work around https://github.com/ripple/rippled/issues/3727
|
||||||
|
if (await server_has_ledger_range(min_ledger, max_ledger)) {
|
||||||
|
// Transaction should have been validated by now.
|
||||||
|
reject({
|
||||||
|
failure_final: true,
|
||||||
|
msg: `Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
reject({
|
||||||
|
failure_final: false,
|
||||||
|
msg: "Can't get final result (1). Check a full history server."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Transaction may still be validated later. Keep waiting.
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.warn(e)
|
||||||
|
if (e.data.error == "txnNotFound") {
|
||||||
|
if (e.data.searched_all) {
|
||||||
|
api.off("ledger", ledger_listener)
|
||||||
|
reject({
|
||||||
|
failure_final: true,
|
||||||
|
msg: `Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (max_ledger > ledger.ledgerVersion) {
|
||||||
|
api.off("ledger", ledger_listener)
|
||||||
|
// Transaction may yet be confirmed. This would not be a bad time
|
||||||
|
// to resubmit the transaction just in case.
|
||||||
|
} else {
|
||||||
|
// Work around https://github.com/ripple/rippled/issues/3750
|
||||||
|
if (await server_has_ledger_range(min_ledger, max_ledger)) {
|
||||||
|
reject({
|
||||||
|
failure_final: true,
|
||||||
|
msg: `Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
reject({
|
||||||
|
failure_final: false,
|
||||||
|
msg: "Can't get final result. Check a full history server."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unknown error; pass it back up
|
||||||
|
reject({
|
||||||
|
failure_final: false,
|
||||||
|
msg: `Unknown Error: ${e}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end ledger event handler
|
||||||
|
api.on('ledger', ledger_listener)
|
||||||
|
}) // end promise def
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Submit a transaction blob and get its final result as a string.
|
||||||
|
// This can be one of these possibilities:
|
||||||
|
// tesSUCCESS. The transaction executed successfully.
|
||||||
|
// tec*. The transaction was validated with a failure code. It destroyed the XRP
|
||||||
|
// transaction cost and may have done some cleanup such as removing
|
||||||
|
// expired objects from the ledger, but nothing else.
|
||||||
|
// See https://xrpl.org/tec-codes.html for the full list.
|
||||||
|
// tefMAX_LEDGER. The transaction expired without ever being included
|
||||||
|
// in a validated ledger.
|
||||||
|
// unknown. Either the server you are querying does not have the
|
||||||
|
// necessary ledger history to find the transaction's final result, or
|
||||||
|
// something else went wrong when trying to look up the results. The
|
||||||
|
// warning written to the console can tell you more about what happened.
|
||||||
|
async function submit_and_verify(api, tx_blob) {
|
||||||
|
const prelim_result = await api.request("submit", {"tx_blob": tx_blob})
|
||||||
|
console.log("Preliminary result:", prelim_result)
|
||||||
|
const min_ledger = prelim_result.validated_ledger_index
|
||||||
|
if (prelim_result.tx_json.LastLedgerSequence === undefined) {
|
||||||
|
console.warn("Transaction has no LastLedgerSequence field. "+
|
||||||
|
"It may be impossible to determine final failure.")
|
||||||
|
}
|
||||||
|
const max_ledger = prelim_result.tx_json.LastLedgerSequence
|
||||||
|
const tx_id = prelim_result.tx_json.hash
|
||||||
|
|
||||||
|
let final_result
|
||||||
|
try {
|
||||||
|
final_result = await lookup_tx_final(api, tx_id, max_ledger, min_ledger)
|
||||||
|
} catch(reason) {
|
||||||
|
if (reason.failure_final) final_result = "tefMAX_LEDGER"
|
||||||
|
else final_result = "unknown"
|
||||||
|
console.warn(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
return final_result;
|
||||||
|
}
|
||||||
@@ -244,7 +244,7 @@ Promiseは、自身の非同期動作を完了すると、渡されたコール
|
|||||||
XRP Ledger(または任意の分散されたシステム)を使用する上で最大の課題の1つとなるのが、最終的かつ不変のトランザクション結果を把握することです。[ベストプラクティスに従っている](reliable-transaction-submission.html)場合も、トランザクションが最終的に受け入れられるか拒否されるまで、[コンセンサスプロセス](consensus.html)を待機しなければならないことに変わりはありません。以下のサンプルコードは、トランザクションの最終的な結果を待機する方法を示しています。
|
XRP Ledger(または任意の分散されたシステム)を使用する上で最大の課題の1つとなるのが、最終的かつ不変のトランザクション結果を把握することです。[ベストプラクティスに従っている](reliable-transaction-submission.html)場合も、トランザクションが最終的に受け入れられるか拒否されるまで、[コンセンサスプロセス](consensus.html)を待機しなければならないことに変わりはありません。以下のサンプルコードは、トランザクションの最終的な結果を待機する方法を示しています。
|
||||||
|
|
||||||
```
|
```
|
||||||
{% include '_code-samples/rippleapi_quickstart/submit-and-verify.js' %}
|
{% include '_code-samples/submit-and-verify/submit-and-verify.js' %}
|
||||||
```
|
```
|
||||||
|
|
||||||
このコードは注文トランザクションを作成して送信するものですが、他のタイプのトランザクションにも同様の原則があてはまります。トランザクションを送信した後、setTimeoutを使用して所定の時間が経過するまで待機し、新しいPromiseでレジャーをもう一度照会して、トランザクションが検証済みとなっているかどうかを確認します。検証済みとなっていない場合は、検証済みレジャーの中にトランザクションが見つかるか、返されたレジャーがLastLedgerSequenceパラメーターの値よりも大きくなるまで、このプロセスを繰り返します。
|
このコードは注文トランザクションを作成して送信するものですが、他のタイプのトランザクションにも同様の原則があてはまります。トランザクションを送信した後、setTimeoutを使用して所定の時間が経過するまで待機し、新しいPromiseでレジャーをもう一度照会して、トランザクションが検証済みとなっているかどうかを確認します。検証済みとなっていない場合は、検証済みレジャーの中にトランザクションが見つかるか、返されたレジャーがLastLedgerSequenceパラメーターの値よりも大きくなるまで、このプロセスを繰り返します。
|
||||||
|
|||||||
@@ -242,17 +242,13 @@ The `catch` method ends this Promise chain. The callback provided here runs if a
|
|||||||
|
|
||||||
# Waiting for Validation
|
# Waiting for Validation
|
||||||
|
|
||||||
One of the biggest challenges in using the XRP Ledger (or any decentralized system) is knowing the final, immutable transaction results. Even if you [follow the best practices](reliable-transaction-submission.html) you still have to wait for the [consensus process](consensus.html) to finally accept or reject your transaction. The following example code demonstrates how to wait for the final outcome of a transaction:
|
Most transactions are validated and have a final result in one or two ledger versions, about 2-7 seconds after submission. However, when things don't go quite as planned, it can be tricky to know what a transaction's final, immutable results are. Even if you [follow the best practices](reliable-transaction-submission.html) you still have to wait for the [consensus process](consensus.html) to finally accept or reject your transaction.
|
||||||
|
|
||||||
```
|
The [submit-and-verify code sample](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/submit-and-verify/) demonstrates how to submit a transaction and wait for it to have a final result.
|
||||||
{% include '_code-samples/rippleapi_quickstart/submit-and-verify.js' %}
|
|
||||||
```
|
|
||||||
|
|
||||||
This code creates and submits an order transaction, although the same principles apply to other types of transactions as well. After submitting the transaction, the code uses a new Promise, which queries the ledger again after using `setTimeout` to wait a fixed amount of time, to see if the transaction has been verified. If it hasn't been verified, the process repeats until either the transaction is found in a validated ledger or the returned ledger is higher than the `LastLedgerSequence` parameter.
|
In rare cases (particularly with a large delay, a brief network outage, or a loss of power), the `rippled` server may be missing a ledger version between when you submitted the transaction and when you determined that the network validated the last ledger version that the transaction . In this case, you cannot be definitively sure whether the transaction has failed, or has been included in one of the missing ledger versions.
|
||||||
|
|
||||||
In rare cases (particularly with a large delay or a loss of power), the `rippled` server may be missing a ledger version between when you submitted the transaction and when you determined that the network has passed the `maxLedgerVersion`. In this case, you cannot be definitively sure whether the transaction has failed, or has been included in one of the missing ledger versions. RippleAPI returns `MissingLedgerHistoryError` in this case.
|
If you are the administrator of the `rippled` server, you can [manually request the missing ledger(s)](ledger_request.html). Otherwise, you can try checking the ledger history using a different server. Several [public full-history servers](public-servers.html) are available for this purpose.
|
||||||
|
|
||||||
If you are the administrator of the `rippled` server, you can [manually request the missing ledger(s)](ledger_request.html). Otherwise, you can try checking the ledger history using a different server. (Ripple runs a public full-history server at `s2.ripple.com` for this purpose.)
|
|
||||||
|
|
||||||
See [Reliable Transaction Submission](reliable-transaction-submission.html) for a more thorough explanation.
|
See [Reliable Transaction Submission](reliable-transaction-submission.html) for a more thorough explanation.
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ blurb: Create your own token and issue it on the XRP Ledger Testnet.
|
|||||||
embed_ripple_lib: true
|
embed_ripple_lib: true
|
||||||
filters:
|
filters:
|
||||||
- interactive_steps
|
- interactive_steps
|
||||||
|
- include_code
|
||||||
labels:
|
labels:
|
||||||
- Tokens
|
- Tokens
|
||||||
---
|
---
|
||||||
@@ -113,9 +114,14 @@ Other settings you may want to, optionally, configure for your cold address (iss
|
|||||||
|
|
||||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended cold address settings:
|
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended cold address settings:
|
||||||
|
|
||||||
```
|
|
||||||
TODO: CODE SAMPLE
|
<!-- MULTICODE_BLOCK_START -->
|
||||||
```
|
|
||||||
|
_JavaScript_
|
||||||
|
|
||||||
|
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Configure issuer", end_before="// Configure hot", language="js") }}
|
||||||
|
|
||||||
|
<!-- MULTICODE_BLOCK_END -->
|
||||||
|
|
||||||
{{ start_step("Configure Issuer") }}
|
{{ start_step("Configure Issuer") }}
|
||||||
<form>
|
<form>
|
||||||
@@ -176,10 +182,13 @@ The hot address does not strictly require any settings changes from the default,
|
|||||||
|
|
||||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended hot address settings:
|
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended hot address settings:
|
||||||
|
|
||||||
```
|
<!-- MULTICODE_BLOCK_START -->
|
||||||
TODO: code
|
|
||||||
```
|
|
||||||
|
|
||||||
|
_JavaScript_
|
||||||
|
|
||||||
|
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Configure hot address", end_before="// Create trust line", language="js") }}
|
||||||
|
|
||||||
|
<!-- MULTICODE_BLOCK_END -->
|
||||||
|
|
||||||
{{ start_step("Configure Hot Address") }}
|
{{ start_step("Configure Hot Address") }}
|
||||||
<form>
|
<form>
|
||||||
@@ -205,9 +214,13 @@ The hot address needs a trust line like this before it can receive tokens from t
|
|||||||
|
|
||||||
The following code sample shows how to send a [TrustSet transaction][] from the hot address, trusting the issuing address for a limit of 1 billion FOO:
|
The following code sample shows how to send a [TrustSet transaction][] from the hot address, trusting the issuing address for a limit of 1 billion FOO:
|
||||||
|
|
||||||
```
|
<!-- MULTICODE_BLOCK_START -->
|
||||||
TODO: code
|
|
||||||
```
|
_JavaScript_
|
||||||
|
|
||||||
|
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Create trust line", end_before="// Send token", language="js") }}
|
||||||
|
|
||||||
|
<!-- MULTICODE_BLOCK_END -->
|
||||||
|
|
||||||
{{ start_step("Make Trust Line") }}
|
{{ start_step("Make Trust Line") }}
|
||||||
<form>
|
<form>
|
||||||
@@ -259,9 +272,13 @@ You can use [auto-filled values](transaction-common-fields.html#auto-fillable-fi
|
|||||||
|
|
||||||
The following code sample shows how to send a [Payment transaction][] to issue 88 FOO from the cold address to the hot address:
|
The following code sample shows how to send a [Payment transaction][] to issue 88 FOO from the cold address to the hot address:
|
||||||
|
|
||||||
```
|
<!-- MULTICODE_BLOCK_START -->
|
||||||
TODO: code
|
|
||||||
```
|
_JavaScript_
|
||||||
|
|
||||||
|
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Send token", end_before="// Check balances", language="js") }}
|
||||||
|
|
||||||
|
<!-- MULTICODE_BLOCK_END -->
|
||||||
|
|
||||||
{{ start_step("Send Token") }}
|
{{ start_step("Send Token") }}
|
||||||
<button id="send-token-button" class="btn btn-primary">Send Token</button>
|
<button id="send-token-button" class="btn btn-primary">Send Token</button>
|
||||||
@@ -282,9 +299,13 @@ Use the [gateway_balances method][] to look up balances from the perspective of
|
|||||||
|
|
||||||
The following code sample shows how to use both methods:
|
The following code sample shows how to use both methods:
|
||||||
|
|
||||||
```
|
<!-- MULTICODE_BLOCK_START -->
|
||||||
TODO: code
|
|
||||||
```
|
_JavaScript_
|
||||||
|
|
||||||
|
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Check balances", end_before="// End of", language="js") }}
|
||||||
|
|
||||||
|
<!-- MULTICODE_BLOCK_END -->
|
||||||
|
|
||||||
{{ start_step("Confirm Balances") }}
|
{{ start_step("Confirm Balances") }}
|
||||||
<button id="check-balances-button" class="btn btn-primary">Confirm Balances</button>
|
<button id="check-balances-button" class="btn btn-primary">Confirm Balances</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user