123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import { Instructions, LedgerClosedEvent, RippleAPI } from 'ripple-lib'
- import { Memo, Wallet } from '../util/types'
- import { MIN_XRP_FEE, MIN_XRP_TX_VALUE } from '../util/protocol.constants';
- import * as zlib from 'zlib'
- import * as util from 'util'
- import { Payment } from 'ripple-lib/dist/npm/transaction/payment';
-
- const chunkString = (str: string, length: number) => str.match(new RegExp('.{1,' + length + '}', 'gs'));
- const PAYLOAD_SIZE = 925
- const debug = false
-
- const cloneApi = async (api: RippleAPI): Promise<RippleAPI> => {
- const subApi = new RippleAPI({ server: api.connection['_url'] })
- await subApi.connect()
- return subApi
- }
-
- export const getLatestSequence = async (api: RippleAPI, accountAddress: string): Promise<number> => {
- if(debug) console.log("Getting acc info for", accountAddress)
- const accountInfo = await api.getAccountInfo(accountAddress, {})
- return Number(accountInfo.sequence-1)
- }
-
- const compressB64 = async (data: string) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64')
- const decompressB64 = async (data: string) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8')
-
- const sendReliably = (api: RippleAPI, signed: any, preparedPayment,): Promise<any> => new Promise((res, rej) => {
- const ledgerClosedCallback = async (event: LedgerClosedEvent) => {
- let status
- try {
- status = await api.getTransaction(signed.id, {
- minLedgerVersion: 20368096
- })
- } catch (e) {
-
- // Typical error when the tx hasn't been validated yet:
- if ((e as Error).name !== 'MissingLedgerHistoryError') {
- //console.log(e)
- }
-
- if (event.ledger_index > preparedPayment.instructions.maxLedgerVersion + 3) {
- // Assumptions:
- // - We are still connected to the same rippled server
- // - No ledger gaps occurred
- // - All ledgers between the time we submitted the tx and now have been checked for the tx
- status = {
- finalResult: 'Transaction was not, and never will be, included in a validated ledger'
- }
- return rej(status);
-
- } else {
- // Check again later:
- api.connection.once('ledgerClosed', ledgerClosedCallback)
- return
- }
- }
- return res(status)
- }
- api.connection.once('ledgerClosed', ledgerClosedCallback)
- })
-
- export const sendPayment = async (api: RippleAPI, data: Memo[], from: string, to: string, secret: string, sequence: number) => {
- if(debug) console.log("Sending payment with seq", sequence)
- const options: Instructions = {
- maxLedgerVersionOffset: 5,
- fee: MIN_XRP_FEE,
- sequence: sequence,
- };
- const payment: Payment = {
- source: {
- address: from,
- maxAmount: {
- value: MIN_XRP_TX_VALUE,
- currency: 'XRP'
- },
- },
- destination: {
- address: to,
- amount: {
- value: MIN_XRP_TX_VALUE,
- currency: 'XRP'
- },
- },
- memos: data,
- };
- try {
- const _api = await cloneApi(api)
- const prepared = await _api.preparePayment(from, payment, options)
- const signed = _api.sign(prepared.txJSON, secret);
- const txHash = await _api.submit(signed.signedTransaction)
- if(debug) console.log("Transaction submitted", txHash)
- await sendReliably(_api, signed, prepared)
- _api.disconnect()
-
- return txHash
- } catch (error) {
- //console.log("SENDPAYMENT ERROR", error)
- throw error
- }
- }
-
- export const getTransactions = async (api: RippleAPI, address: string, minLedgerVersion: number = 19832467): Promise<any[]> => {
- const txs = await api.getTransactions(address, {
- minLedgerVersion: minLedgerVersion,
- earliestFirst: true,
- excludeFailures: true,
- })
- return txs
- }
-
- export const writeRaw = async (api: RippleAPI, data: Memo, from: string, to: string, secret: string, sequence?: number): Promise<string> => {
- //if (memoSize(data) > 1000) throw new Error("data length exceeds capacity")
- try {
- if (!sequence) {
- const accountInfo = await getLatestSequence(api, from)
- sequence = accountInfo + 1
- }
-
- const resp = await sendPayment(api, [data], from, to, secret, sequence)
- return resp['tx_json'].hash
- } catch (error) {
- console.log("WRITERAW ERR", error);
- }
- }
-
-
- export const readRaw = async (api: RippleAPI, hash: string): Promise<Memo> => {
- const tx = await api.getTransaction(hash, {
- minLedgerVersion: 16392480
- })
- if (!tx || !tx.specification || !tx.specification['memos'] || !tx.specification['memos'][0]) {
- throw new Error('Invalid Transaction ' + hash)
- }
-
- return tx.specification['memos'][0]
- }
-
- export const subscribe = async (api: RippleAPI, address: string, callback: (tx) => any) => {
- api.connection.on('transaction', (tx) => callback(tx))
- await api.connection.request({
- command: 'subscribe',
- accounts: [address],
- })
- }
-
- export const treeWrite = async (api: RippleAPI, data: string, wallet: Wallet, to: string, format: 'L'|'N' = 'L'): Promise<string> => {
- data = await compressB64(data)
- const chunks = chunkString(data, PAYLOAD_SIZE)
- const latestSequence = await getLatestSequence(api, wallet.address)
- const hashes = await Promise.all(Object.entries(chunks).map(([i, chunk]) => writeRaw(api, { data: chunk, format: format }, wallet.address, to, wallet.secret, latestSequence + Number(i) + 1)))
-
- if (hashes.length === 1) {
- return hashes[0]
- }
-
- return await treeWrite(api, JSON.stringify(hashes), wallet, to, 'N')
- }
-
- export const treeRead = async (api: RippleAPI, hashes: string[]): Promise<string> => {
- const memos = await Promise.all(hashes.map(hash => readRaw(api, hash)))
- const payload: string = await decompressB64(memos.map(memo => memo.data).join(''))
-
- if (memos.some(memo => memo.format === 'N')) {
- return await treeRead(api, JSON.parse(payload))
- }
-
- return payload
- }
|