This commit is contained in:
nitowa
2023-08-15 22:28:03 +02:00
commit 1dae68b1c7
5529 changed files with 1659171 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
The MIT License (MIT)
=====================
Copyright © `2022` `nitowa`
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+52
View File
@@ -0,0 +1,52 @@
# Overview
[![Build Status](https://drone.nitowa.xyz/api/badges/npm-packages/xrpio/status.svg)](https://drone.nitowa.xyz/npm-packages/xrpio)
[![Current Version](https://img.shields.io/npm/v/xrpio.svg)](https://www.npmjs.com/package/xrpio)
[![Weekly Downloads](https://img.shields.io/npm/dw/xrpio?color=important)](https://www.npmjs.com/package/xrpio)
[![License Type](https://img.shields.io/npm/l/xrpio?color=blueviolet)](https://gitea.nitowa.xyz/docs/xrpio/src/branch/master/LICENSE.md)
xrpio is a library that allows you to write and read arbitrary data in the ripple blockchain.
# How to install
```
npm i xrpio
```
# How it works
Transactions on the ripple blockchain are allowed to carry up to 1kB of arbitrary data via the memo field.
We can use this to store data of any size by building a tree of references between these transactions that can then be reassembled by reading them back from the blockchain.
In order to generate these transactions xrpio sends payments with the minimum denomination between two wallets controlled by the user.
xrpio automatically takes care of the logistics behind this technique as well as compression of the data.
Highly simplified, you can visualize the process like this:
![xrpio treewrite](https://i.imgur.com/G2HofSE.gif)
In practice each node does of course store significantly more data.
# Caution
This library is in an early stage of development and **breaking changes may occur spontaneously and without regard of semantic versioning until the v1.0.0 release**.
## <span style="color:red"> Operation on the main-net is untested and should not be used in production! If you want to deploy this library with the main-net please download the sources and modify them to your needs.</span>
<br />
# Quickstart
```typescript
import { xrpIO } from "xrpio"
const api = new xrpIO("wss://some_ripple_node.net:51233")
await api.connect()
txHash = await api.treeWrite("arbitrary text 123", receiveWallet.address, sendWallet.secret)
data = await api.treeRead([txHash])
console.log(data) //"arbitrary text 123"
```
# Known Bugs
- When using large data (>300kB) and public nodes, the writing process may fail due to rate limits. xrpio will attempt to mitigate this, but success is inconsistent.
# [Full documentation](https://gitea.nitowa.xyz/docs/xrpio)
+105952
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
export * from './util/types';
export * from './util/protocol.constants';
export * from './xrpIO/xrpl-binding';
+18
View File
@@ -0,0 +1,18 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
//export * from './xrpIO/ripple-binding'
__exportStar(require("./util/types"), exports);
__exportStar(require("./util/protocol.constants"), exports);
//export * from 'ripple-lib'
//export { RippleAPI } from 'ripple-lib'
__exportStar(require("./xrpIO/xrpl-binding"), exports);
+2
View File
@@ -0,0 +1,2 @@
export declare const ERR_BAD_TX_HASH: (hash: string) => Error;
export declare const ERR_NO_VERIFY_OWNER: (hash: string, actualAccount: string, desiredAccount: string) => Error;
+7
View File
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERR_NO_VERIFY_OWNER = exports.ERR_BAD_TX_HASH = void 0;
const ERR_BAD_TX_HASH = (hash) => new Error(`Bad tx hash format: "${hash}"`);
exports.ERR_BAD_TX_HASH = ERR_BAD_TX_HASH;
const ERR_NO_VERIFY_OWNER = (hash, actualAccount, desiredAccount) => new Error(`Expected tx "${hash}" to be initiated by ${desiredAccount} but was ${actualAccount}`);
exports.ERR_NO_VERIFY_OWNER = ERR_NO_VERIFY_OWNER;
+14
View File
@@ -0,0 +1,14 @@
export declare const MSG_DELIM: string;
export declare const MSG_DATA_MAX: number;
export declare const PUBKEY_LEN: number;
export declare const NON_ZERO_TX_HASH: RegExp;
export declare const PTR_FORMAT: RegExp;
export declare const DATA_FORMAT: RegExp;
export declare const SIGNATURE_FORMAT: RegExp;
export declare const SIGNER_FORMAT: RegExp;
export declare const MSG_FORMAT: RegExp;
export declare const AMOUNT_DECIMALS = 18;
export declare const MAX_SUPPLY = 20000000;
export declare const AMOUNT_FORMAT: RegExp;
export declare const MIN_XRP_FEE = "0.00001";
export declare const MIN_XRP_TX_VALUE = "0.000001";
+17
View File
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MIN_XRP_TX_VALUE = exports.MIN_XRP_FEE = exports.AMOUNT_FORMAT = exports.MAX_SUPPLY = exports.AMOUNT_DECIMALS = exports.MSG_FORMAT = exports.SIGNER_FORMAT = exports.SIGNATURE_FORMAT = exports.DATA_FORMAT = exports.PTR_FORMAT = exports.NON_ZERO_TX_HASH = exports.PUBKEY_LEN = exports.MSG_DATA_MAX = exports.MSG_DELIM = void 0;
exports.MSG_DELIM = ' ';
exports.MSG_DATA_MAX = 925;
exports.PUBKEY_LEN = 66;
exports.NON_ZERO_TX_HASH = new RegExp(`[0-9A-F]{64}`);
exports.PTR_FORMAT = new RegExp(`^((${exports.NON_ZERO_TX_HASH.source})|0)`);
exports.DATA_FORMAT = new RegExp(`(.{1,${exports.MSG_DATA_MAX}})`);
exports.SIGNATURE_FORMAT = new RegExp(`(\\S{140}|\\S{142})$`);
exports.SIGNER_FORMAT = new RegExp(`(\\S{${exports.PUBKEY_LEN}})`);
exports.MSG_FORMAT = new RegExp(`${exports.PTR_FORMAT.source}${exports.MSG_DELIM}${exports.DATA_FORMAT.source}`, 'm');
exports.AMOUNT_DECIMALS = 18;
exports.MAX_SUPPLY = 20_000_000;
exports.AMOUNT_FORMAT = new RegExp(`\d+(\.\d{1,${exports.AMOUNT_DECIMALS}})?`);
exports.MIN_XRP_FEE = "0.00001";
exports.MIN_XRP_TX_VALUE = "0.000001";
+31
View File
@@ -0,0 +1,31 @@
export declare type Memo = {
type?: string;
format?: string;
data?: string;
};
export declare type Wallet = {
secret: string;
address: string;
};
export declare type Signature = {
signature: string;
signer: PublicKey;
};
export declare type Address = string;
export declare type Secret = string;
export declare type PublicKey = string;
export declare type Amount = number;
export declare type TxHash = string;
export declare type Options = {
debug?: boolean;
connectionTimeout?: number;
readMaxRetry?: number;
readRetryTimeout?: number;
};
export declare const defaultOptions: {
debug: boolean;
connectionTimeout: number;
readFreshApi: boolean;
readMaxRetry: number;
readRetryTimeout: number;
};
+10
View File
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultOptions = void 0;
exports.defaultOptions = {
debug: false,
connectionTimeout: 100000,
readFreshApi: true,
readMaxRetry: -1,
readRetryTimeout: 1000
};
+10
View File
@@ -0,0 +1,10 @@
import { Memo, Wallet } from '../util/types';
import { RippleAPI } from 'ripple-lib';
export declare const getLatestSequence: (api: RippleAPI, accountAddress: string) => Promise<number>;
export declare const sendPayment: (api: RippleAPI, data: Memo[], from: string, to: string, secret: string, sequence: number) => Promise<import("ripple-lib/dist/npm/transaction/submit").FormattedSubmitResponse>;
export declare const getTransactions: (api: RippleAPI, address: string, minLedgerVersion?: number) => Promise<any[]>;
export declare const writeRaw: (api: RippleAPI, data: Memo, from: string, to: string, secret: string, sequence?: number) => Promise<string>;
export declare const readRaw: (api: RippleAPI, hash: string) => Promise<Memo>;
export declare const subscribe: (api: RippleAPI, address: string, callback: (tx: any) => any) => Promise<void>;
export declare const treeWrite: (api: RippleAPI, data: string, wallet: Wallet, to: string, format?: 'L' | 'N') => Promise<string>;
export declare const treeRead: (api: RippleAPI, hashes: string[]) => Promise<string>;
+208
View File
@@ -0,0 +1,208 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.treeRead = exports.treeWrite = exports.subscribe = exports.readRaw = exports.writeRaw = exports.getTransactions = exports.sendPayment = exports.getLatestSequence = void 0;
const protocol_constants_1 = require("../util/protocol.constants");
const ripple_lib_1 = require("ripple-lib");
const zlib = __importStar(require("zlib"));
const util = __importStar(require("util"));
const chunkString = (str, length) => str.match(new RegExp('.{1,' + length + '}', 'gs'));
const PAYLOAD_SIZE = 925;
const debug = false;
const cloneApi = async (api) => {
try {
const subApi = new ripple_lib_1.RippleAPI({ server: api.connection['_url'] });
await subApi.connect();
return subApi;
}
catch (e) {
if (debug) {
console.log("CLONEAPI ERR", e);
}
return await cloneApi(api);
}
};
const getLatestSequence = async (api, accountAddress) => {
if (debug)
console.log("Getting acc info for", accountAddress);
const accountInfo = await api.getAccountInfo(accountAddress, {});
return Number(accountInfo.sequence - 1);
};
exports.getLatestSequence = getLatestSequence;
const compressB64 = async (data) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64');
const decompressB64 = async (data) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8');
const sendReliably = (api, signed, preparedPayment) => new Promise((res, rej) => {
const ledgerClosedCallback = async (event) => {
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.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);
});
const sendPayment = async (api, data, from, to, secret, sequence) => {
if (debug)
console.log("Sending payment with seq", sequence);
const options = {
maxLedgerVersionOffset: 5,
fee: protocol_constants_1.MIN_XRP_FEE,
sequence: sequence,
};
const payment = {
source: {
address: from,
maxAmount: {
value: protocol_constants_1.MIN_XRP_TX_VALUE,
currency: 'XRP'
},
},
destination: {
address: to,
amount: {
value: protocol_constants_1.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();
}
};
exports.sendPayment = sendPayment;
const getTransactions = async (api, address, minLedgerVersion = 25235454) => {
const txs = await api.getTransactions(address, {
minLedgerVersion: minLedgerVersion,
earliestFirst: true,
excludeFailures: true,
});
return txs;
};
exports.getTransactions = getTransactions;
const writeRaw = async (api, data, from, to, secret, sequence) => {
//if (memoSize(data) > 1000) throw new Error("data length exceeds capacity")
try {
if (!sequence) {
const accountInfo = await (0, exports.getLatestSequence)(api, from);
sequence = accountInfo + 1;
}
const resp = await (0, exports.sendPayment)(api, [data], from, to, secret, sequence);
return resp['tx_json'].hash;
}
catch (error) {
if (debug) {
console.log("WRITERAW ERR", error);
}
throw error;
}
};
exports.writeRaw = writeRaw;
const readRaw = async (api, hash) => {
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];
};
exports.readRaw = readRaw;
const subscribe = async (api, address, callback) => {
api.connection.on('transaction', (tx) => callback(tx));
await api.connection.request({
command: 'subscribe',
accounts: [address],
});
};
exports.subscribe = subscribe;
const treeWrite = async (api, data, wallet, to, format = 'L') => {
data = await compressB64(data);
const chunks = chunkString(data, PAYLOAD_SIZE);
const latestSequence = await (0, exports.getLatestSequence)(api, wallet.address);
const hashes = await Promise.all(Object.entries(chunks).map(([i, chunk]) => (0, exports.writeRaw)(api, { data: chunk, format: format }, wallet.address, to, wallet.secret, latestSequence + Number(i) + 1)));
if (hashes.length === 1) {
return hashes[0];
}
return await (0, exports.treeWrite)(api, JSON.stringify(hashes), wallet, to, 'N');
};
exports.treeWrite = treeWrite;
const treeRead = async (api, hashes) => {
const memos = await Promise.all(hashes.map(hash => (0, exports.readRaw)(api, hash)));
const payload = await decompressB64(memos.map(memo => memo.data).join(''));
if (memos.some(memo => memo.format === 'N')) {
return await (0, exports.treeRead)(api, JSON.parse(payload));
}
return payload;
};
exports.treeRead = treeRead;
+21
View File
@@ -0,0 +1,21 @@
import { Memo, Options } from '../util/types';
export declare class xrpIO {
private server;
private options;
private api;
constructor(server: string, options?: Options);
connect(): Promise<void>;
disconnect(): Promise<void>;
private cloneApi;
private sendPayment;
writeRaw(data: Memo, to: string, secret: string, sequence?: number, amount?: string): Promise<string>;
private getTransaction;
readRaw(hash: string, verifyOwner?: string): Promise<Memo>;
treeWrite(data: string, to: string, secret: string, format?: 'L' | 'N'): Promise<string>;
treeRead(hashes: string[], verifyOwner?: string): Promise<string>;
getAccountSequence(address: string): Promise<number>;
estimateFee(data: string, denomination?: 'XRP' | 'DROPS', cost?: number): Promise<number>;
xrpToDrops(xrp: number): number;
dropsToXrp(drops: number): number;
private dbg;
}
+206
View File
@@ -0,0 +1,206 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.xrpIO = void 0;
const types_1 = require("../util/types");
const xrpl_1 = require("xrpl");
const zlib = __importStar(require("zlib"));
const util = __importStar(require("util"));
const protocol_constants_1 = require("../util/protocol.constants");
const errors_1 = require("../util/errors");
const compressB64 = async (data) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64');
const decompressB64 = async (data) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8');
const hexDecode = (str) => Buffer.from(str, 'hex').toString('utf8');
const hexEncode = (str) => Buffer.from(str, 'utf8').toString('hex').toUpperCase();
const chunkString = (str, length) => 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 XRP_PER_DROP = 0.000001;
const DROP_PER_XRP = 1000000;
class xrpIO {
server;
options;
api;
constructor(server, options = types_1.defaultOptions) {
this.server = server;
this.options = options;
this.options.debug = options.debug ? Boolean(options.debug) : types_1.defaultOptions.debug;
this.options.connectionTimeout = options.connectionTimeout ? Number(options.connectionTimeout) : types_1.defaultOptions.connectionTimeout;
this.options.readMaxRetry = options.readMaxRetry ? Number(options.readMaxRetry) : types_1.defaultOptions.readMaxRetry;
this.options.readRetryTimeout = options.readRetryTimeout ? Number(options.readRetryTimeout) : types_1.defaultOptions.readRetryTimeout;
this.api = new xrpl_1.Client(server, {
connectionTimeout: this.options.connectionTimeout
});
}
async connect() {
if (!this.api.isConnected())
await this.api.connect();
}
async disconnect() {
try {
await this.api.disconnect();
}
catch (e) {
console.log("DISCONNECT ERROR", e);
}
}
async cloneApi() {
let _api = new xrpl_1.Client(this.server, {
connectionTimeout: this.options.connectionTimeout
});
while (!_api.isConnected()) {
try {
await _api.connect();
return _api;
}
catch (e) {
this.dbg('CLONEAPI ERR', 'Connection failed', String(e['message']));
await _api.disconnect();
_api = new xrpl_1.Client(this.server, {
connectionTimeout: this.options.connectionTimeout
});
}
}
}
async sendPayment(data, to, secret, sequence, amount = "1") {
const wallet = xrpl_1.Wallet.fromSecret(secret);
this.dbg("Sending payment", wallet.address, '->', to);
const _api = await this.cloneApi();
try {
const 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 || "")
}
}]
});
const response = await _api.submitAndWait(payment, { wallet });
await _api.disconnect();
this.dbg("Tx finalized", response.result.hash, response.result.Sequence);
return response;
}
catch (error) {
this.dbg("SENDPAYMENT ERROR", error);
await _api.disconnect();
throw error;
}
}
async writeRaw(data, to, secret, sequence, amount = "1") {
this.dbg("Writing data", data);
const tx = await this.sendPayment(data, to, secret, sequence, amount);
return tx.result.hash;
}
async getTransaction(hash, retry = 0) {
this.dbg("Getting Tx", hash);
try {
return await this.api.request({
command: 'tx',
transaction: hash,
});
}
catch (e) {
this.dbg(e);
if (this.options.readMaxRetry != -1) {
if (retry >= this.options.readMaxRetry)
console.error("Retry limit exceeded for", hash, ". This is an irrecoverable error");
throw e;
}
await new Promise(res => setTimeout(res, this.options.readRetryTimeout));
return await this.getTransaction(hash, retry + 1);
}
}
async readRaw(hash, verifyOwner) {
if (!protocol_constants_1.NON_ZERO_TX_HASH.test(hash)) {
throw (0, errors_1.ERR_BAD_TX_HASH)(hash);
}
const tx = await this.getTransaction(hash);
if (verifyOwner && tx.result.Account != verifyOwner) {
throw (0, errors_1.ERR_NO_VERIFY_OWNER)(hash, tx.result.Account, verifyOwner);
}
const memo = tx.result.Memos[0].Memo;
const memoParsed = {
data: hexDecode(memo.MemoData),
format: hexDecode(memo.MemoFormat),
type: hexDecode(memo.MemoType)
};
this.dbg(hash, "data", memoParsed);
return memoParsed;
}
async treeWrite(data, to, secret, format = 'L') {
const wallet = xrpl_1.Wallet.fromSecret(secret);
data = await compressB64(data);
const chunks = chunkString(data, PAYLOAD_SIZE);
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))));
if (hashes.length === 1) {
return hashes[0];
}
return await this.treeWrite(JSON.stringify(hashes), to, secret, 'N');
}
async treeRead(hashes, verifyOwner) {
const bad_hash = hashes.find(hash => !protocol_constants_1.NON_ZERO_TX_HASH.test(hash));
if (bad_hash)
throw (0, errors_1.ERR_BAD_TX_HASH)(bad_hash);
const memos = await Promise.all(hashes.map(hash => this.readRaw(hash, verifyOwner)));
const payload = await decompressB64(memos.map(memo => memo.data).join(''));
if (memos.some(memo => memo.format === 'N')) {
return await this.treeRead(JSON.parse(payload), verifyOwner);
}
return payload;
}
async getAccountSequence(address) {
this.dbg("Getting acc info for", address);
const accountInfo = await this.api.request({
command: 'account_info',
account: address,
strict: true,
});
this.dbg("Got account_info", accountInfo);
return Number(accountInfo.result.account_data.Sequence);
}
async estimateFee(data, denomination = 'DROPS', cost = 0) {
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);
}
xrpToDrops(xrp) {
return xrp * DROP_PER_XRP;
}
dropsToXrp(drops) {
return drops * XRP_PER_DROP;
}
dbg(...args) {
if (this.options.debug) {
console.log.apply(console, args);
}
}
}
exports.xrpIO = xrpIO;
+66
View File
@@ -0,0 +1,66 @@
{
"name": "xrpio",
"version": "0.2.1",
"repository": {
"type": "git",
"url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git"
},
"bugs": {
"url": "https://gitea.nitowa.xyz/npm-packages/xrpio/issues",
"email": "peter.millauer@gmail.com"
},
"homepage": "https://gitea.nitowa.xyz/docs/xrpio",
"description": "XRP arbitrary data writer and reader",
"main": "lib/src/index.js",
"files": [
"lib/src",
"lib/browser"
],
"scripts": {
"clean": "rm -rf gui/main.js gui/index.html gateway/main.js build lib docs",
"start": "npm run build && npm run launch",
"launch": "node ./lib/Launcher.js",
"tsc": "tsc",
"build": "npm run clean && tsc && npm run webpack",
"build-all": "npm run build && npm run webpack-gui",
"webpack-gui": "webpack --config webpack.gui.js --progress && cp build/gui/main.js gui",
"webpack-gateway": "webpack --config webpack.gateway.js --progress && cp build/gateway/main.js gateway",
"test": "npm run build && mocha --bail=true ./lib/test/*.js",
"deploy": "node ./lib/Deploy.js",
"webpack": "webpack --config ./src/webpack.js",
"docs": "typedoc --out docs --readme ./README.md --plugin typedoc-plugin-markdown --hideBreadcrumbs ./src/index.ts"
},
"author": "nitowa",
"license": "MIT",
"dependencies": {
"ripple-lib": "^1.10.0",
"xrpl": "^2.7.0"
},
"devDependencies": {
"@types/chai": "^4.2.21",
"@types/mocha": "^8.2.2",
"@types/node": "^14.14.37",
"base-64": "^1.0.0",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"chai": "^4.3.4",
"crypto-browserify": "^3.12.0",
"https-browserify": "^1.0.0",
"mocha": "^9.2.0",
"net": "^1.0.2",
"node-fetch": "^2.6.2",
"process": "^0.11.10",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"tls": "^0.0.1",
"ts-loader": "^8.1.0",
"typedoc": "^0.22.11",
"typedoc-plugin-markdown": "^3.11.12",
"typescript": "^4.5.0",
"url": "^0.11.0",
"utf8": "^3.0.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0",
"wtfnode": "^0.9.1"
}
}