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 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.x,CREATE2 地址)
|
// BSC Testnet RPC 与 Safe 工厂地址(SafeProxyFactory 1.3.x,CREATE2 地址)
|
||||||
@ -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
|
||||||
|
// 校验为 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 地址
|
// 通过 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)
|
||||||
|
// 仅保留有效 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 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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user