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
+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;