dequanto 0.2.53 → 0.2.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/abi/$abi.js +39 -1
- package/lib/cjs/abi/$abi.js.map +1 -1
- package/lib/cjs/contracts/ContractReader.js +2 -2
- package/lib/cjs/contracts/ContractReader.js.map +1 -1
- package/lib/cjs/gen/Generator.js +1 -0
- package/lib/cjs/gen/Generator.js.map +1 -1
- package/lib/cjs/gen/GeneratorFromAbi.js +38 -9
- package/lib/cjs/gen/GeneratorFromAbi.js.map +1 -1
- package/lib/cjs/safe/GnosisSafeHandler.js.map +1 -1
- package/lib/cjs/services/TimelockService/ITimelockService.js.map +1 -1
- package/lib/cjs/services/TimelockService/TimelockService.js +1 -1
- package/lib/cjs/services/TimelockService/TimelockService.js.map +1 -1
- package/lib/cjs/solidity/utils/$types.js +26 -0
- package/lib/cjs/solidity/utils/$types.js.map +1 -1
- package/lib/cjs/txs/TxWriter.js +6 -4
- package/lib/cjs/txs/TxWriter.js.map +1 -1
- package/lib/cjs/txs/agents/BatchAgent.js +32 -7
- package/lib/cjs/txs/agents/BatchAgent.js.map +1 -1
- package/lib/cjs/txs/agents/TxWriterAccountAgents.js +2 -0
- package/lib/cjs/txs/agents/TxWriterAccountAgents.js.map +1 -1
- package/lib/cjs/txs/agents/ZodiacAgent.js +20 -0
- package/lib/cjs/txs/agents/ZodiacAgent.js.map +1 -0
- package/lib/cjs/utils/$account.js +5 -1
- package/lib/cjs/utils/$account.js.map +1 -1
- package/lib/cjs/zodiac/ZodiacTx.js +83 -0
- package/lib/cjs/zodiac/ZodiacTx.js.map +1 -0
- package/lib/esm/abi/$abi.js.map +1 -1
- package/lib/esm/abi/$abi.mjs +36 -1
- package/lib/esm/contracts/ContractReader.js.map +1 -1
- package/lib/esm/contracts/ContractReader.mjs +2 -2
- package/lib/esm/gen/Generator.js.map +1 -1
- package/lib/esm/gen/Generator.mjs +1 -0
- package/lib/esm/gen/GeneratorFromAbi.js.map +1 -1
- package/lib/esm/gen/GeneratorFromAbi.mjs +38 -9
- package/lib/esm/safe/GnosisSafeHandler.js.map +1 -1
- package/lib/esm/services/TimelockService/ITimelockService.js.map +1 -1
- package/lib/esm/services/TimelockService/TimelockService.js.map +1 -1
- package/lib/esm/services/TimelockService/TimelockService.mjs +1 -1
- package/lib/esm/solidity/utils/$types.js.map +1 -1
- package/lib/esm/solidity/utils/$types.mjs +26 -0
- package/lib/esm/txs/TxWriter.js.map +1 -1
- package/lib/esm/txs/TxWriter.mjs +6 -4
- package/lib/esm/txs/agents/BatchAgent.js.map +1 -1
- package/lib/esm/txs/agents/BatchAgent.mjs +32 -7
- package/lib/esm/txs/agents/TxWriterAccountAgents.js.map +1 -1
- package/lib/esm/txs/agents/TxWriterAccountAgents.mjs +2 -0
- package/lib/esm/txs/agents/ZodiacAgent.js.map +1 -0
- package/lib/esm/txs/agents/ZodiacAgent.mjs +16 -0
- package/lib/esm/utils/$account.js.map +1 -1
- package/lib/esm/utils/$account.mjs +5 -1
- package/lib/esm/zodiac/ZodiacTx.js.map +1 -0
- package/lib/esm/zodiac/ZodiacTx.mjs +79 -0
- package/lib/types/contracts/ContractReader.d.ts +1 -1
- package/lib/types/gen/Generator.d.ts +17 -0
- package/lib/types/gen/GeneratorFromAbi.d.ts +7 -0
- package/lib/types/models/TAccount.d.ts +11 -2
- package/lib/types/models/TEth.d.ts +4 -1
- package/lib/types/services/TimelockService/ITimelockService.d.ts +1 -0
- package/lib/types/solidity/utils/$types.d.ts +8 -0
- package/lib/types/txs/agents/BatchAgent.d.ts +2 -1
- package/lib/types/txs/agents/ZodiacAgent.d.ts +7 -0
- package/lib/types/utils/$account.d.ts +2 -1
- package/lib/types/zodiac/ZodiacTx.d.ts +30 -0
- package/package.json +1 -1
- package/skills/dequanto/INDEX.md +1 -1
- package/skills/dequanto/SKILL.md +4 -3
- package/skills/dequanto/examples/browser-wallet.spec.ts +2 -1
- package/skills/dequanto/examples/config.spec.ts +2 -1
- package/skills/dequanto/examples/contract-read.spec.ts +2 -1
- package/skills/dequanto/examples/contract-write.spec.ts +2 -1
- package/skills/dequanto/examples/deploy-contract.spec.ts +2 -1
- package/skills/dequanto/examples/erc4337-userop.spec.ts +2 -1
- package/skills/dequanto/examples/explorer-abi.spec.ts +2 -1
- package/skills/dequanto/examples/generate-contract-class.spec.ts +2 -1
- package/skills/dequanto/examples/hardhat-debug.spec.ts +2 -1
- package/skills/dequanto/examples/hardhat-fork.spec.ts +2 -1
- package/skills/dequanto/examples/index-events.spec.ts +2 -1
- package/skills/dequanto/examples/live-events.spec.ts +2 -1
- package/skills/dequanto/examples/revert-error.spec.ts +2 -1
- package/skills/dequanto/examples/rpc-read.spec.ts +19 -5
- package/skills/dequanto/examples/safe-batch.spec.ts +92 -3
- package/skills/dequanto/examples/send-transaction.spec.ts +2 -1
- package/skills/dequanto/examples/storage.spec.ts +2 -1
- package/skills/dequanto/examples/token-transfer.spec.ts +2 -1
- package/skills/dequanto/examples/utils.spec.ts +2 -1
- package/skills/dequanto/references/safe-erc4337.md +50 -3
- package/skills/dequanto/references/tokens-accounts.md +1 -0
- package/skills/dequanto/references/transactions.md +2 -1
- package/src/abi/$abi.ts +36 -0
- package/src/contracts/ContractReader.ts +2 -2
- package/src/gen/Generator.ts +4 -0
- package/src/gen/GeneratorFromAbi.ts +40 -10
- package/src/models/TAccount.ts +17 -2
- package/src/models/TEth.ts +4 -1
- package/src/safe/GnosisSafeHandler.ts +12 -12
- package/src/services/TimelockService/ITimelockService.ts +2 -0
- package/src/services/TimelockService/TimelockService.ts +1 -1
- package/src/solidity/utils/$types.ts +27 -0
- package/src/txs/TxWriter.ts +6 -4
- package/src/txs/agents/BatchAgent.ts +33 -7
- package/src/txs/agents/TxWriterAccountAgents.ts +2 -0
- package/src/txs/agents/ZodiacAgent.ts +19 -0
- package/src/utils/$account.ts +6 -2
- package/src/zodiac/ZodiacTx.ts +99 -0
- package/typings/index.d.ts +1 -4
- package/typings/globals/atma-utest/index.d.ts +0 -53
- package/typings/globals/atma-utest/typings.json +0 -10
- package/typings/globals/axios/index.d.ts +0 -6
- package/typings/globals/mask/index.d.ts +0 -6
- package/typings/globals/mask/typings.json +0 -9
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
3
|
import { $require } from 'dequanto/utils/$require';
|
|
3
4
|
|
|
4
|
-
UTest({
|
|
5
|
+
UTest.create({
|
|
5
6
|
async 'change balances, storage, and time on Hardhat and roll back'() {
|
|
6
7
|
const client = await Web3ClientFactory.getAsync('hh:memory');
|
|
7
8
|
const address = '0x0000000000000000000000000000000000001234';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
3
|
import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
|
|
3
4
|
import { $erc20 } from 'dequanto/utils/$erc20';
|
|
4
5
|
import { $require } from 'dequanto/utils/$require';
|
|
5
6
|
|
|
6
|
-
UTest({
|
|
7
|
+
UTest.create({
|
|
7
8
|
async 'set an ERC20 balance and transfer from an impersonated account on a fork' () {
|
|
8
9
|
// Requires an accessible Ethereum RPC from the dequanto configuration.
|
|
9
10
|
const client = await Web3ClientFactory.getAsync('hh:memory:eth');
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
3
|
import { EventsIndexer } from 'dequanto/indexer/EventsIndexer';
|
|
3
4
|
import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
|
|
4
5
|
import { $require } from 'dequanto/utils/$require';
|
|
5
6
|
|
|
6
|
-
UTest({
|
|
7
|
+
UTest.create({
|
|
7
8
|
async 'index stable USDC Transfer logs from live Ethereum RPC' () {
|
|
8
9
|
const client = await Web3ClientFactory.getAsync('eth');
|
|
9
10
|
const usdc = new ERC20('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', client);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { TEth } from 'dequanto/models/TEth';
|
|
2
3
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
3
4
|
import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
|
|
@@ -9,7 +10,7 @@ import { $require } from 'dequanto/utils/$require';
|
|
|
9
10
|
const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as const;
|
|
10
11
|
const TRANSFER_AMOUNT = 1_000_000n; // 1 USDC (6 decimals).
|
|
11
12
|
|
|
12
|
-
UTest({
|
|
13
|
+
UTest.create({
|
|
13
14
|
$config: {
|
|
14
15
|
timeout: 60_000
|
|
15
16
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { HardhatProvider } from 'dequanto/hardhat/HardhatProvider';
|
|
2
3
|
import { $account } from 'dequanto/utils/$account';
|
|
3
4
|
import { $promise } from 'dequanto/utils/$promise';
|
|
@@ -8,7 +9,7 @@ import { $require } from 'dequanto/utils/$require';
|
|
|
8
9
|
// if it reverts, dequanto catches the revert, decodes any custom error bytecode using the ABI,
|
|
9
10
|
// captures execution traces, and throws a standard JavaScript Error.
|
|
10
11
|
// If gas estimation succeeds but the transaction reverts on-chain later, the error flow is identical.
|
|
11
|
-
UTest({
|
|
12
|
+
UTest.create({
|
|
12
13
|
async 'catch automatic revert and decode custom error without manual simulation' () {
|
|
13
14
|
const hh = new HardhatProvider();
|
|
14
15
|
const client = hh.client();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
3
|
import { BlockDateResolver } from 'dequanto/blocks/BlockDateResolver';
|
|
3
4
|
import { Config } from 'dequanto/config/Config';
|
|
4
5
|
import { $date } from 'dequanto/utils/$date';
|
|
5
6
|
import { $require } from 'dequanto/utils/$require';
|
|
6
7
|
|
|
7
|
-
UTest({
|
|
8
|
+
UTest.create({
|
|
8
9
|
async 'read common RPC data via Web3Client directly' () {
|
|
9
10
|
const cfg = await Config.fetch();
|
|
10
11
|
|
|
@@ -51,9 +52,22 @@ UTest({
|
|
|
51
52
|
const info = await resolver.getBlockInfoFor(date);
|
|
52
53
|
const blockDate = await resolver.getDate(blockNumber);
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/* max ~1 block diff */
|
|
56
|
+
const block = {
|
|
57
|
+
24576294: {
|
|
58
|
+
nr: 24576294,
|
|
59
|
+
date: new Date('2026-03-03T10:19:47Z'),
|
|
60
|
+
},
|
|
61
|
+
24576295: {
|
|
62
|
+
nr: 24576295,
|
|
63
|
+
date: new Date('2026-03-03T10:19:59Z'),
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const blockData = block[info.block];
|
|
67
|
+
$require.notNull(blockData, `No info for the fetched block ${info.block}`);
|
|
68
|
+
$require.eq(blockNumber, info.block);
|
|
69
|
+
$require.eq(blockDate.toISOString(), blockData.date.toISOString());
|
|
70
|
+
$require.lte(Math.abs($date.toUnixTimestamp(blockData.date) - info.timestamp), 1);
|
|
71
|
+
|
|
58
72
|
}
|
|
59
73
|
});
|
|
@@ -1,12 +1,63 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
2
|
+
import { Config } from 'dequanto/config/Config';
|
|
3
|
+
import { GnosisSafeFactory } from 'dequanto/safe/GnosisSafeFactory';
|
|
4
|
+
import { BatchAgent } from 'dequanto/txs/agents/BatchAgent';
|
|
5
|
+
import { TxWriter } from 'dequanto/txs/TxWriter';
|
|
1
6
|
import { HardhatProvider } from 'dequanto/hardhat/HardhatProvider';
|
|
2
7
|
import { InMemoryServiceTransport } from 'dequanto/safe/transport/InMemoryServiceTransport';
|
|
3
8
|
import { SafeTx } from 'dequanto/safe/SafeTx';
|
|
4
9
|
import { $address } from 'dequanto/utils/$address';
|
|
5
10
|
import { $require } from 'dequanto/utils/$require';
|
|
6
11
|
import { SafeAccount } from 'dequanto/models/TAccount';
|
|
12
|
+
import { Web3Client } from 'dequanto/clients/Web3Client';
|
|
13
|
+
import { TEth } from 'dequanto/models/TEth';
|
|
7
14
|
|
|
8
|
-
UTest({
|
|
9
|
-
async '
|
|
15
|
+
UTest.create({
|
|
16
|
+
async 'collect normal contract calls and execute one Safe batch'() {
|
|
17
|
+
let provider = new HardhatProvider();
|
|
18
|
+
let client = provider.client();
|
|
19
|
+
let owner = provider.deployer(0);
|
|
20
|
+
let receiver = provider.deployer(1);
|
|
21
|
+
|
|
22
|
+
// Local setup; on a live network, use an existing Safe and configured MultiSend.
|
|
23
|
+
let { safeAddress } = await deployLocalTestSafe(owner, provider, client);
|
|
24
|
+
let { token } = await deployLocalTestToken(provider, client);
|
|
25
|
+
let safe: SafeAccount = {
|
|
26
|
+
type: 'safe',
|
|
27
|
+
platform: client.platform,
|
|
28
|
+
address: safeAddress,
|
|
29
|
+
operator: owner,
|
|
30
|
+
owners: [owner]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Dequanto signs and executes when available signers meet the Safe threshold.
|
|
34
|
+
// Otherwise, it submits to the Safe Service API and waits for confirmations.
|
|
35
|
+
TxWriter.DEFAULTS.safeTransport = new InMemoryServiceTransport(client, owner);
|
|
36
|
+
|
|
37
|
+
let batch = new BatchAgent().enable();
|
|
38
|
+
try {
|
|
39
|
+
// Normal write calls return mock receipts and queue transactions without submitting them.
|
|
40
|
+
await token.$receipt().transfer(safe, receiver.address, 50n);
|
|
41
|
+
await token.$receipt().transfer(safe, receiver.address, 25n);
|
|
42
|
+
|
|
43
|
+
$require.eq(batch.transactions.length, 2);
|
|
44
|
+
$require.eq(await token.received(receiver.address), 0n);
|
|
45
|
+
|
|
46
|
+
// Detects the Safe sender and submits both calls as one Safe batch transaction.
|
|
47
|
+
let writers = await batch.execute();
|
|
48
|
+
|
|
49
|
+
$require.eq(writers.length, 1);
|
|
50
|
+
$require.eq(await token.received(receiver.address), 75n);
|
|
51
|
+
$require.True($address.eq(await token.lastSender(), safe.address));
|
|
52
|
+
|
|
53
|
+
} finally {
|
|
54
|
+
batch.disable();
|
|
55
|
+
TxWriter.DEFAULTS.agent = null;
|
|
56
|
+
TxWriter.DEFAULTS.safeTransport = null;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
async 'build a Safe batch from generated tx data'() {
|
|
10
61
|
const provider = new HardhatProvider();
|
|
11
62
|
const client = provider.client();
|
|
12
63
|
const owner1 = provider.deployer(0);
|
|
@@ -53,4 +104,42 @@ UTest({
|
|
|
53
104
|
});
|
|
54
105
|
$require.notNull(safeTx);
|
|
55
106
|
}
|
|
56
|
-
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async function deployLocalTestSafe(owner: TEth.EoAccount, provider: HardhatProvider, client: Web3Client) {
|
|
111
|
+
let config = await Config.get();
|
|
112
|
+
let { contract: singleton } = await provider.deploySol('/test/fixtures/gnosis/GnosisSafe.sol', { client });
|
|
113
|
+
let { contract: proxyFactory } = await provider.deploySol('/test/fixtures/gnosis/proxies/GnosisSafeProxyFactory.sol', { client });
|
|
114
|
+
let { contract: multiSend } = await provider.deploySol('/test/fixtures/gnosis/libraries/MultiSend.sol', { client });
|
|
115
|
+
|
|
116
|
+
config.safe.contracts[client.platform] = {
|
|
117
|
+
Safe: singleton.address,
|
|
118
|
+
SafeProxyFactory: proxyFactory.address,
|
|
119
|
+
MultiSend: multiSend.address
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let handler = await GnosisSafeFactory.create(owner, client, {
|
|
123
|
+
owners: [owner.address]
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
safeAddress: handler.safeAddress
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function deployLocalTestToken(provider: HardhatProvider, client: Web3Client) {
|
|
132
|
+
let { contract: token } = await provider.deployCode(`
|
|
133
|
+
contract Token {
|
|
134
|
+
mapping(address => uint256) public received;
|
|
135
|
+
address public lastSender;
|
|
136
|
+
function transfer(address to, uint256 amount) external returns (bool) {
|
|
137
|
+
received[to] += amount;
|
|
138
|
+
lastSender = msg.sender;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`, { client });
|
|
143
|
+
|
|
144
|
+
return { token };
|
|
145
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
3
|
import { TxDataBuilder } from 'dequanto/txs/TxDataBuilder';
|
|
3
4
|
import { TxWriter } from 'dequanto/txs/TxWriter';
|
|
@@ -6,7 +7,7 @@ import { $address } from 'dequanto/utils/$address';
|
|
|
6
7
|
import { $number } from 'dequanto/utils/$number';
|
|
7
8
|
import { $require } from 'dequanto/utils/$require';
|
|
8
9
|
|
|
9
|
-
UTest({
|
|
10
|
+
UTest.create({
|
|
10
11
|
async 'build native transfer tx data with gas fields' () {
|
|
11
12
|
const client = await Web3ClientFactory.getAsync('hh:memory');
|
|
12
13
|
const alice = $account.generate('alice');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { AavePool } from '../../../0xc/eth/AavePool/AavePool';
|
|
2
3
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
3
4
|
import { HardhatProvider } from 'dequanto/hardhat/HardhatProvider';
|
|
@@ -7,7 +8,7 @@ import { $require } from 'dequanto/utils/$require';
|
|
|
7
8
|
|
|
8
9
|
// Generated classes include the storage layout: use instance.storage.$set(name, value).
|
|
9
10
|
// SlotsParser and SlotsStorage provide the low-level alternative for Solidity source.
|
|
10
|
-
UTest({
|
|
11
|
+
UTest.create({
|
|
11
12
|
async 'read and set AavePool storage by variable name on an Ethereum fork' () {
|
|
12
13
|
const client = await Web3ClientFactory.getAsync('hh:memory:eth');
|
|
13
14
|
const pool = new AavePool(undefined, client);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
3
|
import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
|
|
3
4
|
import { $account } from 'dequanto/utils/$account';
|
|
@@ -8,7 +9,7 @@ const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as const;
|
|
|
8
9
|
const INITIAL_SENDER_BALANCE = 100_000_000n;
|
|
9
10
|
const TRANSFER_AMOUNT = 42_000_000n;
|
|
10
11
|
|
|
11
|
-
UTest({
|
|
12
|
+
UTest.create({
|
|
12
13
|
async 'transfer USDC with a prebuilt ERC20 contract class' () {
|
|
13
14
|
const client = await Web3ClientFactory.getAsync('hh:memory:eth');
|
|
14
15
|
const sender = $account.generate('sender');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UTest } from 'atma-utest';
|
|
1
2
|
import { $abi } from 'dequanto/abi/$abi';
|
|
2
3
|
import { $contract } from 'dequanto/utils/$contract';
|
|
3
4
|
import { $date } from 'dequanto/utils/$date';
|
|
@@ -8,7 +9,7 @@ import { $sig } from 'dequanto/utils/$sig';
|
|
|
8
9
|
|
|
9
10
|
const ACCOUNT = '0x0000000000000000000000000000000000000002';
|
|
10
11
|
|
|
11
|
-
UTest({
|
|
12
|
+
UTest.create({
|
|
12
13
|
'encode and decode ABI values and calldata' () {
|
|
13
14
|
const encoded = $abi.encode(['address', 'uint256'], [ACCOUNT, 5n]);
|
|
14
15
|
const [account, amount] = $abi.decode(['address', 'uint256'], encoded);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Safe And ERC-4337
|
|
1
|
+
# Safe, Zodiac Roles, And ERC-4337
|
|
2
2
|
|
|
3
|
-
Use dequanto account-agent flows for Gnosis Safe and ERC-4337 instead of wiring external SDK calls directly.
|
|
3
|
+
Use dequanto account-agent flows for Gnosis Safe, Zodiac Roles, and ERC-4337 instead of wiring external SDK calls directly.
|
|
4
4
|
|
|
5
5
|
Key source files:
|
|
6
6
|
|
|
@@ -8,6 +8,8 @@ Key source files:
|
|
|
8
8
|
- `src/safe/GnosisSafeFactory.ts`
|
|
9
9
|
- `src/safe/GnosisSafeService.ts`
|
|
10
10
|
- `src/safe/SafeTx.ts`
|
|
11
|
+
- `src/zodiac/ZodiacTx.ts`
|
|
12
|
+
- `src/txs/agents/ZodiacAgent.ts`
|
|
11
13
|
- `src/safe/transport/InMemoryServiceTransport.ts`
|
|
12
14
|
- `src/safe/transport/FileServiceTransport.ts`
|
|
13
15
|
- `src/safe/transport/SafeServiceTransport.ts`
|
|
@@ -20,6 +22,8 @@ Key source files:
|
|
|
20
22
|
Useful tests:
|
|
21
23
|
|
|
22
24
|
- `test/safe/safe.spec.ts`
|
|
25
|
+
- `test/zodiac/ZodiacTx.spec.ts`
|
|
26
|
+
- `test/zodiac/ZodiacAgent.spec.ts`
|
|
23
27
|
- `test/erc4337/erc4337.spec.ts`
|
|
24
28
|
|
|
25
29
|
## Safe Account Transaction
|
|
@@ -101,6 +105,49 @@ See `./transactions.md` for the full `BatchAgent` script pattern.
|
|
|
101
105
|
|
|
102
106
|
Use `GnosisSafeService.decodeSafeTx(data, { decodeContractCall: true })`. Register ABIs on the explorer when decoding local or unknown contracts.
|
|
103
107
|
|
|
108
|
+
## Zodiac Roles Account Transaction
|
|
109
|
+
|
|
110
|
+
Use `ZodiacAccount` for a Zodiac Roles Modifier with the bytes32 role-key API:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { ZodiacAccount } from 'dequanto/models/TAccount';
|
|
114
|
+
|
|
115
|
+
let zodiacAccount: ZodiacAccount = {
|
|
116
|
+
type: 'zodiac',
|
|
117
|
+
platform: client.platform,
|
|
118
|
+
address: rolesModifierAddress,
|
|
119
|
+
operator,
|
|
120
|
+
roleKey,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
let tx = await token.$receipt().transfer(zodiacAccount, receiver, amount);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`address` is the Roles Modifier address; `operator` is the EOA authorized for `roleKey`. Supply the role key as hex, not a role name; `ZodiacTx` pads it to 32 bytes. `ZodiacAgent` wraps the original call in `execTransactionWithRole(..., shouldRevert: true)` and the operator submits it directly on-chain. The Safe/avatar pays the inner call's native value; the operator pays gas.
|
|
127
|
+
|
|
128
|
+
## Zodiac Batch
|
|
129
|
+
|
|
130
|
+
Use `ZodiacTx` to compose a batch from generated `$data()` calls:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { ZodiacTx } from 'dequanto/zodiac/ZodiacTx';
|
|
134
|
+
|
|
135
|
+
let zodiac = new ZodiacTx(zodiacAccount, client);
|
|
136
|
+
let tx = await zodiac.executeBatch(
|
|
137
|
+
await token.$data().approve(zodiacAccount, spender, amount),
|
|
138
|
+
await token.$data().transfer(zodiacAccount, receiver, amount)
|
|
139
|
+
);
|
|
140
|
+
await tx.wait();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Each inner transaction uses CALL. The Roles transaction uses DELEGATECALL to MultiSend. The helper resolves the deployment from `config.safe.contracts[client.network]`, preferring `MultiSendCallOnly` and falling back to `MultiSend`, just like `SafeTx`. Use `options.contracts` for deployment overrides; there is no MultiSend address field on the account.
|
|
144
|
+
|
|
145
|
+
The Roles Modifier must have a transaction unwrapper registered for the selected MultiSend deployment to check each inner call's permissions.
|
|
146
|
+
|
|
147
|
+
Use `execute(call)` for a single transaction. `getTxData(call)` and `getBatchTxData(...calls)` return the encoded transaction to the Roles Modifier without submitting it. These results are already wrapped; submit them as the operator, not as a Zodiac account.
|
|
148
|
+
|
|
149
|
+
`BatchAgent` also collects ordinary contract writes with a Zodiac account and submits them through `ZodiacTx`. It keeps different roles, operators, and chains in separate groups. See `./transactions.md` for the script pattern.
|
|
150
|
+
|
|
104
151
|
## ERC-4337
|
|
105
152
|
|
|
106
153
|
Use `Erc4337TxWriter` for the higher-level flow:
|
|
@@ -130,5 +177,5 @@ Use `Erc4337Service` for low-level operation creation, signing, decoding, and `h
|
|
|
130
177
|
## Avoid
|
|
131
178
|
|
|
132
179
|
- Do not manually assemble Safe `execTransaction` signatures unless the task is explicitly low-level.
|
|
133
|
-
- Do not bypass dequanto `TxWriter` account agents for Safe or ERC-4337 when generated contract writes can use account types.
|
|
180
|
+
- Do not bypass dequanto `TxWriter` account agents for Safe, Zodiac, or ERC-4337 when generated contract writes can use account types.
|
|
134
181
|
|
|
@@ -93,6 +93,7 @@ Transaction APIs accept several account shapes:
|
|
|
93
93
|
- named account: string resolved through `ChainAccountService`
|
|
94
94
|
- impersonated account in Hardhat: `{ type: 'impersonated', address }` (no private key required)
|
|
95
95
|
- Safe account: `{ type: 'safe', address, operator, owners? }`
|
|
96
|
+
- Zodiac Roles account: `{ type: 'zodiac', platform, address: rolesModifierAddress, operator, roleKey }`; the operator is an EOA and the role key is hex. See [Zodiac Roles](safe-erc4337.md#zodiac-roles-account-transaction) for execution and batching.
|
|
96
97
|
- ERC-4337 account: `{ type: 'erc4337', address, operator }`
|
|
97
98
|
|
|
98
99
|
Inspect `src/models/TAccount.ts` before adding a new account shape.
|
|
@@ -72,6 +72,7 @@ Important behavior:
|
|
|
72
72
|
- `batch.execute()` skips duplicate transactions by default; pass `{ includeDuplicates: true }` when duplicates are intentional.
|
|
73
73
|
- Deployment transactions are ignored by default; pass `new BatchAgent({ ignoreContractCreation: false })` when contract creation should also be intercepted.
|
|
74
74
|
- If the account being submitted is a Safe, `execute()` creates one Safe multicall/batch transaction from many cached single transactions.
|
|
75
|
+
- If the account being submitted is Zodiac, `execute()` wraps each compatible group in MultiSend and submits it through `execTransactionWithRole`. Different roles, operators, and chains remain separate. See [Zodiac batching](safe-erc4337.md#zodiac-batch) for deployment configuration and the required unwrapper.
|
|
75
76
|
- If the account being submitted is a Timelock, `execute()` creates one Timelock batch/scheduleBatch flow from many cached single transactions.
|
|
76
77
|
- For normal EOA accounts, `execute()` sends each cached transaction on-chain.
|
|
77
78
|
|
|
@@ -165,5 +166,5 @@ See the [revert and custom error example](../examples/revert-error.spec.ts) for
|
|
|
165
166
|
|
|
166
167
|
- Do not perform manual simulations with `$call()` or `$gas()` solely to catch reverts before sending; `TxWriter` automatically simulates via gas estimation, decodes custom errors, and throws standard JavaScript errors.
|
|
167
168
|
- Do not manually manage gas and nonce unless the task needs it.
|
|
168
|
-
- Do not use external wallet clients when a dequanto `TAccount`, `SafeAccount`, or `Erc4337Account` flow is available.
|
|
169
|
+
- Do not use external wallet clients when a dequanto `TAccount`, `SafeAccount`, `ZodiacAccount`, or `Erc4337Account` flow is available.
|
|
169
170
|
|
package/src/abi/$abi.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { TEth } from 'dequanto/models/TEth';
|
|
|
4
4
|
import { $abiCoder } from './$abiCoder';
|
|
5
5
|
import { $abiUtils } from 'dequanto/utils/$abiUtils';
|
|
6
6
|
import { $abiParser } from 'dequanto/utils/$abiParser';
|
|
7
|
+
import { $hex } from 'dequanto/utils/$hex';
|
|
8
|
+
import alot from 'alot';
|
|
9
|
+
import { $types } from 'dequanto/solidity/utils/$types';
|
|
7
10
|
|
|
8
11
|
export namespace $abi {
|
|
9
12
|
type TAbiType = string | ParamType | TAbiInput;
|
|
@@ -39,6 +42,39 @@ export namespace $abi {
|
|
|
39
42
|
let outputs = typeof abi === 'string'
|
|
40
43
|
? ($abiParser.parseMethod(abi).outputs ?? [])
|
|
41
44
|
: (Array.isArray(abi) ? abi : abi.outputs ?? []);
|
|
45
|
+
try {
|
|
46
|
+
return decodeReturnInner(outputs, hex);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (outputs.length > 1) {
|
|
49
|
+
// Zero-pad missing trailing outputs only when all outputs are static
|
|
50
|
+
// and the data ends at an output boundary.
|
|
51
|
+
// Supports newer ABIs with trailing outputs that an older deployed contract does not return.
|
|
52
|
+
const receivedWords = $hex.getBytesLength(hex) / 32;
|
|
53
|
+
const abiWords = alot(outputs).sum($types.wordSizeOf);
|
|
54
|
+
if (Number.isFinite(abiWords)
|
|
55
|
+
&& Number.isInteger(receivedWords)
|
|
56
|
+
&& receivedWords > 0
|
|
57
|
+
&& receivedWords < abiWords
|
|
58
|
+
) {
|
|
59
|
+
let missingWords = abiWords - receivedWords;
|
|
60
|
+
let boundaryWords = missingWords;
|
|
61
|
+
for (let i = outputs.length - 1; i > -1; i--) {
|
|
62
|
+
boundaryWords -= $types.wordSizeOf(outputs[i]);
|
|
63
|
+
if (boundaryWords <= 0) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (boundaryWords == 0) {
|
|
68
|
+
let expanded = hex + '0'.repeat(64 * missingWords);
|
|
69
|
+
return decodeReturnInner(outputs, expanded);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function decodeReturnInner<T = any>(outputs: TAbiOutput[], hex: string): T {
|
|
42
78
|
let returnAbi = outputs;
|
|
43
79
|
let isDynamic: boolean = null;
|
|
44
80
|
if (outputs.length > 1) {
|
|
@@ -302,9 +302,9 @@ export class ContractReader implements IContractReader {
|
|
|
302
302
|
return filters as RpcTypes.Filter;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
static async read <TResult = any> (client: Web3Client, address: TAddress, methodAbi: string){
|
|
305
|
+
static async read <TResult = any> (client: Web3Client, address: TAddress, methodAbi: string, ...params){
|
|
306
306
|
let reader = new ContractReader(client);
|
|
307
|
-
return reader.readAsync<TResult>(address, methodAbi);
|
|
307
|
+
return reader.readAsync<TResult>(address, methodAbi, ...params);
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|
package/src/gen/Generator.ts
CHANGED
|
@@ -56,6 +56,9 @@ export interface IGenerateOptions {
|
|
|
56
56
|
*/
|
|
57
57
|
implementation?: TEth.Hex | TEth.Address | string
|
|
58
58
|
|
|
59
|
+
/** When false: no files will be saved. Use "files" in result */
|
|
60
|
+
save?: boolean
|
|
61
|
+
|
|
59
62
|
/** ABI will be save alongside with TS classes */
|
|
60
63
|
saveAbi?: boolean
|
|
61
64
|
|
|
@@ -240,6 +243,7 @@ export class Generator {
|
|
|
240
243
|
implementation: implementation,
|
|
241
244
|
sources: sources?.files,
|
|
242
245
|
sourceMain: sourceMain,
|
|
246
|
+
save: this.options.save,
|
|
243
247
|
saveAbi: this.options.saveAbi,
|
|
244
248
|
saveSources: this.options.saveSources,
|
|
245
249
|
client: this.client,
|
|
@@ -33,6 +33,8 @@ export class GeneratorFromAbi {
|
|
|
33
33
|
output: string
|
|
34
34
|
outputFileExt?: string
|
|
35
35
|
implementation: TAddress
|
|
36
|
+
/** No FS writes, use files in response */
|
|
37
|
+
save?: boolean
|
|
36
38
|
saveAbi?: boolean
|
|
37
39
|
saveSources?: boolean
|
|
38
40
|
sources?: {
|
|
@@ -320,24 +322,45 @@ export class GeneratorFromAbi {
|
|
|
320
322
|
.replace(`/* $METHOD_TYPES$ */`, () => Str.indent(methodTypings.map(x => x.code).join('\n'), ' '))
|
|
321
323
|
;
|
|
322
324
|
|
|
325
|
+
if (opts.save !== false) {
|
|
326
|
+
await File.writeAsync(outputPath, code, { skipHooks: true });
|
|
327
|
+
}
|
|
328
|
+
const files = [
|
|
329
|
+
{
|
|
330
|
+
kind: 'binding',
|
|
331
|
+
path: outputPath,
|
|
332
|
+
content: code,
|
|
333
|
+
}
|
|
334
|
+
];
|
|
323
335
|
|
|
324
|
-
await File.writeAsync(outputPath, code, { skipHooks: true });
|
|
325
336
|
if (outputSourceType === 'js') {
|
|
326
337
|
let outputPathDts = outputPath.replace(/\.\w+$/, '.d.ts');
|
|
327
|
-
|
|
338
|
+
if (opts.save !== false) {
|
|
339
|
+
await File.writeAsync(outputPathDts, codeDts, { skipHooks: true });
|
|
340
|
+
}
|
|
341
|
+
files.push({
|
|
342
|
+
kind: 'declaration',
|
|
343
|
+
path: outputPathDts,
|
|
344
|
+
content: codeDts,
|
|
345
|
+
});
|
|
328
346
|
}
|
|
329
347
|
|
|
330
348
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
await File.writeAsync(
|
|
349
|
+
let outputPathAbi = class_Uri.combine(opts.output, outputDirectory, `${outputFilename}Abi.json`);
|
|
350
|
+
if (opts.saveAbi && opts.save !== false) {
|
|
351
|
+
await File.writeAsync(outputPathAbi, abiJson);
|
|
334
352
|
}
|
|
353
|
+
files.push({
|
|
354
|
+
kind: 'abi',
|
|
355
|
+
path: outputPathAbi,
|
|
356
|
+
content: JSON.stringify(abiJson),
|
|
357
|
+
});
|
|
335
358
|
|
|
336
359
|
$logger.log(`bold<green<${className}>> ABI wrapper class created: bold<${outputPath}>`);
|
|
337
360
|
|
|
338
361
|
let sources = opts.sources;
|
|
339
362
|
let sourceFiles = [];
|
|
340
|
-
if (sources
|
|
363
|
+
if (sources) {
|
|
341
364
|
sourceFiles = await alot.fromObject(sources).mapAsync(async entry => {
|
|
342
365
|
let sourceFilename = /\/?([^/]+$)/.exec(entry.key)[1];
|
|
343
366
|
let sourcePath = entry.key;
|
|
@@ -351,9 +374,15 @@ export class GeneratorFromAbi {
|
|
|
351
374
|
// sourcePath = `node_modules/${ sourcePath }`;
|
|
352
375
|
// }
|
|
353
376
|
let path = class_Uri.combine(opts.output, outputDirectory, outputFilename, sourcePath);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
377
|
+
if (opts.saveSources !== false && opts.save !== false) {
|
|
378
|
+
await File.writeAsync(path, entry.value.content, { skipHooks: true });
|
|
379
|
+
$logger.log(`Source code saved: ${path}`);
|
|
380
|
+
}
|
|
381
|
+
files.push({
|
|
382
|
+
kind: 'source',
|
|
383
|
+
path: path,
|
|
384
|
+
content: entry.value.content,
|
|
385
|
+
});
|
|
357
386
|
return path;
|
|
358
387
|
}).toArrayAsync();
|
|
359
388
|
}
|
|
@@ -363,7 +392,8 @@ export class GeneratorFromAbi {
|
|
|
363
392
|
platform: opts.network,
|
|
364
393
|
address: opts.address,
|
|
365
394
|
implementation: opts.implementation,
|
|
366
|
-
contractName: opts.contractName
|
|
395
|
+
contractName: opts.contractName,
|
|
396
|
+
files
|
|
367
397
|
};
|
|
368
398
|
}
|
|
369
399
|
|
package/src/models/TAccount.ts
CHANGED
|
@@ -4,7 +4,12 @@ import { TEth } from './TEth';
|
|
|
4
4
|
import { TPlatform } from './TPlatform';
|
|
5
5
|
|
|
6
6
|
export interface IAccount {
|
|
7
|
-
type?: 'eoa'
|
|
7
|
+
type?: 'eoa'
|
|
8
|
+
| 'safe'
|
|
9
|
+
| 'erc4337'
|
|
10
|
+
| 'timelock'
|
|
11
|
+
| 'impersonated'
|
|
12
|
+
| 'zodiac'
|
|
8
13
|
name?: string
|
|
9
14
|
platform?: TPlatform
|
|
10
15
|
|
|
@@ -68,4 +73,14 @@ export interface TimelockAccount extends IAccount {
|
|
|
68
73
|
operator: IAccount
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
export
|
|
76
|
+
export interface ZodiacAccount extends IAccount {
|
|
77
|
+
type: 'zodiac'
|
|
78
|
+
platform: TPlatform
|
|
79
|
+
/** Zodiac Roles Modifier address, not the Safe/avatar address. */
|
|
80
|
+
address: TAddress
|
|
81
|
+
operator: EoAccount
|
|
82
|
+
/** Roles v2 bytes32 role key. */
|
|
83
|
+
roleKey: TEth.Hex
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type TAccount = string | IAccount | EoAccount | SafeAccount | Erc4337Account | ZodiacAccount;
|
package/src/models/TEth.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ZodiacAccount as TZodiacAccount } from './TAccount';
|
|
1
2
|
import type { RpcTypes } from 'dequanto/rpc/Rpc'
|
|
2
3
|
import { TPlatform } from './TPlatform'
|
|
3
4
|
|
|
@@ -135,7 +136,7 @@ export namespace TEth {
|
|
|
135
136
|
|
|
136
137
|
|
|
137
138
|
export interface IAccount {
|
|
138
|
-
type?: 'eoa' | 'safe' | 'erc4337' | 'timelock' | 'impersonated'
|
|
139
|
+
type?: 'eoa' | 'safe' | 'erc4337' | 'timelock' | 'impersonated' | 'zodiac'
|
|
139
140
|
name?: string
|
|
140
141
|
address?: Address
|
|
141
142
|
platform?: Platform
|
|
@@ -154,6 +155,8 @@ export namespace TEth {
|
|
|
154
155
|
personal_sign (challenge: string, address: TEth.Address): Promise<TEth.Hex>
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
export interface ZodiacAccount extends TZodiacAccount {}
|
|
159
|
+
|
|
157
160
|
export interface SafeAccount extends IAccount {
|
|
158
161
|
type: 'safe'
|
|
159
162
|
provider?: 'gnosis'
|