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
+4
View File
@@ -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>);
}
+127
View File
@@ -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;
+67
View File
@@ -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>;
+17
View File
@@ -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()
});
+15
View File
@@ -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 {};
+53
View File
@@ -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' });
})();
+3
View File
@@ -0,0 +1,3 @@
export * from "./RJSVM/framework/rjsvm";
export * from "./RJSVM/framework/types";
export * from "./util/dataparser";
+19
View File
@@ -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);
+1
View File
@@ -0,0 +1 @@
export {};
+55
View File
@@ -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();
+3
View File
@@ -0,0 +1,3 @@
export declare class DataParser {
static hex_to_ascii(input: any): string;
}
+14
View File
@@ -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;
+15
View File
@@ -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;
+18
View File
@@ -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})`);
+3
View File
@@ -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;
+8
View File
@@ -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");