Skip to content

Commit 5e89744

Browse files
authored
add task to upload contracts to tenderly (#2591)
1 parent 7f498eb commit 5e89744

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-2
lines changed

contracts/dev.env

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,7 @@ VALIDATOR_KEYS_S3_BUCKET_NAME=[validator-keys-test | validator-keys]
7272
VALIDATOR_MASTER_ENCRYPTED_PRIVATE_KEY=
7373

7474
# Beaconcha.in API key can be obtained by creating an account
75-
BEACONCHAIN_API_KEY=
75+
BEACONCHAIN_API_KEY=
76+
77+
# Tenderly access token required to upload newly deployed and verified contracts to Tenderly
78+
TENDERLY_ACCESS_TOKEN=

contracts/hardhat.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ module.exports = {
392392
holesky: process.env.ETHERSCAN_API_KEY,
393393
base: process.env.BASESCAN_API_KEY,
394394
sonic: process.env.SONICSCAN_API_KEY,
395-
plume: 'empty', // this works for: npx hardhat verify...
395+
plume: "empty", // this works for: npx hardhat verify...
396396
},
397397
customChains: [
398398
{

contracts/tasks/tasks.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ const {
103103
resolveNativeStakingStrategyProxy,
104104
snapValidators,
105105
} = require("./validator");
106+
107+
const { tenderlySync } = require("./tenderly");
106108
const { setDefaultValidator, snapSonicStaking } = require("../utils/sonic");
107109
const {
108110
undelegateValidator,
@@ -1829,3 +1831,11 @@ task("lzSetConfig")
18291831
.setAction(async (taskArgs) => {
18301832
await lzSetConfig(taskArgs, hre);
18311833
});
1834+
1835+
subtask(
1836+
"tenderlySync",
1837+
"Fetches all contracts from deployment descriptors and uploads them to Tenderly if they are not there yet."
1838+
).setAction(tenderlySync);
1839+
task("tenderlySync").setAction(async (_, __, runSuper) => {
1840+
return runSuper();
1841+
});

contracts/tasks/tenderly.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const fetch = require("node-fetch");
2+
3+
async function tenderlySync(taskArguments, hre) {
4+
const allDeployments = await hre.deployments.all();
5+
const deployedContracts = Object.entries(allDeployments).map(
6+
([name, deployment]) => ({
7+
name,
8+
address: deployment.address,
9+
})
10+
);
11+
const chainId = parseInt(
12+
(await hre.network.provider.send("eth_chainId")).toString()
13+
);
14+
const allTenderlyContracts = await fetchAllContractsFromTenderly(chainId);
15+
16+
for (let i = 0; i < deployedContracts.length; i++) {
17+
let presentInTenderly = false;
18+
const deployedContract = deployedContracts[i];
19+
for (let j = 0; j < allTenderlyContracts.length; j++) {
20+
if (
21+
deployedContract.address.toLowerCase() ==
22+
allTenderlyContracts[j].toLowerCase()
23+
) {
24+
presentInTenderly = true;
25+
}
26+
}
27+
28+
if (presentInTenderly) {
29+
console.log(
30+
`✓ contract ${deployedContract.name}[${deployedContract.address}] already detected by Tenderly`
31+
);
32+
continue;
33+
}
34+
35+
await uploadContractToTenderly(
36+
deployedContract.address,
37+
deployedContract.name,
38+
chainId
39+
);
40+
console.log(
41+
`✅ contract ${deployedContract.name}[${deployedContract.address}] added to Tenderly`
42+
);
43+
}
44+
}
45+
46+
async function uploadContractToTenderly(address, name, networkId) {
47+
if (!process.env.TENDERLY_ACCESS_TOKEN) {
48+
throw new Error("TENDERLY_ACCESS_TOKEN env var missing");
49+
}
50+
51+
const baseUrl = `https://api.tenderly.co/api/v1/account/origin-protocol/project/origin/address`;
52+
53+
const payload = {
54+
network_id: `${networkId}`,
55+
address: address,
56+
display_name: name,
57+
};
58+
59+
const response = await fetch(baseUrl, {
60+
method: "POST",
61+
headers: {
62+
"X-Access-Key": `${process.env.TENDERLY_ACCESS_TOKEN}`,
63+
"Content-Type": "application/json",
64+
Accept: "application/json",
65+
},
66+
body: JSON.stringify(payload),
67+
});
68+
69+
if (!response.ok) {
70+
throw new Error(
71+
`API request failed with status ${
72+
response.status
73+
}: ${await response.text()}`
74+
);
75+
}
76+
77+
const data = await response.json();
78+
return data;
79+
}
80+
81+
async function fetchAllContractsFromTenderly() {
82+
if (!process.env.TENDERLY_ACCESS_TOKEN) {
83+
throw new Error("TENDERLY_ACCESS_TOKEN env var missing");
84+
}
85+
86+
const baseUrl = `https://api.tenderly.co/api/v1/account/origin-protocol/project/origin/contracts?accountType=contract`;
87+
88+
let url = baseUrl;
89+
const response = await fetch(url, {
90+
method: "GET",
91+
headers: {
92+
"X-Access-Key": `${process.env.TENDERLY_ACCESS_TOKEN}`,
93+
Accept: "application/json",
94+
},
95+
});
96+
97+
if (!response.ok) {
98+
throw new Error(
99+
`API request failed with status ${
100+
response.status
101+
}: ${await response.text()}`
102+
);
103+
}
104+
105+
const data = await response.json();
106+
107+
// return the array of contract addresses
108+
return data.map((contractData) => contractData.contract.address);
109+
}
110+
111+
module.exports = {
112+
tenderlySync,
113+
};

0 commit comments

Comments
 (0)