Skip to content

Commit 112c1d9

Browse files
authored
Merge pull request #5 from kleros/feat/safe-tx-batcher
Safe transaction batcher
2 parents d0edb9a + 9926f21 commit 112c1d9

File tree

2 files changed

+80
-16
lines changed

2 files changed

+80
-16
lines changed

snapshots/cli.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ import { hideBin } from "yargs/helpers";
66
import dayjs from "dayjs";
77
import utc from "dayjs/plugin/utc.js";
88
import { createSnapshotCreator } from "./src/create-snapshot-from-block-limits.js";
9-
import { formatEther } from "ethers/lib/utils.js";
109
import fs from "fs";
1110
import { fileToIpfs } from "./src/fileToIpfs.js";
12-
11+
import { addTransactionToBatch, writeTransactionBatch } from "./src/helpers/tx-builder.js";
1312
dotenv.config();
1413

1514
dayjs.extend(utc);
@@ -18,24 +17,28 @@ const chains = [
1817
// ONLY uncomment Arbitrum Sepolia if you are testing
1918
// {
2019
// chainId: 421614,
20+
// chainShortName: "arb-sep", // https://chainid.network/shortNameMapping.json
2121
// blocksPerSecond: 0.268,
2222
// klerosCoreAddress: "0xA54e7A16d7460e38a8F324eF46782FB520d58CE8",
2323
// token: "0x34B944D42cAcfC8266955D07A80181D2054aa225",
2424
// pnkDropRatio: BigNumber.from("1000000000"),
2525
// fromBlock: 3638878,
2626
// provider: getDefaultProvider(process.env.INFURA_ARB_SEPOLIA_RPC),
2727
// merkleDropAddress: "0x93024F2D53D180074F4575818dE3E8dcE8147CF2",
28+
// safeAddress: "0x66e8DE9B42308c6Ca913D1EE041d6F6fD037A57e", // Safe not supported on Arbitrum Sepolia
2829
// },
2930
{
3031
chainId: 42161,
32+
chainShortName: "arb", // https://chainid.network/shortNameMapping.json
3133
blocksPerSecond: 0.26,
3234
klerosCoreAddress: "0x991d2df165670b9cac3B022f4B68D65b664222ea",
3335
token: "0x330bD769382cFc6d50175903434CCC8D206DCAE5",
3436
pnkDropRatio: BigNumber.from("1000000000"),
3537
fromBlock: 272063254,
3638
provider: getDefaultProvider(process.env.INFURA_ARB_ONE_RPC),
37-
merkleDropAddress: "0x2a23B84078b287753A91C522c3bB3b6B32f6F8f1"
38-
}
39+
merkleDropAddress: "0x2a23B84078b287753A91C522c3bB3b6B32f6F8f1",
40+
safeAddress: "0x66e8DE9B42308c6Ca913D1EE041d6F6fD037A57e",
41+
},
3942
];
4043

