Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve SAFE ownership and threshold change script #1015

Merged
merged 8 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"healthcheck": "tsx ./script/deploy/healthCheck.ts",
"propose-safe-tx": "tsx ./script/deploy/safe/propose-to-safe.ts",
"confirm-safe-tx": "tsx ./script/deploy/safe/confirm-safe-tx.ts",
"add-safe-owners": "tsx ./script/deploy/safe/add-owners-to-safe.ts",
"add-safe-owners-and-threshold": "tsx ./script/deploy/safe/add-safe-owners-and-threshold.ts",
"flatten": "forge flatten --output"
},
"dependencies": {
Expand Down
116 changes: 0 additions & 116 deletions script/deploy/safe/add-owners-to-safe.ts

This file was deleted.

176 changes: 176 additions & 0 deletions script/deploy/safe/add-safe-owners-and-threshold.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { defineCommand, runMain } from 'citty'
import { type SafeApiKitConfig } from '@safe-global/api-kit'
import { getAddress } from 'viem'
import { EthersAdapter } from '@safe-global/protocol-kit'
const { default: SafeApiKit } = await import('@safe-global/api-kit')
const { default: Safe } = await import('@safe-global/protocol-kit')
import { ethers } from 'ethers6'
import { getSafeUtilityContracts } from './config'
import {
NetworksObject,
getViemChainForNetworkName,
} from '../../utils/viemScriptHelpers'
import data from '../../../config/networks.json'
import globalConfig from '../../../config/global.json'
import * as dotenv from 'dotenv'
import { SafeTransaction } from '@safe-global/safe-core-sdk-types'
dotenv.config()

const networks: NetworksObject = data as NetworksObject

const main = defineCommand({
meta: {
name: 'add-safe-owners-and-threshold',
description:
'Adds all SAFE owners from global.json to the SAFE address in networks.json and sets threshold to 3',
},
args: {
network: {
type: 'string',
description: 'Network name',
required: true,
},
privateKey: {
type: 'string',
description: 'Private key of the signer',
},
},
async run({ args }) {
const { network, privateKey: privateKeyArg } = args

const chain = getViemChainForNetworkName(network)

const config: SafeApiKitConfig = {
chainId: BigInt(chain.id),
txServiceUrl: networks[network].safeApiUrl,
}

const privateKey = String(
privateKeyArg || process.env.PRIVATE_KEY_PRODUCTION
)

if (!privateKey)
throw new Error(
'Private key is missing, either provide it as argument or add PRIVATE_KEY_PRODUCTION to your .env'
)

console.info('Setting up connection to SAFE API')

const safeService = new SafeApiKit(config)

const safeAddress = getAddress(networks[network].safeAddress)

const rpcUrl = chain.rpcUrls.default.http[0] || args.rpcUrl

const provider = new ethers.JsonRpcProvider(rpcUrl)
const signer = new ethers.Wallet(privateKey, provider)

const ethAdapter = new EthersAdapter({
ethers,
signerOrProvider: signer,
})

const protocolKit = await Safe.create({
ethAdapter,
safeAddress: safeAddress,
contractNetworks: getSafeUtilityContracts(chain.id),
})

const owners = globalConfig.safeOwners

let nextNonce = await safeService.getNextNonce(safeAddress)
const currentThreshold = (await safeService.getSafeInfo(safeAddress))
?.threshold
if (!currentThreshold)
throw new Error('Could not get current signature threshold')

console.info('Safe Address', safeAddress)
const senderAddress = await signer.getAddress()
console.info('Signer Address', senderAddress)

// go through all owner addresses and add each of them individually
for (const o of owners) {
console.info('-'.repeat(80))
const owner = getAddress(o)
const existingOwners = await protocolKit.getOwners()
if (existingOwners.includes(owner)) {
console.info('Owner already exists', owner)
continue
}

const safeTransaction = await protocolKit.createAddOwnerTx(
{
ownerAddress: owner,
threshold: currentThreshold,
},
{
nonce: nextNonce,
}
)

console.info('Adding owner', owner)

await submitAndExecuteTransaction(
protocolKit,
safeService,
safeTransaction,
senderAddress
)
nextNonce++
}

console.info('-'.repeat(80))

if (currentThreshold != 3) {
console.info('Now changing threshold from 1 to 3')
const changeThresholdTx = await protocolKit.createChangeThresholdTx(3)
await submitAndExecuteTransaction(
protocolKit,
safeService,
changeThresholdTx,
senderAddress
)
} else console.log('Threshold is already set to 3 - no action required')

console.info('-'.repeat(80))
console.info('Script completed without errors')
},
})

async function submitAndExecuteTransaction(
protocolKit: any,
safeService: any,
safeTransaction: SafeTransaction,
senderAddress: string
): Promise<string> {
const safeTxHash = await protocolKit.getTransactionHash(safeTransaction)
const signature = await protocolKit.signHash(safeTxHash)

// Propose the transaction
await safeService.proposeTransaction({
safeAddress: await protocolKit.getAddress(),
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress,
senderSignature: signature.data,
})

console.info('Transaction proposed:', safeTxHash)

// Execute the transaction immediately
try {
const execResult = await protocolKit.executeTransaction(safeTransaction)
const receipt = await execResult.transactionResponse?.wait()
if (receipt?.status === 0) {
throw new Error('Transaction failed')
}
console.info('Transaction executed:', safeTxHash)
} catch (error) {
console.error('Transaction execution failed:', error)
throw error
}

return safeTxHash
}

runMain(main)
9 changes: 7 additions & 2 deletions script/utils/viemScriptHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Chain, defineChain, getAddress } from 'viem'
import networksConfig from '../../config/networks.json'
import { config } from 'dotenv'
config() // Load environment variables from .env
import * as dotenv from 'dotenv'
dotenv.config()

export type NetworksObject = {
[key: string]: Omit<Network, 'id'>
Expand Down Expand Up @@ -47,6 +47,11 @@ export const getViemChainForNetworkName = (networkName: string): Chain => {
const envKey = `ETH_NODE_URI_${networkName.toUpperCase()}`
const rpcUrl = process.env[envKey] || network.rpcUrl // Use .env value if available, otherwise fallback

if (!rpcUrl)
throw new Error(
`Could not find RPC URL for network ${networkName}, please add one with the key ${envKey} to your .env file`
)

const chain = defineChain({
id: network.chainId,
name: network.name,
Expand Down
Loading