Compare commits

...
10 Commits
13 changed files with 2989 additions and 2153 deletions
+2731 -1859
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "xrpio", "name": "xrpio",
"version": "0.2.0", "version": "0.4.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git" "url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git"
@@ -33,13 +33,14 @@
"author": "nitowa", "author": "nitowa",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ripple-lib": "^1.10.0", "xrpl": "^4.0.0"
"xrpl": "^2.7.0"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.2.21", "@types/chai": "^4.2.21",
"@types/mocha": "^8.2.2", "@types/mocha": "^8.2.2",
"@types/node": "^14.14.37", "@types/node": "^14.14.37",
"assert": "^2.1.0",
"axios": "^1.7.7",
"base-64": "^1.0.0", "base-64": "^1.0.0",
"browserify-zlib": "^0.2.0", "browserify-zlib": "^0.2.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -49,6 +50,7 @@
"mocha": "^9.2.0", "mocha": "^9.2.0",
"net": "^1.0.2", "net": "^1.0.2",
"node-fetch": "^2.6.2", "node-fetch": "^2.6.2",
"node-polyfill-webpack-plugin": "^4.0.0",
"process": "^0.11.10", "process": "^0.11.10",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"stream-http": "^3.2.0", "stream-http": "^3.2.0",
@@ -59,6 +61,7 @@
"typescript": "^4.5.0", "typescript": "^4.5.0",
"url": "^0.11.0", "url": "^0.11.0",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"utils": "^0.3.1",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.0", "webpack-cli": "^5.0.0",
"wtfnode": "^0.9.1" "wtfnode": "^0.9.1"
-3
View File
@@ -1,6 +1,3 @@
//export * from './xrpIO/ripple-binding'
export * from './util/types' export * from './util/types'
export * from './util/protocol.constants' export * from './util/protocol.constants'
//export * from 'ripple-lib'
//export { RippleAPI } from 'ripple-lib'
export * from './xrpIO/xrpl-binding' export * from './xrpIO/xrpl-binding'
+11 -1
View File
@@ -1 +1,11 @@
export const ERR_BAD_TX_HASH = (hash:string) => new Error(`Bad tx hash format: "${hash}"`) export class BadTxHashError extends Error{
constructor(hash:string){
super(`Bad tx hash format: "${hash}"`)
}
}
export class CannotVerifyOwnerError extends Error{
constructor(hash: string, actualAccount: string, desiredAccount: string){
super((`Expected tx "${hash}" to be initiated by ${desiredAccount} but was ${actualAccount}`))
}
}
+11 -6
View File
@@ -17,13 +17,18 @@ export type Options = {
debug?: boolean debug?: boolean
connectionTimeout?: number connectionTimeout?: number
readMaxRetry?: number readMaxRetry?: number
readRetryTimeout?: number readRetryTimeout?: number,
readFreshApi?:boolean,
writeMaxRetry?: number
writeRetryTimeout?: number
} }
export const defaultOptions = { export const defaultOptions: Options = {
debug: false, debug: false,
connectionTimeout: 100000, connectionTimeout: 5000,
readFreshApi: true, readFreshApi: false,
readMaxRetry: -1, readMaxRetry: 50,
readRetryTimeout: 1000 readRetryTimeout: 750,
writeMaxRetry: 10,
writeRetryTimeout: 1000
} }
+4 -6
View File
@@ -1,5 +1,7 @@
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
module.exports = { module.exports = {
mode: "production", mode: "production",
@@ -13,12 +15,7 @@ module.exports = {
resolve: { resolve: {
extensions: [".ts", ".tsx", ".js"], extensions: [".ts", ".tsx", ".js"],
fallback: { fallback: {
"https": require.resolve("https-browserify"), buffer: require.resolve('buffer/'),
"zlib": require.resolve("browserify-zlib"),
"stream": require.resolve("stream-browserify"),
"crypto": require.resolve("crypto-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify")
} }
}, },
module: { module: {
@@ -35,6 +32,7 @@ module.exports = {
externals: { externals: {
}, },
plugins: [ plugins: [
new NodePolyfillPlugin(),
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
process: 'process/browser', process: 'process/browser',
Buffer: ['buffer', 'Buffer'] Buffer: ['buffer', 'Buffer']
-197
View File
@@ -1,197 +0,0 @@
import { Memo, Wallet } from '../util/types'
import { MIN_XRP_FEE, MIN_XRP_TX_VALUE } from '../util/protocol.constants';
import { Instructions, LedgerClosedEvent, RippleAPI } from 'ripple-lib'
import { Payment } from 'ripple-lib/dist/npm/transaction/payment';
import * as zlib from 'zlib'
import * as util from 'util'
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> => {
try {
const subApi = new RippleAPI({ server: api.connection['_url'] })
await subApi.connect()
return subApi
} catch (e) {
if (debug) {
console.log("CLONEAPI ERR", e)
}
return await cloneApi(api)
}
}
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: 25235454
})
} 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,
};
const _api = await cloneApi(api)
try {
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)
return txHash
} catch (error) {
if (debug)
console.log("SENDPAYMENT ERROR", error)
throw error
} finally {
_api.disconnect()
}
}
export const getTransactions = async (api: RippleAPI, address: string, minLedgerVersion: number = 25235454): 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) {
if (debug) {
console.log("WRITERAW ERR", error);
}
throw error
}
}
export const readRaw = async (api: RippleAPI, hash: string): Promise<Memo> => {
api = await cloneApi(api)
let tx
try {
tx = await api.getTransaction(hash, {
minLedgerVersion: 25235454
})
} catch (e) {
// if(debug){
console.log("READRAW ERR", e)
api.isConnected
// }
throw e
} finally {
await api.disconnect()
}
if (!tx || !tx.specification || !tx.specification['memos'] || !tx.specification['memos'][0]) {
console.log(tx)
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
}
+121 -48
View File
@@ -1,10 +1,10 @@
import { defaultOptions, Memo, Options } from '../util/types' import { defaultOptions, Memo, Options } from '../util/types'
import { Client, Payment, TxResponse, Wallet } from 'xrpl' import { Client, DisconnectedError, Payment, TxResponse, Wallet, } from 'xrpl'
import * as zlib from 'zlib' import * as zlib from 'zlib'
import * as util from 'util' import * as util from 'util'
import { NON_ZERO_TX_HASH } from '../util/protocol.constants' import { NON_ZERO_TX_HASH } from '../util/protocol.constants'
import { ERR_BAD_TX_HASH } from '../util/errors' import { BadTxHashError, CannotVerifyOwnerError } from '../util/errors'
const compressB64 = async (data: string) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64') 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 decompressB64 = async (data: string) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8')
@@ -16,7 +16,6 @@ const PAYLOAD_SIZE = 925
const XRP_PER_DROP = 0.000001 const XRP_PER_DROP = 0.000001
const DROP_PER_XRP = 1000000 const DROP_PER_XRP = 1000000
export class xrpIO { export class xrpIO {
private api: Client private api: Client
@@ -29,6 +28,9 @@ export class xrpIO {
this.options.connectionTimeout = options.connectionTimeout ? Number(options.connectionTimeout) : defaultOptions.connectionTimeout this.options.connectionTimeout = options.connectionTimeout ? Number(options.connectionTimeout) : defaultOptions.connectionTimeout
this.options.readMaxRetry = options.readMaxRetry ? Number(options.readMaxRetry) : defaultOptions.readMaxRetry this.options.readMaxRetry = options.readMaxRetry ? Number(options.readMaxRetry) : defaultOptions.readMaxRetry
this.options.readRetryTimeout = options.readRetryTimeout ? Number(options.readRetryTimeout) : defaultOptions.readRetryTimeout this.options.readRetryTimeout = options.readRetryTimeout ? Number(options.readRetryTimeout) : defaultOptions.readRetryTimeout
this.options.readFreshApi = options.readFreshApi ? Boolean(options.readFreshApi) : defaultOptions.readFreshApi
this.options.writeMaxRetry = options.writeMaxRetry ? Number(options.readFreshApi) : defaultOptions.writeMaxRetry
this.options.writeRetryTimeout = options.writeRetryTimeout ? Number(options.writeRetryTimeout) : defaultOptions.writeRetryTimeout
this.api = new Client(server, { this.api = new Client(server, {
connectionTimeout: this.options.connectionTimeout connectionTimeout: this.options.connectionTimeout
@@ -67,37 +69,57 @@ export class xrpIO {
} }
} }
private async sendPayment(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<TxResponse> { public async sendPayment(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1", retry = 0): Promise<TxResponse> {
const wallet = Wallet.fromSecret(secret) const wallet = Wallet.fromSecret(secret)
this.dbg("Sending payment", wallet.address, '->', to) this.dbg("Sending payment", wallet.address, '->', to)
const _api = await this.cloneApi() const _api = await this.cloneApi()
const payment: Payment = await _api.autofill({
TransactionType: 'Payment',
Account: wallet.address,
Destination: to,
Sequence: sequence,
Amount: amount,
Memos: [{
Memo: {
MemoData: hexEncode(data.data || ""),
MemoFormat: hexEncode(data.format || ""),
MemoType: hexEncode(data.type || "")
}
}]
})
let response: TxResponse;
try { try {
const payment: Payment = await _api.autofill({ response = await _api.submitAndWait(payment, { wallet })
TransactionType: 'Payment',
Account: wallet.address,
Destination: to,
Sequence: sequence,
Amount: amount,
Memos: [{
Memo: {
MemoData: hexEncode(data.data || ""),
MemoFormat: hexEncode(data.format || ""),
MemoType: hexEncode(data.type || "")
}
}]
})
const response = await _api.submitAndWait(payment, { wallet })
await _api.disconnect()
this.dbg("Tx finalized", response.result.hash, response.result.Sequence)
return response
} catch (error: any) { } catch (error: any) {
this.dbg("SENDPAYMENT ERROR", error) this.dbg("SENDPAYMENT ERROR", error)
await _api.disconnect()
if(error instanceof DisconnectedError){
//Usually caused by hitting rate limits. Recoverable.
if(this.options.writeMaxRetry != -1 && retry >= this.options.writeMaxRetry){
//exceeded retry quota
throw error
}
await new Promise(res => setTimeout(res, this.options.writeRetryTimeout))
return await this.sendPayment(data, to, secret, sequence, amount, retry+1)
}
throw error throw error
}finally{
await _api.disconnect()
} }
//We cannot afford this payment. Irrecoverable error
if(response.result.meta && response.result.meta['TransactionResult'] && response.result.meta['TransactionResult'] === 'tecUNFUNDED_PAYMENT'){
throw new Error(`Insufficient funds to send transaction. See tx ${response.result.hash}`)
}
this.dbg("Tx finalized", response.result.hash, response.result.tx_json.Sequence)
return response
} }
public async writeRaw(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<string> { public async writeRaw(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<string> {
@@ -106,33 +128,71 @@ export class xrpIO {
return tx.result.hash return tx.result.hash
} }
private async getTransaction(hash: string, retry = 0): Promise<TxResponse> { public async getTransaction(hash: string, retry = 0): Promise<TxResponse> {
if (!NON_ZERO_TX_HASH.test(hash)) {
throw new BadTxHashError(hash)
}
this.dbg("Getting Tx", hash) this.dbg("Getting Tx", hash)
const _api = this.options.readFreshApi ? await this.cloneApi() : this.api
try { try {
return await this.api.request({ return await _api.request({
command: 'tx', command: 'tx',
transaction: hash, transaction: hash,
}) })
} catch (e) { } catch (e: any) {
this.dbg(e) this.dbg("getTransaction err", e)
if (this.options.readMaxRetry != -1) {
if (retry >= this.options.readMaxRetry) if(e.data){ //RippledError
console.error("Retry limit exceeded for", hash, ". This is an irrecoverable error") switch(e.data.error){
throw e //irrecoverable errors
case 'amendmentBlocked': //server is amendment blocked and needs to be updated to the latest version to stay synced with the XRP Ledger network.
case 'invalid_API_version': //The server does not support the API version number from the request.
case 'jsonInvalid': //(WebSocket only) The request is not a proper JSON object.
case 'missingCommand': //(WebSocket only) The request did not specify a command field
case 'noClosed': //The server does not have a closed ledger, typically because it has not finished starting up.
case 'txnNotFound': //Either the transaction does not exist, or it was part of an ledger version that rippled does not have available
case 'unknownCmd': //The request does not contain a command that the rippled server recognizes
case 'wsTextRequired': //(WebSocket only) The request's opcode is not text.
case 'invalidParams': //One or more fields are specified incorrectly, or one or more required fields are missing.
case 'excessiveLgrRange': //The min_ledger and max_ledger fields of the request are more than 1000 apart
case 'invalidLgrRange': //The specified min_ledger is larger than the max_ledger, or one of those parameters is not a valid ledger index
throw e
//potentially recoverable errors
case 'failedToForward': //(Reporting Mode servers only) The server tried to forward this request to a P2P Mode server, but the connection failed
case 'noCurrent': //The server does not know what the current ledger is, due to high load, network problems, validator failures, incorrect configuration, or some other problem.
case 'noNetwork': //The server is having trouble connecting to the rest of the XRP Ledger peer-to-peer network (and is not running in stand-alone mode).
case 'tooBusy': //The server is under too much load to do this command right now. Generally not returned if you are connected as an admin
default: //some undocumented error, might as well give it a re-try
//fall through
}
} }
await new Promise(res => setTimeout(res, this.options.readRetryTimeout)) //some other error, potentially recoverable
return await this.getTransaction(hash, retry + 1) if (this.options.readMaxRetry != -1 && retry >= this.options.readMaxRetry) { //not doing infinite retries and exhausted retry quota
throw e
}else{
await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
return await this.getTransaction(hash, retry + 1)
}
}finally{
if(this.options.readFreshApi) await _api.disconnect()
} }
} }
public async readRaw(hash: string): Promise<Memo> { public async readRaw(hash: string, verifyOwner?: string): Promise<Memo> {
if (!NON_ZERO_TX_HASH.test(hash)) { if (!NON_ZERO_TX_HASH.test(hash)) {
throw ERR_BAD_TX_HASH(hash) throw new BadTxHashError(hash)
}
const tx = await this.getTransaction(hash)
if(verifyOwner && tx.result.tx_json.Account != verifyOwner){
throw new CannotVerifyOwnerError(hash, tx.result.tx_json.Account, verifyOwner)
} }
const tx = await this.getTransaction(hash) const memo = tx.result.tx_json.Memos[0].Memo
const memo = tx.result.Memos[0].Memo
const memoParsed = { const memoParsed = {
data: hexDecode(memo.MemoData), data: hexDecode(memo.MemoData),
format: hexDecode(memo.MemoFormat), format: hexDecode(memo.MemoFormat),
@@ -142,30 +202,43 @@ export class xrpIO {
return memoParsed return memoParsed
} }
public async treeWrite(data: string, to: string, secret: string, format: 'L' | 'N' = 'L'): Promise<string> { public async treeWrite(data: string, to: string, secret: string, format: string = "0", progressCallback: Function = (done:number,max:number)=>{}): Promise<string> {
const wallet = Wallet.fromSecret(secret) const wallet = Wallet.fromSecret(secret)
data = await compressB64(data) data = await compressB64(data)
const chunks = chunkString(data, PAYLOAD_SIZE) const chunks = chunkString(data, PAYLOAD_SIZE)
const latestSequence = await this.getAccountSequence(wallet.address) const latestSequence = await this.getAccountSequence(wallet.address)
const hashes = await Promise.all(Object.entries(chunks).map(([i, chunk]) => this.writeRaw({ data: chunk, format: format }, to, secret, latestSequence + Number(i))))
let count = 0
const hashes = await Promise.all(Object.entries(chunks).map(([i, chunk]) => {
const res = this.writeRaw({ data: chunk, format: format }, to, secret, latestSequence + Number(i))
count += 1
progressCallback(count, chunks.length)
return res
}))
if (hashes.length === 1) { if (hashes.length === 1) {
return hashes[0] return hashes[0]
} }
return await this.treeWrite(JSON.stringify(hashes), to, secret, 'N') return await this.treeWrite(JSON.stringify(hashes), to, secret, `${hashes.length}`)
} }
public async treeRead(hashes: string[]): Promise<string> { public async treeRead(hashes: string[], verifyOwner?:string, progressCallback: Function = (done:number,max:number)=>{}): Promise<string> {
const bad_hash = hashes.find(hash => !NON_ZERO_TX_HASH.test(hash)) const bad_hash = hashes.find(hash => !NON_ZERO_TX_HASH.test(hash))
if (bad_hash) if (bad_hash)
throw ERR_BAD_TX_HASH(bad_hash) throw new BadTxHashError(bad_hash)
const memos = await Promise.all(hashes.map(hash => this.readRaw(hash))) let count = 0;
const memos = await Promise.all(hashes.map(async hash => {
const res = this.readRaw(hash, verifyOwner)
count += 1
progressCallback(count, hashes.length)
return res
}))
const payload: string = await decompressB64(memos.map(memo => memo.data).join('')) const payload: string = await decompressB64(memos.map(memo => memo.data).join(''))
if (memos.some(memo => memo.format === 'N')) { if (memos.some(memo => memo.format !== '0')) {
return await this.treeRead(JSON.parse(payload)) return await this.treeRead(JSON.parse(payload), verifyOwner)
} }
return payload return payload
+13 -16
View File
@@ -1,3 +1,4 @@
import axios from 'axios';
import { Wallet } from "../src/util/types"; import { Wallet } from "../src/util/types";
import { readFileSync } from 'fs' import { readFileSync } from 'fs'
const Path = require('path') const Path = require('path')
@@ -7,24 +8,20 @@ export const TEST_CONFIG = {
} }
export const TEST_DATA = "test123123" export const TEST_DATA = "test123123"
import fetch from 'node-fetch'; export const makeTestnetWallet = async () : Promise<Wallet> => {
try{
const response = await axios.post('https://faucet.altnet.rippletest.net/accounts', {})
return ({
secret: response.data.seed,
address: response.data.account.address
})
export const makeTestnetWallet = () : Promise<Wallet> => fetch('https://faucet.altnet.rippletest.net/accounts', { }catch(e){
method: 'POST', console.log(e)
headers: { }
'Accept': 'application/json', }
'Content-Type': 'application/json'
},
}).then((raw:any) => {
return raw.json().then(content => {
return({
secret: content.account.secret,
address: content.account.address
});
})
});
export const htmlTxt = readFileSync(Path.resolve(__dirname, '..', '..', 'test', 'index.html')).toString('ascii') export const htmlTxt = readFileSync(Path.resolve(__dirname, '..', '..', 'test', 'longhtml.html')).toString('ascii')
export const longText = `Software testing export const longText = `Software testing
From Wikipedia, the free encyclopedia From Wikipedia, the free encyclopedia
+14 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
(() => {
"use strict";
const e = new xrpIO("wss://s.altnet.rippletest.net:51233", { debug: false });
e.connect().then((async t => {
const n = await e.treeRead(["1481F8DD37C2D3AE3CE60B25264B902BD9E0377AAA1AEDB924D05049F36DFB15"], void 0); document.write(n)
}))
})();
+63 -11
View File
@@ -7,6 +7,7 @@ const expect = chai.expect
let sendWallet: Wallet let sendWallet: Wallet
let receiveWallet: Wallet let receiveWallet: Wallet
let poorWallet: Wallet
let api: xrpIO let api: xrpIO
describe('XRPIO', () => { describe('XRPIO', () => {
@@ -14,6 +15,7 @@ describe('XRPIO', () => {
this.timeout(15000) this.timeout(15000)
sendWallet = await makeTestnetWallet() sendWallet = await makeTestnetWallet()
receiveWallet = await makeTestnetWallet() receiveWallet = await makeTestnetWallet()
poorWallet = await makeTestnetWallet()
await new Promise((res, rej) => setTimeout(res, 10000)) //it takes a moment for the wallets to become active await new Promise((res, rej) => setTimeout(res, 10000)) //it takes a moment for the wallets to become active
}) })
@@ -35,8 +37,36 @@ describe('XRPIO', () => {
} }
}) })
it('treeRead spc', async function(){
this.timeout(450000)
const data = await api.treeRead(["1481F8DD37C2D3AE3CE60B25264B902BD9E0377AAA1AEDB924D05049F36DFB15"], undefined)
expect(data).to.exist
})
it('throws error if not enough funds', function(done){
this.timeout(15000)
console.log(poorWallet)
api.sendPayment({}, receiveWallet.address, poorWallet.secret, undefined, String(1000000 * 10001))
.then(_ => done(new Error('Expected error but succeeded')))
.catch(_ => done())
})
it('getTransaction with bad hash', function(done){
this.timeout(10000)
api.getTransaction('73FECDA37ABBB2FC17460C5C2467BE6A0A8E1F4EB081FFFFFFFFFFFFFFFFFFFF') //technically this hash could exist, but probably never will
.then(_ => done(new Error('Expected error but succeeded')))
.catch(_ => done())
})
it('sendPayment errors on bad request sequence', function(done){
api.sendPayment({}, receiveWallet.address, sendWallet.secret, -12)
.then(_ => done(new Error('Expected error but succeeded')))
.catch(_ => done())
})
it('getAccountSequence', async function(){ it('getAccountSequence', async function(){
//this.skip()
this.timeout(10000) this.timeout(10000)
const seq = await api.getAccountSequence(sendWallet.address) const seq = await api.getAccountSequence(sendWallet.address)
expect(seq).to.exist expect(seq).to.exist
@@ -44,7 +74,7 @@ describe('XRPIO', () => {
}) })
it('estimateFee', async function () { it('estimateFee', async function () {
this.timeout(2000) this.timeout(10000)
const cost = await api.estimateFee(longText) const cost = await api.estimateFee(longText)
expect(cost).to.be.a('number') expect(cost).to.be.a('number')
expect(cost).to.be.lessThan(50) expect(cost).to.be.lessThan(50)
@@ -53,7 +83,6 @@ describe('XRPIO', () => {
let txHash let txHash
it('writeRaw', async function(){ it('writeRaw', async function(){
//this.skip()
this.timeout(15000) this.timeout(15000)
txHash = await api.writeRaw({data: TEST_DATA}, receiveWallet.address, sendWallet.secret); txHash = await api.writeRaw({data: TEST_DATA}, receiveWallet.address, sendWallet.secret);
expect(txHash).to.exist expect(txHash).to.exist
@@ -61,13 +90,26 @@ describe('XRPIO', () => {
}) })
it('readRaw', async function () { it('readRaw', async function () {
//this.skip()
this.timeout(15000) this.timeout(15000)
const memo = await api.readRaw(txHash) const memo = await api.readRaw(txHash)
expect(memo).to.exist expect(memo).to.exist
expect(memo.data).to.be.equal(TEST_DATA) expect(memo.data).to.be.equal(TEST_DATA)
}) })
it('verifyOwner readRaw', async function (){
this.timeout(15000)
const memo = await api.readRaw(txHash, sendWallet.address)
expect(memo).to.exist
expect(memo.data).to.be.equal(TEST_DATA)
})
it('verifyOwner readRaw bad owner', function (done){
this.timeout(15000)
api.readRaw(txHash, "not the owner")
.then(_ => done(new Error('Expected error but succeeded')))
.catch(_ => done())
})
it('readRaw bad hash', function (done){ it('readRaw bad hash', function (done){
this.timeout(150000) this.timeout(150000)
api.readRaw("123") api.readRaw("123")
@@ -76,7 +118,6 @@ describe('XRPIO', () => {
}) })
it('treeWrite', async function(){ it('treeWrite', async function(){
// this.skip()
this.timeout(45000) this.timeout(45000)
txHash = await api.treeWrite(longText, receiveWallet.address, sendWallet.secret) txHash = await api.treeWrite(longText, receiveWallet.address, sendWallet.secret)
expect(txHash).to.exist expect(txHash).to.exist
@@ -84,15 +125,27 @@ describe('XRPIO', () => {
}) })
it('treeRead', async function(){ it('treeRead', async function(){
// this.skip()
this.timeout(45000) this.timeout(45000)
txHash = await api.treeRead([txHash]) const data = await api.treeRead([txHash], undefined)
expect(txHash).to.exist expect(data).to.exist
expect(txHash).to.be.equal(longText) expect(data).to.be.equal(longText)
})
it('treeRead verify owner', async function(){
this.timeout(45000)
const data = await api.treeRead([txHash], sendWallet.address)
expect(data).to.exist
expect(data).to.be.equal(longText)
})
it('verifyOwner treeRead bad owner', function(done){
this.timeout(45000)
api.treeRead([txHash], "not the owner")
.then(_ => done(new Error('Expected error but succeeded')))
.catch(_ => done())
}) })
it('treeWrite XL', async function(){ it('treeWrite XL', async function(){
//this.skip()
this.timeout(450000) this.timeout(450000)
txHash = await api.treeWrite(htmlTxt, receiveWallet.address, sendWallet.secret) txHash = await api.treeWrite(htmlTxt, receiveWallet.address, sendWallet.secret)
expect(txHash).to.exist expect(txHash).to.exist
@@ -100,7 +153,6 @@ describe('XRPIO', () => {
}) })
it('treeRead XL', async function(){ it('treeRead XL', async function(){
//this.skip()
this.timeout(450000) this.timeout(450000)
const data = await api.treeRead([txHash]) const data = await api.treeRead([txHash])
expect(data).to.exist expect(data).to.exist