@1001-digital/layers.evm 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/app/composables/gasPrice.ts +36 -0
- package/app/composables/priceFeed.ts +103 -0
- package/app/plugins/price-feed.client.ts +7 -0
- package/app/utils/price.ts +15 -0
- package/package.json +3 -3
package/.env.example
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
NUXT_PUBLIC_EVM_WALLET_CONNECT_PROJECT_ID=""
|
|
2
|
+
|
|
2
3
|
NUXT_PUBLIC_EVM_CHAINS_MAINNET_RPC1=""
|
|
3
4
|
NUXT_PUBLIC_EVM_CHAINS_MAINNET_RPC2=""
|
|
4
5
|
NUXT_PUBLIC_EVM_CHAINS_MAINNET_RPC3=""
|
|
6
|
+
|
|
7
|
+
NUXT_PUBLIC_EVM_ENS_INDEXER1=""
|
|
8
|
+
NUXT_PUBLIC_EVM_ENS_INDEXER2=""
|
|
9
|
+
NUXT_PUBLIC_EVM_ENS_INDEXER3=""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { WatchStopHandle } from 'vue'
|
|
2
|
+
import { formatEther, formatGwei } from 'viem'
|
|
3
|
+
import { getGasPrice } from '@wagmi/core'
|
|
4
|
+
import { useConfig, useBlockNumber } from '@wagmi/vue'
|
|
5
|
+
|
|
6
|
+
let priceWatcher: WatchStopHandle | null = null
|
|
7
|
+
const price: Ref<bigint> = ref(0n)
|
|
8
|
+
|
|
9
|
+
export const useGasPrice = () => {
|
|
10
|
+
const config = useConfig()
|
|
11
|
+
const { data: blockNumber } = useBlockNumber()
|
|
12
|
+
|
|
13
|
+
const updatePrice = async () => {
|
|
14
|
+
price.value = await getGasPrice(config)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!priceWatcher) {
|
|
18
|
+
updatePrice()
|
|
19
|
+
priceWatcher = watch(blockNumber, () => updatePrice())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const unitPrice = computed(() => ({
|
|
23
|
+
wei: price.value,
|
|
24
|
+
gwei: formatGwei(price.value),
|
|
25
|
+
eth: formatEther(price.value),
|
|
26
|
+
|
|
27
|
+
formatted: {
|
|
28
|
+
gwei: price.value > 2_000_000_000_000n
|
|
29
|
+
? Math.round(parseFloat(formatGwei(price.value)))
|
|
30
|
+
: parseFloat(formatGwei(price.value)).toFixed(1),
|
|
31
|
+
eth: formatEther(price.value),
|
|
32
|
+
},
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
return unitPrice
|
|
36
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readContract } from '@wagmi/core'
|
|
2
|
+
|
|
3
|
+
const CHAINLINK_ETH_USD_ABI = [
|
|
4
|
+
{
|
|
5
|
+
inputs: [],
|
|
6
|
+
name: 'latestRoundData',
|
|
7
|
+
outputs: [
|
|
8
|
+
{ internalType: 'uint80', name: 'roundId', type: 'uint80' },
|
|
9
|
+
{ internalType: 'int256', name: 'answer', type: 'int256' },
|
|
10
|
+
{ internalType: 'uint256', name: 'startedAt', type: 'uint256' },
|
|
11
|
+
{ internalType: 'uint256', name: 'updatedAt', type: 'uint256' },
|
|
12
|
+
{ internalType: 'uint80', name: 'answeredInRound', type: 'uint80' },
|
|
13
|
+
],
|
|
14
|
+
stateMutability: 'view',
|
|
15
|
+
type: 'function',
|
|
16
|
+
},
|
|
17
|
+
] as const
|
|
18
|
+
|
|
19
|
+
const CHAINLINK_ETH_USD = '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
|
|
20
|
+
const STORAGE_KEY = 'evm:price-feed'
|
|
21
|
+
const CACHE_TTL = 3_600 // 1 hour in seconds
|
|
22
|
+
|
|
23
|
+
interface PriceFeedState {
|
|
24
|
+
ethUSDRaw: bigint | null
|
|
25
|
+
lastUpdated: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const state = reactive<PriceFeedState>({
|
|
29
|
+
ethUSDRaw: null,
|
|
30
|
+
lastUpdated: 0,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
function loadFromStorage() {
|
|
34
|
+
if (!import.meta.client) return
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
38
|
+
if (!stored) return
|
|
39
|
+
|
|
40
|
+
const parsed = parseJSON(stored) as PriceFeedState
|
|
41
|
+
if (parsed.ethUSDRaw) state.ethUSDRaw = parsed.ethUSDRaw
|
|
42
|
+
if (parsed.lastUpdated) state.lastUpdated = parsed.lastUpdated
|
|
43
|
+
} catch {
|
|
44
|
+
// Ignore corrupted storage
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function saveToStorage() {
|
|
49
|
+
if (!import.meta.client) return
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
localStorage.setItem(STORAGE_KEY, stringifyJSON({
|
|
53
|
+
ethUSDRaw: state.ethUSDRaw,
|
|
54
|
+
lastUpdated: state.lastUpdated,
|
|
55
|
+
}))
|
|
56
|
+
} catch {
|
|
57
|
+
// Ignore storage errors
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const usePriceFeed = () => {
|
|
62
|
+
const { $wagmi } = useNuxtApp()
|
|
63
|
+
|
|
64
|
+
// Load cached data on first use
|
|
65
|
+
if (!state.lastUpdated) loadFromStorage()
|
|
66
|
+
|
|
67
|
+
const ethUSD = computed(() => state.ethUSDRaw ? state.ethUSDRaw / BigInt(1e8) : 0n)
|
|
68
|
+
const ethUSC = computed(() => state.ethUSDRaw ? state.ethUSDRaw / BigInt(1e6) : 0n)
|
|
69
|
+
const ethUSDFormatted = computed(() => formatPrice(Number(ethUSC.value) / 100, 2))
|
|
70
|
+
|
|
71
|
+
const weiToUSD = (wei: bigint) => {
|
|
72
|
+
const cents = (wei * (state.ethUSDRaw || 0n)) / (10n ** 18n) / (10n ** 6n)
|
|
73
|
+
return formatPrice(Number(cents) / 100, 2)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function fetchPrice() {
|
|
77
|
+
if (nowInSeconds() - state.lastUpdated < CACHE_TTL) return
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const [, answer] = await readContract($wagmi, {
|
|
81
|
+
address: CHAINLINK_ETH_USD,
|
|
82
|
+
abi: CHAINLINK_ETH_USD_ABI,
|
|
83
|
+
functionName: 'latestRoundData',
|
|
84
|
+
chainId: 1,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
state.ethUSDRaw = answer
|
|
88
|
+
state.lastUpdated = nowInSeconds()
|
|
89
|
+
saveToStorage()
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.warn('Error fetching ETH/USD price:', error)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
ethUSDRaw: computed(() => state.ethUSDRaw),
|
|
97
|
+
ethUSD,
|
|
98
|
+
ethUSC,
|
|
99
|
+
ethUSDFormatted,
|
|
100
|
+
weiToUSD,
|
|
101
|
+
fetchPrice,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const replacer = (_: string, value: unknown) => {
|
|
2
|
+
if (typeof value === 'bigint') return value.toString() + 'n'
|
|
3
|
+
return value
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const reviver = (_: string, value: unknown) => {
|
|
7
|
+
if (typeof value === 'string' && /^\d+n$/.test(value)) return BigInt(value.slice(0, -1))
|
|
8
|
+
return value
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const stringifyJSON = (obj: unknown): string => JSON.stringify(obj, replacer)
|
|
12
|
+
export const parseJSON = (json: string): unknown => JSON.parse(json, reviver)
|
|
13
|
+
|
|
14
|
+
export const formatPrice = (num: number, digits: number = 2) =>
|
|
15
|
+
num?.toLocaleString('en-US', { maximumFractionDigits: digits })
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1001-digital/layers.evm",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.7",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"@nuxt/eslint": "latest",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"nuxt": "^4.3.0",
|
|
11
11
|
"typescript": "^5.9.3",
|
|
12
12
|
"vue": "latest",
|
|
13
|
-
"@1001-digital/layers.base": "^0.0.
|
|
13
|
+
"@1001-digital/layers.base": "^0.0.27"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"@1001-digital/layers.base": "^0.0.
|
|
16
|
+
"@1001-digital/layers.base": "^0.0.27"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@types/qrcode": "^1.5.6",
|