nansen-cli 1.6.0 → 1.8.0
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/AGENTS.md +176 -0
- package/CLAUDE.md +16 -19
- package/README.md +179 -110
- package/SKILL.md +25 -21
- package/TODO.md +17 -0
- package/package.json +3 -1
- package/scripts/check-changeset.js +28 -0
- package/src/api.js +81 -60
- package/src/cli.js +401 -354
- package/src/crypto.js +215 -0
- package/src/ens.js +163 -0
- package/src/trading.js +1081 -0
- package/src/transfer.js +723 -0
- package/src/update-check.js +35 -0
- package/src/wallet.js +764 -0
- package/src/x402-evm.js +207 -0
- package/src/x402-svm.js +474 -0
- package/src/x402.js +205 -0
- package/vitest.e2e.config.js +10 -0
package/src/crypto.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared cryptographic primitives for EVM transaction signing.
|
|
3
|
+
* Exports keccak256, secp256k1 ECDSA signing, RLP encoding.
|
|
4
|
+
* Zero external dependencies — uses Node.js built-in crypto only.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
|
|
9
|
+
// ============= Keccak-256 =============
|
|
10
|
+
|
|
11
|
+
// Keccak-256 (NOT SHA3-256; Ethereum uses original Keccak with 0x01 padding).
|
|
12
|
+
// Uses a flat 25-element state array (lanes indexed as state[x + 5*y])
|
|
13
|
+
// with BigInt64 arithmetic.
|
|
14
|
+
|
|
15
|
+
const RC = [
|
|
16
|
+
0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n,
|
|
17
|
+
0x000000000000808bn, 0x0000000080000001n, 0x8000000080008081n, 0x8000000000008009n,
|
|
18
|
+
0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an,
|
|
19
|
+
0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n,
|
|
20
|
+
0x8000000000008002n, 0x8000000000000080n, 0x000000000000800an, 0x800000008000000an,
|
|
21
|
+
0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const ROT = [
|
|
25
|
+
0, 1, 62, 28, 27,
|
|
26
|
+
36, 44, 6, 55, 20,
|
|
27
|
+
3, 10, 43, 25, 39,
|
|
28
|
+
41, 45, 15, 21, 8,
|
|
29
|
+
18, 2, 61, 56, 14,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const M = 0xffffffffffffffffn;
|
|
33
|
+
|
|
34
|
+
function rot64(v, r) {
|
|
35
|
+
return r === 0 ? v : ((v << BigInt(r)) | (v >> BigInt(64 - r))) & M;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function keccakF(s) {
|
|
39
|
+
for (let round = 0; round < 24; round++) {
|
|
40
|
+
const c0 = s[0] ^ s[5] ^ s[10] ^ s[15] ^ s[20];
|
|
41
|
+
const c1 = s[1] ^ s[6] ^ s[11] ^ s[16] ^ s[21];
|
|
42
|
+
const c2 = s[2] ^ s[7] ^ s[12] ^ s[17] ^ s[22];
|
|
43
|
+
const c3 = s[3] ^ s[8] ^ s[13] ^ s[18] ^ s[23];
|
|
44
|
+
const c4 = s[4] ^ s[9] ^ s[14] ^ s[19] ^ s[24];
|
|
45
|
+
const d0 = (c4 ^ rot64(c1, 1)) & M;
|
|
46
|
+
const d1 = (c0 ^ rot64(c2, 1)) & M;
|
|
47
|
+
const d2 = (c1 ^ rot64(c3, 1)) & M;
|
|
48
|
+
const d3 = (c2 ^ rot64(c4, 1)) & M;
|
|
49
|
+
const d4 = (c3 ^ rot64(c0, 1)) & M;
|
|
50
|
+
for (let y = 0; y < 25; y += 5) {
|
|
51
|
+
s[y] = (s[y] ^ d0) & M;
|
|
52
|
+
s[y + 1] = (s[y + 1] ^ d1) & M;
|
|
53
|
+
s[y + 2] = (s[y + 2] ^ d2) & M;
|
|
54
|
+
s[y + 3] = (s[y + 3] ^ d3) & M;
|
|
55
|
+
s[y + 4] = (s[y + 4] ^ d4) & M;
|
|
56
|
+
}
|
|
57
|
+
const t = new Array(25);
|
|
58
|
+
for (let x = 0; x < 5; x++) {
|
|
59
|
+
for (let y = 0; y < 5; y++) {
|
|
60
|
+
const src = x + 5 * y;
|
|
61
|
+
const dst = y + 5 * ((2 * x + 3 * y) % 5);
|
|
62
|
+
t[dst] = rot64(s[src], ROT[src]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (let y = 0; y < 25; y += 5) {
|
|
66
|
+
const t0 = t[y], t1 = t[y+1], t2 = t[y+2], t3 = t[y+3], t4 = t[y+4];
|
|
67
|
+
s[y] = (t0 ^ ((~t1 & M) & t2)) & M;
|
|
68
|
+
s[y+1] = (t1 ^ ((~t2 & M) & t3)) & M;
|
|
69
|
+
s[y+2] = (t2 ^ ((~t3 & M) & t4)) & M;
|
|
70
|
+
s[y+3] = (t3 ^ ((~t4 & M) & t0)) & M;
|
|
71
|
+
s[y+4] = (t4 ^ ((~t0 & M) & t1)) & M;
|
|
72
|
+
}
|
|
73
|
+
s[0] = (s[0] ^ RC[round]) & M;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function keccak256(input) {
|
|
78
|
+
const rate = 136;
|
|
79
|
+
const s = new Array(25).fill(0n);
|
|
80
|
+
const blocks = Math.max(1, Math.ceil((input.length + 1) / rate));
|
|
81
|
+
const padded = Buffer.alloc(blocks * rate);
|
|
82
|
+
input.copy(padded);
|
|
83
|
+
padded[input.length] ^= 0x01;
|
|
84
|
+
padded[padded.length - 1] ^= 0x80;
|
|
85
|
+
for (let off = 0; off < padded.length; off += rate) {
|
|
86
|
+
for (let i = 0; i < 17; i++) {
|
|
87
|
+
s[i] ^= padded.readBigUInt64LE(off + i * 8);
|
|
88
|
+
}
|
|
89
|
+
keccakF(s);
|
|
90
|
+
}
|
|
91
|
+
const out = Buffer.alloc(32);
|
|
92
|
+
for (let i = 0; i < 4; i++) {
|
|
93
|
+
out.writeBigUInt64LE(s[i] & M, i * 8);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============= secp256k1 ECDSA =============
|
|
99
|
+
|
|
100
|
+
const P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn;
|
|
101
|
+
const N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
|
|
102
|
+
const Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n;
|
|
103
|
+
const Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n;
|
|
104
|
+
|
|
105
|
+
function modInv(a, m) {
|
|
106
|
+
let [old_r, r] = [((a % m) + m) % m, m];
|
|
107
|
+
let [old_s, s] = [1n, 0n];
|
|
108
|
+
while (r !== 0n) {
|
|
109
|
+
const q = old_r / r;
|
|
110
|
+
[old_r, r] = [r, old_r - q * r];
|
|
111
|
+
[old_s, s] = [s, old_s - q * s];
|
|
112
|
+
}
|
|
113
|
+
return ((old_s % m) + m) % m;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function ptAdd(x1, y1, x2, y2) {
|
|
117
|
+
if (x1 === null) return [x2, y2];
|
|
118
|
+
if (x2 === null) return [x1, y1];
|
|
119
|
+
if (x1 === x2 && y1 === y2) {
|
|
120
|
+
const lam = (3n * x1 * x1 * modInv(2n * y1, P)) % P;
|
|
121
|
+
const x3 = ((lam * lam - 2n * x1) % P + P) % P;
|
|
122
|
+
return [x3, ((lam * (x1 - x3) - y1) % P + P) % P];
|
|
123
|
+
}
|
|
124
|
+
if (x1 === x2) return [null, null];
|
|
125
|
+
const lam = (((y2 - y1) % P + P) * modInv(((x2 - x1) % P + P) % P, P)) % P;
|
|
126
|
+
const x3 = ((lam * lam - x1 - x2) % P + P) % P;
|
|
127
|
+
return [x3, ((lam * (x1 - x3) - y1) % P + P) % P];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function ptMul(k, x, y) {
|
|
131
|
+
let [rx, ry] = [null, null];
|
|
132
|
+
let [qx, qy] = [x, y];
|
|
133
|
+
while (k > 0n) {
|
|
134
|
+
if (k & 1n) [rx, ry] = ptAdd(rx, ry, qx, qy);
|
|
135
|
+
[qx, qy] = ptAdd(qx, qy, qx, qy);
|
|
136
|
+
k >>= 1n;
|
|
137
|
+
}
|
|
138
|
+
return [rx, ry];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function rfc6979k(privBuf, hash) {
|
|
142
|
+
let v = Buffer.alloc(32, 0x01);
|
|
143
|
+
let k = Buffer.alloc(32, 0x00);
|
|
144
|
+
k = crypto.createHmac('sha256', k).update(Buffer.concat([v, Buffer.from([0x00]), privBuf, hash])).digest();
|
|
145
|
+
v = crypto.createHmac('sha256', k).update(v).digest();
|
|
146
|
+
k = crypto.createHmac('sha256', k).update(Buffer.concat([v, Buffer.from([0x01]), privBuf, hash])).digest();
|
|
147
|
+
v = crypto.createHmac('sha256', k).update(v).digest();
|
|
148
|
+
while (true) {
|
|
149
|
+
v = crypto.createHmac('sha256', k).update(v).digest();
|
|
150
|
+
const candidate = BigInt('0x' + v.toString('hex'));
|
|
151
|
+
if (candidate >= 1n && candidate < N) return candidate;
|
|
152
|
+
k = crypto.createHmac('sha256', k).update(Buffer.concat([v, Buffer.from([0x00])])).digest();
|
|
153
|
+
v = crypto.createHmac('sha256', k).update(v).digest();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Sign a 32-byte hash with secp256k1 ECDSA.
|
|
159
|
+
* Uses RFC 6979 deterministic k and low-S normalization (EIP-2).
|
|
160
|
+
* Returns { r, s, v } where v is the recovery ID (0 or 1).
|
|
161
|
+
*/
|
|
162
|
+
export function signSecp256k1(hash, privateKey) {
|
|
163
|
+
const z = BigInt('0x' + hash.toString('hex'));
|
|
164
|
+
const d = BigInt('0x' + privateKey.toString('hex'));
|
|
165
|
+
const k = rfc6979k(privateKey, hash);
|
|
166
|
+
const [rx, ry] = ptMul(k, Gx, Gy);
|
|
167
|
+
const r = rx % N;
|
|
168
|
+
if (r === 0n) throw new Error('Invalid signature: r=0');
|
|
169
|
+
let s = (modInv(k, N) * ((z + r * d) % N)) % N;
|
|
170
|
+
if (s === 0n) throw new Error('Invalid signature: s=0');
|
|
171
|
+
let v = (ry % 2n === 0n) ? 0 : 1;
|
|
172
|
+
if (s > N >> 1n) { s = N - s; v ^= 1; }
|
|
173
|
+
return {
|
|
174
|
+
r: Buffer.from(r.toString(16).padStart(64, '0'), 'hex'),
|
|
175
|
+
s: Buffer.from(s.toString(16).padStart(64, '0'), 'hex'),
|
|
176
|
+
v,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============= RLP Encoding =============
|
|
181
|
+
|
|
182
|
+
export function bigIntToMinBuf(n) {
|
|
183
|
+
if (n === 0n) return Buffer.alloc(0);
|
|
184
|
+
const hex = n.toString(16);
|
|
185
|
+
return Buffer.from(hex.length % 2 ? '0' + hex : hex, 'hex');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function rlpEncode(input) {
|
|
189
|
+
if (Array.isArray(input)) {
|
|
190
|
+
const encoded = input.map(rlpEncode);
|
|
191
|
+
const payload = Buffer.concat(encoded);
|
|
192
|
+
if (payload.length < 56) {
|
|
193
|
+
return Buffer.concat([Buffer.from([0xc0 + payload.length]), payload]);
|
|
194
|
+
}
|
|
195
|
+
const lenBytes = bigIntToMinBuf(BigInt(payload.length));
|
|
196
|
+
return Buffer.concat([Buffer.from([0xf7 + lenBytes.length]), lenBytes, payload]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let data;
|
|
200
|
+
if (Buffer.isBuffer(input)) {
|
|
201
|
+
data = input;
|
|
202
|
+
} else {
|
|
203
|
+
let hex = (typeof input === 'string' ? input : '').replace(/^0x/, '');
|
|
204
|
+
hex = hex.replace(/^0+/, '');
|
|
205
|
+
if (hex === '' || hex.length === 0) return Buffer.from([0x80]);
|
|
206
|
+
if (hex.length % 2) hex = '0' + hex;
|
|
207
|
+
data = Buffer.from(hex, 'hex');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (data.length === 0) return Buffer.from([0x80]);
|
|
211
|
+
if (data.length === 1 && data[0] < 0x80) return data;
|
|
212
|
+
if (data.length < 56) return Buffer.concat([Buffer.from([0x80 + data.length]), data]);
|
|
213
|
+
const lenBytes = bigIntToMinBuf(BigInt(data.length));
|
|
214
|
+
return Buffer.concat([Buffer.from([0xb7 + lenBytes.length]), lenBytes, data]);
|
|
215
|
+
}
|
package/src/ens.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ENS (Ethereum Name Service) resolution
|
|
3
|
+
* Resolves .eth names to addresses using public APIs with onchain RPC fallback.
|
|
4
|
+
* Zero external dependencies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import https from 'https';
|
|
8
|
+
import { keccak256 } from './crypto.js';
|
|
9
|
+
|
|
10
|
+
const ENS_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.eth$/;
|
|
11
|
+
|
|
12
|
+
const EVM_CHAINS = [
|
|
13
|
+
'ethereum', 'base', 'optimism', 'arbitrum', 'polygon', 'bnb',
|
|
14
|
+
'avalanche', 'fantom', 'gnosis', 'linea', 'scroll', 'zksync',
|
|
15
|
+
'blast', 'mantle', 'ronin', 'sei', 'plasma', 'sonic', 'unichain', 'monad', 'hyperevm', 'iotaevm'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if a string looks like an ENS name
|
|
20
|
+
*/
|
|
21
|
+
export function isEnsName(name) {
|
|
22
|
+
return typeof name === 'string' && ENS_PATTERN.test(name.trim());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve an address input — if it's an ENS name, resolve it; otherwise pass through.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} addressOrName - Address (0x...) or ENS name (*.eth)
|
|
29
|
+
* @param {string} chain - Chain context (ENS only resolves on EVM chains)
|
|
30
|
+
* @returns {Promise<{address: string, ensName?: string}>}
|
|
31
|
+
*/
|
|
32
|
+
export async function resolveAddress(addressOrName, chain = 'ethereum') {
|
|
33
|
+
if (!addressOrName || typeof addressOrName !== 'string') {
|
|
34
|
+
return { address: addressOrName };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const trimmed = addressOrName.trim();
|
|
38
|
+
|
|
39
|
+
if (!isEnsName(trimmed)) {
|
|
40
|
+
return { address: trimmed };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!EVM_CHAINS.includes(chain)) {
|
|
44
|
+
throw new Error(`ENS names can only be resolved on EVM chains, not ${chain}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const name = trimmed.toLowerCase();
|
|
48
|
+
const errors = [];
|
|
49
|
+
|
|
50
|
+
// Try ensideas API first (fast, no auth)
|
|
51
|
+
try {
|
|
52
|
+
const addr = await resolveViaEnsIdeas(name);
|
|
53
|
+
if (addr) return { address: addr, ensName: name };
|
|
54
|
+
} catch (e) {
|
|
55
|
+
errors.push(`ensideas: ${e.message}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Fallback: onchain resolution via public RPC
|
|
59
|
+
try {
|
|
60
|
+
const addr = await resolveOnchain(name);
|
|
61
|
+
if (addr) return { address: addr, ensName: name };
|
|
62
|
+
} catch (e) {
|
|
63
|
+
errors.push(`onchain: ${e.message}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
throw new Error(`Could not resolve ENS name: ${name}${errors.length ? ` (${errors.join('; ')})` : ''}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============= Resolvers =============
|
|
70
|
+
|
|
71
|
+
function httpsGet(url, timeoutMs = 5000) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const req = https.get(url, { timeout: timeoutMs }, (res) => {
|
|
74
|
+
let data = '';
|
|
75
|
+
res.on('data', chunk => { data += chunk; });
|
|
76
|
+
res.on('end', () => {
|
|
77
|
+
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
|
78
|
+
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
req.on('error', reject);
|
|
82
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function httpsPost(url, body, timeoutMs = 5000) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const payload = JSON.stringify(body);
|
|
89
|
+
const parsed = new URL(url);
|
|
90
|
+
const req = https.request({
|
|
91
|
+
hostname: parsed.hostname,
|
|
92
|
+
path: parsed.pathname,
|
|
93
|
+
method: 'POST',
|
|
94
|
+
timeout: timeoutMs,
|
|
95
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
|
|
96
|
+
}, (res) => {
|
|
97
|
+
let buf = '';
|
|
98
|
+
res.on('data', chunk => { buf += chunk; });
|
|
99
|
+
res.on('end', () => {
|
|
100
|
+
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
|
101
|
+
try { resolve(JSON.parse(buf)); } catch (e) { reject(e); }
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
req.on('error', reject);
|
|
105
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
106
|
+
req.write(payload);
|
|
107
|
+
req.end();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const VALID_ADDR = /^0x[0-9a-fA-F]{40}$/;
|
|
112
|
+
|
|
113
|
+
async function resolveViaEnsIdeas(name) {
|
|
114
|
+
const result = await httpsGet(`https://api.ensideas.com/ens/resolve/${encodeURIComponent(name)}`);
|
|
115
|
+
if (result?.address && VALID_ADDR.test(result.address)) return result.address;
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Compute ENS namehash using keccak256 from crypto.js
|
|
121
|
+
*/
|
|
122
|
+
function namehash(name) {
|
|
123
|
+
let node = Buffer.alloc(32, 0); // bytes32(0)
|
|
124
|
+
if (!name) return node.toString('hex');
|
|
125
|
+
|
|
126
|
+
const labels = name.split('.').reverse();
|
|
127
|
+
for (const label of labels) {
|
|
128
|
+
const labelHash = keccak256(Buffer.from(label, 'utf8'));
|
|
129
|
+
node = keccak256(Buffer.concat([node, labelHash]));
|
|
130
|
+
}
|
|
131
|
+
return node.toString('hex');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const ENS_REGISTRY = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e';
|
|
135
|
+
const ZERO_HASH = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
136
|
+
const RPC_URL = 'https://eth.llamarpc.com';
|
|
137
|
+
|
|
138
|
+
async function resolveOnchain(name) {
|
|
139
|
+
const hash = namehash(name);
|
|
140
|
+
|
|
141
|
+
// Step 1: Get resolver from ENS registry — resolver(bytes32)
|
|
142
|
+
const resolverResult = await httpsPost(RPC_URL, {
|
|
143
|
+
jsonrpc: '2.0', id: 1, method: 'eth_call',
|
|
144
|
+
params: [{ to: ENS_REGISTRY, data: '0x0178b8bf' + hash }, 'latest']
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const resolverHex = resolverResult?.result;
|
|
148
|
+
if (!resolverHex || resolverHex === '0x' || resolverHex.slice(2) === ZERO_HASH) return null;
|
|
149
|
+
|
|
150
|
+
const resolver = '0x' + resolverHex.slice(26);
|
|
151
|
+
|
|
152
|
+
// Step 2: Call addr(bytes32) on the resolver — selector 0x3b3b57de
|
|
153
|
+
const addrResult = await httpsPost(RPC_URL, {
|
|
154
|
+
jsonrpc: '2.0', id: 2, method: 'eth_call',
|
|
155
|
+
params: [{ to: resolver, data: '0x3b3b57de' + hash }, 'latest']
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const addrHex = addrResult?.result;
|
|
159
|
+
if (!addrHex || addrHex === '0x' || addrHex.slice(2) === ZERO_HASH) return null;
|
|
160
|
+
|
|
161
|
+
const address = '0x' + addrHex.slice(26);
|
|
162
|
+
return VALID_ADDR.test(address) ? address : null;
|
|
163
|
+
}
|