This commit is contained in:
2025-08-27 16:55:21 +08:00
parent a9e7a1c201
commit fa8e215538

View File

@ -11,6 +11,7 @@ const progressText = ref('')
const hasInjectedProvider = ref(false) const hasInjectedProvider = ref(false)
const latestBlock = ref(null) const latestBlock = ref(null)
const selectedPreset = ref(50000) const selectedPreset = ref(50000)
const showAllSafes = ref(false)
const allSafes = ref([]) const allSafes = ref([])
// BSC Testnet RPC 与 Safe 工厂地址SafeProxyFactory 1.3.xCREATE2 地址) // BSC Testnet RPC 与 Safe 工厂地址SafeProxyFactory 1.3.xCREATE2 地址)
@ -24,6 +25,7 @@ const toBlockInput = ref('')
const SAFE_ABI = [ const SAFE_ABI = [
'function getOwners() view returns (address[])', 'function getOwners() view returns (address[])',
'function getThreshold() view returns (uint256)',
] ]
function isValidAddress(address) { function isValidAddress(address) {
@ -156,6 +158,58 @@ async function getFactoryLogsChunked(provider, address, topic0, fromBlock, toBlo
return all return all
} }
// 通过 WS 扫描并筛选“与我相关”的 Safe按 getOwners 包含目标地址)
async function fetchMySafesViaWs() {
errorMessage.value = ''
safes.value = []
progressText.value = ''
const targetOwner = (ownerAddress.value || connectedAddress.value || '').trim()
if (!isValidAddress(targetOwner)) {
errorMessage.value = '请输入有效地址或先连接钱包'
return
}
isLoading.value = true
try {
const provider = new ethers.WebSocketProvider(RPC_WS_URL, 97)
const latest = await provider.getBlockNumber()
const fromBlock = fromBlockInput.value ? Number(fromBlockInput.value) : Math.max(1, latest - (Number(selectedPreset.value) || 50000))
const toBlock = toBlockInput.value ? Number(toBlockInput.value) : latest
const factoryTopic = ethers.id('ProxyCreation(address,address)')
const logs = await getFactoryLogsChunked(provider, SAFE_PROXY_FACTORY, factoryTopic, fromBlock, toBlock, 50000, (processedTo) => {
progressText.value = `通过 WS 扫描(与我相关)... 已至区块 ${processedTo}`
})
const eventIface = new ethers.Interface(['event ProxyCreation(address proxy,address singleton)'])
const safeIface = new ethers.Interface(SAFE_ABI)
const resultSet = new Set()
for (const log of logs) {
try {
const decoded = eventIface.decodeEventLog('ProxyCreation', log.data, log.topics)
const proxy = decoded?.proxy || decoded?.[0]
if (!proxy) continue
// 校验为 Safethreshold > 0
const thresholdData = safeIface.encodeFunctionData('getThreshold', [])
const thresholdRes = await provider.call({ to: proxy, data: thresholdData })
const threshold = Number(safeIface.decodeFunctionResult('getThreshold', thresholdRes)[0])
if (!Number.isFinite(threshold) || threshold <= 0) continue
// 校验 owners 是否包含
const ownersData = safeIface.encodeFunctionData('getOwners', [])
const ownersRes = await provider.call({ to: proxy, data: ownersData })
const owners = safeIface.decodeFunctionResult('getOwners', ownersRes)[0]
if (owners.map((o) => ethers.getAddress(o)).includes(ethers.getAddress(targetOwner))) {
resultSet.add(ethers.getAddress(proxy))
}
} catch (_) {}
}
safes.value = Array.from(resultSet)
if (safes.value.length === 0) errorMessage.value = '未找到与您相关的 SafeWS 历史扫描)'
} catch (e) {
errorMessage.value = e?.message || 'WS 查询失败'
} finally {
isLoading.value = false
progressText.value = ''
}
}
// 通过 WS provider 扫描历史所有 Safe 地址 // 通过 WS provider 扫描历史所有 Safe 地址
async function fetchAllSafesViaWs() { async function fetchAllSafesViaWs() {
errorMessage.value = '' errorMessage.value = ''
@ -178,15 +232,16 @@ async function fetchAllSafesViaWs() {
progressText.value = `通过 WS 扫描... 已至区块 ${processedTo}` progressText.value = `通过 WS 扫描... 已至区块 ${processedTo}`
}) })
const iface = new ethers.Interface(['event ProxyCreation(address proxy,address singleton)']) const iface = new ethers.Interface(['event ProxyCreation(address proxy,address singleton)'])
const set = new Set(allSafes.value) const set = new Set(showAllSafes.value ? allSafes.value : [])
for (const log of logs) { for (const log of logs) {
try { try {
const decoded = iface.decodeEventLog('ProxyCreation', log.data, log.topics) const decoded = iface.decodeEventLog('ProxyCreation', log.data, log.topics)
const proxy = decoded?.proxy || decoded?.[0] const proxy = decoded?.proxy || decoded?.[0]
if (proxy) set.add(ethers.getAddress(proxy)) if (!proxy) continue
if (showAllSafes.value) set.add(ethers.getAddress(proxy))
} catch (_) {} } catch (_) {}
} }
allSafes.value = Array.from(set) if (showAllSafes.value) allSafes.value = Array.from(set)
} catch (e) { } catch (e) {
errorMessage.value = e?.message || '通过 WS 获取失败' errorMessage.value = e?.message || '通过 WS 获取失败'
} finally { } finally {
@ -234,11 +289,18 @@ function startWsSubscription() {
const decoded = iface.decodeEventLog('ProxyCreation', log.data, log.topics) const decoded = iface.decodeEventLog('ProxyCreation', log.data, log.topics)
const proxy = decoded?.proxy || decoded?.[0] const proxy = decoded?.proxy || decoded?.[0]
if (!proxy) return if (!proxy) return
// 记录所有 Safe实时新建
const normalized = ethers.getAddress(proxy)
if (!allSafes.value.includes(normalized)) allSafes.value = [normalized, ...allSafes.value]
const provider = new ethers.JsonRpcProvider(RPC_HTTP_URL, 97) const provider = new ethers.JsonRpcProvider(RPC_HTTP_URL, 97)
const safeIface = new ethers.Interface(SAFE_ABI) const safeIface = new ethers.Interface(SAFE_ABI)
// 仅保留有效 Safethreshold > 0
const thresholdData = safeIface.encodeFunctionData('getThreshold', [])
const thresholdRes = await provider.call({ to: proxy, data: thresholdData })
const threshold = Number(safeIface.decodeFunctionResult('getThreshold', thresholdRes)[0])
if (!Number.isFinite(threshold) || threshold <= 0) return
// 按需记录所有 Safe
if (showAllSafes.value) {
const normalized = ethers.getAddress(proxy)
if (!allSafes.value.includes(normalized)) allSafes.value = [normalized, ...allSafes.value]
}
const data = safeIface.encodeFunctionData('getOwners', []) const data = safeIface.encodeFunctionData('getOwners', [])
const result = await provider.call({ to: proxy, data }) const result = await provider.call({ to: proxy, data })
const owners = safeIface.decodeFunctionResult('getOwners', result)[0] const owners = safeIface.decodeFunctionResult('getOwners', result)[0]
@ -295,7 +357,8 @@ onBeforeUnmount(() => {
placeholder="输入钱包地址0x..." placeholder="输入钱包地址0x..."
spellcheck="false" spellcheck="false"
/> />
<button :disabled="isLoading" @click="fetchSafes">{{ isLoading ? '查询中...' : '查询' }}</button> <button :disabled="isLoading" @click="fetchSafes">{{ isLoading ? '查询中...' : '查询(HTTP)' }}</button>
<button :disabled="isLoading" @click="fetchMySafesViaWs">{{ isLoading ? '查询中...' : '查询(WS, 与我相关)' }}</button>
</div> </div>
<div class="form small"> <div class="form small">
@ -330,7 +393,8 @@ onBeforeUnmount(() => {
</ul> </ul>
<div class="form"> <div class="form">
<button :disabled="isLoading" @click="fetchAllSafesViaWs">通过 WS 获取所有 Safe</button> <label><input type="checkbox" v-model="showAllSafes" /> 显示/收集所有 Safe</label>
<button :disabled="isLoading || !showAllSafes" @click="fetchAllSafesViaWs">通过 WS 获取所有 Safe</button>
<span v-if="allSafes.length"> {{ allSafes.length }} </span> <span v-if="allSafes.length"> {{ allSafes.length }} </span>
</div> </div>