add more utility functions and improved tests
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "xrpio",
|
"name": "xrpio",
|
||||||
"version": "0.1.8",
|
"version": "0.2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git"
|
"url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const ERR_BAD_TX_HASH = (hash:string) => new Error(`Bad tx hash format: "${hash}"`)
|
||||||
+44
-12
@@ -3,14 +3,18 @@ import { Client, 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 { ERR_BAD_TX_HASH } 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')
|
||||||
const hexDecode = (str: string) => Buffer.from(str, 'hex').toString('utf8')
|
const hexDecode = (str: string) => Buffer.from(str, 'hex').toString('utf8')
|
||||||
const hexEncode = (str: string) => Buffer.from(str, 'utf8').toString('hex').toUpperCase()
|
const hexEncode = (str: string) => Buffer.from(str, 'utf8').toString('hex').toUpperCase()
|
||||||
const chunkString = (str: string, length: number) => str.match(new RegExp('.{1,' + length + '}', 'gs'));
|
const chunkString = (str: string, length: number) => str.match(new RegExp('.{1,' + length + '}', 'gs'));
|
||||||
|
const genRandHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
|
||||||
const PAYLOAD_SIZE = 925
|
const PAYLOAD_SIZE = 925
|
||||||
|
const XRP_PER_DROP = 0.000001
|
||||||
|
const DROP_PER_XRP = 1000000
|
||||||
|
|
||||||
|
|
||||||
export class xrpIO {
|
export class xrpIO {
|
||||||
@@ -63,7 +67,7 @@ export class xrpIO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendPayment(data: Memo, to: string, secret: string, sequence?: number): Promise<TxResponse> {
|
private async sendPayment(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): 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)
|
||||||
|
|
||||||
@@ -74,7 +78,7 @@ export class xrpIO {
|
|||||||
Account: wallet.address,
|
Account: wallet.address,
|
||||||
Destination: to,
|
Destination: to,
|
||||||
Sequence: sequence,
|
Sequence: sequence,
|
||||||
Amount: "1",
|
Amount: amount,
|
||||||
Memos: [{
|
Memos: [{
|
||||||
Memo: {
|
Memo: {
|
||||||
MemoData: hexEncode(data.data || ""),
|
MemoData: hexEncode(data.data || ""),
|
||||||
@@ -96,32 +100,37 @@ export class xrpIO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async writeRaw(data: Memo, to: string, secret: string, sequence?: number): Promise<string> {
|
public async writeRaw(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<string> {
|
||||||
this.dbg("Writing data", data)
|
this.dbg("Writing data", data)
|
||||||
const tx = await this.sendPayment(data, to, secret, sequence)
|
const tx = await this.sendPayment(data, to, secret, sequence, amount)
|
||||||
return tx.result.hash
|
return tx.result.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTransaction(hash: string, retry=0): Promise<TxResponse> {
|
private async getTransaction(hash: string, retry = 0): Promise<TxResponse> {
|
||||||
this.dbg("Getting Tx", hash)
|
this.dbg("Getting Tx", hash)
|
||||||
try{
|
try {
|
||||||
return await this.api.request({
|
return await this.api.request({
|
||||||
command: 'tx',
|
command: 'tx',
|
||||||
transaction: hash,
|
transaction: hash,
|
||||||
})
|
})
|
||||||
}catch(e){
|
} catch (e) {
|
||||||
this.dbg(e)
|
this.dbg(e)
|
||||||
if(this.options.readMaxRetry != -1){
|
if (this.options.readMaxRetry != -1) {
|
||||||
if(retry >= this.options.readMaxRetry)
|
if (retry >= this.options.readMaxRetry)
|
||||||
console.error("Retry limit exceeded for", hash, ". this is an irrecoverable error")
|
console.error("Retry limit exceeded for", hash, ". This is an irrecoverable error")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
|
await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
|
||||||
return await this.getTransaction(hash, retry+1)
|
return await this.getTransaction(hash, retry + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async readRaw(hash: string): Promise<Memo> {
|
public async readRaw(hash: string): Promise<Memo> {
|
||||||
|
|
||||||
|
if (!NON_ZERO_TX_HASH.test(hash)) {
|
||||||
|
throw ERR_BAD_TX_HASH(hash)
|
||||||
|
}
|
||||||
|
|
||||||
const tx = await this.getTransaction(hash)
|
const tx = await this.getTransaction(hash)
|
||||||
const memo = tx.result.Memos[0].Memo
|
const memo = tx.result.Memos[0].Memo
|
||||||
const memoParsed = {
|
const memoParsed = {
|
||||||
@@ -148,6 +157,10 @@ export class xrpIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async treeRead(hashes: string[]): Promise<string> {
|
public async treeRead(hashes: string[]): Promise<string> {
|
||||||
|
const bad_hash = hashes.find(hash => !NON_ZERO_TX_HASH.test(hash))
|
||||||
|
if (bad_hash)
|
||||||
|
throw ERR_BAD_TX_HASH(bad_hash)
|
||||||
|
|
||||||
const memos = await Promise.all(hashes.map(hash => this.readRaw(hash)))
|
const memos = await Promise.all(hashes.map(hash => this.readRaw(hash)))
|
||||||
const payload: string = await decompressB64(memos.map(memo => memo.data).join(''))
|
const payload: string = await decompressB64(memos.map(memo => memo.data).join(''))
|
||||||
|
|
||||||
@@ -169,6 +182,25 @@ export class xrpIO {
|
|||||||
return Number(accountInfo.result.account_data.Sequence)
|
return Number(accountInfo.result.account_data.Sequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async estimateFee(data: string, denomination: 'XRP' | 'DROPS' = 'DROPS', cost = 0): Promise<number> {
|
||||||
|
data = await compressB64(data)
|
||||||
|
const chunks = chunkString(data, PAYLOAD_SIZE)
|
||||||
|
|
||||||
|
if (chunks.length === 1) {
|
||||||
|
return (denomination === "DROPS" ? (cost + 1) : this.dropsToXrp(cost + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.estimateFee(JSON.stringify(chunks.map(_ => genRandHex(64))), denomination, cost + chunks.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
public xrpToDrops(xrp: number): number {
|
||||||
|
return xrp * DROP_PER_XRP
|
||||||
|
}
|
||||||
|
|
||||||
|
public dropsToXrp(drops: number): number {
|
||||||
|
return drops * XRP_PER_DROP
|
||||||
|
}
|
||||||
|
|
||||||
private dbg(...args: any[]) {
|
private dbg(...args: any[]) {
|
||||||
if (this.options.debug) {
|
if (this.options.debug) {
|
||||||
console.log.apply(console, args)
|
console.log.apply(console, args)
|
||||||
|
|||||||
+23
-7
@@ -1,8 +1,8 @@
|
|||||||
|
var wtf = require('wtfnode');
|
||||||
import { htmlTxt, longText, makeTestnetWallet, TEST_CONFIG, TEST_DATA } from './CONSTANTS'
|
import { htmlTxt, longText, makeTestnetWallet, TEST_CONFIG, TEST_DATA } from './CONSTANTS'
|
||||||
import { xrpIO } from '../src/xrpIO/xrpl-binding'
|
import { xrpIO } from '../src/xrpIO/xrpl-binding'
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import { Wallet } from '../src/util/types';
|
import { Wallet } from '../src/util/types';
|
||||||
var wtf = require('wtfnode');
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
let sendWallet: Wallet
|
let sendWallet: Wallet
|
||||||
@@ -36,16 +36,24 @@ describe('XRPIO', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('getAccountSequence', async function(){
|
it('getAccountSequence', async function(){
|
||||||
// this.skip()
|
//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
|
||||||
expect(seq).to.be.a('number')
|
expect(seq).to.be.a('number')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('estimateFee', async function () {
|
||||||
|
this.timeout(2000)
|
||||||
|
const cost = await api.estimateFee(longText)
|
||||||
|
expect(cost).to.be.a('number')
|
||||||
|
expect(cost).to.be.lessThan(50)
|
||||||
|
expect(cost).to.be.greaterThan(30)
|
||||||
|
})
|
||||||
|
|
||||||
let txHash
|
let txHash
|
||||||
it('writeRaw', async function(){
|
it('writeRaw', async function(){
|
||||||
// this.skip()
|
//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
|
||||||
@@ -53,13 +61,20 @@ describe('XRPIO', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('readRaw', async function () {
|
it('readRaw', async function () {
|
||||||
// this.skip()
|
//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('readRaw bad hash', function (done){
|
||||||
|
this.timeout(150000)
|
||||||
|
api.readRaw("123")
|
||||||
|
.then(_ => done(new Error('Expected error but succeeded')))
|
||||||
|
.catch(_ => done())
|
||||||
|
})
|
||||||
|
|
||||||
it('treeWrite', async function(){
|
it('treeWrite', async function(){
|
||||||
// this.skip()
|
// this.skip()
|
||||||
this.timeout(45000)
|
this.timeout(45000)
|
||||||
@@ -77,7 +92,7 @@ describe('XRPIO', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('treeWrite XL', async function(){
|
it('treeWrite XL', async function(){
|
||||||
// this.skip()
|
//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
|
||||||
@@ -85,7 +100,7 @@ describe('XRPIO', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('treeRead XL', async function(){
|
it('treeRead XL', async function(){
|
||||||
// this.skip()
|
//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
|
||||||
@@ -93,7 +108,8 @@ describe('XRPIO', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('print open handles', function(){
|
it('print open handles', function(){
|
||||||
|
api.disconnect().then(_ => {
|
||||||
wtf.dump()
|
wtf.dump()
|
||||||
console.log(txHash)
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user