genlayer-js 0.4.7 → 0.5.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/CHANGELOG.md +14 -0
- package/dist/index.cjs +164 -19
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +163 -18
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/abi/calldata/consts.ts +14 -0
- package/src/abi/calldata/decoder.ts +86 -0
- package/src/abi/calldata/encoder.ts +92 -31
- package/src/accounts/actions.ts +2 -3
- package/src/client/client.ts +51 -13
- package/src/contracts/actions.ts +58 -18
- package/src/global.d.ts +7 -0
- package/src/types/clients.ts +1 -1
- package/tests/client.test.ts +31 -1
- package/tsconfig.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (2025-01-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add customtransport to use both wallet and local private keys ([#48](https://github.com/yeagerai/genlayer-js/issues/48)) ([905a123](https://github.com/yeagerai/genlayer-js/commit/905a12358c154e6ae865773b809952c8cc9c75b9))
|
|
9
|
+
|
|
10
|
+
## 0.4.8 (2024-12-18)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* added GenVM decoder to readContract method ([#42](https://github.com/yeagerai/genlayer-js/issues/42)) ([096d36d](https://github.com/yeagerai/genlayer-js/commit/096d36de06d3f4d341f6532ddead694c1882651d))
|
|
16
|
+
|
|
3
17
|
## 0.4.7 (2024-12-03)
|
|
4
18
|
|
|
5
19
|
## 0.4.6 (2024-12-02)
|
package/dist/index.cjs
CHANGED
|
@@ -22,21 +22,20 @@ function accountActions(client) {
|
|
|
22
22
|
params: [address, amount]
|
|
23
23
|
});
|
|
24
24
|
},
|
|
25
|
-
getCurrentNonce: async ({ address }) => {
|
|
25
|
+
getCurrentNonce: async ({ address, block = "latest" }) => {
|
|
26
26
|
const addressToUse = address || _optionalChain([client, 'access', _3 => _3.account, 'optionalAccess', _4 => _4.address]);
|
|
27
27
|
if (!addressToUse) {
|
|
28
28
|
throw new Error("No address provided and no account is connected");
|
|
29
29
|
}
|
|
30
30
|
return client.request({
|
|
31
31
|
method: "eth_getTransactionCount",
|
|
32
|
-
params: [addressToUse]
|
|
32
|
+
params: [addressToUse, block]
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// src/abi/calldata/
|
|
39
|
-
|
|
38
|
+
// src/abi/calldata/consts.ts
|
|
40
39
|
var BITS_IN_TYPE = 3;
|
|
41
40
|
var TYPE_SPECIAL = 0;
|
|
42
41
|
var TYPE_PINT = 1;
|
|
@@ -49,6 +48,92 @@ var SPECIAL_NULL = 0 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
|
49
48
|
var SPECIAL_FALSE = 1 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
50
49
|
var SPECIAL_TRUE = 2 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
51
50
|
var SPECIAL_ADDR = 3 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
51
|
+
|
|
52
|
+
// src/abi/calldata/decoder.ts
|
|
53
|
+
function readULeb128(data, index) {
|
|
54
|
+
let res = 0n;
|
|
55
|
+
let accum = 0n;
|
|
56
|
+
let shouldContinue = true;
|
|
57
|
+
while (shouldContinue) {
|
|
58
|
+
const byte = data[index.i];
|
|
59
|
+
index.i++;
|
|
60
|
+
const rest = byte & 127;
|
|
61
|
+
res += BigInt(rest) * (1n << accum);
|
|
62
|
+
accum += 7n;
|
|
63
|
+
shouldContinue = byte >= 128;
|
|
64
|
+
}
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
function decodeImpl(data, index) {
|
|
68
|
+
const cur = readULeb128(data, index);
|
|
69
|
+
switch (cur) {
|
|
70
|
+
case BigInt(SPECIAL_NULL):
|
|
71
|
+
return null;
|
|
72
|
+
case BigInt(SPECIAL_TRUE):
|
|
73
|
+
return true;
|
|
74
|
+
case BigInt(SPECIAL_FALSE):
|
|
75
|
+
return false;
|
|
76
|
+
case BigInt(SPECIAL_ADDR): {
|
|
77
|
+
const res = data.slice(index.i, index.i + 20);
|
|
78
|
+
index.i += 20;
|
|
79
|
+
return new (0, _chunkYI62SDKVcjs.Address)(res);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const type = Number(cur & 0xffn) & (1 << BITS_IN_TYPE) - 1;
|
|
83
|
+
const rest = cur >> BigInt(BITS_IN_TYPE);
|
|
84
|
+
switch (type) {
|
|
85
|
+
case TYPE_BYTES: {
|
|
86
|
+
const ret = data.slice(index.i, index.i + Number(rest));
|
|
87
|
+
index.i += Number(rest);
|
|
88
|
+
return ret;
|
|
89
|
+
}
|
|
90
|
+
case TYPE_PINT:
|
|
91
|
+
return rest;
|
|
92
|
+
case TYPE_NINT:
|
|
93
|
+
return -1n - rest;
|
|
94
|
+
case TYPE_STR: {
|
|
95
|
+
const ret = data.slice(index.i, index.i + Number(rest));
|
|
96
|
+
index.i += Number(rest);
|
|
97
|
+
return new TextDecoder("utf-8").decode(ret);
|
|
98
|
+
}
|
|
99
|
+
case TYPE_ARR: {
|
|
100
|
+
const ret = [];
|
|
101
|
+
let elems = rest;
|
|
102
|
+
while (elems > 0) {
|
|
103
|
+
elems--;
|
|
104
|
+
ret.push(decodeImpl(data, index));
|
|
105
|
+
}
|
|
106
|
+
return ret;
|
|
107
|
+
}
|
|
108
|
+
case TYPE_MAP: {
|
|
109
|
+
const ret = /* @__PURE__ */ new Map();
|
|
110
|
+
let elems = rest;
|
|
111
|
+
while (elems > 0) {
|
|
112
|
+
elems--;
|
|
113
|
+
const strLen = Number(readULeb128(data, index));
|
|
114
|
+
const key = data.slice(index.i, index.i + strLen);
|
|
115
|
+
index.i += strLen;
|
|
116
|
+
const keyStr = new TextDecoder("utf-8").decode(key);
|
|
117
|
+
ret.set(keyStr, decodeImpl(data, index));
|
|
118
|
+
}
|
|
119
|
+
return ret;
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`can't decode type from ${type} rest is ${rest} at pos ${index.i}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function decode(data) {
|
|
126
|
+
const index = { i: 0 };
|
|
127
|
+
const res = decodeImpl(data, index);
|
|
128
|
+
if (index.i !== data.length) {
|
|
129
|
+
throw new Error("some data left");
|
|
130
|
+
}
|
|
131
|
+
return res;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/abi/calldata/encoder.ts
|
|
135
|
+
|
|
136
|
+
|
|
52
137
|
function reportError(msg, data) {
|
|
53
138
|
throw new Error(`invalid calldata input '${data}'`);
|
|
54
139
|
}
|
|
@@ -162,10 +247,7 @@ function encodeImpl(to, data) {
|
|
|
162
247
|
to.push(c);
|
|
163
248
|
}
|
|
164
249
|
} else if (Object.getPrototypeOf(data) === Object.prototype) {
|
|
165
|
-
encodeMap(
|
|
166
|
-
to,
|
|
167
|
-
Object.keys(data).map((k) => [k, data[k]])
|
|
168
|
-
);
|
|
250
|
+
encodeMap(to, Object.entries(data));
|
|
169
251
|
} else {
|
|
170
252
|
reportError("unknown object type", data);
|
|
171
253
|
}
|
|
@@ -210,28 +292,46 @@ var overrideContractActions = (client) => {
|
|
|
210
292
|
client.readContract = async (args) => {
|
|
211
293
|
const { account, address, functionName, args: params } = args;
|
|
212
294
|
const encodedData = encodeAndSerialize({ method: functionName, args: params });
|
|
295
|
+
let senderAddress = _nullishCoalesce(_optionalChain([account, 'optionalAccess', _5 => _5.address]), () => ( _optionalChain([client, 'access', _6 => _6.account, 'optionalAccess', _7 => _7.address])));
|
|
213
296
|
const requestParams = {
|
|
214
297
|
to: address,
|
|
215
|
-
from:
|
|
298
|
+
from: senderAddress,
|
|
216
299
|
data: encodedData
|
|
217
300
|
};
|
|
218
301
|
const result = await client.request({
|
|
219
302
|
method: "eth_call",
|
|
220
303
|
params: [requestParams, "latest"]
|
|
221
304
|
});
|
|
222
|
-
|
|
305
|
+
if (typeof result === "string") {
|
|
306
|
+
const val = Uint8Array.from(atob(result), (c) => c.charCodeAt(0));
|
|
307
|
+
return decode(val);
|
|
308
|
+
} else {
|
|
309
|
+
return "<unknown>";
|
|
310
|
+
}
|
|
223
311
|
};
|
|
224
312
|
client.writeContract = async (args) => {
|
|
225
313
|
const { account, address, functionName, args: params, value = 0n, leaderOnly = false } = args;
|
|
226
314
|
const data = [encode({ method: functionName, args: params }), leaderOnly];
|
|
227
315
|
const serializedData = serialize(data);
|
|
228
316
|
const senderAccount = account || client.account;
|
|
317
|
+
if (_optionalChain([senderAccount, 'optionalAccess', _8 => _8.type]) !== "local") {
|
|
318
|
+
const transaction = {
|
|
319
|
+
from: _optionalChain([senderAccount, 'optionalAccess', _9 => _9.address]),
|
|
320
|
+
to: address,
|
|
321
|
+
data: serializedData,
|
|
322
|
+
value: `0x${value.toString(16)}`
|
|
323
|
+
};
|
|
324
|
+
return await client.request({
|
|
325
|
+
method: "eth_sendTransaction",
|
|
326
|
+
params: [transaction]
|
|
327
|
+
});
|
|
328
|
+
}
|
|
229
329
|
if (!senderAccount) {
|
|
230
330
|
throw new Error(
|
|
231
331
|
"No account set. Configure the client with an account or pass an account to this function."
|
|
232
332
|
);
|
|
233
333
|
}
|
|
234
|
-
if (!_optionalChain([senderAccount, 'optionalAccess',
|
|
334
|
+
if (!_optionalChain([senderAccount, 'optionalAccess', _10 => _10.signTransaction])) {
|
|
235
335
|
throw new Error("Account does not support signTransaction");
|
|
236
336
|
}
|
|
237
337
|
const nonce = await client.getCurrentNonce({ address: senderAccount.address });
|
|
@@ -242,23 +342,34 @@ var overrideContractActions = (client) => {
|
|
|
242
342
|
type: "legacy",
|
|
243
343
|
nonce
|
|
244
344
|
});
|
|
245
|
-
|
|
345
|
+
return await client.request({
|
|
246
346
|
method: "eth_sendRawTransaction",
|
|
247
347
|
params: [signedTransaction]
|
|
248
348
|
});
|
|
249
|
-
return result;
|
|
250
349
|
};
|
|
251
350
|
client.deployContract = async (args) => {
|
|
252
351
|
const { account, code, args: constructorArgs, leaderOnly = false } = args;
|
|
253
352
|
const data = [code, encode({ args: constructorArgs }), leaderOnly];
|
|
254
353
|
const serializedData = serialize(data);
|
|
255
354
|
const senderAccount = account || client.account;
|
|
355
|
+
if (_optionalChain([senderAccount, 'optionalAccess', _11 => _11.type]) !== "local") {
|
|
356
|
+
const transaction = {
|
|
357
|
+
from: _optionalChain([senderAccount, 'optionalAccess', _12 => _12.address]),
|
|
358
|
+
to: null,
|
|
359
|
+
data: serializedData,
|
|
360
|
+
value: "0x0"
|
|
361
|
+
};
|
|
362
|
+
return await client.request({
|
|
363
|
+
method: "eth_sendTransaction",
|
|
364
|
+
params: [transaction]
|
|
365
|
+
});
|
|
366
|
+
}
|
|
256
367
|
if (!senderAccount) {
|
|
257
368
|
throw new Error(
|
|
258
369
|
"No account set. Configure the client with an account or pass an account to this function."
|
|
259
370
|
);
|
|
260
371
|
}
|
|
261
|
-
if (!_optionalChain([senderAccount, 'optionalAccess',
|
|
372
|
+
if (!_optionalChain([senderAccount, 'optionalAccess', _13 => _13.signTransaction])) {
|
|
262
373
|
throw new Error("Account does not support signTransaction");
|
|
263
374
|
}
|
|
264
375
|
const nonce = await client.getCurrentNonce({ address: senderAccount.address });
|
|
@@ -267,11 +378,10 @@ var overrideContractActions = (client) => {
|
|
|
267
378
|
type: "legacy",
|
|
268
379
|
nonce
|
|
269
380
|
});
|
|
270
|
-
|
|
381
|
+
return await client.request({
|
|
271
382
|
method: "eth_sendRawTransaction",
|
|
272
383
|
params: [signedTransaction]
|
|
273
384
|
});
|
|
274
|
-
return result;
|
|
275
385
|
};
|
|
276
386
|
return client;
|
|
277
387
|
};
|
|
@@ -319,13 +429,48 @@ var transactionActions = (client) => ({
|
|
|
319
429
|
var createClient = (config = { chain: _chunkIINRDYKFcjs.simulator }) => {
|
|
320
430
|
const chainConfig = config.chain || _chunkIINRDYKFcjs.simulator;
|
|
321
431
|
const rpcUrl = config.endpoint || chainConfig.rpcUrls.default.http[0];
|
|
432
|
+
const isAddress = typeof config.account !== "object";
|
|
433
|
+
const customTransport = {
|
|
434
|
+
async request({ method, params }) {
|
|
435
|
+
if (method.startsWith("eth_") && isAddress) {
|
|
436
|
+
try {
|
|
437
|
+
return await _optionalChain([window, 'access', _14 => _14.ethereum, 'optionalAccess', _15 => _15.request, 'call', _16 => _16({ method, params })]);
|
|
438
|
+
} catch (err) {
|
|
439
|
+
console.warn(`Error using window.ethereum for method ${method}:`, err);
|
|
440
|
+
throw err;
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
try {
|
|
444
|
+
const response = await fetch(rpcUrl, {
|
|
445
|
+
method: "POST",
|
|
446
|
+
headers: {
|
|
447
|
+
"Content-Type": "application/json"
|
|
448
|
+
},
|
|
449
|
+
body: JSON.stringify({
|
|
450
|
+
jsonrpc: "2.0",
|
|
451
|
+
id: Date.now(),
|
|
452
|
+
method,
|
|
453
|
+
params
|
|
454
|
+
})
|
|
455
|
+
});
|
|
456
|
+
const data = await response.json();
|
|
457
|
+
if (data.error) {
|
|
458
|
+
throw new Error(data.error.message);
|
|
459
|
+
}
|
|
460
|
+
return data.result;
|
|
461
|
+
} catch (err) {
|
|
462
|
+
console.error(`Error fetching ${method} from GenLayer RPC:`, err);
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
322
468
|
const baseClient = _viem.createClient.call(void 0, {
|
|
323
469
|
chain: chainConfig,
|
|
324
|
-
transport: _viem.
|
|
470
|
+
transport: _viem.custom.call(void 0, customTransport),
|
|
325
471
|
...config.account ? { account: config.account } : {}
|
|
326
472
|
}).extend(_viem.publicActions).extend((client) => accountActions(client)).extend((client) => transactionActions(client)).extend((client) => contractActions(client));
|
|
327
|
-
|
|
328
|
-
return genLayerClient;
|
|
473
|
+
return overrideContractActions(baseClient);
|
|
329
474
|
};
|
|
330
475
|
|
|
331
476
|
// src/accounts/account.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as viem from 'viem';
|
|
2
|
-
import { Account } from 'viem';
|
|
2
|
+
import { Account, Address } from 'viem';
|
|
3
3
|
import { S as SimulatorChain } from './chains-BV4Glo-M.cjs';
|
|
4
4
|
import { GenLayerClient } from './types/index.cjs';
|
|
5
5
|
import * as abitype from 'abitype';
|
|
@@ -29,7 +29,7 @@ interface ClientConfig {
|
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
31
|
endpoint?: string;
|
|
32
|
-
account?: Account;
|
|
32
|
+
account?: Account | Address;
|
|
33
33
|
}
|
|
34
34
|
declare const createClient: (config?: ClientConfig) => GenLayerClient<SimulatorChain>;
|
|
35
35
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as viem from 'viem';
|
|
2
|
-
import { Account } from 'viem';
|
|
2
|
+
import { Account, Address } from 'viem';
|
|
3
3
|
import { S as SimulatorChain } from './chains-BV4Glo-M.js';
|
|
4
4
|
import { GenLayerClient } from './types/index.js';
|
|
5
5
|
import * as abitype from 'abitype';
|
|
@@ -29,7 +29,7 @@ interface ClientConfig {
|
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
31
|
endpoint?: string;
|
|
32
|
-
account?: Account;
|
|
32
|
+
account?: Account | Address;
|
|
33
33
|
}
|
|
34
34
|
declare const createClient: (config?: ClientConfig) => GenLayerClient<SimulatorChain>;
|
|
35
35
|
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import "./chunk-MLKGABMK.js";
|
|
9
9
|
|
|
10
10
|
// src/client/client.ts
|
|
11
|
-
import { createClient as createViemClient,
|
|
11
|
+
import { createClient as createViemClient, publicActions, custom } from "viem";
|
|
12
12
|
|
|
13
13
|
// src/accounts/actions.ts
|
|
14
14
|
function accountActions(client) {
|
|
@@ -22,21 +22,20 @@ function accountActions(client) {
|
|
|
22
22
|
params: [address, amount]
|
|
23
23
|
});
|
|
24
24
|
},
|
|
25
|
-
getCurrentNonce: async ({ address }) => {
|
|
25
|
+
getCurrentNonce: async ({ address, block = "latest" }) => {
|
|
26
26
|
const addressToUse = address || client.account?.address;
|
|
27
27
|
if (!addressToUse) {
|
|
28
28
|
throw new Error("No address provided and no account is connected");
|
|
29
29
|
}
|
|
30
30
|
return client.request({
|
|
31
31
|
method: "eth_getTransactionCount",
|
|
32
|
-
params: [addressToUse]
|
|
32
|
+
params: [addressToUse, block]
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// src/abi/calldata/
|
|
39
|
-
import { toHex, toRlp } from "viem";
|
|
38
|
+
// src/abi/calldata/consts.ts
|
|
40
39
|
var BITS_IN_TYPE = 3;
|
|
41
40
|
var TYPE_SPECIAL = 0;
|
|
42
41
|
var TYPE_PINT = 1;
|
|
@@ -49,6 +48,92 @@ var SPECIAL_NULL = 0 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
|
49
48
|
var SPECIAL_FALSE = 1 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
50
49
|
var SPECIAL_TRUE = 2 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
51
50
|
var SPECIAL_ADDR = 3 << BITS_IN_TYPE | TYPE_SPECIAL;
|
|
51
|
+
|
|
52
|
+
// src/abi/calldata/decoder.ts
|
|
53
|
+
function readULeb128(data, index) {
|
|
54
|
+
let res = 0n;
|
|
55
|
+
let accum = 0n;
|
|
56
|
+
let shouldContinue = true;
|
|
57
|
+
while (shouldContinue) {
|
|
58
|
+
const byte = data[index.i];
|
|
59
|
+
index.i++;
|
|
60
|
+
const rest = byte & 127;
|
|
61
|
+
res += BigInt(rest) * (1n << accum);
|
|
62
|
+
accum += 7n;
|
|
63
|
+
shouldContinue = byte >= 128;
|
|
64
|
+
}
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
function decodeImpl(data, index) {
|
|
68
|
+
const cur = readULeb128(data, index);
|
|
69
|
+
switch (cur) {
|
|
70
|
+
case BigInt(SPECIAL_NULL):
|
|
71
|
+
return null;
|
|
72
|
+
case BigInt(SPECIAL_TRUE):
|
|
73
|
+
return true;
|
|
74
|
+
case BigInt(SPECIAL_FALSE):
|
|
75
|
+
return false;
|
|
76
|
+
case BigInt(SPECIAL_ADDR): {
|
|
77
|
+
const res = data.slice(index.i, index.i + 20);
|
|
78
|
+
index.i += 20;
|
|
79
|
+
return new Address(res);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const type = Number(cur & 0xffn) & (1 << BITS_IN_TYPE) - 1;
|
|
83
|
+
const rest = cur >> BigInt(BITS_IN_TYPE);
|
|
84
|
+
switch (type) {
|
|
85
|
+
case TYPE_BYTES: {
|
|
86
|
+
const ret = data.slice(index.i, index.i + Number(rest));
|
|
87
|
+
index.i += Number(rest);
|
|
88
|
+
return ret;
|
|
89
|
+
}
|
|
90
|
+
case TYPE_PINT:
|
|
91
|
+
return rest;
|
|
92
|
+
case TYPE_NINT:
|
|
93
|
+
return -1n - rest;
|
|
94
|
+
case TYPE_STR: {
|
|
95
|
+
const ret = data.slice(index.i, index.i + Number(rest));
|
|
96
|
+
index.i += Number(rest);
|
|
97
|
+
return new TextDecoder("utf-8").decode(ret);
|
|
98
|
+
}
|
|
99
|
+
case TYPE_ARR: {
|
|
100
|
+
const ret = [];
|
|
101
|
+
let elems = rest;
|
|
102
|
+
while (elems > 0) {
|
|
103
|
+
elems--;
|
|
104
|
+
ret.push(decodeImpl(data, index));
|
|
105
|
+
}
|
|
106
|
+
return ret;
|
|
107
|
+
}
|
|
108
|
+
case TYPE_MAP: {
|
|
109
|
+
const ret = /* @__PURE__ */ new Map();
|
|
110
|
+
let elems = rest;
|
|
111
|
+
while (elems > 0) {
|
|
112
|
+
elems--;
|
|
113
|
+
const strLen = Number(readULeb128(data, index));
|
|
114
|
+
const key = data.slice(index.i, index.i + strLen);
|
|
115
|
+
index.i += strLen;
|
|
116
|
+
const keyStr = new TextDecoder("utf-8").decode(key);
|
|
117
|
+
ret.set(keyStr, decodeImpl(data, index));
|
|
118
|
+
}
|
|
119
|
+
return ret;
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`can't decode type from ${type} rest is ${rest} at pos ${index.i}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function decode(data) {
|
|
126
|
+
const index = { i: 0 };
|
|
127
|
+
const res = decodeImpl(data, index);
|
|
128
|
+
if (index.i !== data.length) {
|
|
129
|
+
throw new Error("some data left");
|
|
130
|
+
}
|
|
131
|
+
return res;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/abi/calldata/encoder.ts
|
|
135
|
+
import { toHex } from "viem";
|
|
136
|
+
import { toRlp } from "viem";
|
|
52
137
|
function reportError(msg, data) {
|
|
53
138
|
throw new Error(`invalid calldata input '${data}'`);
|
|
54
139
|
}
|
|
@@ -162,10 +247,7 @@ function encodeImpl(to, data) {
|
|
|
162
247
|
to.push(c);
|
|
163
248
|
}
|
|
164
249
|
} else if (Object.getPrototypeOf(data) === Object.prototype) {
|
|
165
|
-
encodeMap(
|
|
166
|
-
to,
|
|
167
|
-
Object.keys(data).map((k) => [k, data[k]])
|
|
168
|
-
);
|
|
250
|
+
encodeMap(to, Object.entries(data));
|
|
169
251
|
} else {
|
|
170
252
|
reportError("unknown object type", data);
|
|
171
253
|
}
|
|
@@ -210,22 +292,40 @@ var overrideContractActions = (client) => {
|
|
|
210
292
|
client.readContract = async (args) => {
|
|
211
293
|
const { account, address, functionName, args: params } = args;
|
|
212
294
|
const encodedData = encodeAndSerialize({ method: functionName, args: params });
|
|
295
|
+
let senderAddress = account?.address ?? client.account?.address;
|
|
213
296
|
const requestParams = {
|
|
214
297
|
to: address,
|
|
215
|
-
from:
|
|
298
|
+
from: senderAddress,
|
|
216
299
|
data: encodedData
|
|
217
300
|
};
|
|
218
301
|
const result = await client.request({
|
|
219
302
|
method: "eth_call",
|
|
220
303
|
params: [requestParams, "latest"]
|
|
221
304
|
});
|
|
222
|
-
|
|
305
|
+
if (typeof result === "string") {
|
|
306
|
+
const val = Uint8Array.from(atob(result), (c) => c.charCodeAt(0));
|
|
307
|
+
return decode(val);
|
|
308
|
+
} else {
|
|
309
|
+
return "<unknown>";
|
|
310
|
+
}
|
|
223
311
|
};
|
|
224
312
|
client.writeContract = async (args) => {
|
|
225
313
|
const { account, address, functionName, args: params, value = 0n, leaderOnly = false } = args;
|
|
226
314
|
const data = [encode({ method: functionName, args: params }), leaderOnly];
|
|
227
315
|
const serializedData = serialize(data);
|
|
228
316
|
const senderAccount = account || client.account;
|
|
317
|
+
if (senderAccount?.type !== "local") {
|
|
318
|
+
const transaction = {
|
|
319
|
+
from: senderAccount?.address,
|
|
320
|
+
to: address,
|
|
321
|
+
data: serializedData,
|
|
322
|
+
value: `0x${value.toString(16)}`
|
|
323
|
+
};
|
|
324
|
+
return await client.request({
|
|
325
|
+
method: "eth_sendTransaction",
|
|
326
|
+
params: [transaction]
|
|
327
|
+
});
|
|
328
|
+
}
|
|
229
329
|
if (!senderAccount) {
|
|
230
330
|
throw new Error(
|
|
231
331
|
"No account set. Configure the client with an account or pass an account to this function."
|
|
@@ -242,17 +342,28 @@ var overrideContractActions = (client) => {
|
|
|
242
342
|
type: "legacy",
|
|
243
343
|
nonce
|
|
244
344
|
});
|
|
245
|
-
|
|
345
|
+
return await client.request({
|
|
246
346
|
method: "eth_sendRawTransaction",
|
|
247
347
|
params: [signedTransaction]
|
|
248
348
|
});
|
|
249
|
-
return result;
|
|
250
349
|
};
|
|
251
350
|
client.deployContract = async (args) => {
|
|
252
351
|
const { account, code, args: constructorArgs, leaderOnly = false } = args;
|
|
253
352
|
const data = [code, encode({ args: constructorArgs }), leaderOnly];
|
|
254
353
|
const serializedData = serialize(data);
|
|
255
354
|
const senderAccount = account || client.account;
|
|
355
|
+
if (senderAccount?.type !== "local") {
|
|
356
|
+
const transaction = {
|
|
357
|
+
from: senderAccount?.address,
|
|
358
|
+
to: null,
|
|
359
|
+
data: serializedData,
|
|
360
|
+
value: "0x0"
|
|
361
|
+
};
|
|
362
|
+
return await client.request({
|
|
363
|
+
method: "eth_sendTransaction",
|
|
364
|
+
params: [transaction]
|
|
365
|
+
});
|
|
366
|
+
}
|
|
256
367
|
if (!senderAccount) {
|
|
257
368
|
throw new Error(
|
|
258
369
|
"No account set. Configure the client with an account or pass an account to this function."
|
|
@@ -267,11 +378,10 @@ var overrideContractActions = (client) => {
|
|
|
267
378
|
type: "legacy",
|
|
268
379
|
nonce
|
|
269
380
|
});
|
|
270
|
-
|
|
381
|
+
return await client.request({
|
|
271
382
|
method: "eth_sendRawTransaction",
|
|
272
383
|
params: [signedTransaction]
|
|
273
384
|
});
|
|
274
|
-
return result;
|
|
275
385
|
};
|
|
276
386
|
return client;
|
|
277
387
|
};
|
|
@@ -319,13 +429,48 @@ var transactionActions = (client) => ({
|
|
|
319
429
|
var createClient = (config = { chain: simulator }) => {
|
|
320
430
|
const chainConfig = config.chain || simulator;
|
|
321
431
|
const rpcUrl = config.endpoint || chainConfig.rpcUrls.default.http[0];
|
|
432
|
+
const isAddress = typeof config.account !== "object";
|
|
433
|
+
const customTransport = {
|
|
434
|
+
async request({ method, params }) {
|
|
435
|
+
if (method.startsWith("eth_") && isAddress) {
|
|
436
|
+
try {
|
|
437
|
+
return await window.ethereum?.request({ method, params });
|
|
438
|
+
} catch (err) {
|
|
439
|
+
console.warn(`Error using window.ethereum for method ${method}:`, err);
|
|
440
|
+
throw err;
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
try {
|
|
444
|
+
const response = await fetch(rpcUrl, {
|
|
445
|
+
method: "POST",
|
|
446
|
+
headers: {
|
|
447
|
+
"Content-Type": "application/json"
|
|
448
|
+
},
|
|
449
|
+
body: JSON.stringify({
|
|
450
|
+
jsonrpc: "2.0",
|
|
451
|
+
id: Date.now(),
|
|
452
|
+
method,
|
|
453
|
+
params
|
|
454
|
+
})
|
|
455
|
+
});
|
|
456
|
+
const data = await response.json();
|
|
457
|
+
if (data.error) {
|
|
458
|
+
throw new Error(data.error.message);
|
|
459
|
+
}
|
|
460
|
+
return data.result;
|
|
461
|
+
} catch (err) {
|
|
462
|
+
console.error(`Error fetching ${method} from GenLayer RPC:`, err);
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
322
468
|
const baseClient = createViemClient({
|
|
323
469
|
chain: chainConfig,
|
|
324
|
-
transport:
|
|
470
|
+
transport: custom(customTransport),
|
|
325
471
|
...config.account ? { account: config.account } : {}
|
|
326
472
|
}).extend(publicActions).extend((client) => accountActions(client)).extend((client) => transactionActions(client)).extend((client) => contractActions(client));
|
|
327
|
-
|
|
328
|
-
return genLayerClient;
|
|
473
|
+
return overrideContractActions(baseClient);
|
|
329
474
|
};
|
|
330
475
|
|
|
331
476
|
// src/accounts/account.ts
|
package/dist/types/index.d.cts
CHANGED
|
@@ -118,7 +118,7 @@ type GenLayerMethod = {
|
|
|
118
118
|
params: [address: string, filter?: "all" | "from" | "to"];
|
|
119
119
|
} | {
|
|
120
120
|
method: "eth_getTransactionCount";
|
|
121
|
-
params: [address: string];
|
|
121
|
+
params: [address: string, block: string];
|
|
122
122
|
};
|
|
123
123
|
type GenLayerClient<TSimulatorChain extends SimulatorChain> = Omit<Client<Transport, TSimulatorChain>, "transport" | "getTransaction" | "readContract"> & Omit<PublicActions<Transport, TSimulatorChain>, "readContract" | "getTransaction" | "waitForTransactionReceipt"> & {
|
|
124
124
|
request: Client<Transport, TSimulatorChain>["request"] & {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -118,7 +118,7 @@ type GenLayerMethod = {
|
|
|
118
118
|
params: [address: string, filter?: "all" | "from" | "to"];
|
|
119
119
|
} | {
|
|
120
120
|
method: "eth_getTransactionCount";
|
|
121
|
-
params: [address: string];
|
|
121
|
+
params: [address: string, block: string];
|
|
122
122
|
};
|
|
123
123
|
type GenLayerClient<TSimulatorChain extends SimulatorChain> = Omit<Client<Transport, TSimulatorChain>, "transport" | "getTransaction" | "readContract"> & Omit<PublicActions<Transport, TSimulatorChain>, "readContract" | "getTransaction" | "waitForTransactionReceipt"> & {
|
|
124
124
|
request: Client<Transport, TSimulatorChain>["request"] & {
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const BITS_IN_TYPE = 3;
|
|
2
|
+
|
|
3
|
+
export const TYPE_SPECIAL = 0;
|
|
4
|
+
export const TYPE_PINT = 1;
|
|
5
|
+
export const TYPE_NINT = 2;
|
|
6
|
+
export const TYPE_BYTES = 3;
|
|
7
|
+
export const TYPE_STR = 4;
|
|
8
|
+
export const TYPE_ARR = 5;
|
|
9
|
+
export const TYPE_MAP = 6;
|
|
10
|
+
|
|
11
|
+
export const SPECIAL_NULL = (0 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
12
|
+
export const SPECIAL_FALSE = (1 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
13
|
+
export const SPECIAL_TRUE = (2 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
14
|
+
export const SPECIAL_ADDR = (3 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type {CalldataEncodable} from "../../types/calldata";
|
|
2
|
+
import {Address} from "../../types/calldata";
|
|
3
|
+
import * as consts from "./consts";
|
|
4
|
+
|
|
5
|
+
function readULeb128(data: Uint8Array, index: {i: number}): bigint {
|
|
6
|
+
let res: bigint = 0n;
|
|
7
|
+
let accum = 0n;
|
|
8
|
+
let shouldContinue = true;
|
|
9
|
+
while (shouldContinue) {
|
|
10
|
+
const byte = data[index.i];
|
|
11
|
+
index.i++;
|
|
12
|
+
const rest = byte & 0x7f;
|
|
13
|
+
res += BigInt(rest) * (1n << accum);
|
|
14
|
+
accum += 7n;
|
|
15
|
+
shouldContinue = byte >= 128;
|
|
16
|
+
}
|
|
17
|
+
return res;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function decodeImpl(data: Uint8Array, index: {i: number}): CalldataEncodable {
|
|
21
|
+
const cur = readULeb128(data, index);
|
|
22
|
+
switch (cur) {
|
|
23
|
+
case BigInt(consts.SPECIAL_NULL):
|
|
24
|
+
return null;
|
|
25
|
+
case BigInt(consts.SPECIAL_TRUE):
|
|
26
|
+
return true;
|
|
27
|
+
case BigInt(consts.SPECIAL_FALSE):
|
|
28
|
+
return false;
|
|
29
|
+
case BigInt(consts.SPECIAL_ADDR): {
|
|
30
|
+
const res = data.slice(index.i, index.i + 20);
|
|
31
|
+
index.i += 20;
|
|
32
|
+
return new Address(res);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const type = Number(cur & 0xffn) & ((1 << consts.BITS_IN_TYPE) - 1);
|
|
36
|
+
const rest = cur >> BigInt(consts.BITS_IN_TYPE);
|
|
37
|
+
switch (type) {
|
|
38
|
+
case consts.TYPE_BYTES: {
|
|
39
|
+
const ret = data.slice(index.i, index.i + Number(rest));
|
|
40
|
+
index.i += Number(rest);
|
|
41
|
+
return ret;
|
|
42
|
+
}
|
|
43
|
+
case consts.TYPE_PINT:
|
|
44
|
+
return rest;
|
|
45
|
+
case consts.TYPE_NINT:
|
|
46
|
+
return -1n - rest;
|
|
47
|
+
case consts.TYPE_STR: {
|
|
48
|
+
const ret = data.slice(index.i, index.i + Number(rest));
|
|
49
|
+
index.i += Number(rest);
|
|
50
|
+
return new TextDecoder("utf-8").decode(ret);
|
|
51
|
+
}
|
|
52
|
+
case consts.TYPE_ARR: {
|
|
53
|
+
const ret = [] as CalldataEncodable[];
|
|
54
|
+
let elems = rest;
|
|
55
|
+
while (elems > 0) {
|
|
56
|
+
elems--;
|
|
57
|
+
ret.push(decodeImpl(data, index));
|
|
58
|
+
}
|
|
59
|
+
return ret;
|
|
60
|
+
}
|
|
61
|
+
case consts.TYPE_MAP: {
|
|
62
|
+
const ret = new Map<string, CalldataEncodable>();
|
|
63
|
+
let elems = rest;
|
|
64
|
+
while (elems > 0) {
|
|
65
|
+
elems--;
|
|
66
|
+
const strLen = Number(readULeb128(data, index));
|
|
67
|
+
const key = data.slice(index.i, index.i + strLen);
|
|
68
|
+
index.i += strLen;
|
|
69
|
+
const keyStr = new TextDecoder("utf-8").decode(key);
|
|
70
|
+
ret.set(keyStr, decodeImpl(data, index));
|
|
71
|
+
}
|
|
72
|
+
return ret;
|
|
73
|
+
}
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`can't decode type from ${type} rest is ${rest} at pos ${index.i}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function decode(data: Uint8Array): CalldataEncodable {
|
|
80
|
+
const index = {i: 0};
|
|
81
|
+
const res = decodeImpl(data, index);
|
|
82
|
+
if (index.i !== data.length) {
|
|
83
|
+
throw new Error("some data left");
|
|
84
|
+
}
|
|
85
|
+
return res;
|
|
86
|
+
}
|
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
import {toHex
|
|
1
|
+
import {toHex} from "viem";
|
|
2
|
+
import {toRlp} from "viem";
|
|
2
3
|
import type {CalldataEncodable, TransactionDataElement} from "../../types/calldata";
|
|
3
4
|
import {Address} from "../../types/calldata";
|
|
4
|
-
|
|
5
|
-
const BITS_IN_TYPE = 3;
|
|
6
|
-
|
|
7
|
-
const TYPE_SPECIAL = 0;
|
|
8
|
-
const TYPE_PINT = 1;
|
|
9
|
-
const TYPE_NINT = 2;
|
|
10
|
-
const TYPE_BYTES = 3;
|
|
11
|
-
const TYPE_STR = 4;
|
|
12
|
-
const TYPE_ARR = 5;
|
|
13
|
-
const TYPE_MAP = 6;
|
|
14
|
-
|
|
15
|
-
const SPECIAL_NULL = (0 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
16
|
-
const SPECIAL_FALSE = (1 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
17
|
-
const SPECIAL_TRUE = (2 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
18
|
-
const SPECIAL_ADDR = (3 << BITS_IN_TYPE) | TYPE_SPECIAL;
|
|
5
|
+
import * as consts from "./consts";
|
|
19
6
|
|
|
20
7
|
function reportError(msg: string, data: CalldataEncodable): never {
|
|
21
8
|
throw new Error(`invalid calldata input '${data}'`);
|
|
@@ -37,15 +24,15 @@ function writeNum(to: number[], data: bigint) {
|
|
|
37
24
|
}
|
|
38
25
|
|
|
39
26
|
function encodeNumWithType(to: number[], data: bigint, type: number) {
|
|
40
|
-
const res = (data << BigInt(BITS_IN_TYPE)) | BigInt(type);
|
|
27
|
+
const res = (data << BigInt(consts.BITS_IN_TYPE)) | BigInt(type);
|
|
41
28
|
writeNum(to, res);
|
|
42
29
|
}
|
|
43
30
|
|
|
44
31
|
function encodeNum(to: number[], data: bigint) {
|
|
45
32
|
if (data >= 0n) {
|
|
46
|
-
encodeNumWithType(to, data, TYPE_PINT);
|
|
33
|
+
encodeNumWithType(to, data, consts.TYPE_PINT);
|
|
47
34
|
} else {
|
|
48
|
-
encodeNumWithType(to, -data - 1n, TYPE_NINT);
|
|
35
|
+
encodeNumWithType(to, -data - 1n, consts.TYPE_NINT);
|
|
49
36
|
}
|
|
50
37
|
}
|
|
51
38
|
|
|
@@ -76,7 +63,7 @@ function encodeMap(to: number[], arr: Iterable<[string, CalldataEncodable]>) {
|
|
|
76
63
|
}
|
|
77
64
|
}
|
|
78
65
|
|
|
79
|
-
encodeNumWithType(to, BigInt(newEntries.length), TYPE_MAP);
|
|
66
|
+
encodeNumWithType(to, BigInt(newEntries.length), consts.TYPE_MAP);
|
|
80
67
|
for (const [, k, v] of newEntries) {
|
|
81
68
|
writeNum(to, BigInt(k.length));
|
|
82
69
|
for (const c of k) {
|
|
@@ -88,15 +75,15 @@ function encodeMap(to: number[], arr: Iterable<[string, CalldataEncodable]>) {
|
|
|
88
75
|
|
|
89
76
|
function encodeImpl(to: number[], data: CalldataEncodable) {
|
|
90
77
|
if (data === null || data === undefined) {
|
|
91
|
-
to.push(SPECIAL_NULL);
|
|
78
|
+
to.push(consts.SPECIAL_NULL);
|
|
92
79
|
return;
|
|
93
80
|
}
|
|
94
81
|
if (data === true) {
|
|
95
|
-
to.push(SPECIAL_TRUE);
|
|
82
|
+
to.push(consts.SPECIAL_TRUE);
|
|
96
83
|
return;
|
|
97
84
|
}
|
|
98
85
|
if (data === false) {
|
|
99
|
-
to.push(SPECIAL_FALSE);
|
|
86
|
+
to.push(consts.SPECIAL_FALSE);
|
|
100
87
|
return;
|
|
101
88
|
}
|
|
102
89
|
switch (typeof data) {
|
|
@@ -113,7 +100,7 @@ function encodeImpl(to: number[], data: CalldataEncodable) {
|
|
|
113
100
|
}
|
|
114
101
|
case "string": {
|
|
115
102
|
const str = new TextEncoder().encode(data);
|
|
116
|
-
encodeNumWithType(to, BigInt(str.length), TYPE_STR);
|
|
103
|
+
encodeNumWithType(to, BigInt(str.length), consts.TYPE_STR);
|
|
117
104
|
for (const c of str) {
|
|
118
105
|
to.push(c);
|
|
119
106
|
}
|
|
@@ -121,27 +108,24 @@ function encodeImpl(to: number[], data: CalldataEncodable) {
|
|
|
121
108
|
}
|
|
122
109
|
case "object": {
|
|
123
110
|
if (data instanceof Uint8Array) {
|
|
124
|
-
encodeNumWithType(to, BigInt(data.length), TYPE_BYTES);
|
|
111
|
+
encodeNumWithType(to, BigInt(data.length), consts.TYPE_BYTES);
|
|
125
112
|
for (const c of data) {
|
|
126
113
|
to.push(c);
|
|
127
114
|
}
|
|
128
115
|
} else if (data instanceof Array) {
|
|
129
|
-
encodeNumWithType(to, BigInt(data.length), TYPE_ARR);
|
|
116
|
+
encodeNumWithType(to, BigInt(data.length), consts.TYPE_ARR);
|
|
130
117
|
for (const c of data) {
|
|
131
118
|
encodeImpl(to, c);
|
|
132
119
|
}
|
|
133
120
|
} else if (data instanceof Map) {
|
|
134
121
|
encodeMap(to, data);
|
|
135
122
|
} else if (data instanceof Address) {
|
|
136
|
-
to.push(SPECIAL_ADDR);
|
|
123
|
+
to.push(consts.SPECIAL_ADDR);
|
|
137
124
|
for (const c of data.bytes) {
|
|
138
125
|
to.push(c);
|
|
139
126
|
}
|
|
140
127
|
} else if (Object.getPrototypeOf(data) === Object.prototype) {
|
|
141
|
-
encodeMap(
|
|
142
|
-
to,
|
|
143
|
-
Object.keys(data).map((k): [string, CalldataEncodable] => [k, data[k]]),
|
|
144
|
-
);
|
|
128
|
+
encodeMap(to, Object.entries(data));
|
|
145
129
|
} else {
|
|
146
130
|
reportError("unknown object type", data);
|
|
147
131
|
}
|
|
@@ -159,6 +143,83 @@ export function encode(data: CalldataEncodable): Uint8Array {
|
|
|
159
143
|
return new Uint8Array(arr);
|
|
160
144
|
}
|
|
161
145
|
|
|
146
|
+
function toStringImplMap(data: Iterable<[string, CalldataEncodable]>, to: string[]) {
|
|
147
|
+
to.push("{");
|
|
148
|
+
for (const [k, v] of data) {
|
|
149
|
+
to.push(JSON.stringify(k));
|
|
150
|
+
to.push(":");
|
|
151
|
+
toStringImpl(v, to);
|
|
152
|
+
}
|
|
153
|
+
to.push("}");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function toStringImpl(data: CalldataEncodable, to: string[]) {
|
|
157
|
+
if (data === null || data === undefined) {
|
|
158
|
+
to.push("null");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (data === true) {
|
|
162
|
+
to.push("true");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (data === false) {
|
|
166
|
+
to.push("false");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
switch (typeof data) {
|
|
170
|
+
case "number": {
|
|
171
|
+
if (!Number.isInteger(data)) {
|
|
172
|
+
reportError("floats are not supported", data);
|
|
173
|
+
}
|
|
174
|
+
to.push(data.toString());
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
case "bigint": {
|
|
178
|
+
to.push(data.toString());
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
case "string": {
|
|
182
|
+
to.push(JSON.stringify(data));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
case "object": {
|
|
186
|
+
if (data instanceof Uint8Array) {
|
|
187
|
+
to.push("b#");
|
|
188
|
+
for (const b of data) {
|
|
189
|
+
to.push(b.toString(16));
|
|
190
|
+
}
|
|
191
|
+
} else if (data instanceof Array) {
|
|
192
|
+
to.push("[");
|
|
193
|
+
for (const c of data) {
|
|
194
|
+
toStringImpl(c, to);
|
|
195
|
+
to.push(",");
|
|
196
|
+
}
|
|
197
|
+
to.push("]");
|
|
198
|
+
} else if (data instanceof Map) {
|
|
199
|
+
toStringImplMap(data.entries(), to);
|
|
200
|
+
} else if (data instanceof Address) {
|
|
201
|
+
to.push("addr#");
|
|
202
|
+
for (const c of data.bytes) {
|
|
203
|
+
to.push(c.toString(16));
|
|
204
|
+
}
|
|
205
|
+
} else if (Object.getPrototypeOf(data) === Object.prototype) {
|
|
206
|
+
toStringImplMap(Object.entries(data), to);
|
|
207
|
+
} else {
|
|
208
|
+
reportError("unknown object type", data);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
default:
|
|
213
|
+
reportError("unknown base type", data);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function toString(data: CalldataEncodable): string {
|
|
218
|
+
const to: string[] = [];
|
|
219
|
+
toStringImpl(data, to);
|
|
220
|
+
return to.join("");
|
|
221
|
+
}
|
|
222
|
+
|
|
162
223
|
export function serialize(data: TransactionDataElement[]): `0x${string}` {
|
|
163
224
|
return toRlp(data.map(param => toHex(param)));
|
|
164
225
|
}
|
package/src/accounts/actions.ts
CHANGED
|
@@ -13,16 +13,15 @@ export function accountActions(client: GenLayerClient<SimulatorChain>) {
|
|
|
13
13
|
params: [address, amount],
|
|
14
14
|
}) as Promise<TransactionHash>;
|
|
15
15
|
},
|
|
16
|
-
getCurrentNonce: async ({address}: {address: string}): Promise<number> => {
|
|
16
|
+
getCurrentNonce: async ({address, block = 'latest'}: {address: string, block?: string}): Promise<number> => {
|
|
17
17
|
const addressToUse = address || client.account?.address;
|
|
18
18
|
|
|
19
19
|
if (!addressToUse) {
|
|
20
20
|
throw new Error("No address provided and no account is connected");
|
|
21
21
|
}
|
|
22
|
-
|
|
23
22
|
return client.request({
|
|
24
23
|
method: "eth_getTransactionCount",
|
|
25
|
-
params: [addressToUse],
|
|
24
|
+
params: [addressToUse, block],
|
|
26
25
|
}) as Promise<number>;
|
|
27
26
|
},
|
|
28
27
|
};
|
package/src/client/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Account, createClient as createViemClient,
|
|
1
|
+
import {Account, createClient as createViemClient, publicActions, custom, Address} from "viem";
|
|
2
2
|
import {simulator} from "../chains/simulator";
|
|
3
3
|
import {accountActions} from "../accounts/actions";
|
|
4
4
|
import {contractActions, overrideContractActions} from "../contracts/actions";
|
|
@@ -10,24 +10,62 @@ interface ClientConfig {
|
|
|
10
10
|
chain?: {
|
|
11
11
|
id: number;
|
|
12
12
|
name: string;
|
|
13
|
-
rpcUrls: {default: {http: readonly string[]}};
|
|
14
|
-
nativeCurrency: {name: string; symbol: string; decimals: number};
|
|
15
|
-
blockExplorers?: {default: {name: string; url: string}};
|
|
13
|
+
rpcUrls: { default: { http: readonly string[] } };
|
|
14
|
+
nativeCurrency: { name: string; symbol: string; decimals: number };
|
|
15
|
+
blockExplorers?: { default: { name: string; url: string } };
|
|
16
16
|
};
|
|
17
|
-
endpoint?: string; //
|
|
18
|
-
account?: Account;
|
|
17
|
+
endpoint?: string; // Custom RPC endpoint
|
|
18
|
+
account?: Account | Address;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
export const createClient = (config: ClientConfig = {chain: simulator}) => {
|
|
23
|
-
// Determine the RPC URL based on the provided configuration or default to the simulator's RPC UR
|
|
21
|
+
export const createClient = (config: ClientConfig = { chain: simulator }) => {
|
|
24
22
|
const chainConfig = config.chain || simulator;
|
|
25
23
|
const rpcUrl = config.endpoint || chainConfig.rpcUrls.default.http[0];
|
|
24
|
+
const isAddress = typeof config.account !== "object";
|
|
25
|
+
|
|
26
|
+
const customTransport = {
|
|
27
|
+
async request({method, params}: {method: string; params: any[]}) {
|
|
28
|
+
if (method.startsWith('eth_') && isAddress) {
|
|
29
|
+
try {
|
|
30
|
+
return await window.ethereum?.request({method, params});
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.warn(`Error using window.ethereum for method ${method}:`, err);
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(rpcUrl, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
jsonrpc: '2.0',
|
|
44
|
+
id: Date.now(),
|
|
45
|
+
method,
|
|
46
|
+
params,
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
|
|
52
|
+
if (data.error) {
|
|
53
|
+
throw new Error(data.error.message);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return data.result;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(`Error fetching ${method} from GenLayer RPC:`, err);
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
26
65
|
|
|
27
|
-
// Create a Viem client connected to the GenLayer Simulator (or custom chain)
|
|
28
66
|
const baseClient = createViemClient({
|
|
29
67
|
chain: chainConfig,
|
|
30
|
-
transport:
|
|
68
|
+
transport: custom(customTransport),
|
|
31
69
|
...(config.account ? {account: config.account} : {}),
|
|
32
70
|
})
|
|
33
71
|
.extend(publicActions)
|
|
@@ -35,7 +73,7 @@ export const createClient = (config: ClientConfig = {chain: simulator}) => {
|
|
|
35
73
|
.extend(client => transactionActions(client as unknown as GenLayerClient<SimulatorChain>))
|
|
36
74
|
.extend(client => contractActions(client as unknown as GenLayerClient<SimulatorChain>));
|
|
37
75
|
|
|
38
|
-
|
|
76
|
+
return overrideContractActions(baseClient as unknown as GenLayerClient<SimulatorChain>);
|
|
39
77
|
|
|
40
|
-
return genLayerClient;
|
|
41
78
|
};
|
|
79
|
+
|
package/src/contracts/actions.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {decode} from "@/abi/calldata/decoder";
|
|
1
2
|
import {encode, serialize, encodeAndSerialize} from "@/abi/calldata/encoder";
|
|
2
3
|
import {Account, ContractSchema, SimulatorChain, GenLayerClient, CalldataEncodable, Address} from "@/types";
|
|
3
4
|
|
|
@@ -27,21 +28,30 @@ export const overrideContractActions = (client: GenLayerClient<SimulatorChain>)
|
|
|
27
28
|
functionName: string;
|
|
28
29
|
args: CalldataEncodable[];
|
|
29
30
|
}): Promise<unknown> => {
|
|
30
|
-
const {account, address, functionName, args: params} = args;
|
|
31
|
-
const encodedData = encodeAndSerialize({method: functionName, args: params});
|
|
31
|
+
const { account, address, functionName, args: params } = args;
|
|
32
|
+
const encodedData = encodeAndSerialize({ method: functionName, args: params });
|
|
33
|
+
|
|
34
|
+
let senderAddress = account?.address ?? client.account?.address;
|
|
32
35
|
|
|
33
36
|
const requestParams = {
|
|
34
37
|
to: address,
|
|
35
|
-
from:
|
|
38
|
+
from: senderAddress,
|
|
36
39
|
data: encodedData,
|
|
37
40
|
};
|
|
38
41
|
const result = await client.request({
|
|
39
42
|
method: "eth_call",
|
|
40
43
|
params: [requestParams, "latest"],
|
|
41
44
|
});
|
|
42
|
-
|
|
45
|
+
|
|
46
|
+
if (typeof result === "string") {
|
|
47
|
+
const val = Uint8Array.from(atob(result), c => c.charCodeAt(0));
|
|
48
|
+
return decode(val);
|
|
49
|
+
} else {
|
|
50
|
+
return "<unknown>";
|
|
51
|
+
}
|
|
43
52
|
};
|
|
44
53
|
|
|
54
|
+
|
|
45
55
|
client.writeContract = async (args: {
|
|
46
56
|
account?: Account;
|
|
47
57
|
address: Address;
|
|
@@ -50,14 +60,30 @@ export const overrideContractActions = (client: GenLayerClient<SimulatorChain>)
|
|
|
50
60
|
value: bigint;
|
|
51
61
|
leaderOnly?: boolean;
|
|
52
62
|
}): Promise<`0x${string}`> => {
|
|
53
|
-
const {account, address, functionName, args: params, value = 0n, leaderOnly = false} = args;
|
|
54
|
-
const data = [encode({method: functionName, args: params}), leaderOnly];
|
|
63
|
+
const { account, address, functionName, args: params, value = 0n, leaderOnly = false } = args;
|
|
64
|
+
const data = [encode({ method: functionName, args: params }), leaderOnly];
|
|
55
65
|
const serializedData = serialize(data);
|
|
56
|
-
|
|
57
66
|
const senderAccount = account || client.account;
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if (senderAccount?.type !== "local") {
|
|
70
|
+
|
|
71
|
+
const transaction = {
|
|
72
|
+
from: senderAccount?.address,
|
|
73
|
+
to: address,
|
|
74
|
+
data: serializedData,
|
|
75
|
+
value: `0x${value.toString(16)}`,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return await client.request({
|
|
79
|
+
method: "eth_sendTransaction",
|
|
80
|
+
params: [transaction as any],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
58
84
|
if (!senderAccount) {
|
|
59
85
|
throw new Error(
|
|
60
|
-
|
|
86
|
+
"No account set. Configure the client with an account or pass an account to this function."
|
|
61
87
|
);
|
|
62
88
|
}
|
|
63
89
|
|
|
@@ -65,7 +91,7 @@ export const overrideContractActions = (client: GenLayerClient<SimulatorChain>)
|
|
|
65
91
|
throw new Error("Account does not support signTransaction");
|
|
66
92
|
}
|
|
67
93
|
|
|
68
|
-
const nonce = await client.getCurrentNonce({address: senderAccount.address});
|
|
94
|
+
const nonce = await client.getCurrentNonce({ address: senderAccount.address });
|
|
69
95
|
|
|
70
96
|
const signedTransaction = await senderAccount.signTransaction({
|
|
71
97
|
data: serializedData,
|
|
@@ -74,11 +100,11 @@ export const overrideContractActions = (client: GenLayerClient<SimulatorChain>)
|
|
|
74
100
|
type: "legacy",
|
|
75
101
|
nonce,
|
|
76
102
|
});
|
|
77
|
-
|
|
103
|
+
|
|
104
|
+
return await client.request({
|
|
78
105
|
method: "eth_sendRawTransaction",
|
|
79
106
|
params: [signedTransaction],
|
|
80
107
|
});
|
|
81
|
-
return result;
|
|
82
108
|
};
|
|
83
109
|
|
|
84
110
|
client.deployContract = async (args: {
|
|
@@ -87,14 +113,29 @@ export const overrideContractActions = (client: GenLayerClient<SimulatorChain>)
|
|
|
87
113
|
args: CalldataEncodable[];
|
|
88
114
|
leaderOnly?: boolean;
|
|
89
115
|
}) => {
|
|
90
|
-
const {account, code, args: constructorArgs, leaderOnly = false} = args;
|
|
91
|
-
const data = [code, encode({args: constructorArgs}), leaderOnly];
|
|
116
|
+
const { account, code, args: constructorArgs, leaderOnly = false } = args;
|
|
117
|
+
const data = [code, encode({ args: constructorArgs }), leaderOnly];
|
|
92
118
|
const serializedData = serialize(data);
|
|
93
|
-
|
|
94
119
|
const senderAccount = account || client.account;
|
|
120
|
+
|
|
121
|
+
if (senderAccount?.type !== "local") {
|
|
122
|
+
|
|
123
|
+
const transaction = {
|
|
124
|
+
from: senderAccount?.address,
|
|
125
|
+
to: null,
|
|
126
|
+
data: serializedData,
|
|
127
|
+
value: "0x0",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return await client.request({
|
|
131
|
+
method: "eth_sendTransaction",
|
|
132
|
+
params: [transaction as any],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
95
136
|
if (!senderAccount) {
|
|
96
137
|
throw new Error(
|
|
97
|
-
|
|
138
|
+
"No account set. Configure the client with an account or pass an account to this function."
|
|
98
139
|
);
|
|
99
140
|
}
|
|
100
141
|
|
|
@@ -102,7 +143,7 @@ export const overrideContractActions = (client: GenLayerClient<SimulatorChain>)
|
|
|
102
143
|
throw new Error("Account does not support signTransaction");
|
|
103
144
|
}
|
|
104
145
|
|
|
105
|
-
const nonce = await client.getCurrentNonce({address: senderAccount.address});
|
|
146
|
+
const nonce = await client.getCurrentNonce({ address: senderAccount.address });
|
|
106
147
|
|
|
107
148
|
const signedTransaction = await senderAccount.signTransaction({
|
|
108
149
|
data: serializedData,
|
|
@@ -110,11 +151,10 @@ export const overrideContractActions = (client: GenLayerClient<SimulatorChain>)
|
|
|
110
151
|
nonce,
|
|
111
152
|
});
|
|
112
153
|
|
|
113
|
-
|
|
154
|
+
return await client.request({
|
|
114
155
|
method: "eth_sendRawTransaction",
|
|
115
156
|
params: [signedTransaction],
|
|
116
157
|
});
|
|
117
|
-
return result;
|
|
118
158
|
};
|
|
119
159
|
|
|
120
160
|
return client;
|
package/src/global.d.ts
ADDED
package/src/types/clients.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type GenLayerMethod =
|
|
|
13
13
|
| {method: "gen_getContractSchema"; params: [address: string]}
|
|
14
14
|
| {method: "gen_getContractSchemaForCode"; params: [contractCode: string]}
|
|
15
15
|
| {method: "sim_getTransactionsForAddress"; params: [address: string, filter?: "all" | "from" | "to"]}
|
|
16
|
-
| {method: "eth_getTransactionCount"; params: [address: string]};
|
|
16
|
+
| {method: "eth_getTransactionCount"; params: [address: string, block: string]};
|
|
17
17
|
|
|
18
18
|
/*
|
|
19
19
|
Take all the properties from PublicActions<Transport, TSimulatorChain>
|
package/tests/client.test.ts
CHANGED
|
@@ -18,7 +18,7 @@ describe("Client Overrides", () => {
|
|
|
18
18
|
const account = createAccount(generatePrivateKey());
|
|
19
19
|
const client = createClient({
|
|
20
20
|
chain: simulator,
|
|
21
|
-
account,
|
|
21
|
+
account: account,
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
// Mock the client.request method
|
|
@@ -76,4 +76,34 @@ describe("Client Overrides", () => {
|
|
|
76
76
|
],
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
|
+
it("should override client account if address is provided", async () => {
|
|
80
|
+
const account = '0x65e03a3e916CF1dC92d3C8E8186a89CfAB0D2bc2';
|
|
81
|
+
const client = createClient({
|
|
82
|
+
chain: simulator,
|
|
83
|
+
account,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Mock the client.request method
|
|
87
|
+
vi.spyOn(client, "request").mockResolvedValue(undefined);
|
|
88
|
+
|
|
89
|
+
const contractAddress = "0x1234567890123456789012345678901234567890";
|
|
90
|
+
await client.readContract({
|
|
91
|
+
address: contractAddress as Address,
|
|
92
|
+
functionName: "testFunction",
|
|
93
|
+
args: ["arg1", "arg2"],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(client.request).toHaveBeenCalledWith({
|
|
97
|
+
method: "eth_call",
|
|
98
|
+
params: [
|
|
99
|
+
{
|
|
100
|
+
to: contractAddress,
|
|
101
|
+
from: account,
|
|
102
|
+
data: expect.any(String),
|
|
103
|
+
},
|
|
104
|
+
"latest",
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
79
109
|
});
|
package/tsconfig.json
CHANGED
|
@@ -115,5 +115,5 @@
|
|
|
115
115
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
|
116
116
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
117
117
|
},
|
|
118
|
-
"include": ["src/**/*", "tests/**/*"]
|
|
118
|
+
"include": ["src/**/*", "tests/**/*", "src/global.d.ts"]
|
|
119
119
|
}
|