init
This commit is contained in:
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import { RJSVM, RJSVM_Config, RJSVM_Implementations, Function_Map, Endpoints_Of, State_Of, Generic_Ctor_ReturnType } from "./types";
|
||||
export declare abstract class RJSVM_Builder {
|
||||
static from<Base_Ctor extends (new (...args: any) => RJSVM<State_T, Endpoints_T>) | (abstract new (...args: any) => RJSVM<State_T, Endpoints_T>), Impl extends RJSVM_Implementations<any, Endpoints_T>, Endpoints_T extends Function_Map = Endpoints_Of<Generic_Ctor_ReturnType<Base_Ctor>>, State_T = State_Of<Generic_Ctor_ReturnType<Base_Ctor>>>(Base: Base_Ctor, defs: Impl): (new (config: RJSVM_Config) => RJSVM<State_T, Endpoints_T>);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RJSVM_Builder = void 0;
|
||||
const types_1 = require("./types");
|
||||
const xrpl_1 = require("xrpl");
|
||||
const xrpio_1 = require("xrpio");
|
||||
const dataparser_1 = require("../../util/dataparser");
|
||||
const protocol_constants_1 = require("../../util/protocol.constants");
|
||||
class RJSVM_Builder {
|
||||
static from(Base, defs) {
|
||||
return class RJSVM_Runnable extends Base {
|
||||
config;
|
||||
sync_block_height = -1;
|
||||
rippleApi;
|
||||
xrpIO;
|
||||
definitions;
|
||||
constructor(config) {
|
||||
super();
|
||||
this.config = config;
|
||||
if (!protocol_constants_1.XRP_ADDRESS.test(this.owner)) {
|
||||
throw new Error(`Inavlid owner address ${this.owner}`);
|
||||
}
|
||||
this.definitions = Object.freeze(defs);
|
||||
Object.entries(this.definitions).forEach(([k, v]) => {
|
||||
this[k] = (env, ...args) => v.implementation.apply(this, [env, ...args]);
|
||||
});
|
||||
}
|
||||
connect = async () => {
|
||||
this.rippleApi = new xrpl_1.Client(this.config.rippleNode);
|
||||
await this.rippleApi.connect();
|
||||
this.xrpIO = new xrpio_1.xrpIO(this.config.rippleNode);
|
||||
await this.xrpIO.connect();
|
||||
await this.sync();
|
||||
this.rippleApi.on('ledgerClosed', (ledger) => {
|
||||
/*
|
||||
console.log("---- Ledger ----")
|
||||
console.log("index",ledger.ledger_index)
|
||||
console.log("hash", ledger.ledger_hash)
|
||||
console.log("---- /Ledger ----")
|
||||
*/
|
||||
});
|
||||
await this.rippleApi.request({
|
||||
command: 'subscribe',
|
||||
streams: ['ledger']
|
||||
});
|
||||
};
|
||||
handlePayload = async (tx, payload) => {
|
||||
if (!(payload.endpoint in this.definitions)) {
|
||||
return;
|
||||
}
|
||||
const endpointDef = this.definitions[payload.endpoint];
|
||||
if (endpointDef.visibility === 'owner' && tx.Account !== this.owner) {
|
||||
console.log(`owner restricted endpoint "${payload.endpoint}" called from ${tx.hash}. But ${tx.Account} != ${this.owner}`);
|
||||
return;
|
||||
}
|
||||
if (endpointDef.fee && Number(tx.Amount) < endpointDef.fee) {
|
||||
console.log(`Insufficient fee ${tx.hash}. Required ${endpointDef.fee}, was ${tx.Amount}`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = await this.xrpIO.treeRead([payload.data], tx.Account);
|
||||
const jsonData = JSON.parse(data);
|
||||
endpointDef.parameterSchema.parse(jsonData);
|
||||
this[payload.endpoint].apply(this, [tx, jsonData]);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
parseMemos = async (tx, memos) => {
|
||||
memos
|
||||
.map((memo) => {
|
||||
if (!memo.Memo || !memo.Memo.MemoData) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = dataparser_1.DataParser.hex_to_ascii(memo.Memo.MemoData);
|
||||
return JSON.parse(data);
|
||||
}
|
||||
catch (e) {
|
||||
return;
|
||||
}
|
||||
})
|
||||
.filter((data) => data != undefined)
|
||||
.forEach(payload => {
|
||||
try {
|
||||
const parsedPayload = types_1.payloadSchema.parse(payload);
|
||||
this.handlePayload(tx, parsedPayload);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
});
|
||||
};
|
||||
sync = async (marker) => {
|
||||
//If `forward` is set to true, returns values indexed with the oldest ledger first.
|
||||
//Otherwise, the results are indexed with the newest ledger first.
|
||||
//(Each page of results may not be internally ordered, but the pages are overall ordered.)
|
||||
const resp = await this.rippleApi.request({
|
||||
command: "account_tx",
|
||||
account: this.config.listeningAddress,
|
||||
forward: true,
|
||||
ledger_index_min: this.sync_block_height,
|
||||
marker: marker,
|
||||
});
|
||||
//results may not be ordered, so sort them
|
||||
const raw_txs = resp.result.transactions
|
||||
.sort((a, b) => a.tx.ledger_index - b.tx.ledger_index)
|
||||
.map((entry) => entry.tx);
|
||||
//apply any state changes
|
||||
await Promise.all(raw_txs.map((tx) => this.parseMemos(tx, tx.Memos)));
|
||||
//if marker is present the result is paginated.
|
||||
//re-run the same request with the marker included
|
||||
if (resp.result.marker) {
|
||||
await this.sync(resp.result.marker);
|
||||
}
|
||||
else {
|
||||
//presence of no marker means we caught up to current block height
|
||||
this.sync_block_height = resp.result.ledger_index_max + 1;
|
||||
//schedule the next sync
|
||||
setTimeout(this.sync, 10000);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.RJSVM_Builder = RJSVM_Builder;
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
import { z } from "zod";
|
||||
export declare abstract class RJSVM<State_T = any, Definitions_T extends Function_Map = Function_Map> {
|
||||
owner: string;
|
||||
state: State_T;
|
||||
sync_block_height: number;
|
||||
config: RJSVM_Config;
|
||||
definitions: RJSVM_Implementations<any, Definitions_T>;
|
||||
connect: () => Promise<void>;
|
||||
}
|
||||
export type Generic_Ctor_ReturnType<Ctor> = Ctor extends new (...args: any) => infer T ? T : Ctor extends abstract new (...args: any) => infer A ? A : never;
|
||||
export type State_Of<T extends RJSVM<any>> = T extends RJSVM<infer State_T> ? State_T : any;
|
||||
export type Endpoints_Of<T extends RJSVM<any, any>> = T extends RJSVM<any, infer Endpoints_T> ? Endpoints_T : any;
|
||||
export type PaymentTx_T = {
|
||||
Account: string;
|
||||
Amount: string;
|
||||
Destination: string;
|
||||
Fee: string;
|
||||
Flags: number;
|
||||
LastLedgerSequence: number;
|
||||
Memos: any[];
|
||||
Sequence: number;
|
||||
SigningPubKey: string;
|
||||
TransactionType: 'Payment';
|
||||
TxnSignature: string;
|
||||
date: number;
|
||||
hash: string;
|
||||
inLedger: number;
|
||||
ledger_index: number;
|
||||
};
|
||||
export type ParameterizedFunction = (...args: any) => void;
|
||||
export type Function_Map = {
|
||||
[K in string]: ParameterizedFunction;
|
||||
};
|
||||
export type RJSVM_Endpoint<RJSVM_T extends RJSVM<any, Definitions_T>, Definitions_T extends Function_Map = Endpoints_Of<RJSVM_T>, Impl extends RJSVM_EndpointHandler<RJSVM_T, Definitions_T, ParameterizedFunction> = RJSVM_EndpointHandler<RJSVM_T, Definitions_T, ParameterizedFunction>> = {
|
||||
implementation: Impl;
|
||||
visibility: 'owner' | 'public';
|
||||
fee?: number;
|
||||
parameterSchema: z.Schema;
|
||||
};
|
||||
export type RJSVM_Implementations<RJSVM_T extends RJSVM<any, Definitions>, Definitions extends Function_Map = Endpoints_Of<RJSVM_T>> = {
|
||||
[Key in keyof Definitions]: RJSVM_Endpoint<RJSVM_T, Definitions, RJSVM_EndpointHandler<RJSVM_T, Definitions, Definitions[Key]>>;
|
||||
};
|
||||
export type RJSVM_EndpointHandler<RJSVM_T extends RJSVM<any, Definitions_T>, Definitions_T extends Function_Map = Endpoints_Of<RJSVM_T>, Fn extends ParameterizedFunction = ParameterizedFunction> = (this: RJSVM_T & RJSVM_Interface<RJSVM_T, State_Of<RJSVM_T>, Definitions_T>, env: PaymentTx_T, ...args: Parameters<Fn>) => void;
|
||||
export type RJSVM_Interface<RJSVM_T extends RJSVM<State_T, Definitions_T>, State_T = State_Of<RJSVM_T>, Definitions_T extends Function_Map = Endpoints_Of<RJSVM_T>> = {
|
||||
state: State_T;
|
||||
} & {
|
||||
[K in keyof Definitions_T]?: RJSVM_EndpointHandler<RJSVM_T, Definitions_T, Definitions_T[K]>;
|
||||
};
|
||||
export type RJSVM_InitState<State_T> = {
|
||||
owner: string;
|
||||
state: State_T;
|
||||
};
|
||||
export type RJSVM_Config = {
|
||||
rippleNode: string;
|
||||
listeningAddress: string;
|
||||
};
|
||||
export declare const payloadSchema: z.ZodObject<{
|
||||
endpoint: z.ZodString;
|
||||
data: z.ZodString;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
endpoint?: string;
|
||||
data?: string;
|
||||
}, {
|
||||
endpoint?: string;
|
||||
data?: string;
|
||||
}>;
|
||||
export type Payload = z.infer<typeof payloadSchema>;
|
||||
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.payloadSchema = exports.RJSVM = void 0;
|
||||
const zod_1 = require("zod");
|
||||
class RJSVM {
|
||||
owner;
|
||||
state;
|
||||
sync_block_height;
|
||||
config;
|
||||
definitions;
|
||||
connect;
|
||||
}
|
||||
exports.RJSVM = RJSVM;
|
||||
exports.payloadSchema = zod_1.z.object({
|
||||
endpoint: zod_1.z.string(),
|
||||
data: zod_1.z.string()
|
||||
});
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
type Wallet = {
|
||||
address: string;
|
||||
secret: string;
|
||||
};
|
||||
type DatawriterConfig = {
|
||||
receiveAddress: string;
|
||||
sendWallet: Wallet;
|
||||
xrpNode: string;
|
||||
};
|
||||
export declare class Datawriter {
|
||||
private config;
|
||||
constructor(config: DatawriterConfig);
|
||||
callEndpoint(endpointName: string, contractAddress: string, parameter: any, fee?: number): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Datawriter = void 0;
|
||||
const xrpio_1 = require("xrpio");
|
||||
class Datawriter {
|
||||
config;
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
}
|
||||
async callEndpoint(endpointName, contractAddress, parameter, fee) {
|
||||
const xrpio = new xrpio_1.xrpIO(this.config.xrpNode);
|
||||
await xrpio.connect();
|
||||
try {
|
||||
const dataHash = await xrpio.treeWrite(JSON.stringify(parameter), this.config.receiveAddress, this.config.sendWallet.secret);
|
||||
await xrpio.writeRaw({
|
||||
data: JSON.stringify({
|
||||
endpoint: endpointName,
|
||||
data: dataHash
|
||||
})
|
||||
}, contractAddress, this.config.sendWallet.secret, undefined, fee ? String(fee) : undefined);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
finally {
|
||||
await xrpio.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Datawriter = Datawriter;
|
||||
(async () => {
|
||||
const owner_dw = new Datawriter({
|
||||
receiveAddress: "rDuXvMYNCEJCYDMFQykcafXhgi2NvMWqR",
|
||||
sendWallet: {
|
||||
address: "rUsPfG1hn6w6is28p5FUDWdMwvCo1iHrYq",
|
||||
secret: "sEdTMicaTVmfsVMLhxrufyAzEQSnsaP"
|
||||
},
|
||||
xrpNode: "wss://s.altnet.rippletest.net:51233"
|
||||
});
|
||||
//await owner_dw.callEndpoint('submit', 'rLaXUiYvW1EMns69PsAfwSLb2VgNtPpZwq', { title: "1", body: "2", from: "3" }, 100)
|
||||
const user_dw = new Datawriter({
|
||||
receiveAddress: "rwqCiEr3SLF43rAduhCChkR2K1XDhiqx5g",
|
||||
sendWallet: {
|
||||
address: "rHWN4X3hbodryX8H1EoPvmMV7AFHngEiBe",
|
||||
secret: "sEdTdLPWvz69UAUmt1zYijTyWheER9u"
|
||||
},
|
||||
xrpNode: "wss://s.altnet.rippletest.net:51233"
|
||||
});
|
||||
//await owner_dw.callEndpoint('submit', 'rLaXUiYvW1EMns69PsAfwSLb2VgNtPpZwq', { title: "1", body: "2", from: "3" }, 100)
|
||||
//await user_dw.callEndpoint('restricted', 'rLaXUiYvW1EMns69PsAfwSLb2VgNtPpZwq', { title: "user", body: "user", from: "user" })
|
||||
//await owner_dw.callEndpoint('restricted', 'rLaXUiYvW1EMns69PsAfwSLb2VgNtPpZwq', { title: "owner", body: "owner", from: "owner" })
|
||||
await user_dw.callEndpoint('setTns', 'rLaXUiYvW1EMns69PsAfwSLb2VgNtPpZwq', { hash: '01708abcF00636CE10E191FD782DBDC8F4076F28404BD88EBC31DE42DD084C0944B', name: 'test' });
|
||||
})();
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export * from "./RJSVM/framework/rjsvm";
|
||||
export * from "./RJSVM/framework/types";
|
||||
export * from "./util/dataparser";
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (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 });
|
||||
__exportStar(require("./RJSVM/framework/rjsvm"), exports);
|
||||
__exportStar(require("./RJSVM/framework/types"), exports);
|
||||
__exportStar(require("./util/dataparser"), exports);
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const zod_1 = require("zod");
|
||||
const types_1 = require("./RJSVM/framework/types");
|
||||
const main_1 = require("./main");
|
||||
const schemas_1 = require("./util/schemas");
|
||||
const shoutSchema = zod_1.z.object({
|
||||
title: zod_1.z.string(),
|
||||
body: zod_1.z.string(),
|
||||
from: zod_1.z.string(),
|
||||
id: zod_1.z.optional(zod_1.z.string())
|
||||
});
|
||||
const tnsEntrySchema = zod_1.z.object({
|
||||
hash: schemas_1.xrp_transaction_hash_schema,
|
||||
name: zod_1.z.string(),
|
||||
});
|
||||
class RJSVM_Base extends types_1.RJSVM {
|
||||
owner = "rUsPfG1hn6w6is28p5FUDWdMwvCo1iHrYq";
|
||||
state = {
|
||||
shouts: []
|
||||
};
|
||||
}
|
||||
const RJSVM_Contract = {
|
||||
submit: {
|
||||
implementation: function (env, shout) {
|
||||
this.state.shouts.unshift(shout);
|
||||
console.log(shout);
|
||||
},
|
||||
visibility: 'public',
|
||||
fee: 10,
|
||||
parameterSchema: shoutSchema
|
||||
},
|
||||
restricted: {
|
||||
implementation: function (env, shout) {
|
||||
this.state.shouts.unshift(shout);
|
||||
console.log(shout);
|
||||
},
|
||||
visibility: 'owner',
|
||||
parameterSchema: shoutSchema
|
||||
},
|
||||
setTns: {
|
||||
implementation: function (env, tnsEntry) {
|
||||
console.log(tnsEntry);
|
||||
},
|
||||
visibility: 'public',
|
||||
parameterSchema: tnsEntrySchema,
|
||||
}
|
||||
};
|
||||
const Rjsvm = main_1.RJSVM_Builder.from(RJSVM_Base, RJSVM_Contract);
|
||||
const conf = {
|
||||
listeningAddress: "rLaXUiYvW1EMns69PsAfwSLb2VgNtPpZwq",
|
||||
rippleNode: "wss://s.altnet.rippletest.net:51233"
|
||||
};
|
||||
const rjsvm = new Rjsvm(conf);
|
||||
rjsvm.connect();
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export declare class DataParser {
|
||||
static hex_to_ascii(input: any): string;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DataParser = void 0;
|
||||
class DataParser {
|
||||
static hex_to_ascii(input) {
|
||||
var hex = input.toString();
|
||||
var str = '';
|
||||
for (var n = 0; n < hex.length; n += 2) {
|
||||
str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
exports.DataParser = DataParser;
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
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";
|
||||
export declare const XRP_ADDRESS: RegExp;
|
||||
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.XRP_ADDRESS = 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";
|
||||
exports.XRP_ADDRESS = new RegExp(`^([r])([1-9A-HJ-NP-Za-km-z]{24,34})`);
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import { z } from "zod";
|
||||
export declare const xrp_address_schema: z.ZodString;
|
||||
export declare const xrp_transaction_hash_schema: z.ZodString;
|
||||
@@ -0,0 +1,8 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.xrp_transaction_hash_schema = exports.xrp_address_schema = void 0;
|
||||
const zod_1 = require("zod");
|
||||
const protocol_constants_1 = require("./protocol.constants");
|
||||
const xrpio_1 = require("xrpio");
|
||||
exports.xrp_address_schema = zod_1.z.string().regex(protocol_constants_1.XRP_ADDRESS, "Not a valid XRP address");
|
||||
exports.xrp_transaction_hash_schema = zod_1.z.string().regex(xrpio_1.NON_ZERO_TX_HASH, "Not a valid XRP transaction hash");
|
||||
Reference in New Issue
Block a user