From a0ab9460a222005434fd66338069db4b1186a30a Mon Sep 17 00:00:00 2001 From: katspaugh Date: Sat, 25 Oct 2025 11:33:53 +0200 Subject: [PATCH] fix: add on-chain verification and salt nonce increment for Safe deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/commands/account/create.ts | 43 ++++++++++++++++++++++++++++++---- src/commands/account/deploy.ts | 32 ++++++++++++++++++++----- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/commands/account/create.ts b/src/commands/account/create.ts index fbf685d..72d7be6 100644 --- a/src/commands/account/create.ts +++ b/src/commands/account/create.ts @@ -164,10 +164,44 @@ export async function createSafe() { try { const safeService = new SafeService(chain) - const { predictedAddress, safeAccountConfig } = await safeService.createPredictedSafe({ - owners, - threshold: thresholdNum, - }) + + // Find an available salt nonce (increment if Safe already deployed) + let saltNonce = '0' + let predictedAddress: Address | undefined + let safeAccountConfig: { owners: string[]; threshold: number } | undefined + let attempts = 0 + const maxAttempts = 100 + + while (attempts < maxAttempts) { + const result = await safeService.createPredictedSafe({ + owners, + threshold: thresholdNum, + saltNonce, + }) + + predictedAddress = result.predictedAddress + safeAccountConfig = result.safeAccountConfig + + // Check if Safe already deployed at this address + try { + const safeInfo = await safeService.getSafeInfo(predictedAddress) + if (safeInfo.isDeployed) { + // Safe already deployed, try next salt nonce + saltNonce = (BigInt(saltNonce) + 1n).toString() + attempts++ + continue + } + } catch { + // Error checking deployment status, assume not deployed + } + + // Found an available address + break + } + + if (attempts >= maxAttempts || !predictedAddress || !safeAccountConfig) { + throw new Error(`Could not find available Safe address after ${maxAttempts} attempts`) + } spinner.stop('Safe created!') @@ -180,6 +214,7 @@ export async function createSafe() { predictedConfig: { owners: safeAccountConfig.owners, threshold: safeAccountConfig.threshold, + saltNonce, }, }) diff --git a/src/commands/account/deploy.ts b/src/commands/account/deploy.ts index 11426c2..5149dc0 100644 --- a/src/commands/account/deploy.ts +++ b/src/commands/account/deploy.ts @@ -72,16 +72,37 @@ export async function deploySafe(account?: string) { return } - if (safe.deployed) { - logError('Safe is already deployed') - return - } - if (!safe.predictedConfig) { logError('Safe does not have deployment configuration') return } + // Verify on-chain deployment status + const chain = configStore.getChain(safe.chainId)! + const safeService = new SafeService(chain) + + try { + const safeInfo = await safeService.getSafeInfo(address) + + // If Safe is actually deployed on-chain + if (safeInfo.isDeployed) { + // Sync local storage with on-chain reality + if (!safe.deployed) { + safeStorage.updateSafe(chainId, address, { deployed: true }) + } + logError('Safe is already deployed on-chain') + return + } + + // If local storage says deployed but on-chain says not deployed, fix the storage + if (safe.deployed && !safeInfo.isDeployed) { + safeStorage.updateSafe(chainId, address, { deployed: false }) + } + } catch { + // If we can't verify, log warning but continue + console.log(pc.yellow('⚠ Warning: Could not verify on-chain deployment status')) + } + // Get active wallet const activeWallet = walletStorage.getActiveWallet() if (!activeWallet) { @@ -90,7 +111,6 @@ export async function deploySafe(account?: string) { return } - const chain = configStore.getChain(safe.chainId)! const eip3770 = formatSafeAddress(safe.address as Address, safe.chainId, chains) console.log('')