mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-22 04:35:49 +00:00
Re-added code to clean branch
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
const xrpl = require("xrpl");
|
||||
|
||||
// The rippled server and its APIs represent time as an unsigned integer.
|
||||
// This number measures the number of seconds since the "Ripple Epoch" of
|
||||
// January 1, 2000 (00:00 UTC). This is like the way the Unix epoch works,
|
||||
// Reference: https://xrpl.org/basic-data-types.html
|
||||
const RIPPLE_EPOCH = 946684800;
|
||||
|
||||
|
||||
const prepareAccountData = (rawAccountData, reserve) => {
|
||||
const numOwners = rawAccountData.OwnerCount || 0
|
||||
|
||||
let xrpReserve = null
|
||||
if (reserve) {
|
||||
//TODO: Decimal?
|
||||
xrpReserve = reserve.reserveBaseXrp + (reserve.reserveIncrementXrp * numOwners)
|
||||
}
|
||||
|
||||
return {
|
||||
classicAddress: rawAccountData.Account,
|
||||
xAddress: xrpl.classicAddressToXAddress(rawAccountData.Account, false, true),
|
||||
xrpBalance: xrpl.dropsToXrp(rawAccountData.Balance),
|
||||
xrpReserve: xrpReserve
|
||||
}
|
||||
}
|
||||
|
||||
const prepareLedgerData = (rawLedgerData) => {
|
||||
const timestamp = RIPPLE_EPOCH + rawLedgerData.ledger_time
|
||||
const dateTime = new Date(timestamp * 1000)
|
||||
const dateTimeString = dateTime.toLocaleDateString() + ' ' + dateTime.toLocaleTimeString()
|
||||
|
||||
return {
|
||||
ledgerIndex: rawLedgerData.ledger_index,
|
||||
ledgerHash: rawLedgerData.ledger_hash,
|
||||
ledgerCloseTime: dateTimeString
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const prepareReserve = (ledger) => {
|
||||
const reserveBaseXrp = xrpl.dropsToXrp(ledger.reserve_base)
|
||||
const reserveIncrementXrp = xrpl.dropsToXrp(ledger.reserve_inc)
|
||||
|
||||
return { reserveBaseXrp, reserveIncrementXrp }
|
||||
}
|
||||
|
||||
module.exports = { prepareAccountData, prepareLedgerData, prepareReserve }
|
||||
@@ -0,0 +1,14 @@
|
||||
const xrpl = require("xrpl");
|
||||
|
||||
const prepareTxData = (transactions) => {
|
||||
return transactions.map(transaction => ({
|
||||
confirmed: transaction.tx.date,
|
||||
type: transaction.tx.TransactionType,
|
||||
from: transaction.tx.Account,
|
||||
to: transaction.tx.Destination,
|
||||
value: xrpl.dropsToXrp(transaction.tx.Amount),
|
||||
hash: transaction.tx.hash
|
||||
}))
|
||||
}
|
||||
|
||||
module.exports = { prepareTxData }
|
||||
@@ -0,0 +1,139 @@
|
||||
const {prepareReserve, prepareAccountData, prepareLedgerData} = require("./3_helpers");
|
||||
const {prepareTxData} = require("./4_helpers");
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const fernet = require("fernet");
|
||||
|
||||
/**
|
||||
* Fetches some initial data to be displayed on application startup
|
||||
*
|
||||
* @param client
|
||||
* @param wallet
|
||||
* @param appWindow
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const initialize = async (client, wallet, appWindow) => {
|
||||
// Reference: https://xrpl.org/account_info.html
|
||||
const accountInfoResponse = await client.request({
|
||||
"command": "account_info",
|
||||
"account": wallet.address,
|
||||
"ledger_index": "current"
|
||||
})
|
||||
const accountData = prepareAccountData(accountInfoResponse.result.account_data)
|
||||
appWindow.webContents.send('update-account-data', accountData)
|
||||
|
||||
// Reference: https://xrpl.org/account_tx.html
|
||||
const txResponse = await client.request({
|
||||
"command": "account_tx",
|
||||
"account": wallet.address
|
||||
})
|
||||
const transactions = prepareTxData(txResponse.result.transactions)
|
||||
appWindow.webContents.send('update-transaction-data', transactions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the subscriptions to ledger events and the internal routing of the responses
|
||||
*
|
||||
* @param client
|
||||
* @param wallet
|
||||
* @param appWindow
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const subscribe = async (client, wallet, appWindow) => {
|
||||
|
||||
let reserve = null
|
||||
|
||||
// Reference: https://xrpl.org/subscribe.html
|
||||
await client.request({
|
||||
"command": "subscribe",
|
||||
"streams": ["ledger"],
|
||||
"accounts": [wallet.address]
|
||||
})
|
||||
|
||||
// Reference: https://xrpl.org/subscribe.html#ledger-stream
|
||||
client.on("ledgerClosed", async (rawLedgerData) => {
|
||||
reserve = prepareReserve(rawLedgerData)
|
||||
const ledger = prepareLedgerData(rawLedgerData)
|
||||
appWindow.webContents.send('update-ledger-data', ledger)
|
||||
})
|
||||
|
||||
// Wait for transaction on subscribed account and re-request account data
|
||||
client.on("transaction", async (transaction) => {
|
||||
// Reference: https://xrpl.org/account_info.html
|
||||
const accountInfoRequest = {
|
||||
"command": "account_info",
|
||||
"account": wallet.address,
|
||||
"ledger_index": transaction.ledger_index
|
||||
}
|
||||
|
||||
const accountInfoResponse = await client.request(accountInfoRequest)
|
||||
const accountData = prepareAccountData(accountInfoResponse.result.account_data)
|
||||
appWindow.webContents.send('update-account-data', accountData)
|
||||
|
||||
const transactions = prepareTxData([{tx: transaction.transaction}])
|
||||
appWindow.webContents.send('update-transaction-data', transactions)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the wallet seed using proper cryptographic functions
|
||||
*
|
||||
* @param WALLET_DIR
|
||||
* @param seed
|
||||
* @param password
|
||||
*/
|
||||
const saveSaltedSeed = (WALLET_DIR, seed, password)=> {
|
||||
const salt = crypto.randomBytes(20).toString('hex')
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, WALLET_DIR, 'salt.txt'), salt);
|
||||
|
||||
// Hashing salted password using Password-Based Key Derivation Function 2
|
||||
const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
|
||||
|
||||
// Generate a Fernet secret we can use for symmetric encryption
|
||||
const secret = new fernet.Secret(derivedKey.toString('base64'));
|
||||
|
||||
// Generate encryption token with secret, time and initialization vector
|
||||
// In a real-world use case we would have current time and a random IV,
|
||||
// but for demo purposes being deterministic is just fine
|
||||
const token = new fernet.Token({
|
||||
secret: secret,
|
||||
time: Date.parse(1),
|
||||
iv: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||
})
|
||||
|
||||
const privateKey = token.encode(seed)
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, WALLET_DIR, 'seed.txt'), privateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the plaintext value of the encrypted seed
|
||||
*
|
||||
* @param WALLET_DIR
|
||||
* @param password
|
||||
* @returns {*}
|
||||
*/
|
||||
const loadSaltedSeed = (WALLET_DIR, password) => {
|
||||
const salt = fs.readFileSync(path.join(__dirname, WALLET_DIR, 'salt.txt')).toString()
|
||||
|
||||
const encodedSeed = fs.readFileSync(path.join(__dirname, WALLET_DIR, 'seed.txt')).toString()
|
||||
|
||||
// Hashing salted password using Password-Based Key Derivation Function 2
|
||||
const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
|
||||
|
||||
// Generate a Fernet secret we can use for symmetric encryption
|
||||
const secret = new fernet.Secret(derivedKey.toString('base64'));
|
||||
|
||||
// Generate decryption token
|
||||
const token = new fernet.Token({
|
||||
secret: secret,
|
||||
token: encodedSeed,
|
||||
ttl: 0
|
||||
})
|
||||
|
||||
return token.decode();
|
||||
}
|
||||
|
||||
module.exports = { initialize, subscribe, saveSaltedSeed, loadSaltedSeed }
|
||||
@@ -0,0 +1,28 @@
|
||||
const xrpl = require("xrpl");
|
||||
|
||||
/**
|
||||
* Prepares, signs and submits a payment transaction
|
||||
*
|
||||
* @param paymentData
|
||||
* @param client
|
||||
* @param wallet
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
const sendXrp = async (paymentData, client, wallet) => {
|
||||
// Reference: https://xrpl.org/submit.html#request-format-1
|
||||
const paymentTx = {
|
||||
"TransactionType": "Payment",
|
||||
"Account": wallet.address,
|
||||
"Amount": xrpl.xrpToDrops(paymentData.amount),
|
||||
"Destination": paymentData.destinationAddress,
|
||||
"DestinationTag": parseInt(paymentData.destinationTag)
|
||||
}
|
||||
|
||||
const preparedTx = await client.autofill(paymentTx)
|
||||
|
||||
const signedTx = wallet.sign(preparedTx)
|
||||
|
||||
return await client.submitAndWait(signedTx.tx_blob)
|
||||
}
|
||||
|
||||
module.exports = { sendXrp }
|
||||
@@ -0,0 +1,102 @@
|
||||
const fetch = require('node-fetch')
|
||||
const toml = require('toml');
|
||||
const { convertHexToString } = require("xrpl/dist/npm/utils/stringConversion");
|
||||
|
||||
const lsfDisallowXRP = 0x00080000;
|
||||
|
||||
/* Example lookups
|
||||
|
||||
|------------------------------------|---------------|-----------|
|
||||
| Address | Domain | Verified |
|
||||
|------------------------------------|---------------|-----------|
|
||||
| rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW | mduo13.com | YES |
|
||||
| rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn | xrpl.org | NO |
|
||||
| rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe | n/a | NO |
|
||||
|------------------------------------|---------------|-----------|
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check a potential destination address's details, and pass them back to the "Send XRP" dialog:
|
||||
* - Is the account funded? If not, payments below the reserve base will fail
|
||||
* - Do they have DisallowXRP enabled? If so, the user should be warned they don't want XRP, but can click through.
|
||||
* - Do they have a verified Domain? If so, we want to show the user the associated domain info.
|
||||
*
|
||||
* @param accountData
|
||||
* @returns {Promise<{domain: string, verified: boolean}|{domain: string, verified: boolean}>}
|
||||
*/
|
||||
async function checkDestination(accountData) {
|
||||
const accountStatus = {
|
||||
"funded": null,
|
||||
"disallow_xrp": null,
|
||||
"domain_verified": null,
|
||||
"domain_str": "" // the decoded domain, regardless of verification
|
||||
}
|
||||
|
||||
accountStatus["disallow_xrp"] = !!(accountData & lsfDisallowXRP);
|
||||
|
||||
return verifyAccountDomain(accountData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify an account using a xrp-ledger.toml file.
|
||||
* https://xrpl.org/xrp-ledger-toml.html#xrp-ledgertoml-file
|
||||
*
|
||||
* @param accountData
|
||||
* @returns {Promise<{domain: string, verified: boolean}>}
|
||||
*/
|
||||
async function verifyAccountDomain(accountData) {
|
||||
const domainHex = accountData["Domain"]
|
||||
if (!domainHex) {
|
||||
return {
|
||||
domain:"",
|
||||
verified: false
|
||||
}
|
||||
}
|
||||
|
||||
let verified = false
|
||||
const domain = convertHexToString(domainHex)
|
||||
const tomlUrl = `https://${domain}/.well-known/xrp-ledger.toml`
|
||||
const tomlResponse = await fetch(tomlUrl)
|
||||
const tomlData = await tomlResponse.text()
|
||||
const parsedToml = toml.parse(tomlData)
|
||||
const tomlAccounts = parsedToml["ACCOUNTS"]
|
||||
|
||||
for (const tomlAccount of tomlAccounts) {
|
||||
if (tomlAccount["address"] === accountData["Account"]) {
|
||||
verified = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
domain: domain,
|
||||
verified: verified
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if a given address has validated status
|
||||
*
|
||||
* @param accountAddress
|
||||
* @param client
|
||||
* @returns {Promise<{domain: string, verified: boolean}>}
|
||||
*/
|
||||
async function verify(accountAddress, client) {
|
||||
// Reference: https://xrpl.org/account_info.html
|
||||
const request = {
|
||||
"command": "account_info",
|
||||
"account": accountAddress,
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.request(request)
|
||||
return await checkDestination(response.result.account_data)
|
||||
} catch {
|
||||
return {
|
||||
domain: 'domain',
|
||||
verified: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { verify }
|
||||
Reference in New Issue
Block a user