Skip to content

Commit 5d6a825

Browse files
katspaughclaude
andauthored
fix: add on-chain verification and salt nonce increment for Safe deployment (#4)
- Add on-chain deployment verification before attempting to deploy a Safe - Automatically sync local storage with blockchain state to prevent mismatches - Implement automatic salt nonce increment when creating Safes with duplicate configs - Prevent address collisions by checking up to 100 salt nonce variations - Store salt nonce in predictedConfig for deterministic address generation This resolves issues where: 1. Users couldn't deploy Safes that local storage incorrectly marked as deployed 2. Users couldn't create multiple Safes with identical owner/threshold configurations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3184704 commit 5d6a825

File tree

2 files changed

+65
-10
lines changed

2 files changed

+65
-10
lines changed

src/commands/account/create.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,44 @@ export async function createSafe() {
164164

165165
try {
166166
const safeService = new SafeService(chain)
167-
const { predictedAddress, safeAccountConfig } = await safeService.createPredictedSafe({
168-
owners,
169-
threshold: thresholdNum,
170-
})
167+
168+
// Find an available salt nonce (increment if Safe already deployed)
169+
let saltNonce = '0'
170+
let predictedAddress: Address | undefined
171+
let safeAccountConfig: { owners: string[]; threshold: number } | undefined
172+
let attempts = 0
173+
const maxAttempts = 100
174+
175+
while (attempts < maxAttempts) {
176+
const result = await safeService.createPredictedSafe({
177+
owners,
178+
threshold: thresholdNum,
179+
saltNonce,
180+
})
181+
182+
predictedAddress = result.predictedAddress
183+
safeAccountConfig = result.safeAccountConfig
184+
185+
// Check if Safe already deployed at this address
186+
try {
187+
const safeInfo = await safeService.getSafeInfo(predictedAddress)
188+
if (safeInfo.isDeployed) {
189+
// Safe already deployed, try next salt nonce
190+
saltNonce = (BigInt(saltNonce) + 1n).toString()
191+
attempts++
192+
continue
193+
}
194+
} catch {
195+
// Error checking deployment status, assume not deployed
196+
}
197+
198+
// Found an available address
199+
break
200+
}
201+
202+
if (attempts >= maxAttempts || !predictedAddress || !safeAccountConfig) {
203+
throw new Error(`Could not find available Safe address after ${maxAttempts} attempts`)
204+
}
171205

172206
spinner.stop('Safe created!')
173207

@@ -180,6 +214,7 @@ export async function createSafe() {
180214
predictedConfig: {
181215
owners: safeAccountConfig.owners,
182216
threshold: safeAccountConfig.threshold,
217+
saltNonce,
183218
},
184219
})
185220

src/commands/account/deploy.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,37 @@ export async function deploySafe(account?: string) {
7272
return
7373
}
7474

75-
if (safe.deployed) {
76-
logError('Safe is already deployed')
77-
return
78-
}
79-
8075
if (!safe.predictedConfig) {
8176
logError('Safe does not have deployment configuration')
8277
return
8378
}
8479

80+
// Verify on-chain deployment status
81+
const chain = configStore.getChain(safe.chainId)!
82+
const safeService = new SafeService(chain)
83+
84+
try {
85+
const safeInfo = await safeService.getSafeInfo(address)
86+
87+
// If Safe is actually deployed on-chain
88+
if (safeInfo.isDeployed) {
89+
// Sync local storage with on-chain reality
90+
if (!safe.deployed) {
91+
safeStorage.updateSafe(chainId, address, { deployed: true })
92+
}
93+
logError('Safe is already deployed on-chain')
94+
return
95+
}
96+
97+
// If local storage says deployed but on-chain says not deployed, fix the storage
98+
if (safe.deployed && !safeInfo.isDeployed) {
99+
safeStorage.updateSafe(chainId, address, { deployed: false })
100+
}
101+
} catch {
102+
// If we can't verify, log warning but continue
103+
console.log(pc.yellow('⚠ Warning: Could not verify on-chain deployment status'))
104+
}
105+
85106
// Get active wallet
86107
const activeWallet = walletStorage.getActiveWallet()
87108
if (!activeWallet) {
@@ -90,7 +111,6 @@ export async function deploySafe(account?: string) {
90111
return
91112
}
92113

93-
const chain = configStore.getChain(safe.chainId)!
94114
const eip3770 = formatSafeAddress(safe.address as Address, safe.chainId, chains)
95115

96116
console.log('')

0 commit comments

Comments
 (0)