Skip to content

Commit f3d7293

Browse files
Merge pull request #43 from Piyushbijarania/feat/sepolia-composed-feeds
Feat/sepolia composed feeds
2 parents 6ebb898 + a718393 commit f3d7293

4 files changed

Lines changed: 287 additions & 59 deletions

File tree

components/createOracle.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export default function CreateOracleIntegrated() {
126126
1: 'https://etherscan.io',
127127
8453: 'https://basescan.org',
128128
534351: 'https://sepolia.scrollscan.com',
129+
11155111: 'https://sepolia.etherscan.io',
129130
}
130131
return explorers[chainId] ? `${explorers[chainId]}/tx/${txHash}` : ''
131132
}
@@ -135,6 +136,7 @@ export default function CreateOracleIntegrated() {
135136
1: 'https://etherscan.io',
136137
8453: 'https://basescan.org',
137138
534351: 'https://sepolia.scrollscan.com',
139+
11155111: 'https://sepolia.etherscan.io',
138140
}
139141
return explorers[chainId] ? `${explorers[chainId]}/address/${address}` : ''
140142
}

hooks/useOracles.ts

Lines changed: 266 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { useReadContract, useChainId, useConfig } from 'wagmi'
55
import { readContract } from '@wagmi/core'
66
import { OracleFactoryAbi } from '@/utils/abi/OracleFactory'
77
import { OracleAbi } from '@/utils/abi/Oracle'
8-
import { OracleFactories } from '@/utils/addresses'
8+
import { ComposedOracleFactoryAbi } from '@/utils/abi/ComposedOracleFactory'
9+
import { ComposedOracleAbi } from '@/utils/abi/ComposedOracle'
10+
import { OracleFactories, ComposedOracleFactories } from '@/utils/addresses'
911

1012
const formatTimestamp = (value: bigint | undefined | null) => {
1113
if (!value || value === BigInt(0)) {
@@ -34,6 +36,12 @@ export interface Oracle {
3436
lastUpdate: string
3537
lastUpdated: string
3638
lastTimestamp: string
39+
isComposed?: boolean
40+
feedA?: string
41+
feedB?: string
42+
operation?: number
43+
invertResult?: boolean
44+
defaultSampleSize?: bigint
3745
}
3846

3947
export function useOracles() {
@@ -50,30 +58,40 @@ export function useOracles() {
5058
setError(null)
5159

5260
const factoryAddress = OracleFactories[chainId as keyof typeof OracleFactories]
61+
const composedFactoryAddress = ComposedOracleFactories[chainId as keyof typeof ComposedOracleFactories]
5362

54-
if (!factoryAddress || factoryAddress === '0x0000000000000000000000000000000000000000') {
55-
setError('Oracle Factory not deployed on this network')
56-
setLoading(false)
57-
return
63+
let oracleInfos: Array<{ oracle: string; token: string; creator: string }> = []
64+
let composedOracleInfos: Array<{ oracle: string; feedA: string; feedB: string; operation: number; creator: string }> = []
65+
66+
// Read standard oracles
67+
if (factoryAddress && factoryAddress !== '0x0000000000000000000000000000000000000000') {
68+
try {
69+
oracleInfos = await readContract(config, {
70+
address: factoryAddress,
71+
abi: OracleFactoryAbi,
72+
functionName: 'allOracles',
73+
}) as any
74+
} catch (e) {
75+
console.error('Error fetching standard oracles:', e)
76+
}
5877
}
5978

60-
// Read all oracles from the factory
61-
const oracleInfos = await readContract(config, {
62-
address: factoryAddress,
63-
abi: OracleFactoryAbi,
64-
functionName: 'allOracles',
65-
}) as Array<{ oracle: string; token: string; creator: string }>
66-
67-
if (!oracleInfos || !Array.isArray(oracleInfos)) {
68-
setOracles([])
69-
setLoading(false)
70-
return
79+
// Read composed oracles
80+
if (composedFactoryAddress && composedFactoryAddress !== '0x0000000000000000000000000000000000000000') {
81+
try {
82+
composedOracleInfos = await readContract(config, {
83+
address: composedFactoryAddress,
84+
abi: ComposedOracleFactoryAbi,
85+
functionName: 'allComposedOracles',
86+
}) as any
87+
} catch (e) {
88+
console.error('Error fetching composed oracles:', e)
89+
}
7190
}
7291

73-
// Fetch details for each oracle
74-
const oraclePromises = oracleInfos.map(async (info: any, index: number) => {
92+
// Fetch details for standard oracles
93+
const oraclePromises = (oracleInfos || []).map(async (info: any, index: number) => {
7594
try {
76-
// Read oracle name and description from the contract
7795
const [name, description, lastUpdated, lastTimestamp] = await Promise.all([
7896
readContract(config, {
7997
address: info.oracle as `0x${string}`,
@@ -111,6 +129,7 @@ export function useOracles() {
111129
lastUpdate: new Date().toISOString(),
112130
lastUpdated: formatTimestamp(lastUpdated as bigint),
113131
lastTimestamp: formatTimestamp(lastTimestamp as bigint),
132+
isComposed: false,
114133
}
115134
} catch (err) {
116135
console.error(`Error fetching details for oracle ${info.oracle}:`, err)
@@ -128,12 +147,83 @@ export function useOracles() {
128147
lastUpdate: new Date().toISOString(),
129148
lastUpdated: '—',
130149
lastTimestamp: '—',
150+
isComposed: false,
131151
}
132152
}
133153
})
134154

135-
const oracleDetails = await Promise.all(oraclePromises)
136-
setOracles(oracleDetails)
155+
// Fetch details for composed oracles
156+
const composedPromises = (composedOracleInfos || []).map(async (info: any, index: number) => {
157+
try {
158+
const lastUpdated = await readContract(config, {
159+
address: info.oracle as `0x${string}`,
160+
abi: ComposedOracleAbi,
161+
functionName: 'lastUpdated',
162+
}).catch(() => BigInt(0))
163+
164+
return {
165+
id: info.oracle,
166+
name: `Composed Oracle ${index + 1}`,
167+
description: `Price Feed`,
168+
address: info.oracle,
169+
token: '0x0000000000000000000000000000000000000000',
170+
creator: info.creator,
171+
category: 'Price Feed',
172+
status: 'active' as const,
173+
updateFrequency: '1min',
174+
accuracy: '99.9%',
175+
lastUpdate: new Date().toISOString(),
176+
lastUpdated: formatTimestamp(lastUpdated as bigint),
177+
lastTimestamp: formatTimestamp(lastUpdated as bigint),
178+
isComposed: true,
179+
feedA: info.feedA,
180+
feedB: info.feedB,
181+
operation: info.operation,
182+
}
183+
} catch (err) {
184+
console.error(`Error fetching details for composed oracle ${info.oracle}:`, err)
185+
return {
186+
id: info.oracle,
187+
name: `Composed Oracle ${index + 1}`,
188+
description: 'Error loading price feed',
189+
address: info.oracle,
190+
token: '0x0000000000000000000000000000000000000000',
191+
creator: info.creator,
192+
category: 'Price Feed',
193+
status: 'inactive' as const,
194+
updateFrequency: 'Unknown',
195+
accuracy: 'Unknown',
196+
lastUpdate: new Date().toISOString(),
197+
lastUpdated: '—',
198+
lastTimestamp: '—',
199+
isComposed: true,
200+
feedA: info.feedA,
201+
feedB: info.feedB,
202+
operation: info.operation,
203+
}
204+
}
205+
})
206+
207+
const baseOraclesList = await Promise.all(oraclePromises)
208+
const composedOraclesList = await Promise.all(composedPromises)
209+
210+
// Map base addresses to names for dynamic composed labeling
211+
const addressToName = new Map<string, string>()
212+
baseOraclesList.forEach(o => addressToName.set(o.address.toLowerCase(), o.name))
213+
214+
const enhancedComposedOracles = composedOraclesList.map(co => {
215+
const nameA = addressToName.get(co.feedA.toLowerCase()) || co.feedA.slice(0, 6) + '...' + co.feedA.slice(-4)
216+
const nameB = addressToName.get(co.feedB.toLowerCase()) || co.feedB.slice(0, 6) + '...' + co.feedB.slice(-4)
217+
const opSymbol = co.operation === 0 ? '×' : '/'
218+
const combinedName = `${nameA} ${opSymbol} ${nameB}`
219+
return {
220+
...co,
221+
name: combinedName,
222+
description: `${combinedName} Price Feed`,
223+
}
224+
})
225+
226+
setOracles([...baseOraclesList, ...enhancedComposedOracles])
137227
} catch (err) {
138228
console.error('Error fetching oracles:', err)
139229
setError('Failed to fetch oracles from contract')
@@ -184,45 +274,163 @@ export function useOracle(oracleAddress: string, targetChainId?: number) {
184274
}
185275

186276
try {
187-
// Read oracle details from the contract
188-
const [name, description, lastUpdated, lastTimestamp] = await Promise.all([
189-
readContract(config, {
190-
address: oracleAddress as `0x${string}`,
191-
abi: OracleAbi,
192-
functionName: 'name',
193-
}).catch(() => 'Unknown Oracle'),
194-
readContract(config, {
195-
address: oracleAddress as `0x${string}`,
196-
abi: OracleAbi,
197-
functionName: 'description',
198-
}).catch(() => 'No description available'),
199-
readContract(config, {
200-
address: oracleAddress as `0x${string}`,
201-
abi: OracleAbi,
202-
functionName: 'lastUpdated',
203-
}).catch(() => BigInt(0)),
204-
readContract(config, {
277+
// Check if it is a composed oracle first by reading feedA
278+
let isComposed = false
279+
let feedA = '0x0000000000000000000000000000000000000000'
280+
let feedB = '0x0000000000000000000000000000000000000000'
281+
let invertResult = false
282+
let defaultSampleSize = BigInt(100)
283+
284+
try {
285+
const fA = await readContract(config, {
205286
address: oracleAddress as `0x${string}`,
206-
abi: OracleAbi,
207-
functionName: 'lastTimestamp',
208-
}).catch(() => BigInt(0)),
209-
])
287+
abi: ComposedOracleAbi,
288+
functionName: 'feedA',
289+
})
290+
feedA = fA as string
291+
isComposed = true
292+
} catch (e) {
293+
// Standard base oracle
294+
}
210295

211-
setOracle({
212-
id: oracleAddress,
213-
name: name as string,
214-
description: description as string,
215-
address: oracleAddress,
216-
token: '0x0000000000000000000000000000000000000000',
217-
creator: '0x0000000000000000000000000000000000000000',
218-
category: 'Price Feed',
219-
status: 'active',
220-
updateFrequency: '1min',
221-
accuracy: '99.9%',
222-
lastUpdate: new Date().toISOString(),
223-
lastUpdated: formatTimestamp(lastUpdated as bigint),
224-
lastTimestamp: formatTimestamp(lastTimestamp as bigint),
225-
})
296+
if (isComposed) {
297+
try {
298+
const [fB, inv, size] = await Promise.all([
299+
readContract(config, {
300+
address: oracleAddress as `0x${string}`,
301+
abi: ComposedOracleAbi,
302+
functionName: 'feedB',
303+
}),
304+
readContract(config, {
305+
address: oracleAddress as `0x${string}`,
306+
abi: ComposedOracleAbi,
307+
functionName: 'invertResult',
308+
}),
309+
readContract(config, {
310+
address: oracleAddress as `0x${string}`,
311+
abi: ComposedOracleAbi,
312+
functionName: 'defaultSampleSize',
313+
}),
314+
])
315+
feedB = fB as string
316+
invertResult = inv as boolean
317+
defaultSampleSize = size as bigint
318+
} catch (e) {
319+
console.error('Error fetching remaining composed oracle parameters:', e)
320+
}
321+
}
322+
323+
if (isComposed) {
324+
// Query dynamic names for parent feeds
325+
const [nameA, nameB, lastUpdated] = await Promise.all([
326+
readContract(config, {
327+
address: feedA as `0x${string}`,
328+
abi: OracleAbi,
329+
functionName: 'name',
330+
}).catch(() => feedA.slice(0, 6) + '...' + feedA.slice(-4)),
331+
readContract(config, {
332+
address: feedB as `0x${string}`,
333+
abi: OracleAbi,
334+
functionName: 'name',
335+
}).catch(() => feedB.slice(0, 6) + '...' + feedB.slice(-4)),
336+
readContract(config, {
337+
address: oracleAddress as `0x${string}`,
338+
abi: ComposedOracleAbi,
339+
functionName: 'lastUpdated',
340+
}).catch(() => BigInt(0)),
341+
])
342+
343+
// Retrieve operation and creator from ComposedOracleFactory
344+
const composedFactoryAddress = ComposedOracleFactories[chainId as keyof typeof ComposedOracleFactories]
345+
let operation = 0
346+
let creator = '0x0000000000000000000000000000000000000000'
347+
348+
if (composedFactoryAddress) {
349+
try {
350+
const composedOracleInfos = await readContract(config, {
351+
address: composedFactoryAddress,
352+
abi: ComposedOracleFactoryAbi,
353+
functionName: 'allComposedOracles',
354+
}) as any[]
355+
356+
const matchingInfo = composedOracleInfos.find(
357+
(info: any) => info.oracle.toLowerCase() === oracleAddress.toLowerCase()
358+
)
359+
if (matchingInfo) {
360+
operation = matchingInfo.operation
361+
creator = matchingInfo.creator
362+
}
363+
} catch (e) {
364+
console.error('Error matching composed oracle details from factory:', e)
365+
}
366+
}
367+
368+
const opSymbol = operation === 0 ? '×' : '/'
369+
370+
const combinedName = `${nameA} ${opSymbol} ${nameB}`
371+
setOracle({
372+
id: oracleAddress,
373+
name: combinedName,
374+
description: `${combinedName} Price Feed`,
375+
address: oracleAddress,
376+
token: '0x0000000000000000000000000000000000000000',
377+
creator: creator,
378+
category: 'Price Feed',
379+
status: 'active',
380+
updateFrequency: '1min',
381+
accuracy: '99.9%',
382+
lastUpdate: new Date().toISOString(),
383+
lastUpdated: formatTimestamp(lastUpdated as bigint),
384+
lastTimestamp: formatTimestamp(lastUpdated as bigint),
385+
isComposed: true,
386+
feedA,
387+
feedB,
388+
operation,
389+
invertResult,
390+
defaultSampleSize,
391+
})
392+
} else {
393+
// Standard base oracle
394+
const [name, description, lastUpdated, lastTimestamp] = await Promise.all([
395+
readContract(config, {
396+
address: oracleAddress as `0x${string}`,
397+
abi: OracleAbi,
398+
functionName: 'name',
399+
}).catch(() => 'Unknown Oracle'),
400+
readContract(config, {
401+
address: oracleAddress as `0x${string}`,
402+
abi: OracleAbi,
403+
functionName: 'description',
404+
}).catch(() => 'No description available'),
405+
readContract(config, {
406+
address: oracleAddress as `0x${string}`,
407+
abi: OracleAbi,
408+
functionName: 'lastUpdated',
409+
}).catch(() => BigInt(0)),
410+
readContract(config, {
411+
address: oracleAddress as `0x${string}`,
412+
abi: OracleAbi,
413+
functionName: 'lastTimestamp',
414+
}).catch(() => BigInt(0)),
415+
])
416+
417+
setOracle({
418+
id: oracleAddress,
419+
name: name as string,
420+
description: description as string,
421+
address: oracleAddress,
422+
token: '0x0000000000000000000000000000000000000000',
423+
creator: '0x0000000000000000000000000000000000000000',
424+
category: 'Price Feed',
425+
status: 'active',
426+
updateFrequency: '1min',
427+
accuracy: '99.9%',
428+
lastUpdate: new Date().toISOString(),
429+
lastUpdated: formatTimestamp(lastUpdated as bigint),
430+
lastTimestamp: formatTimestamp(lastTimestamp as bigint),
431+
isComposed: false,
432+
})
433+
}
226434
} catch (err) {
227435
console.error('Error fetching oracle details:', err)
228436
setError('Failed to fetch oracle details')

0 commit comments

Comments
 (0)