dequanto 0.2.50 → 0.2.51

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 (67) hide show
  1. package/lib/cjs/explorer/BlockchainExplorer.js +9 -9
  2. package/lib/cjs/explorer/BlockchainExplorer.js.map +1 -1
  3. package/lib/cjs/explorer/BlockchainExplorerFactory.js +1 -1
  4. package/lib/cjs/explorer/BlockchainExplorerFactory.js.map +1 -1
  5. package/lib/cjs/explorer/BlockchainExplorerProvider.js +1 -1
  6. package/lib/cjs/explorer/BlockchainExplorerProvider.js.map +1 -1
  7. package/lib/cjs/explorer/BlockchainExplorerStorage.js +2 -2
  8. package/lib/cjs/explorer/BlockchainExplorerStorage.js.map +1 -1
  9. package/lib/cjs/explorer/ContractVerifier.js +10 -10
  10. package/lib/cjs/explorer/ContractVerifier.js.map +1 -1
  11. package/lib/cjs/explorer/verifiers/FsHtmlVerifier.js +1 -1
  12. package/lib/cjs/explorer/verifiers/FsHtmlVerifier.js.map +1 -1
  13. package/lib/esm/explorer/BlockchainExplorer.js.map +1 -1
  14. package/lib/esm/explorer/BlockchainExplorer.mjs +9 -9
  15. package/lib/esm/explorer/BlockchainExplorerFactory.js.map +1 -1
  16. package/lib/esm/explorer/BlockchainExplorerFactory.mjs +1 -1
  17. package/lib/esm/explorer/BlockchainExplorerProvider.js.map +1 -1
  18. package/lib/esm/explorer/BlockchainExplorerProvider.mjs +1 -1
  19. package/lib/esm/explorer/BlockchainExplorerStorage.js.map +1 -1
  20. package/lib/esm/explorer/BlockchainExplorerStorage.mjs +2 -2
  21. package/lib/esm/explorer/ContractVerifier.js.map +1 -1
  22. package/lib/esm/explorer/ContractVerifier.mjs +10 -10
  23. package/lib/esm/explorer/verifiers/FsHtmlVerifier.js.map +1 -1
  24. package/lib/esm/explorer/verifiers/FsHtmlVerifier.mjs +1 -1
  25. package/lib/types/explorer/BlockchainExplorerFactory.d.ts +1 -1
  26. package/package.json +1 -1
  27. package/skills/README.md +45 -0
  28. package/{docs/ai → skills/dequanto}/INDEX.md +12 -2
  29. package/{docs/ai/skill → skills}/dequanto/SKILL.md +27 -17
  30. package/{docs/ai → skills/dequanto}/examples/config.spec.ts +5 -5
  31. package/{docs/ai → skills/dequanto}/examples/contract-read.spec.ts +7 -7
  32. package/{docs/ai → skills/dequanto}/examples/contract-write.spec.ts +7 -7
  33. package/{docs/ai → skills/dequanto}/examples/deploy-contract.spec.ts +5 -5
  34. package/{docs/ai → skills/dequanto}/examples/erc4337-userop.spec.ts +7 -7
  35. package/{docs/ai → skills/dequanto}/examples/explorer-abi.spec.ts +3 -3
  36. package/{docs/ai → skills/dequanto}/examples/generate-contract-class.spec.ts +2 -2
  37. package/skills/dequanto/examples/hardhat-debug.spec.ts +29 -0
  38. package/skills/dequanto/examples/hardhat-fork.spec.ts +31 -0
  39. package/{docs/ai → skills/dequanto}/examples/index-events.spec.ts +4 -4
  40. package/{docs/ai → skills/dequanto}/examples/rpc-read.spec.ts +21 -4
  41. package/{docs/ai → skills/dequanto}/examples/safe-batch.spec.ts +6 -6
  42. package/{docs/ai → skills/dequanto}/examples/send-transaction.spec.ts +7 -7
  43. package/skills/dequanto/examples/storage.spec.ts +58 -0
  44. package/{docs/ai → skills/dequanto}/examples/token-transfer.spec.ts +5 -5
  45. package/skills/dequanto/examples/utils.spec.ts +63 -0
  46. package/{docs/ai → skills/dequanto}/references/contracts.md +1 -1
  47. package/{docs/ai → skills/dequanto}/references/deployments.md +4 -2
  48. package/{docs/ai → skills/dequanto}/references/events-indexing.md +1 -1
  49. package/{docs/ai → skills/dequanto}/references/explorer.md +1 -1
  50. package/{docs/ai → skills/dequanto}/references/generation.md +6 -6
  51. package/skills/dequanto/references/hardhat.md +113 -0
  52. package/{docs/ai → skills/dequanto}/references/rpc-clients.md +25 -5
  53. package/{docs/ai → skills/dequanto}/references/safe-erc4337.md +2 -2
  54. package/skills/dequanto/references/storage.md +122 -0
  55. package/{docs/ai → skills/dequanto}/references/tokens-accounts.md +12 -6
  56. package/{docs/ai → skills/dequanto}/references/transactions.md +6 -4
  57. package/skills/dequanto/references/utils.md +131 -0
  58. package/src/explorer/BlockchainExplorer.ts +9 -9
  59. package/src/explorer/BlockchainExplorerFactory.ts +1 -1
  60. package/src/explorer/BlockchainExplorerProvider.ts +1 -1
  61. package/src/explorer/BlockchainExplorerStorage.ts +2 -2
  62. package/src/explorer/ContractVerifier.ts +10 -10
  63. package/src/explorer/verifiers/FsHtmlVerifier.ts +1 -1
  64. package/tsconfig.json +2 -1
  65. package/docs/ai/examples/storage-read.spec.ts +0 -35
  66. package/docs/ai/references/storage.md +0 -93
  67. /package/{docs/ai → skills/dequanto}/references/migration-from-ethers-viem.md +0 -0
