Skip to content

Commit ae5179c

Browse files
committed
feat: generate a tx batch file for the transaction builder Safe app
1 parent 245f07d commit ae5179c

File tree

4 files changed

+74
-2
lines changed

4 files changed

+74
-2
lines changed

contracts/scripts/populateCourts.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import courtsV2ArbitrumTestnet from "../config/courts.v2.testnet.json";
77
import courtsV2ArbitrumDevnet from "../config/courts.v2.devnet.json";
88
import courtsV2MainnetNeo from "../config/courts.v2.mainnet-neo.json";
99
import { isDevnet } from "../deploy/utils";
10-
import { execute } from "./utils/execution";
10+
import { execute, writeTransactionBatch } from "./utils/execution";
1111
import { getContracts, Cores } from "./utils/contracts";
1212

1313
enum HomeChains {
@@ -269,4 +269,6 @@ task("populate:courts", "Populates the courts and their parameters")
269269

270270
await new Promise((resolve) => setTimeout(resolve, 100));
271271
}
272+
273+
writeTransactionBatch({ name: "populate-courts" });
272274
});

contracts/scripts/populatePolicyRegistry.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import policiesV2ArbitrumTestnet from "../config/policies.v2.testnet.json";
66
import policiesV2ArbitrumDevnet from "../config/policies.v2.devnet.json";
77
import policiesV2MainnetNeo from "../config/policies.v2.mainnet-neo.json";
88
import { isDevnet } from "../deploy/utils";
9-
import { execute } from "./utils/execution";
9+
import { execute, writeTransactionBatch } from "./utils/execution";
1010

1111
enum HomeChains {
1212
ARBITRUM_ONE = 42161,
@@ -112,4 +112,6 @@ task("populate:policy-registry", "Populates the policy registry for each court")
112112
console.log("Populating policy for %s Court (%d): %s", policy.name, policy.court, policy.uri);
113113
await policyRegistry.setPolicy.populateTransaction(policy.court, policy.name, policy.uri).then(execute);
114114
}
115+
116+
writeTransactionBatch({ name: "populate-policy-registry" });
115117
});

contracts/scripts/utils/execution.ts

+24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { type ContractTransaction } from "ethers";
2+
import fs from "fs";
3+
import { type BuilderTransaction, template, transaction, transactionBuilderUrl } from "./tx-builder";
24

35
const governableAbi = [
46
{
@@ -16,6 +18,8 @@ const governableAbi = [
1618
},
1719
];
1820

21+
const transactions: BuilderTransaction[] = [];
22+
1923
export const execute = async (tx: ContractTransaction) => {
2024
const hre = require("hardhat");
2125
const { ethers } = hre;
@@ -25,10 +29,30 @@ export const execute = async (tx: ContractTransaction) => {
2529
const isContract = (await ethers.provider.getCode(governor)).length > 2;
2630
if (isContract) {
2731
// Don't execute, just log the tx. It must be submitted for execution separately.
32+
const { to, value, data } = tx;
33+
transactions.push(transaction({ to, value, data }));
2834
console.log("tx = %O", tx);
2935
} else {
3036
// Execute the tx
3137
const signer = (await ethers.getSigners())[0];
3238
await signer.sendTransaction(tx);
3339
}
3440
};
41+
42+
export function writeTransactionBatch({ name, outputPath = "tx-batch.json" }: { name: string; outputPath?: string }) {
43+
if (!name?.trim()) throw new Error("Batch name is required");
44+
45+
if (!transactions?.length) {
46+
console.log("No transaction batch to write");
47+
return;
48+
}
49+
50+
try {
51+
const templateObject = template({ name, transactions });
52+
fs.writeFileSync(outputPath, JSON.stringify(templateObject, null, 2));
53+
console.log(`Transaction batch written to ${outputPath}`);
54+
console.log(`The batch can be submitted to the Safe app at: ${transactionBuilderUrl}`);
55+
} catch (error) {
56+
throw new Error(`Failed to write transaction batch: ${(error as Error).message}`);
57+
}
58+
}

contracts/scripts/utils/tx-builder.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Transaction batch example: https://github.com/safe-global/safe-wallet-monorepo/blob/8bbf3b82edc347b70a038629cd9afd45eb1ed38a/apps/web/cypress/fixtures/test-working-batch.json
2+
3+
const governor = "0x66e8DE9B42308c6Ca913D1EE041d6F6fD037A57e";
4+
const deployer = "0xf1C7c037891525E360C59f708739Ac09A7670c59";
5+
6+
export const template = ({ name, transactions }: { name: string; transactions: BuilderTransaction[] }) => ({
7+
version: "1.0",
8+
chainId: "42161", // Arbitrum One
9+
createdAt: Date.now(),
10+
meta: {
11+
name,
12+
description: "", // Not used because the Safe app doesn't show it
13+
txBuilderVersion: "1.18.0",
14+
createdFromSafeAddress: governor,
15+
createdFromOwnerAddress: deployer,
16+
},
17+
transactions,
18+
});
19+
20+
export const transaction = ({
21+
to,
22+
value,
23+
data,
24+
}: {
25+
to: string;
26+
value: bigint | undefined;
27+
data: string;
28+
}): BuilderTransaction => ({
29+
to,
30+
value: value?.toString() ?? "0",
31+
data,
32+
contractMethod: null,
33+
contractInputsValues: null,
34+
});
35+
36+
export interface BuilderTransaction {
37+
to: string;
38+
value: string;
39+
data: string;
40+
contractMethod: null;
41+
contractInputsValues: null;
42+
}
43+
44+
export const transactionBuilderUrl = `https://app.safe.global/apps/open?safe=arb1:${governor}&appUrl=https%3A%2F%2Fapps-portal.safe.global%2Ftx-builder`;

0 commit comments

Comments
 (0)