@gibs/common 1.6.27

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/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@gibs/common",
3
+ "version": "1.6.27",
4
+ "main": "index.js",
5
+ "access": "public",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./erc20": "./src/erc20.ts",
10
+ "./cache": "./src/cache.ts",
11
+ "./serialize": "./src/serialize.ts",
12
+ "./types": "./src/types.ts",
13
+ "./multicall": "./src/multicall.ts",
14
+ "./client": "./src/client.ts"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc"
18
+ },
19
+ "keywords": [],
20
+ "author": "",
21
+ "license": "ISC",
22
+ "description": "",
23
+ "dependencies": {
24
+ "lodash": "^4.17.21",
25
+ "viem": "^2.31.0"
26
+ }
27
+ }
package/src/cache.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { jsonAnyStringify } from "./serialize.js"
2
+
3
+ /**
4
+ * Memoize a function result and return a single value
5
+ * @param fn the function to memoize
6
+ * @param max the maximum number of values to cache
7
+ * @returns the memoized function
8
+ */
9
+ export const maxMemoize = <A extends unknown[], B>(fn: (...a: A) => B, max = 1024) => {
10
+ const cache = new Map<string, B>()
11
+ return (...a: A) => {
12
+ const key = JSON.stringify(a, jsonAnyStringify)
13
+ const existing = cache.get(key)
14
+ if (cache.has(key)) {
15
+ return existing
16
+ }
17
+ for (const k of cache.keys()) {
18
+ if (cache.size < max) break
19
+ cache.delete(k)
20
+ }
21
+ const result = fn(...a)
22
+ cache.set(key, result)
23
+ return result
24
+ }
25
+ }
26
+
27
+ const hour1 = 1000 * 60 * 60
28
+ /**
29
+ * Memoize a function result and return a single value
30
+ * @param fn the function to memoize
31
+ * @param ttl the time to live for the cache
32
+ * @returns the memoized function
33
+ */
34
+ export const ttlMemoizeSingle = <A extends unknown[], B>(fn: (...a: A) => B, ttl = hour1) => {
35
+ let cache: {
36
+ timestamp: number
37
+ result: B
38
+ } | null = null
39
+ return (...a: A) => {
40
+ const now = Date.now()
41
+ if (cache && cache.timestamp + ttl > now) {
42
+ return cache.result
43
+ }
44
+ const result = fn(...a)
45
+ cache = { timestamp: now, result }
46
+ return result
47
+ }
48
+ }
package/src/client.ts ADDED
@@ -0,0 +1,59 @@
1
+ import { type Chain, type PublicClient, createPublicClient, fallback, http, webSocket } from "viem"
2
+
3
+ export const defaultBatchConfig = {
4
+ batch: {
5
+ wait: 10,
6
+ batchSize: 32,
7
+ },
8
+ }
9
+
10
+ export const chainKey = (chainId: number, urls: string[]) => {
11
+ return `${chainId},${urls.join(',')}`.toLowerCase()
12
+ }
13
+
14
+ export const clientCache = new Map<number, {
15
+ key: string
16
+ client: PublicClient
17
+ }>()
18
+
19
+ export const clientFromChain = ({
20
+ chain,
21
+ urls,
22
+ }: {
23
+ chain: Chain
24
+ urls: string[]
25
+ }): PublicClient => {
26
+ const key = chainKey(chain.id, urls)
27
+ const existing = clientCache.get(chain.id)
28
+ if (existing && existing.key === key) {
29
+ return existing.client
30
+ }
31
+ const transport = !urls?.length
32
+ ? http()
33
+ : fallback(
34
+ urls.map((rpc) =>
35
+ rpc.startsWith('http')
36
+ ? http(rpc, {
37
+ ...defaultBatchConfig,
38
+ })
39
+ : webSocket(rpc, {
40
+ keepAlive: true,
41
+ reconnect: true,
42
+ retryDelay: 250,
43
+ retryCount: 10,
44
+ timeout: 4_000,
45
+ ...defaultBatchConfig,
46
+ }),
47
+ ),
48
+ { rank: true },
49
+ )
50
+ const client = createPublicClient({
51
+ chain,
52
+ transport,
53
+ })
54
+ clientCache.set(chain.id, {
55
+ key,
56
+ client,
57
+ })
58
+ return client
59
+ }
package/src/erc20.ts ADDED
@@ -0,0 +1,41 @@
1
+ import {
2
+ type Chain,
3
+ type PublicClient,
4
+ type Hex,
5
+ type Abi,
6
+ erc20Abi,
7
+ erc20Abi_bytes32,
8
+ getAddress,
9
+ } from 'viem'
10
+ import _ from 'lodash'
11
+ import type { Erc20Metadata } from './types.js'
12
+ import { multicallRead } from './multicall.js'
13
+
14
+ export const erc20MetadataCalls = [
15
+ { functionName: 'name' },
16
+ { functionName: 'symbol' },
17
+ { functionName: 'decimals' },
18
+ ]
19
+
20
+ export const multicallErc20 = _.memoize(
21
+ async ({ client, target, chain }: { client: PublicClient; target: Hex; chain: Chain }) => {
22
+ const options = {
23
+ chain: chain,
24
+ client: client,
25
+ abi: erc20Abi,
26
+ target,
27
+ calls: erc20MetadataCalls,
28
+ }
29
+ try {
30
+ return await multicallRead<Erc20Metadata>(options)
31
+ } catch {
32
+ return await multicallRead<Erc20Metadata>({
33
+ ...options,
34
+ abi: erc20Abi_bytes32,
35
+ })
36
+ }
37
+ },
38
+ ({ target, chain }) => {
39
+ return !target ? chain.id : getAddress(target, chain.id)
40
+ },
41
+ )
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './types.js'
2
+ export * from './serialize.js'
3
+ export * from './cache.js'
4
+ export * from './multicall.js'
5
+ export * from './erc20.js'
@@ -0,0 +1,67 @@
1
+ import {
2
+ type Chain,
3
+ type PublicClient,
4
+ type Hex,
5
+ type Abi,
6
+ getContract,
7
+ encodeFunctionData,
8
+ decodeFunctionResult,
9
+ multicall3Abi,
10
+ } from 'viem'
11
+ import _ from 'lodash'
12
+ import type { Call } from './types.js'
13
+
14
+ export const multicallRead = async <T>({
15
+ chain,
16
+ client,
17
+ abi,
18
+ calls,
19
+ target,
20
+ }: {
21
+ chain: Chain
22
+ client: PublicClient
23
+ abi: Abi
24
+ calls: Call[]
25
+ target?: Hex
26
+ }) => {
27
+ const multicall = getContract({
28
+ abi: multicall3Abi,
29
+ address: chain.contracts!.multicall3!.address!,
30
+ // this seems like a bug in viem, it should be able to infer the type of the client
31
+ client: client as any,
32
+ }) as any
33
+ const arg = calls.map((call) => ({
34
+ callData: encodeFunctionData({
35
+ abi: call.abi || abi,
36
+ functionName: call.functionName,
37
+ // this seems like a bug in viem, it should be able to infer the type of the args
38
+ args: (call.args || []) as any,
39
+ }),
40
+ allowFailure: call.allowFailure || false,
41
+ target: (call.target || target) as Hex,
42
+ }))
43
+ let reads: null | Awaited<ReturnType<typeof multicall.read.aggregate3>> = null
44
+ try {
45
+ reads = await multicall.read.aggregate3([arg])
46
+ if (!reads) throw reads
47
+ const r = reads
48
+ return calls.map((call, i) =>
49
+ call.allowFailure
50
+ ? r[i].success
51
+ ? decodeFunctionResult({
52
+ abi: call.abi || abi,
53
+ functionName: call.functionName,
54
+ data: r[i].returnData,
55
+ })
56
+ : r[i].returnData
57
+ : decodeFunctionResult({
58
+ abi: call.abi || abi,
59
+ functionName: call.functionName,
60
+ data: r[i].returnData,
61
+ }),
62
+ ) as T
63
+ } catch (err) {
64
+ console.error(client.chain?.id, target, calls, reads)
65
+ throw err
66
+ }
67
+ }
@@ -0,0 +1,28 @@
1
+
2
+ const BIGINT_TYPE = 'bigint' as const
3
+ type SerializedBigInt = {
4
+ __type__: typeof BIGINT_TYPE
5
+ value: string
6
+ }
7
+ export const jsonAnyStringify = (key: string, value: unknown) => {
8
+ if (typeof value === BIGINT_TYPE) {
9
+ return {
10
+ __type__: BIGINT_TYPE,
11
+ value: (value as bigint).toString(),
12
+ }
13
+ }
14
+ return value
15
+ }
16
+
17
+ export const isSerializedBigInt = (value: unknown): value is SerializedBigInt => {
18
+ return (
19
+ !!value && typeof value === 'object' && (value as SerializedBigInt).__type__ === BIGINT_TYPE
20
+ )
21
+ }
22
+
23
+ export const jsonAnyParse = (_key: string, value: unknown) => {
24
+ if (isSerializedBigInt(value)) {
25
+ return BigInt(value.value)
26
+ }
27
+ return value
28
+ }
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { Abi, Hex } from "viem"
2
+
3
+ export type Call = {
4
+ allowFailure?: boolean
5
+ functionName: string
6
+ target?: Hex
7
+ abi?: Abi
8
+ args?: (bigint | Hex | Hex[] | bigint[] | string | string[])[]
9
+ }
10
+ export type Erc20Metadata = [string, string, number]
11
+
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "module": "NodeNext",
6
+ "composite": true,
7
+ "allowJs": true,
8
+ "outDir": "./dist",
9
+ "strict": true,
10
+ "isolatedModules": true,
11
+ "skipLibCheck": true,
12
+ "moduleDetection": "force",
13
+ "moduleResolution": "nodenext"
14
+ },
15
+ "include": ["src/**/*.ts", "src/**/*.js"]
16
+ }