@hoodag/sdk 0.1.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/README.md +67 -0
- package/hood.js +107 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @hoodag/sdk
|
|
2
|
+
|
|
3
|
+
Resolve **.hood** names on [Robinhood Chain](https://hood.ag) (chain id `4663`). ENS-standard,
|
|
4
|
+
so it behaves like ENS resolution — but every read goes through a single contract, the
|
|
5
|
+
**UniversalResolver**, which makes integration a one-liner.
|
|
6
|
+
|
|
7
|
+
Zero dependencies (uses global `fetch`, works in the browser and Node 18+). Optional
|
|
8
|
+
[viem](https://viem.sh) helpers are exported if your app already uses viem.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @hoodag/sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import { resolveHood, reverseHood, resolveTextHood } from '@hoodag/sdk';
|
|
20
|
+
|
|
21
|
+
// Forward: name -> address
|
|
22
|
+
const addr = await resolveHood('robin.hood');
|
|
23
|
+
// '0x45aa14…' (or null if unset)
|
|
24
|
+
|
|
25
|
+
// Reverse: address -> primary name (verified = forward record points back)
|
|
26
|
+
const { name, verified } = await reverseHood(addr);
|
|
27
|
+
// { name: 'robin.hood', verified: true }
|
|
28
|
+
|
|
29
|
+
// Text records
|
|
30
|
+
const twitter = await resolveTextHood('robin.hood', 'com.twitter');
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Every function accepts an optional `rpcUrl` as the last argument if you run your own node:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
await resolveHood('robin.hood', 'https://my-node.example/rpc');
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## viem helpers
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
import { createPublicClient, http } from 'viem';
|
|
43
|
+
import { HOOD, UNIVERSAL_RESOLVER_ABI } from '@hoodag/sdk';
|
|
44
|
+
|
|
45
|
+
const client = createPublicClient({ transport: http(HOOD.rpcUrl) });
|
|
46
|
+
const addr = await client.readContract({
|
|
47
|
+
address: HOOD.contracts.universalResolver,
|
|
48
|
+
abi: UNIVERSAL_RESOLVER_ABI,
|
|
49
|
+
functionName: 'resolve',
|
|
50
|
+
args: ['robin.hood'],
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Contracts (Robinhood Chain, id 4663)
|
|
55
|
+
|
|
56
|
+
| Contract | Address |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| Registry (immutable) | `0x3E717dc89AAF4605b607324329EEeB0a8B3C8A1c` |
|
|
59
|
+
| UniversalResolver | `0xa9ed3C73F522875F18DAa631429125da893D8E53` |
|
|
60
|
+
| Registrar (.hood NFT) | `0x9a122C54e15B6287c164b90833bcDA6E16520D50` |
|
|
61
|
+
| Public Resolver | `0x072C7a66a6D52B23274470b86096AFCBF816CA0B` |
|
|
62
|
+
| Registrar Controller | `0x7900b2B8Cc3f0616B18C68AdA7E115A9022Ca04a` |
|
|
63
|
+
| Reverse Registrar | `0x4be6D0379f56404B563afAe17c6b095b1dfcf479` |
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
package/hood.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .hood name service SDK — resolve .hood names on Robinhood Chain (4663).
|
|
3
|
+
*
|
|
4
|
+
* Drop-in for any app. Two flavors:
|
|
5
|
+
* 1) Zero-dependency (uses global fetch) — resolveHood / reverseHood / resolveTextHood
|
|
6
|
+
* 2) viem helpers (if you already use viem) — at the bottom
|
|
7
|
+
*
|
|
8
|
+
* All calls hit ONE contract (the HoodUniversalResolver), so integration is a single read.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const HOOD = {
|
|
12
|
+
chainId: 4663,
|
|
13
|
+
rpcUrl: 'https://rpc.mainnet.chain.robinhood.com',
|
|
14
|
+
contracts: {
|
|
15
|
+
registry: '0x3E717dc89AAF4605b607324329EEeB0a8B3C8A1c', // immutable trust anchor
|
|
16
|
+
universalResolver: '0xa9ed3C73F522875F18DAa631429125da893D8E53', // <-- call this
|
|
17
|
+
registrar: '0x9a122C54e15B6287c164b90833bcDA6E16520D50',
|
|
18
|
+
resolver: '0x072C7a66a6D52B23274470b86096AFCBF816CA0B',
|
|
19
|
+
controller: '0x7900b2B8Cc3f0616B18C68AdA7E115A9022Ca04a',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// ---------- zero-dependency implementation (works in browser & node 18+) ----------
|
|
24
|
+
function keccakSelector(sig) {
|
|
25
|
+
// precomputed 4-byte selectors (verified against the deployed UniversalResolver):
|
|
26
|
+
const map = {
|
|
27
|
+
'resolve(string)': '0x461a4478',
|
|
28
|
+
'reverse(address)': '0xe30bd740',
|
|
29
|
+
'resolveText(string,string)': '0xcb7cef3c',
|
|
30
|
+
};
|
|
31
|
+
return map[sig];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function encStringArg(str, extra = '') {
|
|
35
|
+
// ABI-encode a single dynamic string (+ optional already-encoded trailing words)
|
|
36
|
+
const bytes = new TextEncoder().encode(str);
|
|
37
|
+
const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
38
|
+
const len = bytes.length.toString(16).padStart(64, '0');
|
|
39
|
+
const padded = hex.padEnd(Math.ceil(hex.length / 64) * 64, '0');
|
|
40
|
+
// head: offset to string = 0x20 (or 0x40 if there's a second static/dynamic arg — handled by caller)
|
|
41
|
+
return { len, padded, hexLen: bytes.length };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function ethCall(to, data, rpcUrl = HOOD.rpcUrl) {
|
|
45
|
+
const res = await fetch(rpcUrl, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_call', params: [{ to, data }, 'latest'] }),
|
|
49
|
+
});
|
|
50
|
+
const j = await res.json();
|
|
51
|
+
if (j.error) throw new Error(j.error.message);
|
|
52
|
+
return j.result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** resolveHood("robin.hood") -> "0x..." (or null if unset) */
|
|
56
|
+
export async function resolveHood(name, rpcUrl = HOOD.rpcUrl) {
|
|
57
|
+
const { len, padded } = encStringArg(name);
|
|
58
|
+
const data = keccakSelector('resolve(string)') +
|
|
59
|
+
'0000000000000000000000000000000000000000000000000000000000000020' + len + padded;
|
|
60
|
+
const out = await ethCall(HOOD.contracts.universalResolver, data, rpcUrl);
|
|
61
|
+
const addr = '0x' + out.slice(-40);
|
|
62
|
+
return /^0x0{40}$/.test(addr) ? null : addr;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** reverseHood("0x...") -> { name, verified } */
|
|
66
|
+
export async function reverseHood(address, rpcUrl = HOOD.rpcUrl) {
|
|
67
|
+
const data = keccakSelector('reverse(address)') + address.toLowerCase().replace('0x', '').padStart(64, '0');
|
|
68
|
+
const out = await ethCall(HOOD.contracts.universalResolver, data, rpcUrl);
|
|
69
|
+
const b = out.slice(2);
|
|
70
|
+
const strOffset = parseInt(b.slice(0, 64), 16) * 2;
|
|
71
|
+
const verified = parseInt(b.slice(64, 128), 16) === 1;
|
|
72
|
+
const strLen = parseInt(b.slice(strOffset, strOffset + 64), 16) * 2;
|
|
73
|
+
const strHex = b.slice(strOffset + 64, strOffset + 64 + strLen);
|
|
74
|
+
const name = new TextDecoder().decode(Uint8Array.from(strHex.match(/../g)?.map(h => parseInt(h, 16)) ?? []));
|
|
75
|
+
return { name: name || null, verified };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** resolveTextHood("robin.hood", "com.twitter") -> "..." */
|
|
79
|
+
export async function resolveTextHood(name, key, rpcUrl = HOOD.rpcUrl) {
|
|
80
|
+
const n = encStringArg(name), k = encStringArg(key);
|
|
81
|
+
// two dynamic args: head = [off1=0x40, off2], then arg1, then arg2
|
|
82
|
+
const head = '0000000000000000000000000000000000000000000000000000000000000040';
|
|
83
|
+
const arg1 = n.len + n.padded;
|
|
84
|
+
const off2 = (0x40 + arg1.length / 2).toString(16).padStart(64, '0');
|
|
85
|
+
const arg2 = k.len + k.padded;
|
|
86
|
+
const data = keccakSelector('resolveText(string,string)') + head + off2 + arg1 + arg2;
|
|
87
|
+
const out = await ethCall(HOOD.contracts.universalResolver, data, rpcUrl);
|
|
88
|
+
const b = out.slice(2);
|
|
89
|
+
const strLen = parseInt(b.slice(64, 128), 16) * 2;
|
|
90
|
+
const strHex = b.slice(128, 128 + strLen);
|
|
91
|
+
return new TextDecoder().decode(Uint8Array.from(strHex.match(/../g)?.map(h => parseInt(h, 16)) ?? []));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------- viem helpers (optional; if your app already uses viem) ----------
|
|
95
|
+
export const UNIVERSAL_RESOLVER_ABI = [
|
|
96
|
+
{ type: 'function', name: 'resolve', stateMutability: 'view', inputs: [{ name: 'name', type: 'string' }], outputs: [{ type: 'address' }] },
|
|
97
|
+
{ type: 'function', name: 'reverse', stateMutability: 'view', inputs: [{ name: 'a', type: 'address' }], outputs: [{ name: 'name', type: 'string' }, { name: 'verified', type: 'bool' }] },
|
|
98
|
+
{ type: 'function', name: 'resolveText', stateMutability: 'view', inputs: [{ name: 'name', type: 'string' }, { name: 'key', type: 'string' }], outputs: [{ type: 'string' }] },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
/* viem usage:
|
|
102
|
+
import { createPublicClient, http } from 'viem';
|
|
103
|
+
import { HOOD, UNIVERSAL_RESOLVER_ABI } from './hood.js';
|
|
104
|
+
const client = createPublicClient({ transport: http(HOOD.rpcUrl) });
|
|
105
|
+
const addr = await client.readContract({ address: HOOD.contracts.universalResolver, abi: UNIVERSAL_RESOLVER_ABI, functionName: 'resolve', args: ['robin.hood'] });
|
|
106
|
+
const [name, verified] = await client.readContract({ address: HOOD.contracts.universalResolver, abi: UNIVERSAL_RESOLVER_ABI, functionName: 'reverse', args: [addr] });
|
|
107
|
+
*/
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hoodag/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Resolve .hood names on Robinhood Chain (ENS-standard) — one call, zero dependencies, optional viem helpers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./hood.js",
|
|
7
|
+
"module": "./hood.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./hood.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"hood.js",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"keywords": [
|
|
17
|
+
"hood",
|
|
18
|
+
"ens",
|
|
19
|
+
"robinhood-chain",
|
|
20
|
+
"name-service",
|
|
21
|
+
"resolver",
|
|
22
|
+
"web3",
|
|
23
|
+
"viem",
|
|
24
|
+
"ethereum"
|
|
25
|
+
],
|
|
26
|
+
"homepage": "https://hood.ag",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
}
|