Skip to content

Instantly share code, notes, and snippets.

@intelliot
Created March 16, 2021 23:16
Show Gist options
  • Save intelliot/b646392ef9b7ca06e5bc2bdfa30329f8 to your computer and use it in GitHub Desktop.
Save intelliot/b646392ef9b7ca06e5bc2bdfa30329f8 to your computer and use it in GitHub Desktop.
Code from RippleXDev Twitch episode on March 16, 2021
// 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