@logrix/sdk 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +231 -0
- package/dist/commands/add.js +235 -0
- package/dist/commands/build.js +106 -0
- package/dist/commands/codegen.js +364 -0
- package/dist/commands/export_values.js +127 -0
- package/dist/commands/init.js +200 -0
- package/dist/commands/test.js +124 -0
- package/dist/commands/validate.js +159 -0
- package/dist/index.js +24 -0
- package/dist/templates.js +269 -0
- package/index.ts +3 -0
- package/package.json +51 -0
- package/src/runtime/index.ts +736 -0
- package/tsconfig.as.json +4 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
// AssemblyScript runtime library for Logrix blockchain indexers.
|
|
2
|
+
// Import this in your handler files: import { store, log, BigInt, Address, Bytes, crypto, formatUnits } from "@logrix/sdk"
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Host function declarations
|
|
6
|
+
// The Logrix Rust engine registers these in both "env" and "logrix" namespaces.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
@external("env", "logrix_emit")
|
|
10
|
+
declare function host_emit(
|
|
11
|
+
entity_ptr: i32,
|
|
12
|
+
entity_len: i32,
|
|
13
|
+
val_ptr: i32,
|
|
14
|
+
val_len: i32
|
|
15
|
+
): i32;
|
|
16
|
+
|
|
17
|
+
@external("env", "logrix_db_get")
|
|
18
|
+
declare function host_db_get(
|
|
19
|
+
key_ptr: i32,
|
|
20
|
+
key_len: i32,
|
|
21
|
+
out_ptr: i32,
|
|
22
|
+
max_out_len: i32
|
|
23
|
+
): i32;
|
|
24
|
+
|
|
25
|
+
@external("env", "logrix_db_set")
|
|
26
|
+
declare function host_db_set(
|
|
27
|
+
key_ptr: i32,
|
|
28
|
+
key_len: i32,
|
|
29
|
+
val_ptr: i32,
|
|
30
|
+
val_len: i32
|
|
31
|
+
): i32;
|
|
32
|
+
|
|
33
|
+
@external("env", "logrix_db_remove")
|
|
34
|
+
declare function host_db_remove(
|
|
35
|
+
key_ptr: i32,
|
|
36
|
+
key_len: i32
|
|
37
|
+
): i32;
|
|
38
|
+
|
|
39
|
+
@external("env", "logrix_log")
|
|
40
|
+
declare function host_log(level: i32, msg_ptr: i32, msg_len: i32): i32;
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Memory allocator export
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export function allocate(size: i32): i32 {
|
|
47
|
+
return heap.alloc(size) as i32;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Raw blockchain event log payload delivered to indexer handlers.
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
export class EventLog {
|
|
55
|
+
address: string = "";
|
|
56
|
+
block_number: u64 = 0;
|
|
57
|
+
block_hash: string = "";
|
|
58
|
+
transaction_hash: string = "";
|
|
59
|
+
log_index: u32 = 0;
|
|
60
|
+
block_timestamp: u64 = 0;
|
|
61
|
+
topics: Array<string> = [];
|
|
62
|
+
data: string = "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Public Low-Level Host API
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
export function logrix_emit(entityType: string, jsonPayload: string): void {
|
|
70
|
+
const eBuf = String.UTF8.encode(entityType);
|
|
71
|
+
const pBuf = String.UTF8.encode(jsonPayload);
|
|
72
|
+
host_emit(
|
|
73
|
+
changetype<i32>(eBuf),
|
|
74
|
+
eBuf.byteLength,
|
|
75
|
+
changetype<i32>(pBuf),
|
|
76
|
+
pBuf.byteLength
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function logrix_db_get(key: string): string {
|
|
81
|
+
const kBuf = String.UTF8.encode(key);
|
|
82
|
+
const outBuf = new ArrayBuffer(8192);
|
|
83
|
+
const written = host_db_get(
|
|
84
|
+
changetype<i32>(kBuf),
|
|
85
|
+
kBuf.byteLength,
|
|
86
|
+
changetype<i32>(outBuf),
|
|
87
|
+
outBuf.byteLength
|
|
88
|
+
);
|
|
89
|
+
if (written <= 0) return "";
|
|
90
|
+
return String.UTF8.decode(outBuf.slice(0, written));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function logrix_db_set(key: string, value: string): void {
|
|
94
|
+
const kBuf = String.UTF8.encode(key);
|
|
95
|
+
const vBuf = String.UTF8.encode(value);
|
|
96
|
+
host_db_set(
|
|
97
|
+
changetype<i32>(kBuf),
|
|
98
|
+
kBuf.byteLength,
|
|
99
|
+
changetype<i32>(vBuf),
|
|
100
|
+
vBuf.byteLength
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function logrix_db_remove(key: string): void {
|
|
105
|
+
const kBuf = String.UTF8.encode(key);
|
|
106
|
+
host_db_remove(changetype<i32>(kBuf), kBuf.byteLength);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function logrix_log(level: i32, message: string): void {
|
|
110
|
+
const mBuf = String.UTF8.encode(message);
|
|
111
|
+
host_log(level, changetype<i32>(mBuf), mBuf.byteLength);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Store namespace (Graph Protocol compatible Entity store)
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
export namespace store {
|
|
119
|
+
export function get(entityType: string, id: string): string {
|
|
120
|
+
return logrix_db_get(entityType + ":" + id);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function set(entityType: string, id: string, jsonPayload: string): void {
|
|
124
|
+
logrix_db_set(entityType + ":" + id, jsonPayload);
|
|
125
|
+
logrix_emit(entityType, jsonPayload);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function remove(entityType: string, id: string): void {
|
|
129
|
+
logrix_db_remove(entityType + ":" + id);
|
|
130
|
+
logrix_emit(entityType, "{\"id\":\"" + id + "\",\"_deleted\":true}");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Log namespace (Graph Protocol compatible logging)
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
export namespace log {
|
|
139
|
+
export function debug(message: string): void {
|
|
140
|
+
logrix_log(0, message);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function info(message: string): void {
|
|
144
|
+
logrix_log(1, message);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function warning(message: string): void {
|
|
148
|
+
logrix_log(2, message);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function error(message: string): void {
|
|
152
|
+
logrix_log(3, message);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Address Type
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
export class Address {
|
|
161
|
+
private _val: string;
|
|
162
|
+
|
|
163
|
+
constructor(hex: string) {
|
|
164
|
+
let clean = hex.trim().toLowerCase();
|
|
165
|
+
if (clean.startsWith("0x")) clean = clean.slice(2);
|
|
166
|
+
// If 32-byte topic (64 chars), take the last 40 chars for EVM 20-byte address
|
|
167
|
+
if (clean.length == 64) {
|
|
168
|
+
clean = clean.slice(24);
|
|
169
|
+
}
|
|
170
|
+
this._val = "0x" + clean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static fromString(hex: string): Address {
|
|
174
|
+
return new Address(hex);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
toHexString(): string {
|
|
178
|
+
return this._val;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
toString(): string {
|
|
182
|
+
return this._val;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
equals(other: Address): bool {
|
|
186
|
+
return this._val == other._val;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Bytes Type
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
function parseHexChar(c: i32): i32 {
|
|
195
|
+
if (c >= 48 && c <= 57) return c - 48;
|
|
196
|
+
if (c >= 97 && c <= 102) return c - 97 + 10;
|
|
197
|
+
if (c >= 65 && c <= 70) return c - 65 + 10;
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export class Bytes {
|
|
202
|
+
private _hex: string;
|
|
203
|
+
|
|
204
|
+
constructor(hex: string) {
|
|
205
|
+
let clean = hex.trim().toLowerCase();
|
|
206
|
+
if (clean.startsWith("0x")) clean = clean.slice(2);
|
|
207
|
+
if (clean.length % 2 != 0) clean = "0" + clean;
|
|
208
|
+
this._hex = "0x" + clean;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static fromHexString(hex: string): Bytes {
|
|
212
|
+
return new Bytes(hex);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static fromUTF8(str: string): Bytes {
|
|
216
|
+
const buf = String.UTF8.encode(str);
|
|
217
|
+
const u8 = Uint8Array.wrap(buf);
|
|
218
|
+
return Bytes.fromByteArray(u8);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static fromByteArray(arr: Uint8Array): Bytes {
|
|
222
|
+
const hexChars = "0123456789abcdef";
|
|
223
|
+
let res = "0x";
|
|
224
|
+
for (let i = 0; i < arr.length; i++) {
|
|
225
|
+
const b = arr[i];
|
|
226
|
+
res += hexChars.charAt((b >> 4) & 0x0f) + hexChars.charAt(b & 0x0f);
|
|
227
|
+
}
|
|
228
|
+
return new Bytes(res);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
toByteArray(): Uint8Array {
|
|
232
|
+
const clean = this._hex.slice(2);
|
|
233
|
+
const len = clean.length / 2;
|
|
234
|
+
const res = new Uint8Array(len);
|
|
235
|
+
for (let i = 0; i < len; i++) {
|
|
236
|
+
const high = parseHexChar(clean.charCodeAt(i * 2));
|
|
237
|
+
const low = parseHexChar(clean.charCodeAt(i * 2 + 1));
|
|
238
|
+
res[i] = ((high << 4) | low) as u8;
|
|
239
|
+
}
|
|
240
|
+
return res;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
toHexString(): string {
|
|
244
|
+
return this._hex;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
toString(): string {
|
|
248
|
+
return this._hex;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
get length(): i32 {
|
|
252
|
+
const raw = this._hex.slice(2);
|
|
253
|
+
return (raw.length as i32) / 2;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
equals(other: Bytes): bool {
|
|
257
|
+
return this._hex == other._hex;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Keccak-256 Implementation & Crypto namespace
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
const KECCAK_RC: u64[] = [
|
|
266
|
+
0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000,
|
|
267
|
+
0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009,
|
|
268
|
+
0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
|
|
269
|
+
0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003,
|
|
270
|
+
0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a,
|
|
271
|
+
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const KECCAK_R: i32[] = [
|
|
275
|
+
0, 36, 3, 41, 18,
|
|
276
|
+
1, 44, 10, 45, 2,
|
|
277
|
+
62, 6, 43, 15, 61,
|
|
278
|
+
28, 55, 25, 21, 56,
|
|
279
|
+
27, 20, 39, 8, 14
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
function rotl64(x: u64, n: i32): u64 {
|
|
283
|
+
const shift = (n as u64) & 63;
|
|
284
|
+
if (shift == 0) return x;
|
|
285
|
+
return (x << shift) | (x >> (64 - shift));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function keccakF1600(A: u64[]): void {
|
|
289
|
+
const B = new Array<u64>(25);
|
|
290
|
+
for (let i = 0; i < 25; i++) B[i] = 0;
|
|
291
|
+
const C = new Array<u64>(5);
|
|
292
|
+
for (let i = 0; i < 5; i++) C[i] = 0;
|
|
293
|
+
const D = new Array<u64>(5);
|
|
294
|
+
for (let i = 0; i < 5; i++) D[i] = 0;
|
|
295
|
+
|
|
296
|
+
for (let ir = 0; ir < 24; ir++) {
|
|
297
|
+
for (let x = 0; x < 5; x++) {
|
|
298
|
+
C[x] = A[x * 5 + 0] ^ A[x * 5 + 1] ^ A[x * 5 + 2] ^ A[x * 5 + 3] ^ A[x * 5 + 4];
|
|
299
|
+
}
|
|
300
|
+
for (let x = 0; x < 5; x++) {
|
|
301
|
+
D[x] = C[(x + 4) % 5] ^ rotl64(C[(x + 1) % 5], 1);
|
|
302
|
+
}
|
|
303
|
+
for (let x = 0; x < 5; x++) {
|
|
304
|
+
for (let y = 0; y < 5; y++) {
|
|
305
|
+
A[x * 5 + y] ^= D[x];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (let x = 0; x < 5; x++) {
|
|
309
|
+
for (let y = 0; y < 5; y++) {
|
|
310
|
+
B[y * 5 + ((2 * x + 3 * y) % 5)] = rotl64(A[x * 5 + y], KECCAK_R[x * 5 + y]);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
for (let x = 0; x < 5; x++) {
|
|
314
|
+
for (let y = 0; y < 5; y++) {
|
|
315
|
+
const b1 = B[((x + 1) % 5) * 5 + y];
|
|
316
|
+
const b2 = B[((x + 2) % 5) * 5 + y];
|
|
317
|
+
A[x * 5 + y] = B[x * 5 + y] ^ ((~b1) & b2);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
A[0] ^= KECCAK_RC[ir];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function keccak256Raw(data: Uint8Array): Uint8Array {
|
|
325
|
+
const A = new Array<u64>(25);
|
|
326
|
+
for (let i = 0; i < 25; i++) A[i] = 0;
|
|
327
|
+
|
|
328
|
+
const rate = 136;
|
|
329
|
+
let offset = 0;
|
|
330
|
+
const len = data.length;
|
|
331
|
+
|
|
332
|
+
while (offset + rate <= len) {
|
|
333
|
+
for (let i = 0; i < 17; i++) {
|
|
334
|
+
const x = i % 5;
|
|
335
|
+
const y = i / 5;
|
|
336
|
+
let w: u64 = 0;
|
|
337
|
+
const base = offset + i * 8;
|
|
338
|
+
for (let b = 0; b < 8; b++) {
|
|
339
|
+
w |= (data[base + b] as u64) << (b * 8);
|
|
340
|
+
}
|
|
341
|
+
A[x * 5 + y] ^= w;
|
|
342
|
+
}
|
|
343
|
+
keccakF1600(A);
|
|
344
|
+
offset += rate;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const remaining = len - offset;
|
|
348
|
+
const pad = new Uint8Array(rate);
|
|
349
|
+
for (let i = 0; i < remaining; i++) {
|
|
350
|
+
pad[i] = data[offset + i];
|
|
351
|
+
}
|
|
352
|
+
pad[remaining] = 0x01;
|
|
353
|
+
pad[rate - 1] |= 0x80;
|
|
354
|
+
|
|
355
|
+
for (let i = 0; i < 17; i++) {
|
|
356
|
+
const x = i % 5;
|
|
357
|
+
const y = i / 5;
|
|
358
|
+
let w: u64 = 0;
|
|
359
|
+
const base = i * 8;
|
|
360
|
+
for (let b = 0; b < 8; b++) {
|
|
361
|
+
w |= (pad[base + b] as u64) << (b * 8);
|
|
362
|
+
}
|
|
363
|
+
A[x * 5 + y] ^= w;
|
|
364
|
+
}
|
|
365
|
+
keccakF1600(A);
|
|
366
|
+
|
|
367
|
+
const out = new Uint8Array(32);
|
|
368
|
+
for (let i = 0; i < 4; i++) {
|
|
369
|
+
const x = i % 5;
|
|
370
|
+
const y = i / 5;
|
|
371
|
+
const w = A[x * 5 + y];
|
|
372
|
+
const base = i * 8;
|
|
373
|
+
for (let b = 0; b < 8; b++) {
|
|
374
|
+
out[base + b] = ((w >> (b * 8)) & 0xff) as u8;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export namespace crypto {
|
|
381
|
+
export function keccak256(data: Bytes): Bytes {
|
|
382
|
+
const raw = data.toByteArray();
|
|
383
|
+
const digest = keccak256Raw(raw);
|
|
384
|
+
return Bytes.fromByteArray(digest);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
// Arbitrary-Precision BigInt Type (Supports Hex & Decimal, EVM 256-bit safe)
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
export class BigInt {
|
|
393
|
+
private _raw: string;
|
|
394
|
+
|
|
395
|
+
constructor(val: string) {
|
|
396
|
+
let clean = val.trim();
|
|
397
|
+
if (clean.startsWith("0x") || clean.startsWith("0X")) {
|
|
398
|
+
this._raw = hexToDecimal(clean);
|
|
399
|
+
} else {
|
|
400
|
+
this._raw = clean.length > 0 ? clean : "0";
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
static fromString(val: string): BigInt {
|
|
405
|
+
return new BigInt(val);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
static fromI32(val: i32): BigInt {
|
|
409
|
+
return new BigInt(val.toString());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
static fromU64(val: u64): BigInt {
|
|
413
|
+
return new BigInt(val.toString());
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
static zero(): BigInt {
|
|
417
|
+
return new BigInt("0");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
toString(): string {
|
|
421
|
+
return this._raw;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
toHexString(): string {
|
|
425
|
+
return "0x" + decimalToHex(this._raw);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
plus(other: BigInt): BigInt {
|
|
429
|
+
return new BigInt(addDecimal(this._raw, other._raw));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
minus(other: BigInt): BigInt {
|
|
433
|
+
return new BigInt(subDecimal(this._raw, other._raw));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
times(other: BigInt): BigInt {
|
|
437
|
+
return new BigInt(mulDecimal(this._raw, other._raw));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
div(other: BigInt): BigInt {
|
|
441
|
+
return new BigInt(divDecimal(this._raw, other._raw));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
equals(other: BigInt): bool {
|
|
445
|
+
return cmpDecimal(this._raw, other._raw) == 0;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
gt(other: BigInt): bool {
|
|
449
|
+
return cmpDecimal(this._raw, other._raw) > 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
lt(other: BigInt): bool {
|
|
453
|
+
return cmpDecimal(this._raw, other._raw) < 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
gte(other: BigInt): bool {
|
|
457
|
+
return cmpDecimal(this._raw, other._raw) >= 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
lte(other: BigInt): bool {
|
|
461
|
+
return cmpDecimal(this._raw, other._raw) <= 0;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
// formatUnits Helper (EVM Decimal Formatting)
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
|
|
469
|
+
export function formatUnits(value: BigInt, decimals: i32 = 18): string {
|
|
470
|
+
let s = value.toString();
|
|
471
|
+
if (decimals <= 0) return s;
|
|
472
|
+
let isNegative = false;
|
|
473
|
+
if (s.startsWith("-")) {
|
|
474
|
+
isNegative = true;
|
|
475
|
+
s = s.slice(1);
|
|
476
|
+
}
|
|
477
|
+
if (s.length <= decimals) {
|
|
478
|
+
let zeros = "";
|
|
479
|
+
for (let i = 0; i < decimals - s.length; i++) {
|
|
480
|
+
zeros += "0";
|
|
481
|
+
}
|
|
482
|
+
s = "0." + zeros + s;
|
|
483
|
+
} else {
|
|
484
|
+
const intPart = s.slice(0, s.length - decimals);
|
|
485
|
+
const fracPart = s.slice(s.length - decimals);
|
|
486
|
+
s = intPart + "." + fracPart;
|
|
487
|
+
}
|
|
488
|
+
const dotIdx = s.indexOf(".");
|
|
489
|
+
if (dotIdx != -1) {
|
|
490
|
+
let end = s.length - 1;
|
|
491
|
+
while (end > dotIdx + 1 && s.charAt(end) == "0") {
|
|
492
|
+
end--;
|
|
493
|
+
}
|
|
494
|
+
s = s.slice(0, end + 1);
|
|
495
|
+
}
|
|
496
|
+
return isNegative ? "-" + s : s;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
// Internal Arbitrary-Precision Decimal Helpers
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
|
|
503
|
+
function addDecimal(a: string, b: string): string {
|
|
504
|
+
let i = a.length - 1;
|
|
505
|
+
let j = b.length - 1;
|
|
506
|
+
let carry = 0;
|
|
507
|
+
let res = "";
|
|
508
|
+
|
|
509
|
+
while (i >= 0 || j >= 0 || carry > 0) {
|
|
510
|
+
let sum = carry;
|
|
511
|
+
if (i >= 0) sum += a.charCodeAt(i) - 48;
|
|
512
|
+
if (j >= 0) sum += b.charCodeAt(j) - 48;
|
|
513
|
+
res = (sum % 10).toString() + res;
|
|
514
|
+
carry = sum / 10;
|
|
515
|
+
i--;
|
|
516
|
+
j--;
|
|
517
|
+
}
|
|
518
|
+
return res.length > 0 ? res : "0";
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function cmpDecimal(a: string, b: string): i32 {
|
|
522
|
+
if (a.length > b.length) return 1;
|
|
523
|
+
if (a.length < b.length) return -1;
|
|
524
|
+
for (let i = 0; i < a.length; i++) {
|
|
525
|
+
const c1 = a.charCodeAt(i);
|
|
526
|
+
const c2 = b.charCodeAt(i);
|
|
527
|
+
if (c1 > c2) return 1;
|
|
528
|
+
if (c1 < c2) return -1;
|
|
529
|
+
}
|
|
530
|
+
return 0;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function subDecimal(a: string, b: string): string {
|
|
534
|
+
const cmp = cmpDecimal(a, b);
|
|
535
|
+
if (cmp == 0) return "0";
|
|
536
|
+
if (cmp < 0) return "-" + subDecimal(b, a);
|
|
537
|
+
|
|
538
|
+
let i = a.length - 1;
|
|
539
|
+
let j = b.length - 1;
|
|
540
|
+
let borrow = 0;
|
|
541
|
+
let res = "";
|
|
542
|
+
|
|
543
|
+
while (i >= 0) {
|
|
544
|
+
let d1 = a.charCodeAt(i) - 48 - borrow;
|
|
545
|
+
let d2 = j >= 0 ? b.charCodeAt(j) - 48 : 0;
|
|
546
|
+
if (d1 < d2) {
|
|
547
|
+
d1 += 10;
|
|
548
|
+
borrow = 1;
|
|
549
|
+
} else {
|
|
550
|
+
borrow = 0;
|
|
551
|
+
}
|
|
552
|
+
res = (d1 - d2).toString() + res;
|
|
553
|
+
i--;
|
|
554
|
+
j--;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
let start = 0;
|
|
558
|
+
while (start < res.length - 1 && res.charAt(start) == "0") {
|
|
559
|
+
start++;
|
|
560
|
+
}
|
|
561
|
+
return res.slice(start);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function mulDecimal(a: string, b: string): string {
|
|
565
|
+
if (a == "0" || b == "0") return "0";
|
|
566
|
+
const lenA = a.length;
|
|
567
|
+
const lenB = b.length;
|
|
568
|
+
const result = new Array<i32>(lenA + lenB);
|
|
569
|
+
for (let idx = 0; idx < lenA + lenB; idx++) result[idx] = 0;
|
|
570
|
+
|
|
571
|
+
for (let i = lenA - 1; i >= 0; i--) {
|
|
572
|
+
for (let j = lenB - 1; j >= 0; j--) {
|
|
573
|
+
const mul = (a.charCodeAt(i) - 48) * (b.charCodeAt(j) - 48);
|
|
574
|
+
const sum = mul + result[i + j + 1];
|
|
575
|
+
result[i + j + 1] = sum % 10;
|
|
576
|
+
result[i + j] += sum / 10;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
let res = "";
|
|
581
|
+
let i = 0;
|
|
582
|
+
while (i < result.length && result[i] == 0) i++;
|
|
583
|
+
while (i < result.length) {
|
|
584
|
+
res += result[i].toString();
|
|
585
|
+
i++;
|
|
586
|
+
}
|
|
587
|
+
return res.length > 0 ? res : "0";
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function divDecimal(a: string, b: string): string {
|
|
591
|
+
if (b == "0") return "0";
|
|
592
|
+
if (cmpDecimal(a, b) < 0) return "0";
|
|
593
|
+
if (cmpDecimal(a, b) == 0) return "1";
|
|
594
|
+
|
|
595
|
+
let quotient = "";
|
|
596
|
+
let remainder = "";
|
|
597
|
+
|
|
598
|
+
for (let i = 0; i < a.length; i++) {
|
|
599
|
+
remainder += a.charAt(i);
|
|
600
|
+
let start = 0;
|
|
601
|
+
while (start < remainder.length - 1 && remainder.charAt(start) == "0") {
|
|
602
|
+
start++;
|
|
603
|
+
}
|
|
604
|
+
remainder = remainder.slice(start);
|
|
605
|
+
|
|
606
|
+
let count = 0;
|
|
607
|
+
while (cmpDecimal(remainder, b) >= 0) {
|
|
608
|
+
remainder = subDecimal(remainder, b);
|
|
609
|
+
count++;
|
|
610
|
+
}
|
|
611
|
+
quotient += count.toString();
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
let start = 0;
|
|
615
|
+
while (start < quotient.length - 1 && quotient.charAt(start) == "0") {
|
|
616
|
+
start++;
|
|
617
|
+
}
|
|
618
|
+
return quotient.slice(start);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function decimalToHex(dec: string): string {
|
|
622
|
+
if (dec == "0" || dec.length == 0) return "0";
|
|
623
|
+
let hex = "";
|
|
624
|
+
let current = dec;
|
|
625
|
+
const hexChars = "0123456789abcdef";
|
|
626
|
+
|
|
627
|
+
while (current != "0" && current.length > 0) {
|
|
628
|
+
let next = "";
|
|
629
|
+
let rem = 0;
|
|
630
|
+
for (let i = 0; i < current.length; i++) {
|
|
631
|
+
const cur = rem * 10 + (current.charCodeAt(i) - 48);
|
|
632
|
+
const div = cur / 16;
|
|
633
|
+
rem = cur % 16;
|
|
634
|
+
if (next.length > 0 || div > 0) {
|
|
635
|
+
next += div.toString();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
hex = hexChars.charAt(rem) + hex;
|
|
639
|
+
current = next.length > 0 ? next : "0";
|
|
640
|
+
}
|
|
641
|
+
return hex.length > 0 ? hex : "0";
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function hexToDecimal(hexStr: string): string {
|
|
645
|
+
let hex = hexStr.trim().toLowerCase();
|
|
646
|
+
if (hex.startsWith("0x")) hex = hex.slice(2);
|
|
647
|
+
if (hex.length == 0) return "0";
|
|
648
|
+
|
|
649
|
+
let dec = "0";
|
|
650
|
+
for (let i = 0; i < hex.length; i++) {
|
|
651
|
+
const c = hex.charCodeAt(i);
|
|
652
|
+
let digit = 0;
|
|
653
|
+
if (c >= 48 && c <= 57) {
|
|
654
|
+
digit = c - 48;
|
|
655
|
+
} else if (c >= 97 && c <= 102) {
|
|
656
|
+
digit = c - 97 + 10;
|
|
657
|
+
} else if (c >= 65 && c <= 70) {
|
|
658
|
+
digit = c - 65 + 10;
|
|
659
|
+
} else {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
let carry = digit;
|
|
664
|
+
let nextDec = "";
|
|
665
|
+
for (let j = dec.length - 1; j >= 0; j--) {
|
|
666
|
+
const prod = (dec.charCodeAt(j) - 48) * 16 + carry;
|
|
667
|
+
nextDec = (prod % 10).toString() + nextDec;
|
|
668
|
+
carry = prod / 10;
|
|
669
|
+
}
|
|
670
|
+
while (carry > 0) {
|
|
671
|
+
nextDec = (carry % 10).toString() + nextDec;
|
|
672
|
+
carry = carry / 10;
|
|
673
|
+
}
|
|
674
|
+
dec = nextDec.length > 0 ? nextDec : "0";
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
let start = 0;
|
|
678
|
+
while (start < dec.length - 1 && dec.charAt(start) == "0") {
|
|
679
|
+
start++;
|
|
680
|
+
}
|
|
681
|
+
return dec.slice(start);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// ---------------------------------------------------------------------------
|
|
685
|
+
// JSON Parsing Helper for Entity.load()
|
|
686
|
+
// ---------------------------------------------------------------------------
|
|
687
|
+
|
|
688
|
+
export function extractJsonField(json: string, key: string): string {
|
|
689
|
+
const needle = '"' + key + '":';
|
|
690
|
+
const idx = json.indexOf(needle);
|
|
691
|
+
if (idx == -1) return "";
|
|
692
|
+
let start = idx + needle.length;
|
|
693
|
+
while (
|
|
694
|
+
start < json.length &&
|
|
695
|
+
(json.charAt(start) == " " ||
|
|
696
|
+
json.charAt(start) == "\t" ||
|
|
697
|
+
json.charAt(start) == "\n" ||
|
|
698
|
+
json.charAt(start) == "\r")
|
|
699
|
+
) {
|
|
700
|
+
start++;
|
|
701
|
+
}
|
|
702
|
+
if (start >= json.length) return "";
|
|
703
|
+
const ch = json.charAt(start);
|
|
704
|
+
if (ch == '"') {
|
|
705
|
+
start++;
|
|
706
|
+
let end = start;
|
|
707
|
+
while (end < json.length) {
|
|
708
|
+
if (json.charAt(end) == '"' && json.charAt(end - 1) != "\\") {
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
end++;
|
|
712
|
+
}
|
|
713
|
+
if (end >= json.length) return "";
|
|
714
|
+
return json.slice(start, end);
|
|
715
|
+
} else {
|
|
716
|
+
let end = start;
|
|
717
|
+
while (end < json.length) {
|
|
718
|
+
const c = json.charCodeAt(end);
|
|
719
|
+
if (
|
|
720
|
+
c == 44 ||
|
|
721
|
+
c == 125 ||
|
|
722
|
+
c == 93 ||
|
|
723
|
+
c == 32 ||
|
|
724
|
+
c == 10 ||
|
|
725
|
+
c == 13 ||
|
|
726
|
+
c == 9
|
|
727
|
+
) {
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
end++;
|
|
731
|
+
}
|
|
732
|
+
const val = json.slice(start, end).trim();
|
|
733
|
+
if (val == "null") return "";
|
|
734
|
+
return val;
|
|
735
|
+
}
|
|
736
|
+
}
|