@1001-digital/proxies 0.0.1

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/src/rpc.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { ProxiesFetchError, errorMessage } from './errors'
2
+
3
+ interface JsonRpcResponse {
4
+ result?: string
5
+ error?: { message: string }
6
+ }
7
+
8
+ async function jsonRpcCall(
9
+ rpc: string,
10
+ method: string,
11
+ params: unknown[],
12
+ fetchFn: typeof globalThis.fetch,
13
+ ): Promise<string> {
14
+ let res: Response
15
+ try {
16
+ res = await fetchFn(rpc, {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
20
+ })
21
+ } catch (error) {
22
+ throw new ProxiesFetchError(
23
+ `RPC request failed: ${errorMessage(error)}`,
24
+ { status: 0 },
25
+ { cause: error },
26
+ )
27
+ }
28
+
29
+ if (!res.ok) {
30
+ throw new ProxiesFetchError(
31
+ `RPC request failed: ${res.status}`,
32
+ { status: res.status },
33
+ )
34
+ }
35
+
36
+ let json: JsonRpcResponse
37
+ try {
38
+ json = await res.json() as JsonRpcResponse
39
+ } catch (error) {
40
+ throw new ProxiesFetchError(
41
+ `Invalid JSON from RPC: ${errorMessage(error)}`,
42
+ { status: res.status },
43
+ { cause: error },
44
+ )
45
+ }
46
+
47
+ if (json.error) {
48
+ throw new ProxiesFetchError(
49
+ `RPC error: ${json.error.message}`,
50
+ { status: 0 },
51
+ )
52
+ }
53
+
54
+ return json.result ?? '0x'
55
+ }
56
+
57
+ /**
58
+ * Minimal JSON-RPC `eth_call` client. Invokes a view call on the given address
59
+ * at the latest block and returns the raw hex result.
60
+ */
61
+ export function ethCall(
62
+ rpc: string,
63
+ to: string,
64
+ data: string,
65
+ fetchFn: typeof globalThis.fetch,
66
+ ): Promise<string> {
67
+ return jsonRpcCall(rpc, 'eth_call', [{ to, data }, 'latest'], fetchFn)
68
+ }
69
+
70
+ /**
71
+ * Minimal JSON-RPC `eth_getStorageAt` client. Reads a single 32-byte storage
72
+ * slot at the latest block.
73
+ */
74
+ export function ethGetStorageAt(
75
+ rpc: string,
76
+ address: string,
77
+ slot: string,
78
+ fetchFn: typeof globalThis.fetch,
79
+ ): Promise<string> {
80
+ return jsonRpcCall(rpc, 'eth_getStorageAt', [address, slot, 'latest'], fetchFn)
81
+ }
82
+
83
+ /**
84
+ * Minimal JSON-RPC `eth_getCode` client. Returns the deployed runtime bytecode
85
+ * at the latest block.
86
+ */
87
+ export function ethGetCode(
88
+ rpc: string,
89
+ address: string,
90
+ fetchFn: typeof globalThis.fetch,
91
+ ): Promise<string> {
92
+ return jsonRpcCall(rpc, 'eth_getCode', [address, 'latest'], fetchFn)
93
+ }
@@ -0,0 +1,37 @@
1
+ import { keccak_256 } from '@noble/hashes/sha3'
2
+ import type { AbiFunctionLike, AbiParam } from './types'
3
+
4
+ const encoder = new TextEncoder()
5
+
6
+ /**
7
+ * Compute the 4-byte selector for a canonical function signature.
8
+ * Example: `computeSelector('transfer(address,uint256)')` → `'0xa9059cbb'`.
9
+ */
10
+ export function computeSelector(signature: string): string {
11
+ const hash = keccak_256(encoder.encode(signature))
12
+ let hex = '0x'
13
+ for (let i = 0; i < 4; i++) {
14
+ hex += hash[i].toString(16).padStart(2, '0')
15
+ }
16
+ return hex
17
+ }
18
+
19
+ /**
20
+ * Build the canonical signature for an ABI function/event/error entry,
21
+ * recursively expanding `tuple` into `(innerTypes)` while preserving any
22
+ * `[]` or `[N]` array suffix. This is the form hashed to produce selectors.
23
+ */
24
+ export function canonicalSignature(fn: AbiFunctionLike): string {
25
+ if (!fn.name) throw new Error('Cannot build signature: entry has no name')
26
+ const types = (fn.inputs ?? []).map(canonicalType).join(',')
27
+ return `${fn.name}(${types})`
28
+ }
29
+
30
+ function canonicalType(p: AbiParam): string {
31
+ if (p.type.startsWith('tuple')) {
32
+ const suffix = p.type.slice('tuple'.length)
33
+ const inner = (p.components ?? []).map(canonicalType).join(',')
34
+ return `(${inner})${suffix}`
35
+ }
36
+ return p.type
37
+ }
package/src/types.ts ADDED
@@ -0,0 +1,131 @@
1
+ // ── Patterns ──
2
+
3
+ /** Discriminator for all supported proxy patterns. */
4
+ export type ProxyPattern =
5
+ | 'eip-2535-diamond'
6
+ | 'eip-1967'
7
+ | 'eip-1967-beacon'
8
+ | 'eip-1822'
9
+ | 'eip-1167'
10
+ | 'gnosis-safe'
11
+ | 'eip-897'
12
+
13
+ // ── Configuration ──
14
+
15
+ export interface ProxiesConfig {
16
+ /**
17
+ * Default enricher used by `fetch` when no per-call enricher is passed.
18
+ * Receives each target address and returns whatever ABI the caller can find.
19
+ * Leave unset to return targets with address + selectors only.
20
+ */
21
+ enrich?: TargetEnricher
22
+ /** Custom fetch function. Default: `globalThis.fetch`. */
23
+ fetch?: typeof globalThis.fetch
24
+ }
25
+
26
+ export interface FetchProxyOptions {
27
+ /**
28
+ * Override the client's default enricher for this call. Pass `false` to
29
+ * disable enrichment entirely (address + selectors only).
30
+ */
31
+ enrich?: TargetEnricher | false
32
+ }
33
+
34
+ // ── On-chain / raw ──
35
+
36
+ /**
37
+ * A resolved target behind a proxy — an implementation address, with optional
38
+ * selector scope.
39
+ *
40
+ * - `selectors: string[]` — this target serves exactly these selectors (diamond facets).
41
+ * - `selectors: undefined` — this target receives all calls (every other proxy pattern).
42
+ */
43
+ export interface ResolvedTarget {
44
+ address: string
45
+ selectors?: string[]
46
+ }
47
+
48
+ /**
49
+ * Raw on-chain detection result. Contains a pattern discriminator and the
50
+ * resolved target(s). Single-impl proxies carry exactly one target; diamonds
51
+ * carry one per facet.
52
+ */
53
+ export interface RawProxy {
54
+ pattern: ProxyPattern
55
+ targets: ResolvedTarget[]
56
+ /** EIP-1967 beacon address (only set for `eip-1967-beacon`). */
57
+ beacon?: string
58
+ /** EIP-1967 admin address (only set for `eip-1967` when the admin slot is non-zero). */
59
+ admin?: string
60
+ }
61
+
62
+ // ── Enrichment ──
63
+
64
+ /**
65
+ * User-supplied per-target enricher. Receives a target's address and returns
66
+ * its ABI. Return `null` for unknown targets — errors are swallowed per-target
67
+ * so one bad fetch does not fail the whole resolution.
68
+ *
69
+ * The package is intentionally narrow here: it only composes ABIs. For
70
+ * richer per-target metadata, use {@link ProxiesClient.detect} and enrich
71
+ * the raw targets yourself.
72
+ */
73
+ export type TargetEnricher = (address: string) => Promise<TargetEnrichment | null>
74
+
75
+ export interface TargetEnrichment {
76
+ abi?: unknown[]
77
+ }
78
+
79
+ // ── Public result shapes ──
80
+
81
+ export interface EnrichedTarget {
82
+ address: string
83
+ /** Selector scope for diamond facets; undefined for single-impl proxies (all selectors). */
84
+ selectors?: string[]
85
+ /**
86
+ * ABI for this target. Filtered to `selectors` for diamonds; full implementation
87
+ * ABI for single-impl proxies.
88
+ */
89
+ abi?: unknown[]
90
+ }
91
+
92
+ export interface Proxy {
93
+ pattern: ProxyPattern
94
+ targets: EnrichedTarget[]
95
+ beacon?: string
96
+ admin?: string
97
+ /** Composite ABI across targets, deduped by selector (first-wins). */
98
+ compositeAbi?: unknown[]
99
+ }
100
+
101
+ // ── Client ──
102
+
103
+ export interface ProxiesClient {
104
+ /**
105
+ * Detect the proxy pattern and resolve its target(s). Returns `null` if the
106
+ * contract is not a recognised proxy.
107
+ */
108
+ detect: (rpc: string, address: string) => Promise<RawProxy | null>
109
+ /**
110
+ * Detect + enrich. Returns `null` when the contract is not a recognised proxy.
111
+ * Uses the config-level enricher by default; override per-call via `options.enrich`.
112
+ */
113
+ fetch: (
114
+ rpc: string,
115
+ address: string,
116
+ options?: FetchProxyOptions,
117
+ ) => Promise<Proxy | null>
118
+ }
119
+
120
+ // ── ABI shape helpers (structural) ──
121
+
122
+ export interface AbiParam {
123
+ type: string
124
+ components?: AbiParam[]
125
+ }
126
+
127
+ export interface AbiFunctionLike {
128
+ type: string
129
+ name?: string
130
+ inputs?: AbiParam[]
131
+ }