init
This commit is contained in:
80
src/App.vue
80
src/App.vue
@ -11,6 +11,7 @@ const progressText = ref('')
|
||||
const hasInjectedProvider = ref(false)
|
||||
const latestBlock = ref(null)
|
||||
const selectedPreset = ref(50000)
|
||||
const showAllSafes = ref(false)
|
||||
const allSafes = ref([])
|
||||
|
||||
// BSC Testnet RPC 与 Safe 工厂地址(SafeProxyFactory 1.3.x,CREATE2 地址)
|
||||
@ -24,6 +25,7 @@ const toBlockInput = ref('')
|
||||
|
||||
const SAFE_ABI = [
|
||||
'function getOwners() view returns (address[])',
|
||||
'function getThreshold() view returns (uint256)',
|
||||
]
|
||||
|
||||
function isValidAddress(address) {
|
||||
@ -156,6 +158,58 @@ async function getFactoryLogsChunked(provider, address, topic0, fromBlock, toBlo
|
||||
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
|
||||
// 校验为 Safe:threshold > 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 = '未找到与您相关的 Safe(WS 历史扫描)'
|
||||
} catch (e) {
|
||||
errorMessage.value = e?.message || 'WS 查询失败'
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
progressText.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 通过 WS provider 扫描历史所有 Safe 地址
|
||||
async function fetchAllSafesViaWs() {
|
||||
errorMessage.value = ''
|
||||
@ -178,15 +232,16 @@ async function fetchAllSafesViaWs() {
|
||||
progressText.value = `通过 WS 扫描... 已至区块 ${processedTo}`
|
||||
})
|
||||
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) {
|
||||
try {
|
||||
const decoded = iface.decodeEventLog('ProxyCreation', log.data, log.topics)
|
||||
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 (_) {}
|
||||
}
|
||||
allSafes.value = Array.from(set)
|
||||
if (showAllSafes.value) allSafes.value = Array.from(set)
|
||||
} catch (e) {
|
||||
errorMessage.value = e?.message || '通过 WS 获取失败'
|
||||
} finally {
|
||||
@ -234,11 +289,18 @@ function startWsSubscription() {
|
||||
const decoded = iface.decodeEventLog('ProxyCreation', log.data, log.topics)
|
||||
const proxy = decoded?.proxy || decoded?.[0]
|
||||
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 safeIface = new ethers.Interface(SAFE_ABI)
|
||||
// 仅保留有效 Safe:threshold > 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 result = await provider.call({ to: proxy, data })
|
||||
const owners = safeIface.decodeFunctionResult('getOwners', result)[0]
|
||||
@ -295,7 +357,8 @@ onBeforeUnmount(() => {
|
||||
placeholder="输入钱包地址(0x...)"
|
||||
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 class="form small">
|
||||
@ -330,7 +393,8 @@ onBeforeUnmount(() => {
|
||||
</ul>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user