4144
const argv = yargs(hideBin(process.argv))
@@ -167,7 +170,9 @@ const main = async () => {
167170
const snapshot = await createSnapshot({ fromBlock: c.fromBlock, startDate, endDate });
168171
snapshotInfos.push({
169172
// edit when arbitrum inclusion
170-
filename: `${c.chainId == "42161" ? "arbitrum-" : "arbitrumSepolia-"}snapshot-${startDate.toISOString().slice(0, 7)}.json`,
173+
filename: `${c.chainId == "42161" ? "arbitrum-" : "arbitrumSepolia-"}snapshot-${startDate
174+
.toISOString()
175+
.slice(0, 7)}.json`,
171176
chain: c,
172177
snapshot,
173178
period: periods[c.chainId],
@@ -185,27 +190,29 @@ const main = async () => {
185190
// txs to run sequentially, hardcoded section.
186191
console.log("PNK should be already approved to MerkleRedeem contract for each chain");
187192

188-
const merkleDropABI = [
189-
"function seedAllocations(uint _month, bytes32 _merkleRoot, uint _totalAllocation) external"
190-
];
191-
192-
// Helper function to generate transaction URLs
193-
const txToUrl = (tx, chainId) =>
194-
`https://greenlucid.github.io/lame-tx-prompt/site?to=${tx.to}&data=${tx.data}&value=0&chainId=${chainId}`;
193+
const merkleDropABI = ["function seedAllocations(uint _month, bytes32 _merkleRoot, uint _totalAllocation) external"];
195194

196195
// Loop through snapshotInfos to generate transactions for each chain
197196
for (const sinfo of snapshotInfos) {
198197
const merkleContract = new Contract(sinfo.chain.merkleDropAddress, merkleDropABI);
199198

200199
// Populate the seedAllocations transaction
201200
const tx = await merkleContract.populateTransaction.seedAllocations(
202-
sinfo.period, // The period (month) for this snapshot
201+
sinfo.period, // The period (month) for this snapshot
203202
sinfo.snapshot.merkleTree.root, // The Merkle root from the snapshot
204-
sinfo.snapshot.droppedAmount // The total allocation to drop
203+
sinfo.snapshot.droppedAmount // The total allocation to drop
205204
);
206205

207-
// Log the transaction URL for this chain
208-
console.log(`Transaction for chain ${sinfo.chain.chainId}:`, txToUrl(tx, sinfo.chain.chainId));
206+
addTransactionToBatch(tx);
207+
208+
const { chainId, chainShortName, safeAddress } = sinfo.chain;
209+
writeTransactionBatch({
210+
name: "Seed allocations",
211+
chainId,
212+
chainShortName,
213+
safeAddress,
214+
outputPath: `tx-batch-${chainShortName}.json`,
215+
});
209216
}
210217
};
211218

snapshots/src/helpers/tx-builder.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import fs from "fs";
2+
3+
// Transaction batch example: https://github.com/safe-global/safe-wallet-monorepo/blob/8bbf3b82edc347b70a038629cd9afd45eb1ed38a/apps/web/cypress/fixtures/test-working-batch.json
4+
5+
const signer = "0xBe7ee23877D530b8a17971CfDA7B5925b57e87B1";
6+
const transactions = [];
7+
8+
const template = ({ name, chainId, safeAddress, transactions }) => ({
9+
version: "1.0",
10+
chainId,
11+
createdAt: Date.now(),
12+
meta: {
13+
name,
14+
description: "", // Not used because the Safe app doesn't show it
15+
txBuilderVersion: "1.18.0",
16+
createdFromSafeAddress: safeAddress,
17+
createdFromOwnerAddress: signer,
18+
},
19+
transactions,
20+
});
21+
22+
const transaction = ({ to, value, data }) => ({
23+
to,
24+
value: value?.toString() ?? "0",
25+
data,
26+
contractMethod: null,
27+
contractInputsValues: null,
28+
});
29+
30+
const transactionBuilderUrl = ({ chainShortName, safeAddress }) =>
31+
`https://app.safe.global/apps/open?safe=${chainShortName}:${safeAddress}&appUrl=https%3A%2F%2Fapps-portal.safe.global%2Ftx-builder`;
32+
33+
export const addTransactionToBatch = (tx) => {
34+
const { to, value, data } = tx;
35+
transactions.push(transaction({ to, value, data }));
36+
console.log("tx = %O", tx);
37+
};
38+
39+
export function writeTransactionBatch({ name, chainId, chainShortName, safeAddress, outputPath = "tx-batch.json" }) {
40+
if (!name?.trim()) throw new Error("Batch name is required");
41+
42+
if (!transactions?.length) {
43+
console.log("No transaction batch to write");
44+
return;
45+
}
46+
47+
try {
48+
const templateObject = template({ name, chainId, safeAddress, transactions });
49+
fs.writeFileSync(outputPath, JSON.stringify(templateObject, null, 2));
50+
console.log(`Transaction batch written to ${outputPath}`);
51+
console.log(
52+
`The batch can be submitted to the Safe app at: ${transactionBuilderUrl({ chainShortName, safeAddress })}`
53+
);
54+
} catch (error) {
55+
throw new Error(`Failed to write transaction batch: ${error.message}`);
56+
}
57+
}

0 commit comments

Comments
 (0)