@gibs/common 1.7.2 → 1.7.3
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/README.md +597 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,600 @@
|
|
|
1
|
-
# Common
|
|
1
|
+
# Common Utilities
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A collection of essential blockchain utilities and helpers that provide foundational functionality for decentralized applications. This package contains reusable, blockchain-agnostic tools that are not specific to bridge operations but commonly needed across DeFi applications.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 🎯 Use Cases
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
### Primary Use Cases
|
|
8
|
+
- **RPC Client Management**: Create and cache blockchain RPC connections with fallback support
|
|
9
|
+
- **Token Operations**: Load ERC20 token metadata directly from blockchain contracts
|
|
10
|
+
- **Data Serialization**: Handle BigInt serialization in JSON for blockchain data
|
|
11
|
+
- **Performance Optimization**: Cache expensive blockchain calls and computations
|
|
12
|
+
- **Batch Operations**: Execute multiple blockchain calls efficiently using multicall
|
|
13
|
+
|
|
14
|
+
### Target Applications
|
|
15
|
+
- Multi-chain DeFi applications
|
|
16
|
+
- Blockchain data aggregators
|
|
17
|
+
- Token analytics platforms
|
|
18
|
+
- Wallet backends
|
|
19
|
+
- DApp infrastructure
|
|
20
|
+
- Bridge applications (used by `@gibs/bridge-sdk`)
|
|
21
|
+
|
|
22
|
+
## 🔧 Core Functionality
|
|
23
|
+
|
|
24
|
+
### RPC Client Management
|
|
25
|
+
- **Connection Pooling**: Cached RPC clients to avoid duplicate connections
|
|
26
|
+
- **Fallback Support**: Automatic failover between multiple RPC endpoints
|
|
27
|
+
- **WebSocket & HTTP**: Support for both HTTP and WebSocket connections
|
|
28
|
+
- **Batch Configuration**: Optimized batching for better performance
|
|
29
|
+
|
|
30
|
+
### ERC20 Token Operations
|
|
31
|
+
- **Metadata Loading**: Fetch token name, symbol, decimals from blockchain
|
|
32
|
+
- **Multiple ABI Support**: Handles both standard and bytes32 symbol formats
|
|
33
|
+
- **Caching**: Memoized token calls to reduce redundant blockchain requests
|
|
34
|
+
- **Error Handling**: Graceful fallbacks for non-standard token contracts
|
|
35
|
+
|
|
36
|
+
### Advanced Caching
|
|
37
|
+
- **TTL Caching**: Time-based cache expiration for data freshness
|
|
38
|
+
- **Size-Limited Caching**: LRU-style caching with maximum entry limits
|
|
39
|
+
- **Serialization Support**: Cache complex objects including BigInt values
|
|
40
|
+
- **Memory Efficient**: Automatic cleanup of expired cache entries
|
|
41
|
+
|
|
42
|
+
### Multicall Operations
|
|
43
|
+
- **Batch Calls**: Execute multiple contract calls in a single transaction
|
|
44
|
+
- **Gas Optimization**: Reduce gas costs through call aggregation
|
|
45
|
+
- **Type Safety**: Fully typed multicall results
|
|
46
|
+
- **Error Resilience**: Handle partial failures in batch operations
|
|
47
|
+
|
|
48
|
+
## 📦 Installation
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
npm install @gibs/common
|
|
52
|
+
# or
|
|
53
|
+
yarn add @gibs/common
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 📖 API Examples
|
|
57
|
+
|
|
58
|
+
### Client (`@gibs/common/client`)
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import {
|
|
62
|
+
clientFromChain,
|
|
63
|
+
chainKey,
|
|
64
|
+
clientCache,
|
|
65
|
+
defaultBatchConfig
|
|
66
|
+
} from '@gibs/common/client'
|
|
67
|
+
import { mainnet, polygon } from 'viem/chains'
|
|
68
|
+
|
|
69
|
+
// Create a cached RPC client with fallback URLs
|
|
70
|
+
const client = clientFromChain({
|
|
71
|
+
chain: mainnet,
|
|
72
|
+
urls: [
|
|
73
|
+
'https://mainnet.infura.io/v3/your-key',
|
|
74
|
+
'https://eth-mainnet.g.alchemy.com/v2/your-key'
|
|
75
|
+
]
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Create client with custom batch configuration
|
|
79
|
+
const polygonClient = clientFromChain({
|
|
80
|
+
chain: polygon,
|
|
81
|
+
urls: ['https://polygon-rpc.com']
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Generate cache key for debugging
|
|
85
|
+
const key = chainKey(1, ['https://rpc.url']) // "1,https://rpc.url"
|
|
86
|
+
const multiKey = chainKey(137, ['https://rpc1.url', 'https://rpc2.url']) // "137,https://rpc1.url,https://rpc2.url"
|
|
87
|
+
|
|
88
|
+
// Access client cache directly
|
|
89
|
+
const cached = clientCache.get(1) // { key: string, client: PublicClient } | undefined
|
|
90
|
+
console.log(cached?.key) // Cache key used for this client
|
|
91
|
+
|
|
92
|
+
// View default batch configuration
|
|
93
|
+
console.log(defaultBatchConfig)
|
|
94
|
+
// {
|
|
95
|
+
// batch: {
|
|
96
|
+
// wait: 10, // Wait 10ms before batching
|
|
97
|
+
// batchSize: 32, // Maximum 32 calls per batch
|
|
98
|
+
// }
|
|
99
|
+
// }
|
|
100
|
+
|
|
101
|
+
// Clear cache entry
|
|
102
|
+
clientCache.delete(1)
|
|
103
|
+
|
|
104
|
+
// Check cache size
|
|
105
|
+
console.log(clientCache.size) // Number of cached clients
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### ERC20 (`@gibs/common/erc20`)
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { multicallErc20, erc20MetadataCalls } from '@gibs/common/erc20'
|
|
112
|
+
import { mainnet, arbitrum } from 'viem/chains'
|
|
113
|
+
|
|
114
|
+
// Load token metadata (with automatic caching)
|
|
115
|
+
const tokenAddress = '0xA0b86a33E6441e4f4a1b95c0B5a7F1ac03c7B3B0'
|
|
116
|
+
const [name, symbol, decimals] = await multicallErc20({
|
|
117
|
+
client,
|
|
118
|
+
target: tokenAddress,
|
|
119
|
+
chain: mainnet
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
console.log(name) // "Wrapped Ether"
|
|
123
|
+
console.log(symbol) // "WETH"
|
|
124
|
+
console.log(decimals) // 18
|
|
125
|
+
|
|
126
|
+
// Load multiple tokens efficiently
|
|
127
|
+
const tokens = [
|
|
128
|
+
'0xA0b86a33E6441e4f4a1b95c0B5a7F1ac03c7B3B0', // WETH
|
|
129
|
+
'0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
|
|
130
|
+
'0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
const tokenMetadata = await Promise.all(
|
|
134
|
+
tokens.map(async (address) => {
|
|
135
|
+
const [name, symbol, decimals] = await multicallErc20({
|
|
136
|
+
client,
|
|
137
|
+
target: address,
|
|
138
|
+
chain: mainnet
|
|
139
|
+
})
|
|
140
|
+
return { name, symbol, decimals, address }
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
// Handle tokens with bytes32 symbols (automatically handled)
|
|
145
|
+
const [usdcName, usdcSymbol, usdcDecimals] = await multicallErc20({
|
|
146
|
+
client,
|
|
147
|
+
target: '0xA0b86a33E6441e4f4a1b95c0B5a7F1ac03c7B3B0',
|
|
148
|
+
chain: mainnet
|
|
149
|
+
}) // Falls back to bytes32 ABI if standard fails
|
|
150
|
+
|
|
151
|
+
// View the standard metadata calls used
|
|
152
|
+
console.log(erc20MetadataCalls)
|
|
153
|
+
// [
|
|
154
|
+
// { functionName: 'name' },
|
|
155
|
+
// { functionName: 'symbol' },
|
|
156
|
+
// { functionName: 'decimals' }
|
|
157
|
+
// ]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Serialize (`@gibs/common/serialize`)
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import {
|
|
164
|
+
jsonAnyStringify,
|
|
165
|
+
jsonAnyParse,
|
|
166
|
+
isSerializedBigInt
|
|
167
|
+
} from '@gibs/common/serialize'
|
|
168
|
+
|
|
169
|
+
// Serialize data with BigInt values
|
|
170
|
+
const data = {
|
|
171
|
+
balance: 1500000000000000000n, // 1.5 ETH
|
|
172
|
+
user: '0x123...',
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
amounts: [100n, 200n, 300n], // Array with BigInts
|
|
175
|
+
nested: {
|
|
176
|
+
value: 999999999999999999n
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Convert to JSON string
|
|
181
|
+
const jsonString = JSON.stringify(data, jsonAnyStringify)
|
|
182
|
+
console.log(jsonString)
|
|
183
|
+
// '{"balance":{"__type__":"bigint","value":"1500000000000000000"},"user":"0x123...","timestamp":1699123456789,"amounts":[{"__type__":"bigint","value":"100"},{"__type__":"bigint","value":"200"},{"__type__":"bigint","value":"300"}],"nested":{"value":{"__type__":"bigint","value":"999999999999999999"}}}'
|
|
184
|
+
|
|
185
|
+
// Parse back to original types
|
|
186
|
+
const parsed = JSON.parse(jsonString, jsonAnyParse)
|
|
187
|
+
console.log(typeof parsed.balance) // "bigint"
|
|
188
|
+
console.log(parsed.balance === 1500000000000000000n) // true
|
|
189
|
+
console.log(parsed.amounts[0] === 100n) // true
|
|
190
|
+
|
|
191
|
+
// Check if value is serialized BigInt
|
|
192
|
+
const serializedValue = { __type__: 'bigint', value: '123' }
|
|
193
|
+
console.log(isSerializedBigInt(serializedValue)) // true
|
|
194
|
+
console.log(isSerializedBigInt({ type: 'bigint', value: '123' })) // false
|
|
195
|
+
console.log(isSerializedBigInt('123')) // false
|
|
196
|
+
|
|
197
|
+
// Use in localStorage/sessionStorage
|
|
198
|
+
const cacheData = { price: 1234567890123456789n, lastUpdate: Date.now() }
|
|
199
|
+
localStorage.setItem('cache', JSON.stringify(cacheData, jsonAnyStringify))
|
|
200
|
+
const restored = JSON.parse(localStorage.getItem('cache')!, jsonAnyParse)
|
|
201
|
+
console.log(typeof restored.price) // "bigint"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Cache (`@gibs/common/cache`)
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { maxMemoize, ttlMemoizeSingle } from '@gibs/common/cache'
|
|
208
|
+
|
|
209
|
+
// Size-limited caching (default: 1024 entries)
|
|
210
|
+
const expensiveCalc = maxMemoize((x: number, y: number) => {
|
|
211
|
+
console.log(`Computing ${x} * ${y}...`) // Only logs on cache miss
|
|
212
|
+
return x * y * Math.random()
|
|
213
|
+
}, 100) // Limit to 100 entries
|
|
214
|
+
|
|
215
|
+
console.log(expensiveCalc(5, 10)) // Computes and caches
|
|
216
|
+
console.log(expensiveCalc(5, 10)) // Returns cached result
|
|
217
|
+
console.log(expensiveCalc(3, 7)) // Computes new value
|
|
218
|
+
|
|
219
|
+
// TTL-based caching (default: 1 hour)
|
|
220
|
+
const fetchPrice = ttlMemoizeSingle((symbol: string) => {
|
|
221
|
+
console.log(`Fetching price for ${symbol}...`)
|
|
222
|
+
return fetch(`/api/price/${symbol}`).then(r => r.json())
|
|
223
|
+
}, 30000) // Cache for 30 seconds
|
|
224
|
+
|
|
225
|
+
const ethPrice1 = await fetchPrice('ETH') // Fetches from API
|
|
226
|
+
const ethPrice2 = await fetchPrice('ETH') // Returns cached result (if within 30s)
|
|
227
|
+
|
|
228
|
+
// Advanced caching with BigInt serialization
|
|
229
|
+
const tokenPriceCache = maxMemoize((tokenAddress: string, chainId: number) => {
|
|
230
|
+
console.log(`Loading price for ${tokenAddress} on chain ${chainId}`)
|
|
231
|
+
return {
|
|
232
|
+
price: 1500000000000000000n, // BigInt price
|
|
233
|
+
timestamp: Date.now()
|
|
234
|
+
}
|
|
235
|
+
}, 500)
|
|
236
|
+
|
|
237
|
+
const wethPrice = tokenPriceCache('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 1)
|
|
238
|
+
console.log(typeof wethPrice.price) // "bigint"
|
|
239
|
+
|
|
240
|
+
// TTL cache with complex return types
|
|
241
|
+
const getBlockInfo = ttlMemoizeSingle(async (blockNumber: bigint) => {
|
|
242
|
+
return {
|
|
243
|
+
number: blockNumber,
|
|
244
|
+
timestamp: BigInt(Date.now()),
|
|
245
|
+
hash: '0x...',
|
|
246
|
+
gasUsed: 21000n
|
|
247
|
+
}
|
|
248
|
+
}, 15000) // Cache for 15 seconds
|
|
249
|
+
|
|
250
|
+
const block = await getBlockInfo(18500000n)
|
|
251
|
+
console.log(typeof block.number) // "bigint"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Multicall (`@gibs/common/multicall`)
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { multicallRead } from '@gibs/common/multicall'
|
|
258
|
+
import { erc20Abi } from 'viem'
|
|
259
|
+
|
|
260
|
+
// Basic multicall - batch multiple contract calls efficiently
|
|
261
|
+
const calls = [
|
|
262
|
+
{ functionName: 'balanceOf', args: ['0x123...'] },
|
|
263
|
+
{ functionName: 'allowance', args: ['0x123...', '0x456...'] },
|
|
264
|
+
{ functionName: 'totalSupply', args: [] }
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
const results = await multicallRead({
|
|
268
|
+
client,
|
|
269
|
+
chain: mainnet,
|
|
270
|
+
abi: erc20Abi,
|
|
271
|
+
target: '0xA0b86a33E6441e4f4a1b95c0B5a7F1ac03c7B3B0', // WETH
|
|
272
|
+
calls
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
const [balance, allowance, totalSupply] = results
|
|
276
|
+
console.log('Balance:', balance) // bigint
|
|
277
|
+
console.log('Allowance:', allowance) // bigint
|
|
278
|
+
console.log('Total Supply:', totalSupply) // bigint
|
|
279
|
+
|
|
280
|
+
// Advanced multicall with different targets and ABIs
|
|
281
|
+
const mixedCalls = [
|
|
282
|
+
{
|
|
283
|
+
functionName: 'balanceOf',
|
|
284
|
+
args: ['0x123...'],
|
|
285
|
+
target: '0xA0b86a33E6441e4f4a1b95c0B5a7F1ac03c7B3B0', // WETH
|
|
286
|
+
abi: erc20Abi
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
functionName: 'balanceOf',
|
|
290
|
+
args: ['0x123...'],
|
|
291
|
+
target: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
|
|
292
|
+
abi: erc20Abi
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
functionName: 'name',
|
|
296
|
+
args: [],
|
|
297
|
+
target: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
|
|
298
|
+
abi: erc20Abi
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
const mixedResults = await multicallRead({
|
|
303
|
+
client,
|
|
304
|
+
chain: mainnet,
|
|
305
|
+
abi: erc20Abi, // Default ABI
|
|
306
|
+
calls: mixedCalls
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
const [wethBalance, daiBalance, usdtName] = mixedResults
|
|
310
|
+
|
|
311
|
+
// Multicall with failure handling
|
|
312
|
+
const riskyCalls = [
|
|
313
|
+
{
|
|
314
|
+
functionName: 'balanceOf',
|
|
315
|
+
args: ['0x123...'],
|
|
316
|
+
target: '0xValidToken...',
|
|
317
|
+
allowFailure: false // Will throw if this fails
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
functionName: 'balanceOf',
|
|
321
|
+
args: ['0x123...'],
|
|
322
|
+
target: '0xMaybeInvalidToken...',
|
|
323
|
+
allowFailure: true // Will return error object instead of throwing
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
const riskyResults = await multicallRead({
|
|
328
|
+
client,
|
|
329
|
+
chain: mainnet,
|
|
330
|
+
abi: erc20Abi,
|
|
331
|
+
calls: riskyCalls
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// riskyResults[1] might be an error object if the call failed
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Types (`@gibs/common/types`)
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import type { Erc20Metadata, Call } from '@gibs/common/types'
|
|
341
|
+
|
|
342
|
+
// ERC20 token metadata structure (returned by multicallErc20)
|
|
343
|
+
const tokenMetadata: Erc20Metadata = ['Wrapped Ether', 'WETH', 18]
|
|
344
|
+
const [name, symbol, decimals] = tokenMetadata
|
|
345
|
+
console.log(`${name} (${symbol}) has ${decimals} decimals`)
|
|
346
|
+
|
|
347
|
+
// Multicall call configuration
|
|
348
|
+
const balanceCall: Call = {
|
|
349
|
+
functionName: 'balanceOf',
|
|
350
|
+
args: ['0x123...'],
|
|
351
|
+
target: '0xTokenAddress...',
|
|
352
|
+
abi: erc20Abi,
|
|
353
|
+
allowFailure: false
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const nameCall: Call = {
|
|
357
|
+
functionName: 'name',
|
|
358
|
+
args: [],
|
|
359
|
+
// target and abi will be inherited from multicallRead params
|
|
360
|
+
allowFailure: true
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Use in function signatures
|
|
364
|
+
function processTokenMetadata(metadata: Erc20Metadata) {
|
|
365
|
+
const [name, symbol, decimals] = metadata
|
|
366
|
+
console.log(`Processing ${symbol} (${name}) with ${decimals} decimals`)
|
|
367
|
+
return {
|
|
368
|
+
displayName: `${name} (${symbol})`,
|
|
369
|
+
precision: decimals,
|
|
370
|
+
isStablecoin: name.toLowerCase().includes('usd')
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function buildMulticall(calls: Call[]) {
|
|
375
|
+
return multicallRead({
|
|
376
|
+
client,
|
|
377
|
+
chain: mainnet,
|
|
378
|
+
abi: erc20Abi,
|
|
379
|
+
calls
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Advanced Call configurations
|
|
384
|
+
const complexCalls: Call[] = [
|
|
385
|
+
{
|
|
386
|
+
functionName: 'getReserves',
|
|
387
|
+
args: [],
|
|
388
|
+
target: '0xPairAddress...',
|
|
389
|
+
abi: pairAbi,
|
|
390
|
+
allowFailure: false
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
functionName: 'token0',
|
|
394
|
+
args: [],
|
|
395
|
+
target: '0xPairAddress...',
|
|
396
|
+
abi: pairAbi,
|
|
397
|
+
allowFailure: false
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
functionName: 'token1',
|
|
401
|
+
args: [],
|
|
402
|
+
target: '0xPairAddress...',
|
|
403
|
+
abi: pairAbi,
|
|
404
|
+
allowFailure: false
|
|
405
|
+
}
|
|
406
|
+
]
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## 🔧 Configuration
|
|
410
|
+
|
|
411
|
+
### Default Batch Configuration
|
|
412
|
+
```typescript
|
|
413
|
+
import { defaultBatchConfig } from '@gibs/common/client'
|
|
414
|
+
|
|
415
|
+
console.log(defaultBatchConfig)
|
|
416
|
+
// {
|
|
417
|
+
// batch: {
|
|
418
|
+
// wait: 10, // Wait 10ms before batching
|
|
419
|
+
// batchSize: 32, // Maximum 32 calls per batch
|
|
420
|
+
// }
|
|
421
|
+
// }
|
|
422
|
+
|
|
423
|
+
// This configuration is automatically applied to all HTTP transports
|
|
424
|
+
// WebSocket transports get additional configuration:
|
|
425
|
+
// {
|
|
426
|
+
// keepAlive: true,
|
|
427
|
+
// reconnect: true,
|
|
428
|
+
// retryDelay: 250,
|
|
429
|
+
// retryCount: 10,
|
|
430
|
+
// timeout: 4000,
|
|
431
|
+
// ...defaultBatchConfig
|
|
432
|
+
// }
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## 📋 API Reference
|
|
436
|
+
|
|
437
|
+
### Exports
|
|
438
|
+
|
|
439
|
+
| Export Path | Description |
|
|
440
|
+
|-------------|-------------|
|
|
441
|
+
| `@gibs/common` | Main entry point with all exports |
|
|
442
|
+
| `@gibs/common/client` | RPC client creation and management |
|
|
443
|
+
| `@gibs/common/erc20` | ERC20 token metadata utilities |
|
|
444
|
+
| `@gibs/common/cache` | Caching and memoization functions |
|
|
445
|
+
| `@gibs/common/serialize` | JSON serialization with BigInt support |
|
|
446
|
+
| `@gibs/common/multicall` | Batch contract call utilities |
|
|
447
|
+
| `@gibs/common/types` | TypeScript type definitions |
|
|
448
|
+
|
|
449
|
+
### Key Functions
|
|
450
|
+
|
|
451
|
+
#### Client Management
|
|
452
|
+
```typescript
|
|
453
|
+
// Create cached RPC client with fallback support
|
|
454
|
+
function clientFromChain(params: {
|
|
455
|
+
chain: Chain
|
|
456
|
+
urls: string[]
|
|
457
|
+
}): PublicClient
|
|
458
|
+
|
|
459
|
+
// Generate cache key for client identification
|
|
460
|
+
function chainKey(chainId: number, urls: string[]): string
|
|
461
|
+
|
|
462
|
+
// Client cache Map
|
|
463
|
+
const clientCache: Map<number, { key: string, client: PublicClient }>
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
#### ERC20 Operations
|
|
467
|
+
```typescript
|
|
468
|
+
// Load ERC20 metadata with caching and fallback ABIs
|
|
469
|
+
function multicallErc20(params: {
|
|
470
|
+
client: PublicClient
|
|
471
|
+
target: Hex
|
|
472
|
+
chain: Chain
|
|
473
|
+
}): Promise<[string, string, number]> // [name, symbol, decimals]
|
|
474
|
+
|
|
475
|
+
// Standard ERC20 metadata calls
|
|
476
|
+
const erc20MetadataCalls: readonly [
|
|
477
|
+
{ functionName: 'name' },
|
|
478
|
+
{ functionName: 'symbol' },
|
|
479
|
+
{ functionName: 'decimals' }
|
|
480
|
+
]
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Caching
|
|
484
|
+
```typescript
|
|
485
|
+
// Size-limited memoization
|
|
486
|
+
function maxMemoize<A extends unknown[], B>(
|
|
487
|
+
fn: (...a: A) => B,
|
|
488
|
+
max?: number
|
|
489
|
+
): (...a: A) => B
|
|
490
|
+
|
|
491
|
+
// TTL-based single value caching
|
|
492
|
+
function ttlMemoizeSingle<A extends unknown[], B>(
|
|
493
|
+
fn: (...a: A) => B,
|
|
494
|
+
ttl?: number
|
|
495
|
+
): (...a: A) => B
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
#### Serialization
|
|
499
|
+
```typescript
|
|
500
|
+
// JSON replacer for BigInt serialization
|
|
501
|
+
function jsonAnyStringify(key: string, value: any): any
|
|
502
|
+
|
|
503
|
+
// JSON reviver for BigInt deserialization
|
|
504
|
+
function jsonAnyParse(key: string, value: any): any
|
|
505
|
+
|
|
506
|
+
// Type guard for serialized BigInt values
|
|
507
|
+
function isSerializedBigInt(value: unknown): value is SerializedBigInt
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
#### Multicall
|
|
511
|
+
```typescript
|
|
512
|
+
// Execute multiple contract calls in a single transaction
|
|
513
|
+
function multicallRead<T = readonly unknown[]>(params: {
|
|
514
|
+
client: PublicClient
|
|
515
|
+
chain: Chain
|
|
516
|
+
abi: Abi
|
|
517
|
+
target: Hex
|
|
518
|
+
calls: readonly unknown[]
|
|
519
|
+
}): Promise<T>
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Key Types
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
// ERC20 token metadata tuple
|
|
526
|
+
type Erc20Metadata = [string, string, number] // [name, symbol, decimals]
|
|
527
|
+
|
|
528
|
+
// Multicall call configuration
|
|
529
|
+
interface Call {
|
|
530
|
+
functionName: string
|
|
531
|
+
args?: (bigint | Hex | Hex[] | bigint[] | string | string[])[]
|
|
532
|
+
target?: Hex // Override default target
|
|
533
|
+
abi?: Abi // Override default ABI
|
|
534
|
+
allowFailure?: boolean // Default: false
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Serialized BigInt structure
|
|
538
|
+
interface SerializedBigInt {
|
|
539
|
+
__type__: 'bigint'
|
|
540
|
+
value: string
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## ⚡ Performance Features
|
|
545
|
+
|
|
546
|
+
### Client Caching
|
|
547
|
+
- RPC clients are cached by chain ID and URL combination
|
|
548
|
+
- Prevents unnecessary connection overhead
|
|
549
|
+
- Automatic cache invalidation when URLs change
|
|
550
|
+
|
|
551
|
+
### Request Optimization
|
|
552
|
+
- Batch configuration reduces RPC call frequency
|
|
553
|
+
- Fallback RPC URLs provide reliability
|
|
554
|
+
- WebSocket connections for real-time data when available
|
|
555
|
+
|
|
556
|
+
### Memory Management
|
|
557
|
+
- Size-limited caches prevent memory leaks
|
|
558
|
+
- TTL caches automatically expire stale data
|
|
559
|
+
- Efficient cache key generation for complex objects
|
|
560
|
+
|
|
561
|
+
## 🔗 Dependencies
|
|
562
|
+
|
|
563
|
+
- `viem`: Type-safe Ethereum library for blockchain interactions
|
|
564
|
+
- `lodash`: Utility functions for data manipulation and memoization
|
|
565
|
+
|
|
566
|
+
## 📝 Best Practices
|
|
567
|
+
|
|
568
|
+
### Client Management
|
|
569
|
+
```typescript
|
|
570
|
+
// ✅ Good: Reuse clients with consistent URLs
|
|
571
|
+
const client = clientFromChain({ chain: mainnet, urls: rpcUrls })
|
|
572
|
+
|
|
573
|
+
// ❌ Avoid: Creating new clients with different URL arrays each time
|
|
574
|
+
const client1 = clientFromChain({ chain: mainnet, urls: [url1, url2] })
|
|
575
|
+
const client2 = clientFromChain({ chain: mainnet, urls: [url2, url1] }) // Different cache key!
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Caching Strategy
|
|
579
|
+
```typescript
|
|
580
|
+
// ✅ Good: Use appropriate cache types for your use case
|
|
581
|
+
const tokenMetadata = maxMemoize(fetchTokenMetadata, 500) // Bounded cache
|
|
582
|
+
const priceData = ttlMemoizeSingle(fetchPrices, 30000) // 30-second TTL
|
|
583
|
+
|
|
584
|
+
// ✅ Good: Cache expensive computations, not simple operations
|
|
585
|
+
const expensiveCalc = maxMemoize(heavyComputation)
|
|
586
|
+
const simpleCalc = (a, b) => a + b // Don't cache simple operations
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Error Handling
|
|
590
|
+
```typescript
|
|
591
|
+
// ✅ Good: Handle multicall failures gracefully
|
|
592
|
+
try {
|
|
593
|
+
const results = await multicallRead({ ... })
|
|
594
|
+
} catch (error) {
|
|
595
|
+
console.warn('Multicall failed, falling back to individual calls')
|
|
596
|
+
// Implement fallback logic
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
This package provides the foundation for robust, efficient blockchain applications by handling common infrastructure concerns so you can focus on your application logic.
|