@@ -1,10 +1,10 @@
1
- import { Web3ClientFactory } from '@dequanto/clients/Web3ClientFactory';
2
- import { TxDataBuilder } from '@dequanto/txs/TxDataBuilder';
3
- import { TxWriter } from '@dequanto/txs/TxWriter';
4
- import { $account } from '@dequanto/utils/$account';
5
- import { $address } from '@dequanto/utils/$address';
6
- import { $number } from '@dequanto/utils/$number';
7
- import { $require } from '@dequanto/utils/$require';
1
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
2
+ import { TxDataBuilder } from 'dequanto/txs/TxDataBuilder';
3
+ import { TxWriter } from 'dequanto/txs/TxWriter';
4
+ import { $account } from 'dequanto/utils/$account';
5
+ import { $address } from 'dequanto/utils/$address';
6
+ import { $number } from 'dequanto/utils/$number';
7
+ import { $require } from 'dequanto/utils/$require';
8
8
 
9
9
  UTest({
10
10
  async 'build native transfer tx data with gas fields' () {
@@ -0,0 +1,58 @@
1
+ import { AavePool } from '../../../0xc/eth/AavePool/AavePool';
2
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
3
+ import { HardhatProvider } from 'dequanto/hardhat/HardhatProvider';
4
+ import { SlotsParser } from 'dequanto/solidity/SlotsParser';
5
+ import { SlotsStorage } from 'dequanto/solidity/SlotsStorage';
6
+ import { $require } from 'dequanto/utils/$require';
7
+
8
+ // Generated classes include the storage layout: use instance.storage.$set(name, value).
9
+ // SlotsParser and SlotsStorage provide the low-level alternative for Solidity source.
10
+ UTest({
11
+ async 'read and set AavePool storage by variable name on an Ethereum fork' () {
12
+ const client = await Web3ClientFactory.getAsync('hh:memory:eth');
13
+ const pool = new AavePool(undefined, client);
14
+ const premiumBefore = await pool.FLASHLOAN_PREMIUM_TOTAL();
15
+ const packedNeighborBefore = await pool.storage.__DEPRECATED_flashLoanPremiumToProtocol();
16
+ const id = await client.debug.snapshot();
17
+
18
+ try {
19
+ const premium = premiumBefore + 1n;
20
+ await pool.storage.$set('_flashLoanPremium', premium);
21
+
22
+ $require.eq(await pool.storage._flashLoanPremium(), premium);
23
+ $require.eq(await pool.FLASHLOAN_PREMIUM_TOTAL(), premium);
24
+ // Both uint128 fields share slot 58; preserve the neighboring field.
25
+ $require.eq(await pool.storage.__DEPRECATED_flashLoanPremiumToProtocol(), packedNeighborBefore);
26
+ } finally {
27
+ await client.debug.revert(id);
28
+ }
29
+
30
+ $require.eq(await pool.FLASHLOAN_PREMIUM_TOTAL(), premiumBefore);
31
+ },
32
+
33
+ async 'read and write Solidity storage with SlotsParser and SlotsStorage' () {
34
+ const code = `
35
+ contract Vault {
36
+ uint256 public count = 123;
37
+ mapping(address => uint256) balances;
38
+
39
+ constructor () {
40
+ balances[address(0x1000000000000000000000000000000000000001)] = 7;
41
+ }
42
+ }
43
+ `;
44
+ const hh = new HardhatProvider();
45
+ const client = await Web3ClientFactory.getAsync('hh:memory');
46
+ const { contract } = await hh.deployCode(code, { client });
47
+
48
+ const slots = await SlotsParser.slots({ path: '', code }, 'Vault');
49
+ const storage = SlotsStorage.createWithClient(client, contract.address, slots);
50
+
51
+ $require.eq(await storage.get('count'), 123n);
52
+ $require.eq(await storage.get('balances["0x1000000000000000000000000000000000000001"]'), 7n);
53
+
54
+ // Direct storage writes require a debug network such as Hardhat.
55
+ await storage.set('count', 500n);
56
+ $require.eq(await storage.get('count'), 500n);
57
+ }
58
+ });
@@ -1,8 +1,8 @@
1
- import { Web3ClientFactory } from '@dequanto/clients/Web3ClientFactory';
2
- import { ERC20 } from '@dequanto/prebuilt/openzeppelin/ERC20';
3
- import { $account } from '@dequanto/utils/$account';
4
- import { $erc20 } from '@dequanto/utils/$erc20';
5
- import { $require } from '@dequanto/utils/$require';
1
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
2
+ import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
3
+ import { $account } from 'dequanto/utils/$account';
4
+ import { $erc20 } from 'dequanto/utils/$erc20';
5
+ import { $require } from 'dequanto/utils/$require';
6
6
 
7
7
  const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as const;
8
8
  const INITIAL_SENDER_BALANCE = 100_000_000n;
@@ -0,0 +1,63 @@
1
+ import { $abi } from 'dequanto/abi/$abi';
2
+ import { $contract } from 'dequanto/utils/$contract';
3
+ import { $date } from 'dequanto/utils/$date';
4
+ import { $hex } from 'dequanto/utils/$hex';
5
+ import { $promise } from 'dequanto/utils/$promise';
6
+ import { $require } from 'dequanto/utils/$require';
7
+ import { $sig } from 'dequanto/utils/$sig';
8
+
9
+ const ACCOUNT = '0x0000000000000000000000000000000000000002';
10
+
11
+ UTest({
12
+ 'encode and decode ABI values and calldata' () {
13
+ const encoded = $abi.encode(['address', 'uint256'], [ACCOUNT, 5n]);
14
+ const [account, amount] = $abi.decode(['address', 'uint256'], encoded);
15
+ const calldata = $abi.encodeCall(
16
+ 'function transfer(address to, uint256 amount)',
17
+ [ACCOUNT, 5n]
18
+ );
19
+
20
+ $require.eq(account, ACCOUNT);
21
+ $require.eq(amount, 5n);
22
+ $require.eq(
23
+ calldata,
24
+ '0xa9059cbb00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005'
25
+ );
26
+ $require.eq(
27
+ $abi.decodeReturn<bigint>(
28
+ 'function totalSupply() view returns (uint256)',
29
+ $abi.encode('uint256', 10n)
30
+ ),
31
+ 10n
32
+ );
33
+ },
34
+
35
+ 'hash Solidity values and manipulate hex' () {
36
+ const topic = $contract.keccak256('Transfer(address,address,uint256)');
37
+ const payload = $hex.concat([$hex.ensure('1234'), $hex.toHexBuffer(5)]);
38
+
39
+ $require.eq(topic, '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef');
40
+ $require.eq(payload, '0x123405');
41
+ $require.eq($hex.padBytes('0x2a', 4), '0x0000002a');
42
+ $require.eq($hex.getBytes(payload, 0, 2), '0x1234');
43
+ $require.eq($hex.getBytesLength(payload), 3);
44
+ },
45
+
46
+ async 'validate values, dates, signatures, and promise results' () {
47
+ $require.eq($date.parseTimespan('5days', { get: 's' }), 432_000);
48
+ $require.AddressNotEmpty(ACCOUNT);
49
+ $require.BigInt(5n, 'Expected an amount', { min: 1n });
50
+
51
+ const signer = $sig.$account.generate();
52
+ const signature = await $sig.signMessage('Hello dequanto', signer);
53
+ $require.eq($sig.recoverMessage('Hello dequanto', signature), signer.address);
54
+
55
+ const success = await $promise.caught(Promise.resolve(42));
56
+ const failure = await $promise.caught(Promise.reject(new Error('Expected failure')));
57
+
58
+ $require.eq(success.result, 42);
59
+ $require.Null(success.error);
60
+ $require.notNull(failure.error);
61
+ $require.eq(failure.error.message, 'Expected failure');
62
+ }
63
+ });
@@ -48,7 +48,7 @@ const balance = await token.balanceOf(account);
48
48
  For ad hoc reads without a generated class:
49
49
 
50
50
  ```ts
51
- import { ContractReader } from '@dequanto/contracts/ContractReader';
51
+ import { ContractReader } from 'dequanto/contracts/ContractReader';
52
52
 
53
53
  const reader = new ContractReader(client);
54
54
  const symbol = await reader.readAsync<string>(
@@ -2,6 +2,8 @@
2
2
 
3
3
  Use `HardhatProvider` for compile/deploy helper flows and `Deployments` for idempotent deployments, deployment storage, bytecode checks, proxy upgrades, beacon proxies, and verification.
4
4
 
5
+ See [Hardhat development and forking](hardhat.md) for creating development networks and live-contract forks, impersonating accounts, changing balances/storage, and restoring snapshots.
6
+
5
7
  Key source files:
6
8
 
7
9
  - `src/hardhat/HardhatProvider.ts`
@@ -21,7 +23,7 @@ Useful tests:
21
23
  ## HardhatProvider
22
24
 
23
25
  ```ts
24
- import { HardhatProvider } from '@dequanto/hardhat/HardhatProvider';
26
+ import { HardhatProvider } from 'dequanto/hardhat/HardhatProvider';
25
27
 
26
28
  const hh = new HardhatProvider();
27
29
  const client = hh.client();
@@ -43,7 +45,7 @@ Common methods:
43
45
  ## Idempotent Deployments
44
46
 
45
47
  ```ts
46
- import { Deployments } from '@dequanto/contracts/deploy/Deployments';
48
+ import { Deployments } from 'dequanto/contracts/deploy/Deployments';
47
49
 
48
50
  const deployments = new Deployments(client, deployer, {
49
51
  directory: './deployments/',
@@ -34,7 +34,7 @@ Use `'*'` for all events, or an event-name array for multiple events.
34
34
  ## EventsIndexer
35
35
 
36
36
  ```ts
37
- import { EventsIndexer } from '@dequanto/indexer/EventsIndexer';
37
+ import { EventsIndexer } from 'dequanto/indexer/EventsIndexer';
38
38
 
39
39
  const indexer = new EventsIndexer(contract, {
40
40
  name: 'TokenTransfers',
@@ -19,7 +19,7 @@ Useful tests:
19
19
  ## Create Explorer
20
20
 
21
21
  ```ts
22
- import { BlockchainExplorerFactory } from '@dequanto/explorer/BlockchainExplorerFactory';
22
+ import { BlockchainExplorerFactory } from 'dequanto/explorer/BlockchainExplorerFactory';
23
23
 
24
24
  const explorer = BlockchainExplorerFactory.get('eth');
25
25
  ```
@@ -2,13 +2,13 @@
2
2
 
3
3
  TypeScript class generation is a core dequanto workflow. Prefer generated contract classes over raw ABI calls: generated classes let application code call contracts as ordinary TypeScript classes, with no direct web3-specific ABI encoding, decoding, or JSON-RPC plumbing.
4
4
 
5
- Before generating a contract, check whether dequanto already ships a prebuilt class. The package includes OpenZeppelin contract classes under `dequanto/prebuilt/**` / `@dequanto/prebuilt/openzeppelin/**`. Class names match the OpenZeppelin contract names, for example `ERC20`, `ERC721`, `Ownable`, or `TimelockController`.
5
+ Before generating a contract, check whether dequanto already ships a prebuilt class. The package includes OpenZeppelin contract classes under `dequanto/prebuilt/**` / `dequanto/prebuilt/openzeppelin/**`. Class names match the OpenZeppelin contract names, for example `ERC20`, `ERC721`, `Ownable`, or `TimelockController`.
6
6
 
7
7
  For standard OpenZeppelin contracts, do not generate a new class. Import the prebuilt class and pass the target contract address plus a chain client loaded by platform/chain abbreviation:
8
8
 
9
9
  ```ts
10
- import { Web3ClientFactory } from '@dequanto/clients/Web3ClientFactory';
11
- import { ERC20 } from '@dequanto/prebuilt/openzeppelin/ERC20';
10
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
11
+ import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
12
12
 
13
13
  const platform = 'eth';
14
14
  const address = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
@@ -71,7 +71,7 @@ By default, `0xweb install` saves the downloaded Solidity sources next to the ge
71
71
  The generated class can then be imported and used like a normal TypeScript class:
72
72
 
73
73
  ```ts
74
- import { Web3ClientFactory } from '@dequanto/clients/Web3ClientFactory';
74
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
75
75
  import { USDC } from './0xc/eth/USDC/USDC';
76
76
 
77
77
  const client = await Web3ClientFactory.getAsync('eth');
@@ -121,7 +121,7 @@ Use `Generator` directly only for custom generation tasks, test fixtures, non-Ha
121
121
  ### Generate From Compiled Artifact
122
122
 
123
123
  ```ts
124
- import { Generator } from '@dequanto/gen/Generator';
124
+ import { Generator } from 'dequanto/gen/Generator';
125
125
 
126
126
  const gen = new Generator({
127
127
  name: 'AnyERC20',
@@ -180,7 +180,7 @@ When a contract is generated from verified on-chain source, such as with `0xweb
180
180
  Use `GeneratorStorageReader` when the task needs generated storage accessors without generating the full contract class.
181
181
 
182
182
  ```ts
183
- import { GeneratorStorageReader } from '@dequanto/gen/GeneratorStorageReader';
183
+ import { GeneratorStorageReader } from 'dequanto/gen/GeneratorStorageReader';
184
184
 
185
185
  const gen = new GeneratorStorageReader();
186
186
  const result = await gen.generate({
@@ -0,0 +1,113 @@
1
+ # Hardhat Development And Forking
2
+
3
+ Dequanto integrates Hardhat as a development and forking network for contract development and experimenting with live contracts on a local fork. Use `client.debug` to mine blocks, change balances and storage, impersonate accounts, and restore snapshots. These changes affect the local network only.
4
+
5
+ Key source files:
6
+
7
+ - `src/clients/Web3ClientFactory.ts`
8
+ - `src/hardhat/HardhatProvider.ts`
9
+ - `src/hardhat/HardhatWeb3Client.ts`
10
+ - `src/clients/debug/ClientDebugMethods.ts`
11
+ - `src/utils/$erc20.ts`
12
+ - `src/txs/TxWriter.ts`
13
+
14
+ Runnable examples: [Hardhat debug operations](../examples/hardhat-debug.spec.ts) and [forked ERC20 transfers](../examples/hardhat-fork.spec.ts). The fork example requires an accessible Ethereum RPC.
15
+
16
+ ## Create A Network Or Fork
17
+
18
+ Use a project with Hardhat installed and a `hardhat.config.*` file.
19
+
20
+ ```ts
21
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
22
+
23
+ // Initialize an in-memory Ethereum fork using the platform's default/configured RPCs.
24
+ const client = await Web3ClientFactory.getAsync('hh:memory:eth');
25
+ ```
26
+
27
+ Replace `eth` with the platform to fork. Optionally pass `{ block: 20_000_000, url: 'https://...' }` as the second argument to select a block and override the source RPC. Initializing an in-memory fork resets the shared Hardhat network; create it before taking snapshots or changing state.
28
+
29
+ To use a separately running local Hardhat node, start it with Ethereum forking configured through Hardhat's normal node setup, then connect:
30
+
31
+ ```ts
32
+ const client = await Web3ClientFactory.getAsync('hh:eth');
33
+ ```
34
+
35
+ This connects to the local node (by default `127.0.0.1:8545`) and associates it with Ethereum. It does not start the node or configure its fork.
36
+
37
+ For an in-memory development network without a fork, use `hh:memory`. Use `hardhat` to connect to a separately running local development node. See [deployments](deployments.md) for compiling and deploying contracts.
38
+
39
+ ## Impersonate An Account
40
+
41
+ On Hardhat, submit transactions from any address, including a contract address, by passing an account with `type: 'impersonated'`. No private key is required. The transaction writer enables impersonation automatically.
42
+
43
+ ```ts
44
+ import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
45
+ import { $erc20 } from 'dequanto/utils/$erc20';
46
+
47
+ const usdc = new ERC20('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', client);
48
+ const account = {
49
+ address: '0x0000000000000000000000000000000000001234',
50
+ type: 'impersonated'
51
+ } as const;
52
+ const to = '0x0000000000000000000000000000000000005678';
53
+ const amount = 1_000_000n; // 1 USDC in its smallest units.
54
+
55
+ await client.debug.setBalance(account.address, 10n ** 18n);
56
+ await $erc20.setBalanceAny(client, usdc.address, account.address, amount);
57
+ const writer = await usdc.$receipt().transfer(account, to, amount);
58
+ console.log(writer.receipt.transactionHash);
59
+ ```
60
+
61
+ Contract checks still apply, including sufficient token balances and allowances. For explicit control, use `client.debug.impersonateAccount(address)` and `client.debug.stopImpersonatingAccount(address)`.
62
+
63
+ ## Advance The Network
64
+
65
+ ```ts
66
+ await client.debug.mine('5days');
67
+ await client.debug.mine(10); // Mine 10 blocks.
68
+ ```
69
+
70
+ Time strings are converted to seconds and used as the block count, with a default interval of one second per block. `mine('5days')` therefore mines `432_000` blocks, advancing network time by approximately five days. The optional second argument sets the interval in seconds.
71
+
72
+ ## Set Native And ERC20 Balances
73
+
74
+ Set the native balance of any account or contract in wei:
75
+
76
+ ```ts
77
+ await client.debug.setBalance(account.address, 10n ** 18n); // 1 ETH.
78
+ ```
79
+
80
+ Use `$erc20.setBalanceAny` to set an ERC20 balance without knowing its storage slot:
81
+
82
+ ```ts
83
+ await $erc20.setBalanceAny(client, usdc.address, account.address, 1_000_000n);
84
+ // A number is interpreted in whole-token units instead:
85
+ await $erc20.setBalanceAny(client, usdc.address, account.address, 1);
86
+ ```
87
+
88
+ The helper traces `balanceOf(account)` with `debug_traceCall`, examines accessed storage for the returned balance, and writes the matching slot. A `bigint` amount is in the token's smallest units; a `number` is converted using the token's decimals. This changes balance storage directly without minting tokens or updating total supply. Tokens whose balances are computed rather than stored directly may not be supported.
89
+
90
+ ## Set Contract Storage
91
+
92
+ For a known storage slot, use:
93
+
94
+ ```ts
95
+ await client.debug.setStorageAt(contractAddress, slot, '0x2a');
96
+ ```
97
+
98
+ The value is padded to 32 bytes. For named variables, mappings, and packed fields, use a resolved storage layout with `SlotsStorage.set(...)`; see [Solidity storage](storage.md#write-storage-in-tests).
99
+
100
+ ## Snapshots And Rollbacks
101
+
102
+ ```ts
103
+ const id = await client.debug.snapshot();
104
+ try {
105
+ await client.debug.mine('5days');
106
+ await client.debug.setBalance(account.address, 10n ** 18n);
107
+ // Run transactions or assertions against the modified state.
108
+ } finally {
109
+ await client.debug.revert(id);
110
+ }
111
+ ```
112
+
113
+ Take the snapshot before modifying state. Create a fresh snapshot for each independent scenario.
@@ -8,6 +8,7 @@ Key source files:
8
8
  - `src/clients/Web3Client.ts`
9
9
  - `src/clients/EvmWeb3Client.ts`
10
10
  - `src/clients/ClientPool.ts`
11
+ - `src/blocks/BlockDateResolver.ts`
11
12
  - `src/rpc/Rpc.ts`
12
13
  - `src/rpc/RpcBase.ts`
13
14
 
@@ -21,27 +22,29 @@ Useful tests:
21
22
  ## Client Creation
22
23
 
23
24
  ```ts
24
- import { Web3ClientFactory } from '@dequanto/clients/Web3ClientFactory';
25
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
25
26
 
26
27
  const client = await Web3ClientFactory.getAsync('eth');
27
28
  ```
28
29
 
29
- For Hardhat:
30
+ For an in-memory Hardhat fork using the platform's default/configured RPCs:
30
31
 
31
32
  ```ts
32
- const client = await Web3ClientFactory.getAsync('hardhat');
33
+ const client = await Web3ClientFactory.getAsync('hh:memory:eth');
33
34
  ```
34
35
 
35
- For a fork-like platform string:
36
+ To connect to an already running local Hardhat node configured as an Ethereum fork:
36
37
 
37
38
  ```ts
38
39
  const client = await Web3ClientFactory.getAsync('hh:eth');
39
40
  ```
40
41
 
42
+ Use `hh:memory` for an in-memory development network without a fork, or `hardhat` for a separately running local development node. See [Hardhat development and forking](hardhat.md) for setup, impersonation, balance/storage changes, mining, and snapshots.
43
+
41
44
  For direct endpoints:
42
45
 
43
46
  ```ts
44
- import { EvmWeb3Client } from '@dequanto/clients/EvmWeb3Client';
47
+ import { EvmWeb3Client } from 'dequanto/clients/EvmWeb3Client';
45
48
 
46
49
  const client = new EvmWeb3Client({
47
50
  platform: 'eth',
@@ -80,6 +83,23 @@ const result = await client.readContract({
80
83
  });
81
84
  ```
82
85
 
86
+ ## Resolve Blocks By Date
87
+
88
+ Use `BlockDateResolver` to find the block nearest to a date, or to resolve a block number back to its date:
89
+
90
+ ```ts
91
+ import { BlockDateResolver } from 'dequanto/blocks/BlockDateResolver';
92
+
93
+ const resolver = new BlockDateResolver(client);
94
+ const date = new Date('2024-03-03T10:20:00Z');
95
+
96
+ const blockNumber = await resolver.getBlockNumberFor(date);
97
+ const { block, timestamp } = await resolver.getBlockInfoFor(date);
98
+ const blockDate = await resolver.getDate(blockNumber);
99
+ ```
100
+
101
+ `getBlockNumberFor(date)` returns the nearest block number. `getBlockInfoFor(date)` also returns that block's Unix timestamp. The resolver estimates from the chain's average block time and refines the estimate by loading blocks, so the result can be immediately before or after the requested time. Compare the returned timestamp when an exact time boundary matters. Dates before the chain was active throw a `Date out of range` error.
102
+
83
103
  ## Logs
84
104
 
85
105
  Use `getPastLogs(filter, options?)` for raw log queries. It handles block-range pagination and node range limits.
@@ -51,8 +51,8 @@ const tx = await token
51
51
  ## Manual Safe Flow
52
52
 
53
53
  ```ts
54
- import { GnosisSafeHandler } from '@dequanto/safe/GnosisSafeHandler';
55
- import { ContractWriter } from '@dequanto/contracts/ContractWriter';
54
+ import { GnosisSafeHandler } from 'dequanto/safe/GnosisSafeHandler';
55
+ import { ContractWriter } from 'dequanto/contracts/ContractWriter';
56
56
 
57
57
  const writer = new ContractWriter(target.address, client);
58
58
  const txWriter = await writer.writeAsync(safeAccount, 'airdrop()', [], {
@@ -0,0 +1,122 @@
1
+ # Solidity Storage
2
+
3
+ Prefer the storage reader included in generated contract classes. For manual layouts, use `SlotsParser` to derive layout and `SlotsStorage` to read or write slots. Do not decode raw storage words as ABI payloads.
4
+
5
+ Key source files:
6
+
7
+ - `src/solidity/SlotsParser.ts`
8
+ - `src/solidity/SlotsStorage.ts`
9
+ - `src/solidity/storage/handlers/SlotStructHandler.ts`
10
+ - `src/solidity/storage/handlers/SlotMappingHandler.ts`
11
+ - `src/solidity/storage/handlers/SlotDynamicArrayHandler.ts`
12
+ - `src/solidity/storage/SlotsStorageTransport.ts`
13
+ - `src/gen/GeneratorStorageReader.ts`
14
+
15
+ Useful tests:
16
+
17
+ - `test/solidity/SlotsReader.spec.ts`
18
+ - `test/solidity/Storage.spec.ts`
19
+ - `test/solidity/SlotsParser.spec.ts`
20
+ - `test/generate/slotreader.spec.ts`
21
+
22
+ ## Generated Contract Storage Readers
23
+
24
+ Contract classes installed from a blockchain explorer with `0xweb install`, or generated from Solidity sources with `@0xweb/hardhat`, include a storage reader on `contractInstance.storage`. It contains the storage layout derived from the source, including slot positions and packed fields, so you can access storage by variable name without manually resolving slots.
25
+
26
+ On a Hardhat development network or fork, write a variable with `await contractInstance.storage.$set('someVar', value)`. The generated reader exposes `$set` and `$get`; `set` and `get` are methods on the underlying `SlotsStorage`.
27
+
28
+ For example, using the generated AavePool class:
29
+
30
+ ```ts
31
+ import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
32
+ import { AavePool } from '../../../0xc/eth/AavePool/AavePool';
33
+
34
+ const client = await Web3ClientFactory.getAsync('hh:memory:eth');
35
+ const pool = new AavePool(undefined, client);
36
+ const id = await client.debug.snapshot();
37
+
38
+ try {
39
+ await pool.storage.$set('_flashLoanPremium', 12_345n);
40
+ const premium = await pool.storage._flashLoanPremium();
41
+ const publicPremium = await pool.FLASHLOAN_PREMIUM_TOTAL();
42
+ } finally {
43
+ await client.debug.revert(id);
44
+ }
45
+ ```
46
+
47
+ The AavePool import points to this repository's generated class; use your project's generated path. Keep generated storage layouts in sync with the deployed implementation, especially after proxy upgrades. See the [storage example](../examples/storage.spec.ts) for assertions and rollback verification.
48
+
49
+ ## Parse Slots From Solidity
50
+
51
+ ```ts
52
+ import { SlotsParser } from 'dequanto/solidity/SlotsParser';
53
+
54
+ const slots = await SlotsParser.slots({
55
+ path: './contracts/Vault.sol'
56
+ }, 'Vault');
57
+ ```
58
+
59
+ For inline code:
60
+
61
+ ```ts
62
+ const slots = await SlotsParser.slots({ path: '', code }, 'Foo');
63
+ ```
64
+
65
+ ## Parse Slots From ABI Inputs
66
+
67
+ Use this for standalone struct or tuple ABI data:
68
+
69
+ ```ts
70
+ const slots = await SlotsParser.slotsFromAbi(
71
+ '(uint256 foo, address owner, bool enabled)'
72
+ );
73
+ ```
74
+
75
+ ## Read Contract Storage
76
+
77
+ ```ts
78
+ import { SlotsStorage } from 'dequanto/solidity/SlotsStorage';
79
+
80
+ const storage = SlotsStorage.createWithClient(client, contract.address, slots);
81
+
82
+ const owner = await storage.get('owner');
83
+ const user = await storage.get('users[0]');
84
+ const balance = await storage.get(`balances["${account}"]`);
85
+ ```
86
+
87
+ Supported path forms include:
88
+
89
+ - `field`
90
+ - `struct.field`
91
+ - `array[0]`
92
+ - `mapping["0x..."]`
93
+ - `mapping["0x..."].field`
94
+ - `nested[0].balances[1]`
95
+ - breadcrumb arrays such as `['users', account, 'balance']`
96
+
97
+ ## Read Diamond Or Offset Storage
98
+
99
+ For library-selected storage roots:
100
+
101
+ ```ts
102
+ const storage = SlotsStorage.createWithClient(client, contract.address, slots, {
103
+ storageOffset: $contract.keccak256('diamond.app.storage')
104
+ });
105
+ ```
106
+
107
+ ## Write Storage In Tests
108
+
109
+ `SlotsStorage.set(...)` exists and is useful in Hardhat/debug contexts:
110
+
111
+ ```ts
112
+ await storage.set('user.balance', 1000n);
113
+ ```
114
+
115
+ For a known raw slot on Hardhat, use `await client.debug.setStorageAt(contractAddress, slot, '0x2a')`; the value is padded to 32 bytes. To set an ERC20 balance without resolving its layout manually, use `$erc20.setBalanceAny(client, tokenAddress, accountAddress, amount)`. See [Hardhat development and forking](hardhat.md) for examples and snapshot/rollback handling.
116
+
117
+ Do not use storage writes in production code unless the transport explicitly supports it and the task requires test/debug mutation.
118
+
119
+ ## Raw Slot Reads
120
+
121
+ Use `client.getStorageAt(address, slot)` or `client.getStorageAtBatched(address, slots)` only when raw words are needed.
122
+
@@ -21,7 +21,7 @@ Useful tests:
21
21
  ## Token Metadata
22
22
 
23
23
  ```ts
24
- import { TokensService } from '@dequanto/tokens/TokensService';
24
+ import { TokensService } from 'dequanto/tokens/TokensService';
25
25
 
26
26
  const tokens = new TokensService('eth');
27
27
  const usdc = await tokens.getKnownToken('USDC');
@@ -30,7 +30,7 @@ const usdc = await tokens.getKnownToken('USDC');
30
30
  For forked Hardhat platform strings:
31
31
 
32
32
  ```ts
33
- import { TokensServiceFactory } from '@dequanto/tokens/TokensServiceFactory';
33
+ import { TokensServiceFactory } from 'dequanto/tokens/TokensServiceFactory';
34
34
 
35
35
  const tokens = await TokensServiceFactory.getAsync('hh:eth');
36
36
  ```
@@ -38,7 +38,7 @@ const tokens = await TokensServiceFactory.getAsync('hh:eth');
38
38
  ## Token Transfer Service
39
39
 
40
40
  ```ts
41
- import { TokenTransferService } from '@dequanto/tokens/TokenTransferService';
41
+ import { TokenTransferService } from 'dequanto/tokens/TokenTransferService';
42
42
 
43
43
  const service = new TokenTransferService(client);
44
44
  const tx = await service.transfer(sender, receiver, 'USDC', 10);
@@ -69,7 +69,7 @@ const tx = await service.transferAllWithRemainder(sender, receiver, 'USDC', 1);
69
69
  Generate or derive EOA accounts:
70
70
 
71
71
  ```ts
72
- import { $sig } from '@dequanto/utils/$sig';
72
+ import { $sig } from 'dequanto/utils/$sig';
73
73
 
74
74
  const account = $sig.$account.generate();
75
75
  const fromMnemonic = await $sig.$account.fromMnemonic(mnemonic, 0);
@@ -79,7 +79,7 @@ const fromKey = await $sig.$account.fromKey(privateKey);
79
79
  Use `ChainAccountService` for named encrypted account storage:
80
80
 
81
81
  ```ts
82
- import { ChainAccountService } from '@dequanto/ChainAccountService';
82
+ import { ChainAccountService } from 'dequanto/ChainAccountService';
83
83
 
84
84
  const service = new ChainAccountService();
85
85
  const account = await service.get('deployer');
@@ -91,9 +91,15 @@ Transaction APIs accept several account shapes:
91
91
 
92
92
  - EOA account: `{ address, key }`
93
93
  - named account: string resolved through `ChainAccountService`
94
- - impersonated account in Hardhat
94
+ - impersonated account in Hardhat: `{ type: 'impersonated', address }` (no private key required)
95
95
  - Safe account: `{ type: 'safe', address, operator, owners? }`
96
96
  - ERC-4337 account: `{ type: 'erc4337', address, operator }`
97
97
 
98
98
  Inspect `src/models/TAccount.ts` before adding a new account shape.
99
99
 
100
+ ## Balances And Impersonation On Hardhat
101
+
102
+ Use `client.debug.setBalance(address, 10n ** 18n)` to set a native balance to 1 ETH. Use `$erc20.setBalanceAny(client, tokenAddress, accountAddress, amount)` to resolve and overwrite an ERC20 balance slot: `bigint` amounts are raw smallest units, while `number` amounts are converted using token decimals.
103
+
104
+ Pass an impersonated account to generated write methods, for example `await usdc.$receipt().transfer({ address, type: 'impersonated' }, to, amount)`. See [Hardhat development and forking](hardhat.md) and the [fork example](../examples/hardhat-fork.spec.ts).
105
+