@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/LICENSE +21 -0
- package/README.md +276 -0
- package/dist/index.d.ts +353 -0
- package/dist/index.js +442 -0
- package/package.json +56 -0
- package/src/abi.ts +72 -0
- package/src/constants.ts +42 -0
- package/src/decode.ts +103 -0
- package/src/detect.ts +60 -0
- package/src/enrich.ts +36 -0
- package/src/errors.ts +33 -0
- package/src/index.ts +122 -0
- package/src/patterns/diamond.ts +77 -0
- package/src/patterns/eip1167.ts +45 -0
- package/src/patterns/eip1822.ts +34 -0
- package/src/patterns/eip1967-beacon.ts +43 -0
- package/src/patterns/eip1967.ts +44 -0
- package/src/patterns/eip897.ts +35 -0
- package/src/patterns/safe.ts +37 -0
- package/src/rpc.ts +93 -0
- package/src/selector.ts +37 -0
- package/src/types.ts +131 -0
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
|
+
}
|
package/src/selector.ts
ADDED
|
@@ -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
|
+
}
|