Created
March 16, 2021 23:16
-
-
Save intelliot/b646392ef9b7ca06e5bc2bdfa30329f8 to your computer and use it in GitHub Desktop.
Code from RippleXDev Twitch episode on March 16, 2021
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Code from RippleXDev Twitch episode on March 16, 2021 | |
// | |
// Code is provided "as is" and solely for informational purposes only. | |
// Warning: Use at your own risk. | |
// Install dependencies: | |
// npm install bignumber.js ripple-lib | |
// | |
// Milliseconds between checks for transaction validation | |
const INTERVAL = 1000; | |
// Testnet Server | |
const websocketServer = 'wss://s.altnet.rippletest.net:51233' | |
// Local server | |
// const websocketServer = 'ws://127.0.0.1:6006' | |
// JSON-RPC | |
// https://s.altnet.rippletest.net:51234 | |
// Free secrets! (Use on Testnet only :) | |
const receiver = { | |
address: 'rfmfaKGK5GEkiSVrRDiLdntdQn87vUpHcU', | |
secret: 'ssi3iriWrv5mcQtNs9B8Knx3qwi7q' | |
} | |
const issuer = { | |
address: 'rDL7x2mZPntghiKwht7SRaY5N1REusab4F', | |
secret: 'ss6t8cSEnhwzpzZJ9JJxfZbMdPYWK' | |
} | |
// Max length 20 bytes (160 bits) | |
const tokenSymbol = (Buffer.from('DogecoinV2', 'ascii').toString('hex').toUpperCase()).padEnd(40, '0') | |
var BigNumber = require('bignumber.js') | |
const RippleAPI = require('ripple-lib').RippleAPI | |
const api = new RippleAPI({server: websocketServer}) | |
const xAmount = '9000' // Amount of token | |
const processedTxs = [] | |
let ledgerVersion | |
start() | |
async function start() { | |
await connectWithRetry() | |
ledgerVersion = await api.getLedgerVersion() | |
if (!ledgerVersion) { | |
console.log('Failed to get ledger version') | |
return | |
} | |
console.log('Connected at ledger:', ledgerVersion) | |
let tx, result | |
// Issuer // | |
//////////// | |
// Set DefaultRipple: true | |
tx = await api.prepareSettings(issuer.address, { | |
// Enable rippling on this account’s trust lines by default | |
defaultRipple: true | |
}) | |
result = await submitTransaction(ledgerVersion, tx, issuer.secret) | |
console.log('Issuer: set DefaultRipple?', result) | |
// Receiver // | |
///////////// | |
// Create trustline for token | |
tx = await api.prepareTrustline(receiver.address, { | |
currency: tokenSymbol, | |
counterparty: issuer.address, | |
limit: xAmount | |
}) | |
result = await submitTransaction(ledgerVersion, tx, receiver.secret) | |
console.log('Receiver: created trustline?', result) | |
const balances = await api.getBalances(receiver.address, { | |
counterparty: issuer.address, | |
currency: tokenSymbol | |
}) | |
const topUpAmount = BigNumber(xAmount).minus(balances[0].value).toString(10) | |
// Issuer // | |
//////////// | |
// Send topUpAmount of token to Receiver | |
tx = await api.preparePayment(issuer.address, { | |
source: { | |
address: issuer.address, | |
maxAmount: { | |
value: topUpAmount, | |
currency: tokenSymbol, | |
counterparty: issuer.address | |
} | |
}, | |
destination: { | |
address: receiver.address, | |
amount: { | |
value: topUpAmount, | |
currency: tokenSymbol, | |
counterparty: issuer.address | |
} | |
} | |
}) | |
console.log('tx:', tx) | |
result = await submitTransaction(ledgerVersion, tx, issuer.secret) | |
console.log(`Issuer: paid ${topUpAmount} ${tokenSymbol} to Receiver?`, result) | |
// Receiver // | |
///////////// | |
// Create offer to buy token | |
tx = await api.prepareOrder(receiver.address, { | |
direction: 'buy', | |
quantity: { | |
currency: tokenSymbol, | |
counterparty: issuer.address, | |
value: xAmount | |
}, | |
totalPrice: { | |
currency: 'XRP', | |
value: xAmount | |
} | |
}) | |
result = await submitTransaction(ledgerVersion, tx, receiver.secret) | |
console.log('Receiver: created offer?', result) | |
// Receiver // | |
///////////// | |
// Poll for transactions | |
// | |
// This is for a test of partial payments! | |
// Repeat forever | |
setInterval(() => { | |
processTransactions(api) | |
}, 5000) | |
} | |
async function processTransactions(api) { | |
console.log('Checking for transactions...') | |
const txs = await api.getTransactions(receiver.address, { | |
minLedgerVersion: ledgerVersion | |
}) | |
txs.forEach(tx => { | |
processTx(tx) | |
if (tx.outcome.ledgerVersion > ledgerVersion) { | |
console.log(`Advancing ledgerVersion from ${ledgerVersion} to ${tx.outcome.ledgerVersion + 1}`) | |
ledgerVersion = tx.outcome.ledgerVersion + 1 | |
// ASSUMPTION: rippled won't fail to include all of the transactions | |
// in the ledger of the latest transaction that we saw. | |
} | |
}) | |
} | |
async function processTx(tx) { | |
if (tx.outcome.deliveredAmount && tx.outcome.deliveredAmount.currency === 'XRP') { | |
// If transaction has not yet been processed | |
if (!processedTxs.includes(tx.id)) { | |
tx = await api.preparePayment(receiver.address, { | |
source: { | |
address: receiver.address, | |
maxAmount: { | |
value: tx.outcome.deliveredAmount.value, | |
currency: tokenSymbol, | |
counterparty: issuer.address | |
} | |
}, | |
destination: { | |
address: tx.specification.source.address, | |
amount: { | |
value: '999999', // Almost 1M XRP | |
currency: 'XRP' | |
} | |
}, | |
allowPartialPayment: true | |
}) | |
const signedData = api.sign(tx.txJSON, receiver.secret); | |
// Prevent infinite loop by ensuring we don't process our own txs | |
processedTxs.push(signedData.id) | |
result = await submitSignedTransaction(ledgerVersion, signedData.signedTransaction, signedData.id, tx.instructions.maxLedgerVersion) | |
console.log('Receiver: returned partial payment?', result) | |
// Abort script if tx failed | |
if (!result) process.exit(1) | |
processedTxs.push(tx.id) | |
} else { | |
console.log(`Already processed tx: ${tx.id} in ledger: ${tx.outcome.ledgerVersion}`) | |
} | |
} else { | |
console.log(`Ignoring non-XRP tx: ${tx.id} in ledger: ${tx.outcome.ledgerVersion}`) | |
} | |
} | |
function sleep(ms) { | |
return new Promise(resolve => { | |
setTimeout(resolve, ms) | |
}) | |
} | |
async function connectWithRetry() { | |
let connected = false | |
while (!connected) { | |
try { | |
await api.connect() | |
connected = true | |
} catch (e) { | |
// When rippled hasn't finished starting up yet: | |
// RippledNotInitializedError: Rippled not initialized | |
console.log('Failed to connect:', e, ' Will retry...') | |
await sleep(1000) | |
} | |
} | |
} | |
/* Verify a transaction is in a validated XRP Ledger version */ | |
function verifyTransaction(hash, options) { | |
console.log('Verifing 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 submitSignedTransaction(lastClosedLedgerVersion, signedTransaction, id, maxLedgerVersion) { | |
return api.submit(signedTransaction).then(data => { | |
console.log('Tentative Result: ', data.resultCode); | |
console.log('Tentative Message: ', data.resultMessage); | |
/* Begin validation workflow */ | |
const options = { | |
minLedgerVersion: lastClosedLedgerVersion, | |
maxLedgerVersion: maxLedgerVersion | |
}; | |
return new Promise((resolve, reject) => { | |
setTimeout(() => verifyTransaction(id, options) | |
.then(resolve, reject), INTERVAL); | |
}); | |
}); | |
} | |
/* Function to prepare, sign, and submit a transaction to the XRP Ledger. */ | |
function submitTransaction(lastClosedLedgerVersion, prepared, secret) { | |
if (!prepared.txJSON) { | |
console.log(prepared) | |
console.log('FATAL: Missing txJSON. Did you use `await`?') | |
process.exit(1) | |
} | |
const signedData = api.sign(prepared.txJSON, secret); | |
console.log('Submitting tx id:', signedData.id) | |
return submitSignedTransaction(lastClosedLedgerVersion, signedData.signedTransaction, signedData.id, prepared.instructions.maxLedgerVersion) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment