dequanto 0.1.3 → 0.1.7

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.
@@ -21,6 +21,9 @@ jobs:
21
21
  - run:
22
22
  name: Build Project
23
23
  command: npm run build
24
+ - run:
25
+ name: Build d.ts Types
26
+ command: npm run build-types
24
27
  - run:
25
28
  name: Launch Server
26
29
  command: npm run server
@@ -26,6 +26,7 @@ jobs:
26
26
 
27
27
  - run: npm ci
28
28
  - run: npm run build
29
+ - run: npm run build-types
29
30
  - run: npm run test-node
30
31
  - run: npm publish --provenance --access public
31
32
  env:
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dequanto",
3
- "version": "0.1.3",
3
+ "version": "0.1.7",
4
4
  "author": {
5
5
  "name": "Alex Kit",
6
6
  "email": "alex.kit@atmajs.com"
@@ -21,8 +21,7 @@
21
21
  "build": "app-bundler",
22
22
  "build-cjs": "app-bundler --app cjs",
23
23
  "build-esm": "app-bundler --app esm",
24
- "build-dts": "tsc -p tsconfig.umd.json && atma run tools/build-dts",
25
- "build-types": "tsc -p tsconfig-src.json",
24
+ "build-types": "tsc -p tsconfig-src.json && atma run tools/copy-dts",
26
25
  "watch": "app-bundler --watch",
27
26
  "server": "atma server --TEST",
28
27
  "test": "atma --openssl-legacy-provider test --config='settings.base=./' --TEST",
package/readme.md CHANGED
@@ -3,92 +3,85 @@
3
3
  [![Documentation Link](https://img.shields.io/badge/%E2%9D%93-documentation-green.svg)](https://docs.0xweb.org/dequanto)
4
4
  [![CircleCI](https://circleci.com/gh/0xweb-org/dequanto.svg?style=svg)](https://circleci.com/gh/0xweb-org/dequanto)
5
5
 
6
- ![dequanto](https://github.com/0xweb-org/dequanto/assets/550353/d7542ede-8392-44ea-b552-7c3e960ac8fb)
7
-
8
- Here we share our library to work with Ethereum Virtual Machine RPC Nodes: Ethereum, Polygon, BSC, Arbitrum, etc. We use web3 and ethers to provide new Features and to simplify the API.
9
-
10
- 1. RPC Client Pool
11
- 2. Transaction Builder
12
- 3. Generated TypeScript classes for contracts
13
- 4. Etherscan & Co clients
14
- 5. Tokens provider - get token data by symbol, address, platform.
15
- 6. Tokens service - transfer and swap
16
- 7. Transaction **Indexers** - listens to blocks and parse new transactions to simplify further processing
17
- 8. Native **`BigInt`** types and various utility methods
18
- 9. OpenZeppelin contracts
19
-
20
-
21
- ### **`Source-Code`** Distribution
22
-
23
- Not as a library, but as the source code. Clone or fork it and use it as a **submodule**. Means:
24
- a) you can `import` any `class`, `namespace` and `utility` method.
25
- b) you can easily extend and modify any `class`, `namespace` and `utility` method.
26
- c) you get the documentation direct from the code. _We don't have much time yet to document all the parts_
27
-
28
- And path prefix (alias) to your `tsconfig.json`
29
- ```json
30
- {
31
- "compilerOptions": {
32
- "paths": {
33
- "@dequanto/*": [ "your-sub-module-folder/src/*" ],
34
- "@dequanto-contract/*": [ "your-sub-module-folder/contract/*" ]
35
- }
36
- }
37
- }
38
- ```
6
+ <p align='center'>
7
+ <img src='assets/background.jpg'/>
8
+ </p>
39
9
 
40
- ### Configuration
41
10
 
42
- Before you use the code, you should prepare the configuration for it: Node RPC URLs, Accounts, Blockchain Explorer API Keys, etc.
11
+ A versatile library for EVM Blockchain Developers and Consumers. Based on your requirements and expertise, this library can either fully abstract the Blockchain Layer with autogenerated classes for contract communication or give you direct access to low-level methods.
43
12
 
44
- ## 1. RPC Client Pool
13
+ This library includes numerous classes to cover all facets of blockchain development. Here’s a summary of some key features:
45
14
 
46
- Provide multiple RPC Node URLs, this will make your application much more reliable. Even if one of the Nodes goes down or for some reason is not in-sync, all your RPC calls will still work.
15
+ ### TypeScript and ES6 Class Generation
47
16
 
48
- [Web3Client](src/clients/Web3Client.ts) is the base class for all the EVM Platforms. This wraps web3 to provide pool-based communication with nodes. It handles also Gas and Nonce Values.
17
+ Generate contract clients based on ABI or source code, which can be automatically retrieved from blockchain explorers by address.
49
18
 
19
+ > This can serve as an all-in-one tool for submitting transactions, querying contracts, or fetching events, as the contract clients handle the underlying steps.
50
20
 
51
- ## 2. Transaction Builder
21
+ ---
52
22
 
53
- [TxDataBuilder](src/txs/TxDataBuilder.ts) and [TxWriter](src/txs/TxWriter.ts) to create and submit transaction.
23
+ ### RPC Clients
54
24
 
25
+ A robust communication layer to connect to blockchain nodes. You can add multiple nodes (private or public) to the pool to manage **request balancing**, **throttling**, **rate limits**, and **automatic retries**.
55
26
 
56
- ## 3. Generated TypeScript classes for contracts
27
+ ### Transaction Builder
57
28
 
58
- With auto-generated classes you can
29
+ Provides fine-tuning for transaction submission. Supports Gnosis {Safe} and Account Abstraction Transactions.
59
30
 
60
- a) type-safe method read and write(transaction) calls to blockchain
61
- b) parse receipts of the contract transactions
62
- c) easily subscribe to event streams of the contract
31
+ ### Etherscan & Co Clients
63
32
 
64
- ## 4. Etherscan&Co clients
33
+ An integrated API for blockchain explorers.
65
34
 
66
- Ready to use Etherscan&Co classes to consume their API. Just drop-in the KEYs.
35
+ ### Event Indexer
67
36
 
68
- ## 5. Tokens provider
37
+ An easy-to-use indexer for fetching contract events.
69
38
 
70
- Get Tokens Data (`address`, `decimals`, etc) by `symbol` and `platform`
39
+ ### Event and Transaction Watcher
71
40
 
72
- ## 6. Tokens service
41
+ Listen to events and monitor transactions in real-time.
73
42
 
74
- One-liners to transfer tokens from A to B, or swap the tokens using popular DEXes
43
+ ### Native **`BigInt`** Types with **`BigFloat`** Support for Mantissa Math
75
44
 
76
- ## 7. Transaction **Indexers**
45
+ ### Pre-generated OpenZeppelin Contracts
77
46
 
78
- Start listening the blockchain with less code
47
+ ### Flashbots Support
79
48
 
80
- ## 8. Utils
49
+ ## Installation
81
50
 
82
- Lots of Utility methods. BigInt are the first-class citizens in the code.
51
+ ### Using 0xweb CLI
83
52
 
53
+ You can use the [0xweb](https://github.com/0xweb-org/0xweb) tool to install contracts and dependencies.
84
54
 
85
- ### 9. OpenZeppelin contracts
55
+ ### NPM
86
56
 
87
- We prebuild generic OpenZeppelin contracts
57
+ Install the library using npm:
88
58
 
89
59
  ```
90
- import { ERC20 } from '@dequanto-contracts/openzeppelin/ERC20
91
-
92
- let erc20 = new ERC20(`0x...`);
93
- let balance = await erc20.balanceOf('0x...');
60
+ npm i dequanto
94
61
  ```
62
+
63
+ The package includes three types of source files:
64
+
65
+ 1. **TypeScript Sources**: Configure paths in your `tsconfig.json` like so:
66
+
67
+ ```json
68
+ {
69
+ "compilerOptions": {
70
+ "paths": {
71
+ "@dequanto/*": [ "node_modules/dequanto/src/*" ],
72
+ "@dequanto-contract/*": [ "node_modules/dequanto/src/prebuilt/*" ]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ 2. **Compiled CommonJS (CJS) Files**: Located at `node_modules/dequanto/lib/cjs/*/**.js`
79
+ 3. **Compiled ESM (Modules)**: Located at `node_modules/dequanto/lib/esm/*/**.mjs`
80
+
81
+ ### Configuration
82
+
83
+ While the library includes configurations for various blockchains and {Safe} infrastructure, you may need to set custom RPC URLs, Blockchain Explorer API Keys, and other settings. This can be done in a **YAML** configuration file that loads when the application starts.
84
+
85
+ ----
86
+
87
+ (c) 2024 0xweb.org
@@ -335,7 +335,12 @@ export class Generator {
335
335
 
336
336
  if (typeof path === 'string' && path.endsWith('.json')) {
337
337
  let json = await this.readFile(path);
338
+ if (Array.isArray(json)) {
339
+ return { abi: json, bytecode: null, artifact: null, source: null }
340
+ }
338
341
  let { abi, bytecode, sourceName, contractName } = json
342
+ $require.True(Array.isArray(abi), `Invalid abi json: ${path}. Expected the "abi" property to be the array.`);
343
+
339
344
  let source = await this.getSources(contractName, {
340
345
  sourcePath: sourceName,
341
346
  contractName,
@@ -1,7 +1,7 @@
1
1
  import alot from 'alot';
2
2
  import { type TAbiItem } from '@dequanto/types/TAbi';
3
3
  import { TAddress } from '@dequanto/models/TAddress';
4
- import { File } from 'atma-io';
4
+ import { env, File } from 'atma-io';
5
5
  import { class_Uri } from 'atma-utils';
6
6
  import { $abiType } from '@dequanto/utils/$abiType';
7
7
  import { $date } from '@dequanto/utils/$date';
@@ -19,11 +19,11 @@ import { $str } from '@dequanto/solidity/utils/$str';
19
19
 
20
20
  export class GeneratorFromAbi {
21
21
 
22
- static get Gen () {
22
+ static get Gen() {
23
23
  return Gen;
24
24
  }
25
25
 
26
- async generate (abiJson: TAbiItem[], opts: {
26
+ async generate(abiJson: TAbiItem[], opts: {
27
27
  network: TPlatform
28
28
  name: string
29
29
  contractName: string
@@ -88,7 +88,6 @@ export class GeneratorFromAbi {
88
88
  .map(x => Gen.serializeEvent(x))
89
89
  .filter(Boolean)
90
90
  .map(Str.formatMethod);
91
- ;
92
91
 
93
92
  let eventTypes = alot(abiJson)
94
93
  .filter(x => x.type === 'event')
@@ -120,7 +119,6 @@ export class GeneratorFromAbi {
120
119
  // .map(x => Gen.serializeEventInterface(x))
121
120
  // .filter(Boolean)
122
121
  // .map(Str.formatMethod);
123
- ;
124
122
 
125
123
  let methods = methodsArr.join('\n\n');
126
124
  let events = eventsArr.join('\n\n');
@@ -223,7 +221,7 @@ export class GeneratorFromAbi {
223
221
  : void 0,
224
222
  class: this.getRelativePath(outputPath)
225
223
  };
226
- let metaProperty = `$meta = ${ JSON.stringify(meta, null, 4) }` ;
224
+ let metaProperty = `$meta = ${JSON.stringify(meta, null, 4)}`;
227
225
 
228
226
 
229
227
  let storageReaderInitializer = '';
@@ -297,7 +295,17 @@ export class GeneratorFromAbi {
297
295
  if (sources && opts.saveSources !== false) {
298
296
  sourceFiles = await alot.fromObject(sources).mapAsync(async entry => {
299
297
  let sourceFilename = /\/?([^/]+$)/.exec(entry.key)[1];
300
- let path = class_Uri.combine(opts.output, outputDirectory, outputFilename, sourceFilename);
298
+ let sourcePath = entry.key;
299
+ let cwd = env.currentDir.toString().toLowerCase();
300
+ if (sourcePath.toLowerCase().startsWith(cwd)) {
301
+ sourcePath = sourcePath.substring(cwd.length + 1);
302
+ }
303
+ // Remove: solc should load deps also from the root directory
304
+ // if (sourcePath.startsWith('@')) {
305
+ // // handle @openzeppelin and other npm packages
306
+ // sourcePath = `node_modules/${ sourcePath }`;
307
+ // }
308
+ let path = class_Uri.combine(opts.output, outputDirectory, outputFilename, sourcePath);
301
309
  await File.writeAsync(path, entry.value.content, { skipHooks: true });
302
310
 
303
311
  $logger.log(`Source code saved: ${path}`);
@@ -315,7 +323,7 @@ export class GeneratorFromAbi {
315
323
  }
316
324
 
317
325
 
318
- private getRelativePath (path: string) {
326
+ private getRelativePath(path: string) {
319
327
  if (path == null) {
320
328
  return path;
321
329
  }
@@ -330,7 +338,7 @@ export class GeneratorFromAbi {
330
338
 
331
339
  namespace Gen {
332
340
 
333
- export function serializeMethodTs (abi: TAbiItem) {
341
+ export function serializeMethodTs(abi: TAbiItem) {
334
342
  if (abi.type === 'constructor') {
335
343
  return serializeConstructorMethodTs(abi);
336
344
  }
@@ -340,7 +348,7 @@ namespace Gen {
340
348
  }
341
349
  return serializeWriteMethodTs(abi);
342
350
  }
343
- export function serializeMethodTsOverloads (abis: TAbiItem[]) {
351
+ export function serializeMethodTsOverloads(abis: TAbiItem[]) {
344
352
  let isRead = abis.every(abi => $abiUtils.isReadMethod(abi));
345
353
  if (isRead) {
346
354
  return serializeReadMethodTsOverloads(abis);
@@ -349,7 +357,7 @@ namespace Gen {
349
357
  }
350
358
 
351
359
  // abi.length > 1 if has method overloads
352
- export function serializeMethodInterfacesTs (name: string, abis: TAbiItem[]) {
360
+ export function serializeMethodInterfacesTs(name: string, abis: TAbiItem[]) {
353
361
  let args = abis.map(abi => {
354
362
  let { fnInputArguments } = serializeArgumentsTs(abi);
355
363
  return `[ ${fnInputArguments} ]`;
@@ -369,7 +377,7 @@ namespace Gen {
369
377
  };
370
378
  }
371
379
 
372
- export function serializeMethodTypeTs (name: string, abis: TAbiItem[]) {
380
+ export function serializeMethodTypeTs(name: string, abis: TAbiItem[]) {
373
381
  let args = abis.map(abi => {
374
382
  let { fnInputArguments } = serializeArgumentsTs(abi);
375
383
  return `[ ${fnInputArguments} ]`;
@@ -389,7 +397,7 @@ namespace Gen {
389
397
  };
390
398
  }
391
399
 
392
- export function serializeMethodInterfacesAllTs (methods: { method: string, interface: string }[] ) {
400
+ export function serializeMethodInterfacesAllTs(methods: { method: string, interface: string }[]) {
393
401
  let fields = methods.map(method => {
394
402
  return ` ${method.method}: ${method.interface}`;
395
403
  });
@@ -403,7 +411,7 @@ namespace Gen {
403
411
  code: code.join('\n')
404
412
  };
405
413
  }
406
- export function serializeEventsInterfacesAllTs (events: TAbiItem[] ) {
414
+ export function serializeEventsInterfacesAllTs(events: TAbiItem[]) {
407
415
  let fields = events.map(item => {
408
416
  return ` ${item.name}: TLog${item.name}Parameters`;
409
417
  });
@@ -417,14 +425,14 @@ namespace Gen {
417
425
  code: code.join('\n')
418
426
  };
419
427
  }
420
- export function serializeEventType (event: TAbiItem ) {
428
+ export function serializeEventType(event: TAbiItem) {
421
429
  let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(event);
422
430
  let outputParams = `{ ${fnInputArguments} }`;
423
431
  let outputArgs = `[ ${fnInputArguments.replace('\n', '')} ]`;
424
432
  let code = [
425
433
  `${event.name}: {`,
426
- ` outputParams: ${ outputParams },`,
427
- ` outputArgs: ${ outputArgs },`,
434
+ ` outputParams: ${outputParams},`,
435
+ ` outputArgs: ${outputArgs},`,
428
436
  `}`
429
437
  ];
430
438
  return {
@@ -432,7 +440,7 @@ namespace Gen {
432
440
  };
433
441
  }
434
442
 
435
- export function serializeEvent (abi: TAbiItem) {
443
+ export function serializeEvent(abi: TAbiItem) {
436
444
  let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
437
445
  return `
438
446
  on${abi.name} (fn?: (event: TClientEventsStreamData<TEventArguments<'${abi.name}'>>) => void): ClientEventsStream<TClientEventsStreamData<TEventArguments<'${abi.name}'>>> {
@@ -440,7 +448,7 @@ namespace Gen {
440
448
  }
441
449
  `;
442
450
  }
443
- export function serializeEventExtractor (abi: TAbiItem) {
451
+ export function serializeEventExtractor(abi: TAbiItem) {
444
452
  return `
445
453
  extractLogs${abi.name} (tx: TEth.TxReceipt): ITxLogItem<TEventParams<'${abi.name}'>>[] {
446
454
  let abi = this.$getAbiItem('event', '${abi.name}');
@@ -448,10 +456,10 @@ namespace Gen {
448
456
  }
449
457
  `;
450
458
  }
451
- export function serializeEventFetcher (abi: TAbiItem) {
459
+ export function serializeEventFetcher(abi: TAbiItem) {
452
460
  let inputs = abi.inputs;
453
461
  let indexed = alot(inputs).takeWhile(x => x.indexed).toArray();
454
- let indexedParams = indexed.map(param => `${param.name}?: ${ $abiType.getTsType(param.type, param) }`)
462
+ let indexedParams = indexed.map(param => `${param.name}?: ${$abiType.getTsType(param.type, param)}`)
455
463
  return `
456
464
  async getPastLogs${abi.name} (options?: {
457
465
  fromBlock?: number | Date
@@ -488,7 +496,7 @@ namespace Gen {
488
496
  return `function ${abi.name}(${params}) ${returnsStr}`.trim();
489
497
  }
490
498
 
491
- function serializeReadMethodTs (abi: TAbiItem) {
499
+ function serializeReadMethodTs(abi: TAbiItem) {
492
500
  let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
493
501
  if (callInputArguments) {
494
502
  callInputArguments = `, ${callInputArguments}`;
@@ -500,7 +508,7 @@ namespace Gen {
500
508
  }
501
509
  `;
502
510
  }
503
- function serializeReadMethodTsOverloads (abis: TAbiItem[]) {
511
+ function serializeReadMethodTsOverloads(abis: TAbiItem[]) {
504
512
  let overrides = abis.map(abi => {
505
513
  let { fnInputArguments, fnResult } = serializeArgumentsTs(abi);
506
514
  return `
@@ -513,7 +521,7 @@ namespace Gen {
513
521
  let { fnResult } = serializeArgumentsTs(abi);
514
522
  let sigs = abis.map(abi => serializeMethodAbi(abi)).map(x => `'${x}'`).join(', ');
515
523
  return `
516
- ${ overrides }
524
+ ${overrides}
517
525
  async ${abi.name} (...args): ${fnResult} {
518
526
  let abi = this.$getAbiItemOverload([ ${sigs} ], args);
519
527
  return this.$read(abi, ...args);
@@ -521,7 +529,7 @@ namespace Gen {
521
529
  `;
522
530
  }
523
531
 
524
- function serializeWriteMethodTs (abi: TAbiItem) {
532
+ function serializeWriteMethodTs(abi: TAbiItem) {
525
533
  let { fnInputArguments, callInputArguments } = serializeArgumentsTs(abi);
526
534
  if (callInputArguments) {
527
535
  callInputArguments = `, ${callInputArguments}`;
@@ -534,7 +542,7 @@ namespace Gen {
534
542
  }
535
543
  `;
536
544
  }
537
- function serializeConstructorMethodTs (abi: TAbiItem) {
545
+ function serializeConstructorMethodTs(abi: TAbiItem) {
538
546
  let { fnInputArguments, callInputArguments } = serializeArgumentsTs(abi);
539
547
  if (callInputArguments) {
540
548
  callInputArguments = `, ${callInputArguments}`;
@@ -545,7 +553,7 @@ namespace Gen {
545
553
  }
546
554
  `;
547
555
  }
548
- function serializeWriteMethodTsOverloads (abis: TAbiItem[]) {
556
+ function serializeWriteMethodTsOverloads(abis: TAbiItem[]) {
549
557
  let overrides = abis.map(abi => {
550
558
  let { fnInputArguments, fnResult } = serializeArgumentsTs(abi);
551
559
  return `
@@ -557,7 +565,7 @@ namespace Gen {
557
565
  let abi = abis[0];
558
566
  let sigs = abis.map(abi => serializeMethodAbi(abi)).map(x => `'${x}'`).join(', ');
559
567
  return `
560
- ${ overrides }
568
+ ${overrides}
561
569
  async ${abi.name} (sender: TSender, ...args): Promise<TxWriter> {
562
570
  let abi = this.$getAbiItemOverload([ ${sigs} ], args);
563
571
  return this.$write(abi, sender, ...args);
@@ -565,7 +573,7 @@ namespace Gen {
565
573
  `;
566
574
  }
567
575
 
568
- function serializeArgumentsTs (abi: TAbiItem) {
576
+ function serializeArgumentsTs(abi: TAbiItem) {
569
577
  let inputs = abi.inputs.map((input, i) => {
570
578
  let result = { ...input };
571
579
  if (result.name == null || result.name === '') {
@@ -599,10 +607,10 @@ namespace Gen {
599
607
  return { fnInputArguments, callInputArguments, fnResult };
600
608
  }
601
609
 
602
- function isObjectParams (params: { name?, type }[]) {
610
+ function isObjectParams(params: { name?, type }[]) {
603
611
  return params?.every(x => Boolean(x.name));
604
612
  }
605
- function serializeMethodAbiReturns (params: { name?, type, components? }[]) {
613
+ function serializeMethodAbiReturns(params: { name?, type, components?}[]) {
606
614
  if (params == null) {
607
615
  return '';
608
616
  }
@@ -611,23 +619,23 @@ namespace Gen {
611
619
  // }
612
620
  return params?.map(x => serializeMethodAbiReturnsSingle(x)).join(',');
613
621
  }
614
- function serializeMethodAbiReturnsSingle (param: { name?, type, components? }) {
622
+ function serializeMethodAbiReturnsSingle(param: { name?, type, components?}) {
615
623
  if (param == null) {
616
624
  return null;
617
625
  }
618
626
  if (param.components) {
619
627
  // tuple, tuple[]
620
628
  let fields = serializeMethodAbiReturns(param.components);
621
- return `[${fields}]${ param.type === 'tuple[]' ? '[]' : '' }`;
629
+ return `[${fields}]${param.type === 'tuple[]' ? '[]' : ''}`;
622
630
  }
623
631
  // if (param.name && param.type) {
624
632
  // return `${param.type} ${param.name}`;
625
633
  // }
626
634
  return param.type;
627
635
  }
628
- function serializeMethodTsReturns (params: { name?, type }[]) {
636
+ function serializeMethodTsReturns(params: { name?, type }[]) {
629
637
  if (params == null || params.length === 0) {
630
- params = [ { name: '', type: 'uint256' } ];
638
+ params = [{ name: '', type: 'uint256' }];
631
639
  }
632
640
 
633
641
  let tsTypes = params.map(param => {
@@ -668,19 +676,19 @@ namespace Gen {
668
676
  lines.push(` type: '${error.name}'`);
669
677
  lines.push(` params: {`);
670
678
  error.inputs.forEach(input => {
671
- lines.push(` ${input.name}: ${ $abiType.getTsType( input.type, input ) }`);
679
+ lines.push(` ${input.name}: ${$abiType.getTsType(input.type, input)}`);
672
680
  });
673
681
  lines.push(` }`);
674
682
  lines.push(` }`);
675
683
  });
676
684
 
677
- lines.push(` export type Error = ${ errors.map(x => x.name).join(' | ') }`);
685
+ lines.push(` export type Error = ${errors.map(x => x.name).join(' | ')}`);
678
686
  lines.push(`}`);
679
687
 
680
688
  return lines.join('\n');
681
689
  }
682
690
 
683
- export function serializeTxCallerMethods (className: string, abiJson: TAbiItem[]): string {
691
+ export function serializeTxCallerMethods(className: string, abiJson: TAbiItem[]): string {
684
692
  let methods = abiJson.filter(x => x.type === 'function');
685
693
  let writeMethods = methods.filter(abi => $abiUtils.isReadMethod(abi) === false);
686
694
  let lines = [];
@@ -691,16 +699,16 @@ namespace Gen {
691
699
  return lines.join('\n');
692
700
 
693
701
 
694
- function serializeMethodTs (abi: TAbiItem) {
702
+ function serializeMethodTs(abi: TAbiItem) {
695
703
  let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
696
704
  if (callInputArguments) {
697
705
  callInputArguments = `, ${callInputArguments}`;
698
706
  }
699
- return ` ${abi.name} (sender: TSender, ${fnInputArguments}): Promise<{ error?: Error & { data?: { type: string, params } }, result? }>`;
707
+ return ` ${abi.name} (sender: TSender, ${fnInputArguments}): Promise<{ error?: Error & { data?: { type: string, params } }, result? }>`;
700
708
  }
701
709
  }
702
710
 
703
- export function serializeTxDataMethods (className: string, abiJson: TAbiItem[]): string {
711
+ export function serializeTxDataMethods(className: string, abiJson: TAbiItem[]): string {
704
712
  let methods = abiJson.filter(x => x.type === 'function');
705
713
  let writeMethods = methods.filter(abi => $abiUtils.isReadMethod(abi) === false);
706
714
  let lines = [];
@@ -711,7 +719,7 @@ namespace Gen {
711
719
  return lines.join('\n');
712
720
 
713
721
 
714
- function serializeInterfaceMethodTs (abi: TAbiItem) {
722
+ function serializeInterfaceMethodTs(abi: TAbiItem) {
715
723
  let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
716
724
  if (callInputArguments) {
717
725
  callInputArguments = `, ${callInputArguments}`;
@@ -30,6 +30,8 @@ import { $address } from '@dequanto/utils/$address';
30
30
  import { $promise } from '@dequanto/utils/$promise';
31
31
  import { $date } from '@dequanto/utils/$date';
32
32
  import { $hex } from '@dequanto/utils/$hex';
33
+ import { TAddress } from '@dequanto/models/TAddress';
34
+ import { $contract } from '@dequanto/utils/$contract';
33
35
 
34
36
  export class HardhatProvider {
35
37
 
@@ -272,6 +274,7 @@ export class HardhatProvider {
272
274
  artifact: string
273
275
  source: IGeneratorSources
274
276
  ContractCtor: Constructor<T>
277
+ linkReferences?: Record<string /* path */, Record<string /* name */, any>>
275
278
  }> {
276
279
 
277
280
  solContractPath = $path.normalize(solContractPath);
@@ -353,7 +356,12 @@ export class HardhatProvider {
353
356
  });
354
357
  }
355
358
 
356
- let { abi, bytecode, contractName } = await File.readAsync<{ abi, bytecode, contractName }> (output);
359
+ let { abi, bytecode, contractName, linkReferences } = await File.readAsync<{
360
+ abi,
361
+ bytecode,
362
+ contractName,
363
+ linkReferences: Record<string /* path */, Record<string /* name */, any>>
364
+ }> (output);
357
365
  let files = await Directory.readFilesAsync(dir, '*.sol');
358
366
  let { contract, ContractCtor } = ContractClassFactory.fromAbi<T>(null, abi, null, null, {
359
367
  $meta: {
@@ -379,6 +387,7 @@ export class HardhatProvider {
379
387
  contractName: contractName ?? options?.contractName,
380
388
  files: fileMap
381
389
  },
390
+ linkReferences,
382
391
  ContractCtor
383
392
  };
384
393
  }
@@ -399,6 +408,25 @@ export class HardhatProvider {
399
408
  }
400
409
  }
401
410
 
411
+ async linkReferences (
412
+ bytecode: TEth.Hex,
413
+ linkReferences: Record<string /* path */, Record<string /* name */, any>>,
414
+ addresses: Record<string /* name */, TAddress>
415
+ ): Promise<TEth.Hex> {
416
+ for (let path in linkReferences) {
417
+ for (let name in linkReferences[path]) {
418
+ let address = addresses[name];
419
+ $require.AddressNotEmpty(address, `Address for "${path}:${name}" not found`);
420
+
421
+ let str = `${path}:${name}`;
422
+ let hash = $contract.keccak256(str, 'hex').substring(2, 34 + 2);
423
+ let placeholder = `__$${hash}$__`;
424
+ bytecode = bytecode.replaceAll(placeholder, address.substring(2)) as TEth.Hex;
425
+ }
426
+ }
427
+ return bytecode;
428
+ }
429
+
402
430
  async compileCode (solidityCode: string, options: Parameters<HardhatProvider['deploySol']>[1] = {}) {
403
431
  let { tmpFile, tmpDir, options: optionsNormalized } = await this.createTmpFile(solidityCode, options);
404
432
 
@@ -1,4 +1,4 @@
1
- import { Constructor } from 'atma-utils'
1
+ import type { Constructor } from 'atma-utils'
2
2
  import { JsonConvert } from 'class-json'
3
3
  import { JsonStoreFs } from './JsonStoreFs';
4
4
 
@@ -48,7 +48,7 @@ export class SafeTx {
48
48
  [`uint8`, tx.operation ],
49
49
  [`address`, tx.to],
50
50
  [`uint256`, tx.value],
51
- [`uint256`, $hex.getBytesLength(tx.data)],
51
+ [`uint256`, $hex.getBytesLength(tx.data ?? '0x')],
52
52
  [`bytes`, tx.data]
53
53
  );
54
54
  });
@@ -0,0 +1,123 @@
1
+ import { ContractBase } from '@dequanto/contracts/ContractBase';
2
+ import { IAccount } from '@dequanto/models/TAccount'
3
+ import { TAddress } from '@dequanto/models/TAddress'
4
+ import { TEth } from '@dequanto/models/TEth'
5
+ import { TxWriter } from '@dequanto/txs/TxWriter';
6
+ import { TTxWriteMethodKeys } from '@dequanto/utils/types';
7
+
8
+ export interface ITimelockService {
9
+
10
+ getPendingByTitle (title: string): Promise<{
11
+ status: ETimelockTxStatus;
12
+ schedule: ITimelockTx;
13
+ }>
14
+
15
+ executePendingByTitle (sender: IAccount, title: string): Promise<{
16
+ tx: TxWriter;
17
+ schedule: ITimelockTx;
18
+ }>
19
+
20
+ executePending (sender: IAccount, txParams: ITimelockTx): Promise<{
21
+ tx: TxWriter;
22
+ schedule: ITimelockTx;
23
+ }>
24
+ /** Schedules-Wait-Execute a task distinguished by the unique task name
25
+ *
26
+ * 1. if schedule doesn't exist, create it
27
+ * 2. if schedule date NOT yet ready, exit
28
+ * 3. if schedule date IS ready, execute it
29
+ * 4. if executed, exit
30
+ */
31
+ process <
32
+ T extends ContractBase,
33
+ TMethodName extends TTxWriteMethodKeys<T>,
34
+ > (
35
+ uniqueTaskName: string,
36
+ sender: IAccount,
37
+ contract: T,
38
+ method: TMethodName,
39
+ ...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
40
+ ): Promise<{
41
+ prevStatus: ETimelockTxStatus
42
+ status: ETimelockTxStatus
43
+ schedule: ITimelockTx
44
+ tx?: TEth.Hex
45
+ }>
46
+
47
+ /** Schedules-Wait-Execute a task distinguished by the unique task name
48
+ *
49
+ * 1. if schedule doesn't exist, create it
50
+ * 2. if schedule date NOT yet ready, exit
51
+ * 3. if schedule date IS ready, execute it
52
+ * 4. if executed, exit
53
+ */
54
+ processBatch <
55
+ T extends ContractBase,
56
+ TMethodName extends TTxWriteMethodKeys<T>,
57
+ > (
58
+ uniqueTaskName: string,
59
+ sender: IAccount,
60
+ batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
61
+ ): Promise<{
62
+ prevStatus: ETimelockTxStatus
63
+ status: ETimelockTxStatus
64
+ schedule: ITimelockTx
65
+ tx?: TEth.Hex
66
+ }>
67
+ }
68
+
69
+ export interface ITimelockTx {
70
+ // operation hash, unique as includes random salt
71
+ id: TEth.Hex
72
+ // operation key, should be unique only within pending operations
73
+ key: TEth.Hex
74
+
75
+ salt: TEth.Hex
76
+
77
+ title?: string
78
+ sender: string | TAddress
79
+
80
+ to: TAddress | TAddress[]
81
+ data: TEth.Hex | TEth.Hex[]
82
+ value?: TEth.Hex | TEth.Hex[]
83
+ predecessor?: TEth.Hex
84
+
85
+ createdAt: number
86
+ validAt: number
87
+
88
+ status: 'pending' | 'completed' | 'canceled'
89
+ txSchedule: TEth.Hex
90
+ txExecute?: TEth.Hex
91
+ txCancel?: TEth.Hex
92
+ }
93
+
94
+ export interface ITimelockTxParams {
95
+ title?: string
96
+
97
+ sender: IAccount
98
+ to: TAddress | TAddress[]
99
+ data: TEth.Hex | TEth.Hex[]
100
+
101
+ value?: bigint | bigint[]
102
+ predecessor?: TEth.Hex
103
+ delay?: bigint
104
+ }
105
+
106
+ export interface ITimelockTxParamsNormalized {
107
+ sender: IAccount
108
+ to: TAddress | TAddress[]
109
+ data: TEth.Hex | TEth.Hex[]
110
+
111
+ title: string
112
+ value: bigint | bigint[]
113
+ predecessor: TEth.Hex
114
+ delay: bigint
115
+ }
116
+
117
+
118
+ export enum ETimelockTxStatus {
119
+ None = 'none',
120
+ Pending = 'pending',
121
+ Ready = 'ready',
122
+ Executed = 'completed',
123
+ }
@@ -1,5 +1,5 @@
1
+ import memd from 'memd';
1
2
  import { TimelockController } from '@dequanto-contracts/openzeppelin/TimelockController';
2
- import { Web3Client } from '@dequanto/clients/Web3Client';
3
3
  import { ContractBase } from '@dequanto/contracts/ContractBase';
4
4
  import { JsonArrayStore } from '@dequanto/json/JsonArrayStore';
5
5
  import { IAccount } from '@dequanto/models/TAccount';
@@ -16,69 +16,16 @@ import { $number } from '@dequanto/utils/$number';
16
16
  import { $platform } from '@dequanto/utils/$platform';
17
17
  import { $require } from '@dequanto/utils/$require';
18
18
  import { File } from 'atma-io';
19
- import memd from 'memd';
20
-
21
-
22
- interface ITimelockTx {
23
- // operation hash, unique as includes random salt
24
- id: TEth.Hex
25
- // operation key, should be unique only within pending operations
26
- key: TEth.Hex
27
-
28
- salt: TEth.Hex
29
-
30
- title?: string
31
- sender: string | TAddress
32
-
33
- to: TAddress | TAddress[]
34
- data: TEth.Hex | TEth.Hex[]
35
- value?: TEth.Hex | TEth.Hex[]
36
- predecessor?: TEth.Hex
37
-
38
- createdAt: number
39
- validAt: number
40
-
41
- status: 'pending' | 'completed' | 'canceled'
42
- txSchedule: TEth.Hex
43
- txExecute?: TEth.Hex
44
- txCancel?: TEth.Hex
45
- }
19
+ import { ITimelockTx, ITimelockTxParamsNormalized, ITimelockTxParams, ITimelockService, ETimelockTxStatus } from './ITimelockService';
20
+ import { TTxWriteMethodKeys } from '@dequanto/utils/types';
46
21
 
47
- interface ITimelockTxParams {
48
- title?: string
49
22
 
50
- sender: IAccount
51
- to: TAddress | TAddress[]
52
- data: TEth.Hex | TEth.Hex[]
53
-
54
- value?: bigint | bigint[]
55
- predecessor?: TEth.Hex
56
- delay?: bigint
57
- }
58
-
59
- interface ITimelockTxParamsNormalized {
60
- sender: IAccount
61
- to: TAddress | TAddress[]
62
- data: TEth.Hex | TEth.Hex[]
63
-
64
- title: string
65
- value: bigint | bigint[]
66
- predecessor: TEth.Hex
67
- delay: bigint
68
- }
69
-
70
- export enum ETimelockTxStatus {
71
- None = 'none',
72
- Pending = 'pending',
73
- Ready = 'ready',
74
- Executed = 'completed',
75
- }
76
23
 
77
24
  type TTimelockController = ContractBase & Pick<TimelockController,
78
25
  'schedule' | 'scheduleBatch' | 'execute' | 'executeBatch' | 'getMinDelay' | 'cancel'
79
26
  >;
80
27
 
81
- export class TimelockService {
28
+ export class TimelockService implements ITimelockService {
82
29
 
83
30
 
84
31
  constructor(
@@ -90,7 +37,7 @@ export class TimelockService {
90
37
  // Only for hardhat client. Default: false
91
38
  execute?: boolean
92
39
 
93
- // Directory to store the submitted schedules, default: `./data/0x/`
40
+ // Directory to store the submitted schedules, default: `./0x/data/`
94
41
  dir?: string
95
42
  }
96
43
  ) {
@@ -143,7 +90,7 @@ export class TimelockService {
143
90
  */
144
91
  async process <
145
92
  T extends ContractBase,
146
- TMethodName extends WriteMethodKeys<T>,
93
+ TMethodName extends TTxWriteMethodKeys<T>,
147
94
  > (
148
95
  uniqueTaskName: string,
149
96
  sender: IAccount,
@@ -170,7 +117,7 @@ export class TimelockService {
170
117
  */
171
118
  async processBatch <
172
119
  T extends ContractBase,
173
- TMethodName extends WriteMethodKeys<T>,
120
+ TMethodName extends TTxWriteMethodKeys<T>,
174
121
  > (
175
122
  uniqueTaskName: string,
176
123
  sender: IAccount,
@@ -225,7 +172,7 @@ export class TimelockService {
225
172
 
226
173
  async scheduleCall <
227
174
  T extends ContractBase,
228
- TMethodName extends WriteMethodKeys<T>,
175
+ TMethodName extends TTxWriteMethodKeys<T>,
229
176
  > (
230
177
  sender: IAccount,
231
178
  contract: T,
@@ -253,7 +200,7 @@ export class TimelockService {
253
200
 
254
201
  async executeCall <
255
202
  T extends ContractBase,
256
- TMethodName extends WriteMethodKeys<T>,
203
+ TMethodName extends TTxWriteMethodKeys<T>,
257
204
  > (
258
205
  sender: IAccount,
259
206
  contract: T,
@@ -587,7 +534,7 @@ export class TimelockService {
587
534
  }
588
535
  private async getTxParamsNormalizedFromContract <
589
536
  T extends ContractBase,
590
- TMethodName extends WriteMethodKeys<T>,
537
+ TMethodName extends TTxWriteMethodKeys<T>,
591
538
  > (
592
539
  title: string | '' | null,
593
540
  sender: IAccount,
@@ -630,7 +577,7 @@ export class TimelockService {
630
577
  @memd.deco.memoize({ perInstance: true })
631
578
  private async getStore () {
632
579
  let { platform, network } = this.timelock.client;
633
- let dir = this.options?.dir ?? `data/0x`;
580
+ let dir = this.options?.dir ?? `0x/data`;
634
581
 
635
582
  let path = getPath(platform, dir);
636
583
  if (platform === 'hardhat' && network !== platform) {
@@ -641,7 +588,7 @@ export class TimelockService {
641
588
  }
642
589
  }
643
590
  function getPath (p: TPlatform, directory: string) {
644
- return `/${directory}/timelocks-${ $platform.toPath(p) }.json`
591
+ return `/${directory}/timelocks/${ $platform.toPath(p) }.json`
645
592
  }
646
593
  return new JsonArrayStore<ITimelockTx>({
647
594
  path,
@@ -650,12 +597,6 @@ export class TimelockService {
650
597
  }
651
598
  }
652
599
 
653
- type WriteMethodKeys<T> = {
654
- [P in keyof T]: T[P] extends ((sender: IAccount, ...args) => (Promise<TxWriter>)) ? P : never;
655
- }[keyof T];
656
-
657
-
658
-
659
600
  namespace util {
660
601
  export function toBigInt (mix: string | string[]) {
661
602
  if (typeof mix === 'string') {
@@ -0,0 +1,310 @@
1
+ import { TimelockController } from '@dequanto-contracts/openzeppelin/TimelockController';
2
+ import { ContractBase } from '@dequanto/contracts/ContractBase';
3
+ import { IAccount } from '@dequanto/models/TAccount';
4
+ import { TAddress } from '@dequanto/models/TAddress';
5
+ import { TEth } from '@dequanto/models/TEth';
6
+ import { SafeTx } from '@dequanto/safe/SafeTx';
7
+ import { TxWriter } from '@dequanto/txs/TxWriter';
8
+ import { $contract } from '@dequanto/utils/$contract';
9
+ import { $hex } from '@dequanto/utils/$hex';
10
+ import memd from 'memd';
11
+ import { ETimelockTxStatus, ITimelockTx, ITimelockTxParamsNormalized, ITimelockTxParams, ITimelockService } from './ITimelockService';
12
+
13
+
14
+ export class TimelockServiceFallbackSafe implements ITimelockService {
15
+
16
+
17
+ constructor(
18
+ private safeTx: SafeTx,
19
+ private options?: {
20
+ // Only for hardhat client. Default: true
21
+ simulate?: boolean
22
+
23
+ // Only for hardhat client. Default: false
24
+ execute?: boolean
25
+
26
+ // Directory to store the submitted schedules, default: `./0x/data/`
27
+ dir?: string
28
+ }
29
+ ) {
30
+
31
+ }
32
+
33
+ async getPendingByTitle(title: string): Promise<{
34
+ status: ETimelockTxStatus;
35
+ schedule: ITimelockTx;
36
+ }> {
37
+ return { status: ETimelockTxStatus.None, schedule: null };
38
+ }
39
+
40
+ async executePendingByTitle(sender: IAccount, title: string): Promise<{
41
+ tx: TxWriter;
42
+ schedule: ITimelockTx;
43
+ }> {
44
+ throw new Error(`No pending tx found ${title}`);
45
+ }
46
+
47
+ async executePending(sender: IAccount, txParams: ITimelockTx): Promise<{
48
+ tx: TxWriter;
49
+ schedule: ITimelockTx;
50
+ }> {
51
+ let result = await this.execute({
52
+ ...txParams,
53
+ value: util.toBigInt(txParams.value),
54
+ sender: sender,
55
+ });
56
+ return result;
57
+ }
58
+
59
+ /** Schedules-Wait-Execute a task distinguished by the unique task name
60
+ *
61
+ * 1. if schedule doesn't exist, create it
62
+ * 2. if schedule date NOT yet ready, exit
63
+ * 3. if schedule date IS ready, execute it
64
+ * 4. if executed, exit
65
+ */
66
+ async process<
67
+ T extends ContractBase,
68
+ TMethodName extends WriteMethodKeys<T>,
69
+ >(
70
+ uniqueTaskName: string,
71
+ sender: IAccount,
72
+ contract: T,
73
+ method: TMethodName,
74
+ ...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
75
+ ): Promise<{
76
+ prevStatus: ETimelockTxStatus
77
+ status: ETimelockTxStatus
78
+ schedule: ITimelockTx
79
+ tx?: TEth.Hex
80
+ }> {
81
+
82
+ let txParams = await this.getTxParamsNormalizedFromContract(uniqueTaskName, sender, contract, method, ...params);
83
+ return await this.processTxParams(txParams);
84
+ }
85
+
86
+ /** Schedules-Wait-Execute a task distinguished by the unique task name
87
+ *
88
+ * 1. if schedule doesn't exist, create it
89
+ * 2. if schedule date NOT yet ready, exit
90
+ * 3. if schedule date IS ready, execute it
91
+ * 4. if executed, exit
92
+ */
93
+ async processBatch<
94
+ T extends ContractBase,
95
+ TMethodName extends WriteMethodKeys<T>,
96
+ >(
97
+ uniqueTaskName: string,
98
+ sender: IAccount,
99
+ batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
100
+ ): Promise<{
101
+ prevStatus: ETimelockTxStatus
102
+ status: ETimelockTxStatus
103
+ schedule: ITimelockTx
104
+ tx?: TEth.Hex
105
+ }> {
106
+
107
+ let txParams = await this.getTxParamsNormalizedFromContractBatch(uniqueTaskName, sender, batch);
108
+ return await this.processTxParams(txParams);
109
+ }
110
+
111
+ private async processTxParams(txParams: ITimelockTxParamsNormalized): Promise<{
112
+ prevStatus: ETimelockTxStatus
113
+ status: ETimelockTxStatus
114
+ schedule: ITimelockTx
115
+ tx?: TEth.Hex
116
+ }> {
117
+
118
+ let result = await this.execute(txParams);
119
+ return {
120
+ prevStatus: ETimelockTxStatus.None,
121
+ status: ETimelockTxStatus.Executed,
122
+ schedule: result.schedule,
123
+ tx: result.tx.tx?.hash
124
+ };
125
+ }
126
+
127
+ async scheduleCall<
128
+ T extends ContractBase,
129
+ TMethodName extends WriteMethodKeys<T>,
130
+ >(
131
+ sender: IAccount,
132
+ contract: T,
133
+ method: TMethodName,
134
+ ...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
135
+ ) {
136
+ let txParams = await this.getTxParamsNormalizedFromContract('', sender, contract, method, ...params);
137
+ let tx = await this.schedule(txParams);
138
+ return tx;
139
+ }
140
+
141
+ /**
142
+ * E.g. `.scheduleCallBatch(title, sender, [ c1.$data().foo(sender), c2.$data().bar(sender, param1) ])`
143
+ */
144
+ async scheduleCallBatch(
145
+ title: string,
146
+ sender: IAccount,
147
+ batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
148
+ ) {
149
+ let txParams = await this.getTxParamsNormalizedFromContractBatch(title, sender, batch);
150
+ let tx = await this.schedule(txParams);
151
+ return tx;
152
+ }
153
+
154
+
155
+ async executeCall<
156
+ T extends ContractBase,
157
+ TMethodName extends WriteMethodKeys<T>,
158
+ >(
159
+ sender: IAccount,
160
+ contract: T,
161
+ method: TMethodName,
162
+ ...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
163
+ ) {
164
+ let txParams = await this.getTxParamsNormalizedFromContract('', sender, contract, method, ...params);
165
+ let tx = await this.execute(txParams);
166
+ return tx;
167
+ }
168
+
169
+ /**
170
+ * E.g. `.scheduleCallBatch(title, sender, [ c1.$data().foo(sender), c2.$data().bar(sender, param1) ])`
171
+ */
172
+ async executeCallBatch(
173
+ title: string,
174
+ sender: IAccount,
175
+ batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
176
+ ) {
177
+ let txParams = await this.getTxParamsNormalizedFromContractBatch(title, sender, batch);
178
+ let tx = await this.execute(txParams);
179
+ return tx;
180
+ }
181
+
182
+ @memd.deco.queued()
183
+ async schedule(params: ITimelockTxParams): Promise<ITimelockTx> {
184
+ let executed = await this.execute(params);
185
+ return executed.schedule;
186
+ }
187
+
188
+ async execute(params: ITimelockTxParams): Promise<{
189
+ tx: TxWriter
190
+ schedule: ITimelockTx
191
+ }> {
192
+ let txParams = await this.getTxParamsNormalized(params);
193
+ let isBatch = Array.isArray(txParams.to);
194
+ let tx = isBatch
195
+ ? await this.safeTx.executeBatch(
196
+ ...(txParams.to as TAddress[]).map((to, i) => {
197
+ return {
198
+ to,
199
+ value: (txParams.value as bigint[])[i],
200
+ data: (txParams.data as TEth.Hex[])[i]
201
+ }
202
+ }),
203
+ )
204
+ : await this.safeTx.execute(
205
+ {
206
+ to: txParams.to as TAddress,
207
+ value: txParams.value as bigint,
208
+ data: txParams.data as TEth.Hex
209
+ }
210
+ );
211
+
212
+ let receipt = await tx.wait();
213
+ return {
214
+ tx: tx,
215
+ schedule: {
216
+ txExecute: receipt.transactionHash,
217
+ status: ETimelockTxStatus.Executed,
218
+ } as ITimelockTx
219
+ };
220
+ }
221
+
222
+ async cancel(sender: IAccount, id: TEth.Hex, opts?: { storage?: boolean }) {
223
+ throw new Error(`Not implemented for Safe Fallback`);
224
+ }
225
+
226
+ public async clearSchedules() {
227
+ // SafeFallback has no schedules
228
+ }
229
+
230
+ public async updateSchedule(txInfo: Partial<ITimelockTx>) {
231
+ // SafeFallback has no schedules
232
+ }
233
+
234
+ public async debugMoveToSchedule(txInfo: ITimelockTx) {
235
+ // SafeFallback has no schedules
236
+ }
237
+
238
+ private async getTxParamsNormalized(params: ITimelockTxParams): Promise<ITimelockTxParamsNormalized> {
239
+ let value = params.value ?? 0n;
240
+ let data = params.data;
241
+ let predecessor = params.predecessor ?? $hex.ZERO;
242
+ let delay = 0n;
243
+ return {
244
+ title: params.title ?? '',
245
+ sender: params.sender,
246
+ to: params.to,
247
+
248
+ value,
249
+ data,
250
+ predecessor,
251
+ delay
252
+ };
253
+ }
254
+ private async getTxParamsNormalizedFromContract<
255
+ T extends ContractBase,
256
+ TMethodName extends WriteMethodKeys<T>,
257
+ >(
258
+ title: string | '' | null,
259
+ sender: IAccount,
260
+ contract: T,
261
+ method: TMethodName,
262
+ ...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
263
+ ): Promise<ITimelockTxParamsNormalized> {
264
+
265
+ let txData = await contract.$data()[method](sender, ...params);
266
+ let abi = contract.abi?.find(x => x.name === method);
267
+ let methodCallStr = $contract.formatCallFromAbi(abi, params);
268
+
269
+ title ??= `${contract.constructor.name}.${methodCallStr}`;
270
+
271
+ let tx = await this.getTxParamsNormalized({
272
+ title,
273
+ sender,
274
+ to: txData.to,
275
+ data: txData.data,
276
+ })
277
+ return tx;
278
+ }
279
+ private async getTxParamsNormalizedFromContractBatch(
280
+ title: string | '' | null,
281
+ sender: IAccount,
282
+ batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
283
+ ): Promise<ITimelockTxParamsNormalized> {
284
+
285
+
286
+ let tx = await this.getTxParamsNormalized({
287
+ title,
288
+ sender,
289
+ to: batch.map(x => x.to),
290
+ data: batch.map(x => x.data),
291
+ value: batch.map(x => BigInt(x.value)),
292
+ })
293
+ return tx;
294
+ }
295
+ }
296
+
297
+ type WriteMethodKeys<T> = {
298
+ [P in keyof T]: T[P] extends ((sender: IAccount, ...args) => (Promise<TxWriter>)) ? P : never;
299
+ }[keyof T];
300
+
301
+
302
+
303
+ namespace util {
304
+ export function toBigInt(mix: string | string[]) {
305
+ if (typeof mix === 'string') {
306
+ return BigInt(mix);
307
+ }
308
+ return mix.map(BigInt);
309
+ }
310
+ }
@@ -83,7 +83,7 @@ export class TokenTransferService {
83
83
  token = await this.getToken(token);
84
84
  amount = this.getAmount(amount, token);
85
85
 
86
- let isNative = this.tokenService.isNative(token.address);
86
+ let isNative = this.tokenService.isNative(token);
87
87
  if (isNative) {
88
88
  return this.transferNative(from, to, amount);
89
89
  }
@@ -98,7 +98,7 @@ export class TokenTransferService {
98
98
  return token;
99
99
  }
100
100
  isNativeToken (token: string | IToken): boolean {
101
- return this.tokenService.isNative(typeof token === 'string' ? token : token.address);
101
+ return this.tokenService.isNative(typeof token === 'string' ? token : (token.address ?? token.symbol));
102
102
  }
103
103
 
104
104
  private async transferNativeAll (from: EoAccount, to: TAddress, opts?: { remainder: number | bigint }): Promise<TxWriter> {
@@ -1,4 +1,6 @@
1
+ import { IAccount } from '@dequanto/models/TAccount';
1
2
  import { TEth } from '@dequanto/models/TEth';
3
+ import { TxWriter } from '@dequanto/txs/TxWriter';
2
4
 
3
5
  type NoneMethodKeys<T> = {
4
6
  [P in keyof T]: T[P] extends ((...args) => any) ? never : P;
@@ -31,11 +33,9 @@ export type ThenArg<T> = T extends PromiseLike<infer U> ? U : T
31
33
 
32
34
  export type ParametersFromSecond<T extends (x, ...args: any) => any> = T extends (x, ...args: infer P) => any ? P : never;
33
35
 
34
-
35
36
  export type TCallback<TResult = any> = (error: Error, result?: TResult) => void
36
37
  export type TFnWithCallback<TArgs extends any[], TResult> = (...args: [...TArgs, TCallback<TResult>]) => void
37
38
 
38
-
39
39
  export type DataLike<T> = T extends bigint
40
40
  ? bigint | number | TEth.Hex
41
41
  : (T extends number
@@ -49,10 +49,12 @@ export type DataLike<T> = T extends bigint
49
49
  )
50
50
  );
51
51
 
52
-
53
-
54
52
  export type TOverrideReturns<TObject extends { }, TReturn> = {
55
53
  [P in keyof TObject]: TObject[P] extends (...args: infer TParams) => any
56
54
  ? (...args: TParams) => TReturn
57
55
  : never;
58
56
  }
57
+
58
+ export type TTxWriteMethodKeys<T> = {
59
+ [P in keyof T]: T[P] extends ((sender: IAccount, ...args) => (Promise<TxWriter>)) ? P : never;
60
+ }[keyof T];
package/test/ns.spec.ts CHANGED
@@ -26,7 +26,7 @@ UTest({
26
26
  eq_(urlRecord, 'https://vitalik.ca');
27
27
 
28
28
  let { value: contentHash } = await ns.getContent('vitalik.eth');
29
- eq_(contentHash, `ipfs://QmckiKNiVmNyQWyxQVVouUjk2zAg8nVN8qrtvoUivJ2mt8`);
29
+ eq_(contentHash, `ipfs://QmdTpYZMWsNFh6ja6SYUrmDQ6YnBc2vpS1QkV9gfekociF`);
30
30
  },
31
31
  async 'reverse registrar' () {
32
32
  let ns = new NameService(Web3ClientFactory.get('eth'));
@@ -1,6 +1,6 @@
1
1
  import { TimelockController } from '@dequanto-contracts/openzeppelin/TimelockController';
2
2
  import { HardhatProvider } from '@dequanto/hardhat/HardhatProvider'
3
- import { TimelockService } from '@dequanto/services/TimelockService';
3
+ import { TimelockService } from '@dequanto/services/TimelockService/TimelockService';
4
4
  import { $address } from '@dequanto/utils/$address';
5
5
  import { $date } from '@dequanto/utils/$date';
6
6
  import { l } from '@dequanto/utils/$logger';
package/tools/copy-dts.ts CHANGED
@@ -12,7 +12,7 @@ async function process () {
12
12
  await await alot(files).forEachAsync(async (file, i) => {
13
13
 
14
14
  let path = file.uri.toString();
15
- let typesRoot = path.replace(/\/lib\/types\/.$/, '/lib/types/');
15
+ let typesRoot = path.replace(/\/lib\/types\/.+$/, '/lib/types/');
16
16
  let cjsFile = path.replace('/lib/types/', '/lib/cjs/').replace(/\.d\.ts$/, '.js');
17
17
  let mjsFile = path.replace('/lib/types/', '/lib/esm/').replace(/\.d\.ts$/, '.mjs');
18
18
 
@@ -28,8 +28,10 @@ async function process () {
28
28
 
29
29
  content = content.replace(/from (?<q>['"])(?<path>@dequanto\/[^'"]+)['"]/g, (match, q, importPath) => {
30
30
  let importPathFull = importPath.replace(/^@dequanto\//, typesRoot);
31
- let importPathRelative = new class_Uri(importPathFull).toRelativeString(typesRoot);
32
-
31
+ let importPathRelative = new class_Uri(importPathFull).toRelativeString(path + '.js');
32
+ if (importPathRelative.startsWith('.') === false) {
33
+ importPathRelative = './' + importPathRelative;
34
+ }
33
35
  return `from ${q}${importPathRelative}${q}`;
34
36
  });
35
37
 
@@ -43,6 +45,8 @@ async function process () {
43
45
  }
44
46
 
45
47
  }).toArrayAsync();
48
+
49
+ await Directory.removeAsync(`./lib/types/`);
46
50
  }
47
51
 
48
52
  export { process }