dequanto 0.2.47 → 0.2.48

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.
Files changed (53) hide show
  1. package/docs/ai/INDEX.md +41 -0
  2. package/docs/ai/examples/contract-read.spec.ts +44 -0
  3. package/docs/ai/examples/contract-write.spec.ts +16 -0
  4. package/docs/ai/examples/deploy-contract.spec.ts +32 -0
  5. package/docs/ai/examples/erc4337-userop.spec.ts +37 -0
  6. package/docs/ai/examples/explorer-abi.spec.ts +18 -0
  7. package/docs/ai/examples/generate-client.spec.ts +22 -0
  8. package/docs/ai/examples/index-events.spec.ts +27 -0
  9. package/docs/ai/examples/rpc-read.spec.ts +15 -0
  10. package/docs/ai/examples/safe-batch.spec.ts +55 -0
  11. package/docs/ai/examples/send-transaction.spec.ts +22 -0
  12. package/docs/ai/examples/storage-read.spec.ts +27 -0
  13. package/docs/ai/examples/token-transfer.spec.ts +22 -0
  14. package/docs/ai/references/contracts.md +111 -0
  15. package/docs/ai/references/deployments.md +98 -0
  16. package/docs/ai/references/events-indexing.md +97 -0
  17. package/docs/ai/references/explorer.md +77 -0
  18. package/docs/ai/references/generation.md +182 -0
  19. package/docs/ai/references/migration-from-ethers-viem.md +43 -0
  20. package/docs/ai/references/rpc-clients.md +115 -0
  21. package/docs/ai/references/safe-erc4337.md +134 -0
  22. package/docs/ai/references/storage.md +93 -0
  23. package/docs/ai/references/tokens-accounts.md +99 -0
  24. package/docs/ai/references/transactions.md +146 -0
  25. package/docs/ai/skill/dequanto/SKILL.md +59 -0
  26. package/lib/cjs/abi/$abiCoder.js +14 -0
  27. package/lib/cjs/abi/$abiCoder.js.map +1 -1
  28. package/lib/cjs/clients/Web3Client.js +10 -3
  29. package/lib/cjs/clients/Web3Client.js.map +1 -1
  30. package/lib/cjs/clients/Web3ClientFactory.js.map +1 -1
  31. package/lib/cjs/utils/$abiParser.js +85 -1
  32. package/lib/cjs/utils/$abiParser.js.map +1 -1
  33. package/lib/esm/abi/$abiCoder.js.map +1 -1
  34. package/lib/esm/abi/$abiCoder.mjs +14 -0
  35. package/lib/esm/clients/Web3Client.js.map +1 -1
  36. package/lib/esm/clients/Web3Client.mjs +10 -3
  37. package/lib/esm/clients/Web3ClientFactory.js.map +1 -1
  38. package/lib/esm/utils/$abiParser.js.map +1 -1
  39. package/lib/esm/utils/$abiParser.mjs +85 -1
  40. package/lib/types/abi/$abiCoder.d.ts +11 -9
  41. package/lib/types/clients/Web3Client.d.ts +4 -1
  42. package/lib/types/clients/Web3ClientFactory.d.ts +3 -2
  43. package/lib/types/contracts/ContractBase.d.ts +1 -1
  44. package/lib/types/contracts/wrappers/FnRequestWrapper.d.ts +1 -1
  45. package/lib/types/models/TAccount.d.ts +1 -1
  46. package/lib/types/utils/$abiParser.d.ts +6 -0
  47. package/package.json +1 -1
  48. package/src/abi/$abiCoder.ts +31 -10
  49. package/src/clients/Web3Client.ts +16 -6
  50. package/src/clients/Web3ClientFactory.ts +10 -5
  51. package/src/contracts/wrappers/FnRequestWrapper.ts +1 -1
  52. package/src/models/TAccount.ts +1 -1
  53. package/src/utils/$abiParser.ts +93 -1
@@ -6,11 +6,24 @@ import { TEth } from '@dequanto/models/TEth';
6
6
  import { $abiParser } from '@dequanto/utils/$abiParser';
7
7
 
8
8
  export namespace $abiCoder {
9
- export function encode(types: (string | ParamType | TAbiInput)[], values: any[]): TEth.Hex {
9
+
10
+ type TAbiType = string | ParamType | TAbiInput;
11
+ type TDecodeOptions = {
12
+ loose?: boolean
13
+ dynamic?: boolean
14
+ };
15
+
16
+ export function encode(types: TAbiType, values: any): TEth.Hex
17
+ export function encode(types: TAbiType[], values: any[]): TEth.Hex
18
+ export function encode(types: TAbiType | TAbiType[], values: any | any[]): TEth.Hex {
19
+ if (Array.isArray(types) == false) {
20
+ return encodeSingle(types, values)
21
+ }
10
22
  let coder = new AbiCoder();
11
23
  return coder.encode(types, values) as TEth.Hex;
12
24
  }
13
- export function encodeSingle(type: (string | ParamType | TAbiInput), value: any): TEth.Hex {
25
+ export function encodeSingle(type: TAbiType, value: any): TEth.Hex {
26
+ type = normalizeType(type);
14
27
  let coder = new AbiCoder();
15
28
  return coder.encodeSingle(type, value) as TEth.Hex;
16
29
  }
@@ -19,20 +32,20 @@ export namespace $abiCoder {
19
32
  return solidityPacked(types, values) as TEth.Hex;
20
33
  }
21
34
 
22
- export function decode(types: (string | ParamType | TAbiInput)[], hex: string, opts?: {
23
- loose?: boolean
24
- dynamic?: boolean
25
- }): any {
35
+ export function decode(types: TAbiType, hex: string, opts?: TDecodeOptions): any
36
+ export function decode(types: TAbiType[], hex: string, opts?: TDecodeOptions): any[]
37
+ export function decode(types: TAbiType| TAbiType[], hex: string, opts?: TDecodeOptions): any {
38
+ if (Array.isArray(types) == false) {
39
+ return decodeSingle(types, hex, opts)
40
+ }
26
41
  let coder = new AbiCoder();
27
42
  let arr = coder.decode(types, hex, opts);
28
43
  return arr.map((x, i) => {
29
44
  return unwrap(types[i] as TAbiInput, x);
30
45
  });
31
46
  }
32
- export function decodeSingle(type: (string | ParamType | TAbiInput), hex: string, opts?: {
33
- loose?: boolean
34
- dynamic?: boolean
35
- }): any {
47
+ export function decodeSingle(type: TAbiType, hex: string, opts?: TDecodeOptions): any {
48
+ type = normalizeType(type);
36
49
  let coder = new AbiCoder();
37
50
  let x = coder.decodeSingle(type, hex, opts);
38
51
  return unwrap(type as TAbiInput, x);
@@ -72,4 +85,12 @@ export namespace $abiCoder {
72
85
  }
73
86
  return mixValue;
74
87
  }
88
+
89
+
90
+ function normalizeType (type: TAbiType) {
91
+ if (typeof type === 'string' && /\bstruct /.test(type)) {
92
+ return $abiParser.parseStruct(type);
93
+ }
94
+ return type;
95
+ }
75
96
  }
@@ -97,6 +97,8 @@ export abstract class Web3Client implements IWeb3Client {
97
97
  });
98
98
  }
99
99
 
100
+ // Low-level batch for raw RPC calls.
101
+ // For contract batch calls, use batchContractCalls to decode the responses.
100
102
  async batch(requests: TRpc.IRpcAction[], options?: {
101
103
  allowErrors?: boolean
102
104
  }): Promise<any[]> {
@@ -105,6 +107,15 @@ export abstract class Web3Client implements IWeb3Client {
105
107
  });
106
108
  }
107
109
 
110
+ // Submit contract calls in a batch and decode the responses.
111
+ async batchContractCalls(requests: TRpcContractCall[], options?: {
112
+ allowErrors?: boolean
113
+ }) {
114
+ let reader = new RpcContract(this);
115
+ let result = await reader.batch(requests, options);
116
+ return result;
117
+ }
118
+
108
119
  getEventStream(address: TEth.Address, abi: TAbiItem[], event: string) {
109
120
  let eventAbi = abi.find(x => x.type === 'event' && x.name === event);
110
121
  if (eventAbi == null) {
@@ -189,12 +200,11 @@ export abstract class Web3Client implements IWeb3Client {
189
200
  return result;
190
201
  }
191
202
 
203
+ // Alias for batchContractCalls.
192
204
  async readContractBatch(requests: TRpcContractCall[], options?: {
193
205
  allowErrors?: boolean
194
206
  }) {
195
- let reader = new RpcContract(this);
196
- let result = await reader.batch(requests, options);
197
- return result;
207
+ return this.batchContractCalls(requests, options);
198
208
  }
199
209
 
200
210
 
@@ -412,12 +422,12 @@ export abstract class Web3Client implements IWeb3Client {
412
422
  // return tx;
413
423
  // }
414
424
 
415
- async sign(address: TEth.Address, message: string): Promise<string> {
425
+ async sign(address: TEth.Address, message: string): Promise<TEth.Hex> {
416
426
  if (this.wallet.isConnected(address)) {
417
- return this.wallet.eth_sign(address, message);
427
+ return this.wallet.eth_sign(address, message) as Promise<TEth.Hex>;
418
428
  }
419
429
  return this.pool.call(wClient => {
420
- return wClient.sign(address, message);
430
+ return wClient.sign(address, message) as Promise<TEth.Hex>;
421
431
  }, {
422
432
  wallet: true
423
433
  });
@@ -6,16 +6,19 @@ import { Config } from '@dequanto/config/Config';
6
6
  import { EvmWeb3Client } from './EvmWeb3Client';
7
7
  import { $require } from '@dequanto/utils/$require';
8
8
  import { $config } from '@dequanto/utils/$config';
9
+ import type { HardhatWeb3Client } from '@dequanto/hardhat/HardhatWeb3Client';
9
10
 
10
11
  export namespace Web3ClientFactory {
11
12
 
12
- export function get (platform: TPlatform | number, opts?: IWeb3EndpointOptions) {
13
+ export function get <
14
+ TClient extends EvmWeb3Client | HardhatWeb3Client = EvmWeb3Client
15
+ > (platform: TPlatform | number, opts?: IWeb3EndpointOptions): TClient {
13
16
  if (typeof platform === 'string' && (platform === 'hardhat' || platform.startsWith('hh:'))) {
14
17
  let client = di.resolve(HardhatProvider).client('localhost', opts);
15
18
  if (platform.startsWith('hh:')) {
16
19
  client.configureFork(platform.slice(3));
17
20
  }
18
- return client;
21
+ return client as TClient;
19
22
  }
20
23
 
21
24
  let options = $config.getWeb3Options(platform);
@@ -23,12 +26,14 @@ export namespace Web3ClientFactory {
23
26
  return new EvmWeb3Client({
24
27
  ...options,
25
28
  ...(opts ?? {})
26
- });
29
+ }) as TClient;
27
30
  }
28
31
 
29
32
  /** Same as the sync variant, but ensures the config is fetched */
30
- export async function getAsync (platform: TPlatform | string | number, opts?: IWeb3EndpointOptions) {
33
+ export async function getAsync <
34
+ TClient extends EvmWeb3Client | HardhatWeb3Client = EvmWeb3Client
35
+ > (platform: TPlatform | string | number, opts?: IWeb3EndpointOptions): Promise<TClient> {
31
36
  let cfg = await Config.get();
32
- return get(platform, opts);
37
+ return get<TClient>(platform, opts);
33
38
  }
34
39
  }
@@ -9,7 +9,7 @@ export namespace FnRequestWrapper {
9
9
 
10
10
  type TRequests<T extends ContractBase> = {
11
11
  [K in keyof T]: T[K] extends (...any) => Promise<any>
12
- ? (...params: Parameters<T[K]>) => ContractReaderUtils.IContractReadParams<Awaited<ReturnType<T[K]>>>
12
+ ? (...params: Parameters<T[K]>) => Promise<ContractReaderUtils.IContractReadParams<Awaited<ReturnType<T[K]>>>>
13
13
  : never
14
14
  }
15
15
 
@@ -65,7 +65,7 @@ export interface TimelockAccount extends IAccount {
65
65
  // Erc4337 account must include platform information
66
66
  platform: TPlatform
67
67
 
68
- operator: EoAccount
68
+ operator: IAccount
69
69
  }
70
70
 
71
71
  export type TAccount = string | IAccount | EoAccount | SafeAccount | Erc4337Account;
@@ -86,6 +86,69 @@ export namespace $abiParser {
86
86
  };
87
87
  }
88
88
 
89
+
90
+ /**
91
+ * Parses Solidity struct declarations into a tuple ABI component.
92
+ * Supports multiple declarations so later structs can reference earlier ones,
93
+ * and returns the ABI component for the last struct in the input.
94
+ */
95
+ export function parseStruct (struct: string): TAbiItem {
96
+ let structs = Parse.structs(struct);
97
+ $require.gt(structs.length, 0, `No structs found in ${struct}`);
98
+
99
+ let byName = new Map(structs.map(x => [ x.name, x ]));
100
+ let cache = new Map<string, TAbiInput>();
101
+
102
+ function toTuple (name: string): TAbiInput {
103
+ let cached = cache.get(name);
104
+ if (cached != null) {
105
+ return cached;
106
+ }
107
+ let def = byName.get(name);
108
+ $require.notNull(def, `Struct ${name} is not found`);
109
+
110
+ let tuple: TAbiInput = {
111
+ name: '',
112
+ type: 'tuple',
113
+ internalType: `struct ${name}`,
114
+ components: []
115
+ };
116
+ cache.set(name, tuple);
117
+ tuple.components = Parse.splitByDelimiter(def.body, ';')
118
+ .map(field => $abiParser.parseArguments(field)[0])
119
+ .map(resolveStructs);
120
+
121
+ return tuple;
122
+ }
123
+
124
+ function resolveStructs (param: TAbiInput): TAbiInput {
125
+ let { type, array } = Parse.extractArray(param.type);
126
+ if (byName.has(type) === false) {
127
+ if (param.components != null) {
128
+ return {
129
+ ...param,
130
+ components: param.components.map(resolveStructs)
131
+ };
132
+ }
133
+ return param;
134
+ }
135
+
136
+ let tuple = toTuple(type);
137
+ return {
138
+ name: param.name,
139
+ type: `tuple${array}`,
140
+ internalType: `struct ${type}${array}`,
141
+ components: tuple.components
142
+ };
143
+ }
144
+
145
+ let last = structs[structs.length - 1];
146
+ return {
147
+ ...toTuple(last.name),
148
+ name: last.name
149
+ } as any as TAbiItem;
150
+ }
151
+
89
152
  // uint256
90
153
  // address[]
91
154
  // (uint256, uint256)
@@ -247,6 +310,35 @@ namespace Parse {
247
310
 
248
311
  const CLOSE_CHARS = {
249
312
  '[': ']',
250
- '(': ')'
313
+ '(': ')',
314
+ '{': '}'
251
315
  };
316
+
317
+ export function structs (source: string) {
318
+ let rgx = /\bstruct\s+(?<name>[\w_$]+)\s*\{/g;
319
+ let arr: { name: string, body: string }[] = [];
320
+ let match: RegExpExecArray;
321
+ while ((match = rgx.exec(source)) != null) {
322
+ let openI = source.indexOf('{', match.index);
323
+ let closeI = goToClosing(source, openI, '{');
324
+ arr.push({
325
+ name: match.groups.name,
326
+ body: source.substring(openI + 1, closeI).trim()
327
+ });
328
+ rgx.lastIndex = closeI + 1;
329
+ }
330
+ return arr;
331
+ }
332
+
333
+ export function extractArray (type: string) {
334
+ let array = '';
335
+ while (true) {
336
+ let match = /^(?<type>.+?)(?<array>\[[^\]]*\])$/.exec(type);
337
+ if (match == null) {
338
+ return { type, array };
339
+ }
340
+ type = match.groups.type;
341
+ array = `${match.groups.array}${array}`;
342
+ }
343
+ }
252
344
  }