Re-added code to clean branch

This commit is contained in:
AlexanderBuzz
2023-06-20 13:57:07 +02:00
parent 5b841573f8
commit b96d3a31e5
54 changed files with 3261 additions and 11 deletions

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }