@1001-digital/ethereum-names 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/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/index.cjs +194 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +111 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +190 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 1001.digital
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# @1001-digital/ethereum-names
|
|
2
|
+
|
|
3
|
+
One clean, [viem](https://viem.sh)-powered API to resolve Ethereum names across both
|
|
4
|
+
[ENS](https://ens.domains) and the [Gwei Name Service](https://gwei.domains) (GNS).
|
|
5
|
+
|
|
6
|
+
Point it at a name — `vitalik.eth` or `alice.gwei` — and it figures out which system to
|
|
7
|
+
ask. Point it at an address and it gives you back the primary name. No branching in your
|
|
8
|
+
app code.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @1001-digital/ethereum-names viem
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`viem` is a peer dependency.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createEthereumNames } from '@1001-digital/ethereum-names'
|
|
22
|
+
|
|
23
|
+
const names = createEthereumNames()
|
|
24
|
+
|
|
25
|
+
// Forward: name → address (the system is detected from the name)
|
|
26
|
+
await names.resolve('vitalik.eth') // ENS → '0xd8dA...' | null
|
|
27
|
+
await names.resolve('alice.gwei') // GNS → '0x...' | null
|
|
28
|
+
await names.resolve('alice') // bare label → treated as alice.gwei
|
|
29
|
+
await names.resolve('0xd8dA...') // address → returned checksummed
|
|
30
|
+
|
|
31
|
+
// Reverse: address → primary name (tries ENS, then GNS)
|
|
32
|
+
await names.reverse('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
|
|
33
|
+
// => 'vitalik.eth' | 'alice.gwei' | null
|
|
34
|
+
|
|
35
|
+
// Reverse across BOTH systems at once (when an address has names in each)
|
|
36
|
+
await names.reverseAll('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
|
|
37
|
+
// => { ens: 'vitalik.eth', gns: 'vitalik.gwei' }
|
|
38
|
+
|
|
39
|
+
// Rich lookup: resolve or reverse, and learn which system answered
|
|
40
|
+
await names.lookup('alice.gwei')
|
|
41
|
+
// => { input: 'alice.gwei', name: 'alice.gwei', address: '0x...', system: 'gns' }
|
|
42
|
+
|
|
43
|
+
// Records work across both systems
|
|
44
|
+
await names.getAvatar('vitalik.eth')
|
|
45
|
+
await names.getText('alice.gwei', 'url')
|
|
46
|
+
|
|
47
|
+
// Pure, offline system detection
|
|
48
|
+
names.system('alice.gwei') // 'gns'
|
|
49
|
+
names.system('foo.eth') // 'ens'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## How resolution is routed
|
|
53
|
+
|
|
54
|
+
| Input | System |
|
|
55
|
+
| --------------------------- | ------ |
|
|
56
|
+
| `*.gwei` | GNS |
|
|
57
|
+
| bare label (no dot) | GNS |
|
|
58
|
+
| any other dotted name `*.eth`, `*.box`, … | ENS |
|
|
59
|
+
| `0x…` address | passed through (checksummed) |
|
|
60
|
+
|
|
61
|
+
Reverse lookups try each system in order (ENS first by default) and return the first
|
|
62
|
+
match. Configure the order with `reversePriority`, or use `reverseAll` to get the primary
|
|
63
|
+
name from **both** systems at once.
|
|
64
|
+
|
|
65
|
+
By default, reverse lookups are **forward-verified**: after reading an address's primary
|
|
66
|
+
name, the library resolves that name back and confirms it points to the same address
|
|
67
|
+
before trusting it. This guards against spoofed reverse records (ENS reverse records are
|
|
68
|
+
not self-validating). Disable with `verify: false` to save a round-trip.
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { createEthereumNames } from '@1001-digital/ethereum-names'
|
|
74
|
+
import { createPublicClient, http } from 'viem'
|
|
75
|
+
import { mainnet } from 'viem/chains'
|
|
76
|
+
|
|
77
|
+
// Bring your own viem client (recommended for production)
|
|
78
|
+
const client = createPublicClient({ chain: mainnet, transport: http('https://my-rpc') })
|
|
79
|
+
const names = createEthereumNames({ client })
|
|
80
|
+
|
|
81
|
+
// …or let the library create a mainnet client for you
|
|
82
|
+
const quick = createEthereumNames({ rpcUrl: 'https://my-rpc' })
|
|
83
|
+
|
|
84
|
+
// Prefer GNS names on reverse lookups
|
|
85
|
+
const gnsFirst = createEthereumNames({ reversePriority: ['gns', 'ens'] })
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Option | Type | Description |
|
|
89
|
+
| ----------------- | --------------------- | -------------------------------------------------------------------- |
|
|
90
|
+
| `client` | `PublicClient` | A viem client to read from. Its chain must have ENS contracts. |
|
|
91
|
+
| `rpcUrl` | `string` | RPC endpoint used when no `client` is given. |
|
|
92
|
+
| `chain` | `Chain` | Chain used when no `client` is given. Defaults to `mainnet`. |
|
|
93
|
+
| `gnsContract` | `Address` | Override the GNS contract address. |
|
|
94
|
+
| `reversePriority` | `('ens' \| 'gns')[]` | Order reverse lookups try each system. Defaults to `['ens', 'gns']`. |
|
|
95
|
+
| `verify` | `boolean` | Forward-verify reverse lookups before trusting them. Defaults to `true`. |
|
|
96
|
+
|
|
97
|
+
> **Note:** ENS resolution relies on viem's ENS actions, which require a chain with ENS
|
|
98
|
+
> contracts configured (such as `mainnet`). GNS is live at the same address on Ethereum
|
|
99
|
+
> mainnet and Sepolia.
|
|
100
|
+
|
|
101
|
+
## API
|
|
102
|
+
|
|
103
|
+
### `createEthereumNames(config?) → EthereumNames`
|
|
104
|
+
|
|
105
|
+
| Method | Returns | Description |
|
|
106
|
+
| ----------------------- | ----------------------------- | ------------------------------------------------------- |
|
|
107
|
+
| `resolve(nameOrAddress)`| `Promise<Address \| null>` | Name → address. Addresses pass through, checksummed. |
|
|
108
|
+
| `reverse(address)` | `Promise<string \| null>` | Address → primary name across systems. |
|
|
109
|
+
| `reverseAll(address)` | `Promise<ReverseNames>` | Address → `{ ens, gns }` primary names from both systems.|
|
|
110
|
+
| `lookup(input)` | `Promise<ResolvedName>` | Resolve or reverse, with the answering `system`. |
|
|
111
|
+
| `getAvatar(name)` | `Promise<string \| null>` | Avatar record (ENS avatar, or GNS `avatar` text). |
|
|
112
|
+
| `getText(name, key)` | `Promise<string \| null>` | Arbitrary text record. |
|
|
113
|
+
| `system(name)` | `'ens' \| 'gns' \| null` | Offline system detection. |
|
|
114
|
+
| `client` | `PublicClient` | The underlying viem client. |
|
|
115
|
+
|
|
116
|
+
Also exported: `detectSystem(name)`, `DEFAULT_GNS_CONTRACT`, and the types
|
|
117
|
+
`EthereumNames`, `EthereumNamesConfig`, `NameSystem`, `ResolvedName`, `ReverseNames`.
|
|
118
|
+
|
|
119
|
+
## Credits
|
|
120
|
+
|
|
121
|
+
GNS resolution builds on [`@donnoh/gns-utils`](https://www.npmjs.com/package/@donnoh/gns-utils)
|
|
122
|
+
by [lucadonnoh](https://github.com/lucadonnoh/gwei-names).
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT © [1001.digital](https://1001.digital)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var gnsUtils = require('@donnoh/gns-utils');
|
|
4
|
+
var viem = require('viem');
|
|
5
|
+
var chains = require('viem/chains');
|
|
6
|
+
var actions = require('viem/actions');
|
|
7
|
+
var ens = require('viem/ens');
|
|
8
|
+
|
|
9
|
+
// src/client.ts
|
|
10
|
+
function detectSystem(input) {
|
|
11
|
+
const value = input.trim().toLowerCase();
|
|
12
|
+
if (!value) return null;
|
|
13
|
+
if (value.endsWith(".gwei")) return "gns";
|
|
14
|
+
if (value.includes(".")) return "ens";
|
|
15
|
+
return "gns";
|
|
16
|
+
}
|
|
17
|
+
function safeNormalizeEns(name) {
|
|
18
|
+
try {
|
|
19
|
+
return ens.normalize(name);
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/ens.ts
|
|
26
|
+
async function ensResolve(client, name) {
|
|
27
|
+
const normalized = safeNormalizeEns(name);
|
|
28
|
+
if (!normalized) return null;
|
|
29
|
+
return actions.getEnsAddress(client, { name: normalized });
|
|
30
|
+
}
|
|
31
|
+
function ensReverse(client, address) {
|
|
32
|
+
return actions.getEnsName(client, { address });
|
|
33
|
+
}
|
|
34
|
+
async function ensAvatar(client, name) {
|
|
35
|
+
const normalized = safeNormalizeEns(name);
|
|
36
|
+
if (!normalized) return null;
|
|
37
|
+
return actions.getEnsAvatar(client, { name: normalized });
|
|
38
|
+
}
|
|
39
|
+
async function ensText(client, name, key) {
|
|
40
|
+
const normalized = safeNormalizeEns(name);
|
|
41
|
+
if (!normalized) return null;
|
|
42
|
+
return actions.getEnsText(client, { name: normalized, key });
|
|
43
|
+
}
|
|
44
|
+
var DEFAULT_GNS_CONTRACT = gnsUtils.GNS_CONTRACT;
|
|
45
|
+
function tokenId(client, contract, name) {
|
|
46
|
+
return actions.readContract(client, {
|
|
47
|
+
address: contract,
|
|
48
|
+
abi: gnsUtils.gnsAbi,
|
|
49
|
+
functionName: "computeId",
|
|
50
|
+
args: [gnsUtils.normalizeName(name)]
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function gnsResolve(client, contract, name) {
|
|
54
|
+
const id = await tokenId(client, contract, name);
|
|
55
|
+
if (id === 0n) return null;
|
|
56
|
+
const addr = await actions.readContract(client, {
|
|
57
|
+
address: contract,
|
|
58
|
+
abi: gnsUtils.gnsAbi,
|
|
59
|
+
functionName: "resolve",
|
|
60
|
+
args: [id]
|
|
61
|
+
});
|
|
62
|
+
if (!addr || viem.isAddressEqual(addr, viem.zeroAddress)) return null;
|
|
63
|
+
return viem.getAddress(addr);
|
|
64
|
+
}
|
|
65
|
+
async function gnsReverse(client, contract, address) {
|
|
66
|
+
const name = await actions.readContract(client, {
|
|
67
|
+
address: contract,
|
|
68
|
+
abi: gnsUtils.gnsAbi,
|
|
69
|
+
functionName: "reverseResolve",
|
|
70
|
+
args: [address]
|
|
71
|
+
});
|
|
72
|
+
return name || null;
|
|
73
|
+
}
|
|
74
|
+
async function gnsText(client, contract, name, key) {
|
|
75
|
+
const id = await tokenId(client, contract, name);
|
|
76
|
+
if (id === 0n) return null;
|
|
77
|
+
const value = await actions.readContract(client, {
|
|
78
|
+
address: contract,
|
|
79
|
+
abi: gnsUtils.gnsAbi,
|
|
80
|
+
functionName: "text",
|
|
81
|
+
args: [id, key]
|
|
82
|
+
});
|
|
83
|
+
return value || null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/client.ts
|
|
87
|
+
function createEthereumNames(config = {}) {
|
|
88
|
+
const client = config.client ?? viem.createPublicClient({
|
|
89
|
+
chain: config.chain ?? chains.mainnet,
|
|
90
|
+
transport: viem.http(config.rpcUrl)
|
|
91
|
+
});
|
|
92
|
+
const gnsContract = config.gnsContract ?? DEFAULT_GNS_CONTRACT;
|
|
93
|
+
const reversePriority = config.reversePriority ?? ["ens", "gns"];
|
|
94
|
+
const verify = config.verify ?? true;
|
|
95
|
+
function canonical(name, system) {
|
|
96
|
+
if (system === "gns") return gnsUtils.normalizeName(name);
|
|
97
|
+
if (system === "ens") return safeNormalizeEns(name);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
async function resolveName(name, system) {
|
|
101
|
+
try {
|
|
102
|
+
if (system === "ens") return await ensResolve(client, name);
|
|
103
|
+
return await gnsResolve(client, gnsContract, name);
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function rawReverse(system, address) {
|
|
109
|
+
try {
|
|
110
|
+
if (system === "ens") return await ensReverse(client, address);
|
|
111
|
+
return await gnsReverse(client, gnsContract, address);
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function reverseFor(system, address) {
|
|
117
|
+
const name = await rawReverse(system, address);
|
|
118
|
+
if (!name) return null;
|
|
119
|
+
if (!verify) return name;
|
|
120
|
+
const forward = await resolveName(name, system);
|
|
121
|
+
return forward && viem.isAddressEqual(forward, address) ? name : null;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
client,
|
|
125
|
+
system(name) {
|
|
126
|
+
return detectSystem(name);
|
|
127
|
+
},
|
|
128
|
+
async resolve(nameOrAddress) {
|
|
129
|
+
if (!nameOrAddress) return null;
|
|
130
|
+
if (viem.isAddress(nameOrAddress)) return viem.getAddress(nameOrAddress);
|
|
131
|
+
const system = detectSystem(nameOrAddress);
|
|
132
|
+
if (!system) return null;
|
|
133
|
+
return resolveName(nameOrAddress, system);
|
|
134
|
+
},
|
|
135
|
+
async reverse(address) {
|
|
136
|
+
if (!viem.isAddress(address)) return null;
|
|
137
|
+
const checksummed = viem.getAddress(address);
|
|
138
|
+
for (const system of reversePriority) {
|
|
139
|
+
const name = await reverseFor(system, checksummed);
|
|
140
|
+
if (name) return name;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
},
|
|
144
|
+
async reverseAll(address) {
|
|
145
|
+
if (!viem.isAddress(address)) return { ens: null, gns: null };
|
|
146
|
+
const checksummed = viem.getAddress(address);
|
|
147
|
+
const [ens, gns] = await Promise.all([
|
|
148
|
+
reverseFor("ens", checksummed),
|
|
149
|
+
reverseFor("gns", checksummed)
|
|
150
|
+
]);
|
|
151
|
+
return { ens, gns };
|
|
152
|
+
},
|
|
153
|
+
async lookup(input) {
|
|
154
|
+
if (viem.isAddress(input)) {
|
|
155
|
+
const address2 = viem.getAddress(input);
|
|
156
|
+
for (const system2 of reversePriority) {
|
|
157
|
+
const name = await reverseFor(system2, address2);
|
|
158
|
+
if (name) return { input, name, address: address2, system: system2 };
|
|
159
|
+
}
|
|
160
|
+
return { input, name: null, address: address2, system: null };
|
|
161
|
+
}
|
|
162
|
+
const system = detectSystem(input);
|
|
163
|
+
if (!system) return { input, name: null, address: null, system: null };
|
|
164
|
+
const address = await resolveName(input, system);
|
|
165
|
+
return { input, name: canonical(input, system), address, system };
|
|
166
|
+
},
|
|
167
|
+
async getAvatar(name) {
|
|
168
|
+
const system = detectSystem(name);
|
|
169
|
+
try {
|
|
170
|
+
if (system === "ens") return await ensAvatar(client, name);
|
|
171
|
+
if (system === "gns") return await gnsText(client, gnsContract, name, "avatar");
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
},
|
|
177
|
+
async getText(name, key) {
|
|
178
|
+
const system = detectSystem(name);
|
|
179
|
+
try {
|
|
180
|
+
if (system === "ens") return await ensText(client, name, key);
|
|
181
|
+
if (system === "gns") return await gnsText(client, gnsContract, name, key);
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
exports.DEFAULT_GNS_CONTRACT = DEFAULT_GNS_CONTRACT;
|
|
191
|
+
exports.createEthereumNames = createEthereumNames;
|
|
192
|
+
exports.detectSystem = detectSystem;
|
|
193
|
+
//# sourceMappingURL=index.cjs.map
|
|
194
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/ens.ts","../src/gns.ts","../src/client.ts"],"names":["normalize","getEnsAddress","getEnsName","getEnsAvatar","getEnsText","GNS_CONTRACT","readContract","gnsAbi","normalizeName","isAddressEqual","zeroAddress","getAddress","createPublicClient","mainnet","http","isAddress","address","system"],"mappings":"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","file":"index.cjs","sourcesContent":["import { normalize } from 'viem/ens'\nimport type { NameSystem } from './types.js'\n\n/**\n * Detect which name system an input belongs to, purely from its shape — no\n * network calls.\n *\n * - `*.gwei` and bare labels (no dot) → `gns`\n * - any other dotted name (`*.eth`, `*.box`, …) → `ens`\n * - empty input → `null`\n */\nexport function detectSystem(input: string): NameSystem | null {\n const value = input.trim().toLowerCase()\n if (!value) return null\n if (value.endsWith('.gwei')) return 'gns'\n if (value.includes('.')) return 'ens'\n // A bare label is treated as a `.gwei` name, matching @donnoh/gns-utils.\n return 'gns'\n}\n\n/** Normalize an ENS name (ENSIP-15), returning `null` if it is invalid. */\nexport function safeNormalizeEns(name: string): string | null {\n try {\n return normalize(name)\n } catch {\n return null\n }\n}\n","import type { Address, PublicClient } from 'viem'\nimport { getEnsAddress, getEnsAvatar, getEnsName, getEnsText } from 'viem/actions'\nimport { safeNormalizeEns } from './utils.js'\n\n/** Resolve an ENS name to an address via viem's universal resolver. */\nexport async function ensResolve(client: PublicClient, name: string): Promise<Address | null> {\n const normalized = safeNormalizeEns(name)\n if (!normalized) return null\n return getEnsAddress(client, { name: normalized })\n}\n\n/** Reverse resolve an address to its primary ENS name. */\nexport function ensReverse(client: PublicClient, address: Address): Promise<string | null> {\n return getEnsName(client, { address })\n}\n\n/** Read the ENS avatar record for a name. */\nexport async function ensAvatar(client: PublicClient, name: string): Promise<string | null> {\n const normalized = safeNormalizeEns(name)\n if (!normalized) return null\n return getEnsAvatar(client, { name: normalized })\n}\n\n/** Read an arbitrary ENS text record for a name. */\nexport async function ensText(\n client: PublicClient,\n name: string,\n key: string,\n): Promise<string | null> {\n const normalized = safeNormalizeEns(name)\n if (!normalized) return null\n return getEnsText(client, { name: normalized, key })\n}\n","import { GNS_CONTRACT, gnsAbi, normalizeName } from '@donnoh/gns-utils'\nimport { type Address, getAddress, isAddressEqual, zeroAddress } from 'viem'\nimport type { PublicClient } from 'viem'\nimport { readContract } from 'viem/actions'\n\n/** The canonical GNS contract (same address on mainnet and Sepolia). */\nexport const DEFAULT_GNS_CONTRACT = GNS_CONTRACT as Address\n\n/** Compute the on-chain token id for a `.gwei` name. */\nfunction tokenId(client: PublicClient, contract: Address, name: string): Promise<bigint> {\n return readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'computeId',\n args: [normalizeName(name)],\n }) as Promise<bigint>\n}\n\n/** Resolve a `.gwei` name to an address. */\nexport async function gnsResolve(\n client: PublicClient,\n contract: Address,\n name: string,\n): Promise<Address | null> {\n const id = await tokenId(client, contract, name)\n if (id === 0n) return null\n const addr = (await readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'resolve',\n args: [id],\n })) as Address\n if (!addr || isAddressEqual(addr, zeroAddress)) return null\n return getAddress(addr)\n}\n\n/** Reverse resolve an address to its primary `.gwei` name. */\nexport async function gnsReverse(\n client: PublicClient,\n contract: Address,\n address: Address,\n): Promise<string | null> {\n const name = (await readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'reverseResolve',\n args: [address],\n })) as string\n return name || null\n}\n\n/** Read a text record for a `.gwei` name. */\nexport async function gnsText(\n client: PublicClient,\n contract: Address,\n name: string,\n key: string,\n): Promise<string | null> {\n const id = await tokenId(client, contract, name)\n if (id === 0n) return null\n const value = (await readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'text',\n args: [id, key],\n })) as string\n return value || null\n}\n","import { normalizeName } from '@donnoh/gns-utils'\nimport { http, type Address, createPublicClient, getAddress, isAddress, isAddressEqual } from 'viem'\nimport { mainnet } from 'viem/chains'\nimport { ensAvatar, ensResolve, ensReverse, ensText } from './ens.js'\nimport { DEFAULT_GNS_CONTRACT, gnsResolve, gnsReverse, gnsText } from './gns.js'\nimport type {\n EthereumNames,\n EthereumNamesConfig,\n NameSystem,\n ResolvedName,\n ReverseNames,\n} from './types.js'\nimport { detectSystem, safeNormalizeEns } from './utils.js'\n\n/**\n * Create a unified ENS + GNS name client.\n *\n * @example\n * ```ts\n * import { createEthereumNames } from '@1001-digital/ethereum-names'\n *\n * const names = createEthereumNames()\n *\n * await names.resolve('vitalik.eth') // ENS → 0x...\n * await names.resolve('alice.gwei') // GNS → 0x...\n * await names.reverse('0xd8dA...') // → 'vitalik.eth' | 'alice.gwei' | null\n * ```\n *\n * @example\n * ```ts\n * // Bring your own viem client\n * import { createPublicClient, http } from 'viem'\n * import { mainnet } from 'viem/chains'\n *\n * const client = createPublicClient({ chain: mainnet, transport: http() })\n * const names = createEthereumNames({ client })\n * ```\n */\nexport function createEthereumNames(config: EthereumNamesConfig = {}): EthereumNames {\n const client =\n config.client ??\n createPublicClient({\n chain: config.chain ?? mainnet,\n transport: http(config.rpcUrl),\n })\n\n const gnsContract = config.gnsContract ?? DEFAULT_GNS_CONTRACT\n const reversePriority: NameSystem[] = config.reversePriority ?? ['ens', 'gns']\n const verify = config.verify ?? true\n\n /** Canonical form of a name for a given system. */\n function canonical(name: string, system: NameSystem | null): string | null {\n if (system === 'gns') return normalizeName(name)\n if (system === 'ens') return safeNormalizeEns(name)\n return null\n }\n\n async function resolveName(name: string, system: NameSystem): Promise<Address | null> {\n try {\n if (system === 'ens') return await ensResolve(client, name)\n return await gnsResolve(client, gnsContract, name)\n } catch {\n return null\n }\n }\n\n async function rawReverse(system: NameSystem, address: Address): Promise<string | null> {\n try {\n if (system === 'ens') return await ensReverse(client, address)\n return await gnsReverse(client, gnsContract, address)\n } catch {\n return null\n }\n }\n\n /** Reverse resolve for a single system, forward-verifying the result when enabled. */\n async function reverseFor(system: NameSystem, address: Address): Promise<string | null> {\n const name = await rawReverse(system, address)\n if (!name) return null\n if (!verify) return name\n const forward = await resolveName(name, system)\n return forward && isAddressEqual(forward, address) ? name : null\n }\n\n return {\n client,\n\n system(name) {\n return detectSystem(name)\n },\n\n async resolve(nameOrAddress) {\n if (!nameOrAddress) return null\n if (isAddress(nameOrAddress)) return getAddress(nameOrAddress)\n const system = detectSystem(nameOrAddress)\n if (!system) return null\n return resolveName(nameOrAddress, system)\n },\n\n async reverse(address) {\n if (!isAddress(address)) return null\n const checksummed = getAddress(address)\n for (const system of reversePriority) {\n const name = await reverseFor(system, checksummed)\n if (name) return name\n }\n return null\n },\n\n async reverseAll(address): Promise<ReverseNames> {\n if (!isAddress(address)) return { ens: null, gns: null }\n const checksummed = getAddress(address)\n const [ens, gns] = await Promise.all([\n reverseFor('ens', checksummed),\n reverseFor('gns', checksummed),\n ])\n return { ens, gns }\n },\n\n async lookup(input): Promise<ResolvedName> {\n if (isAddress(input)) {\n const address = getAddress(input)\n for (const system of reversePriority) {\n const name = await reverseFor(system, address)\n if (name) return { input, name, address, system }\n }\n return { input, name: null, address, system: null }\n }\n\n const system = detectSystem(input)\n if (!system) return { input, name: null, address: null, system: null }\n const address = await resolveName(input, system)\n return { input, name: canonical(input, system), address, system }\n },\n\n async getAvatar(name) {\n const system = detectSystem(name)\n try {\n if (system === 'ens') return await ensAvatar(client, name)\n if (system === 'gns') return await gnsText(client, gnsContract, name, 'avatar')\n } catch {\n return null\n }\n return null\n },\n\n async getText(name, key) {\n const system = detectSystem(name)\n try {\n if (system === 'ens') return await ensText(client, name, key)\n if (system === 'gns') return await gnsText(client, gnsContract, name, key)\n } catch {\n return null\n }\n return null\n },\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { PublicClient, Address, Chain } from 'viem';
|
|
2
|
+
|
|
3
|
+
/** The name systems this library understands. */
|
|
4
|
+
type NameSystem = 'ens' | 'gns';
|
|
5
|
+
/** Every primary name found for an address, keyed by system. */
|
|
6
|
+
interface ReverseNames {
|
|
7
|
+
/** Primary ENS name, or `null` if none is set (or it failed verification). */
|
|
8
|
+
ens: string | null;
|
|
9
|
+
/** Primary GNS (`.gwei`) name, or `null` if none is set (or it failed verification). */
|
|
10
|
+
gns: string | null;
|
|
11
|
+
}
|
|
12
|
+
/** Rich result describing a resolved name or address. */
|
|
13
|
+
interface ResolvedName {
|
|
14
|
+
/** The original input, untouched. */
|
|
15
|
+
input: string;
|
|
16
|
+
/** The canonical name, when known (e.g. `vitalik.eth`, `alice.gwei`). */
|
|
17
|
+
name: string | null;
|
|
18
|
+
/** The resolved address, when known (checksummed). */
|
|
19
|
+
address: Address | null;
|
|
20
|
+
/** Which system produced the result, or `null` if nothing resolved. */
|
|
21
|
+
system: NameSystem | null;
|
|
22
|
+
}
|
|
23
|
+
interface EthereumNamesConfig {
|
|
24
|
+
/**
|
|
25
|
+
* A viem `PublicClient` to read from. The client's chain must have ENS
|
|
26
|
+
* contracts configured (e.g. `mainnet`) for ENS resolution to work.
|
|
27
|
+
* If omitted, a mainnet client is created from `rpcUrl`/`chain`.
|
|
28
|
+
*/
|
|
29
|
+
client?: PublicClient;
|
|
30
|
+
/** RPC endpoint used when no `client` is supplied. Defaults to viem's public mainnet RPC. */
|
|
31
|
+
rpcUrl?: string;
|
|
32
|
+
/** Chain used when no `client` is supplied. Defaults to `mainnet`. */
|
|
33
|
+
chain?: Chain;
|
|
34
|
+
/** Override the GNS contract address. Defaults to the canonical deployment. */
|
|
35
|
+
gnsContract?: Address;
|
|
36
|
+
/** Order in which reverse lookups try each system. Defaults to `['ens', 'gns']`. */
|
|
37
|
+
reversePriority?: NameSystem[];
|
|
38
|
+
/**
|
|
39
|
+
* Forward-verify reverse lookups: after reading an address's primary name,
|
|
40
|
+
* resolve that name back and confirm it points to the same address before
|
|
41
|
+
* trusting it. Guards against spoofed reverse records (notably for ENS, whose
|
|
42
|
+
* reverse records are not self-validating). Defaults to `true`.
|
|
43
|
+
*/
|
|
44
|
+
verify?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface EthereumNames {
|
|
47
|
+
/** The underlying viem client. */
|
|
48
|
+
readonly client: PublicClient;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a name to an address. Accepts ENS names (`vitalik.eth`), GNS names
|
|
51
|
+
* (`alice.gwei` or the bare label `alice`), or an address (returned checksummed).
|
|
52
|
+
* Returns `null` when nothing resolves.
|
|
53
|
+
*/
|
|
54
|
+
resolve(nameOrAddress: string): Promise<Address | null>;
|
|
55
|
+
/** Reverse resolve an address to its primary name, trying each system in priority order. */
|
|
56
|
+
reverse(address: string): Promise<string | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Reverse resolve an address across *both* systems, returning every primary
|
|
59
|
+
* name found. Use this when an address may have both an ENS and a GNS name.
|
|
60
|
+
*/
|
|
61
|
+
reverseAll(address: string): Promise<ReverseNames>;
|
|
62
|
+
/** Resolve or reverse-resolve `input`, returning a rich {@link ResolvedName}. */
|
|
63
|
+
lookup(input: string): Promise<ResolvedName>;
|
|
64
|
+
/** Read the `avatar` for a name (ENS avatar record, or the GNS `avatar` text record). */
|
|
65
|
+
getAvatar(name: string): Promise<string | null>;
|
|
66
|
+
/** Read an arbitrary text record for a name. */
|
|
67
|
+
getText(name: string, key: string): Promise<string | null>;
|
|
68
|
+
/** Detect which system a name belongs to, without any network call. */
|
|
69
|
+
system(name: string): NameSystem | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a unified ENS + GNS name client.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { createEthereumNames } from '@1001-digital/ethereum-names'
|
|
78
|
+
*
|
|
79
|
+
* const names = createEthereumNames()
|
|
80
|
+
*
|
|
81
|
+
* await names.resolve('vitalik.eth') // ENS → 0x...
|
|
82
|
+
* await names.resolve('alice.gwei') // GNS → 0x...
|
|
83
|
+
* await names.reverse('0xd8dA...') // → 'vitalik.eth' | 'alice.gwei' | null
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* // Bring your own viem client
|
|
89
|
+
* import { createPublicClient, http } from 'viem'
|
|
90
|
+
* import { mainnet } from 'viem/chains'
|
|
91
|
+
*
|
|
92
|
+
* const client = createPublicClient({ chain: mainnet, transport: http() })
|
|
93
|
+
* const names = createEthereumNames({ client })
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare function createEthereumNames(config?: EthereumNamesConfig): EthereumNames;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Detect which name system an input belongs to, purely from its shape — no
|
|
100
|
+
* network calls.
|
|
101
|
+
*
|
|
102
|
+
* - `*.gwei` and bare labels (no dot) → `gns`
|
|
103
|
+
* - any other dotted name (`*.eth`, `*.box`, …) → `ens`
|
|
104
|
+
* - empty input → `null`
|
|
105
|
+
*/
|
|
106
|
+
declare function detectSystem(input: string): NameSystem | null;
|
|
107
|
+
|
|
108
|
+
/** The canonical GNS contract (same address on mainnet and Sepolia). */
|
|
109
|
+
declare const DEFAULT_GNS_CONTRACT: Address;
|
|
110
|
+
|
|
111
|
+
export { DEFAULT_GNS_CONTRACT, type EthereumNames, type EthereumNamesConfig, type NameSystem, type ResolvedName, type ReverseNames, createEthereumNames, detectSystem };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { PublicClient, Address, Chain } from 'viem';
|
|
2
|
+
|
|
3
|
+
/** The name systems this library understands. */
|
|
4
|
+
type NameSystem = 'ens' | 'gns';
|
|
5
|
+
/** Every primary name found for an address, keyed by system. */
|
|
6
|
+
interface ReverseNames {
|
|
7
|
+
/** Primary ENS name, or `null` if none is set (or it failed verification). */
|
|
8
|
+
ens: string | null;
|
|
9
|
+
/** Primary GNS (`.gwei`) name, or `null` if none is set (or it failed verification). */
|
|
10
|
+
gns: string | null;
|
|
11
|
+
}
|
|
12
|
+
/** Rich result describing a resolved name or address. */
|
|
13
|
+
interface ResolvedName {
|
|
14
|
+
/** The original input, untouched. */
|
|
15
|
+
input: string;
|
|
16
|
+
/** The canonical name, when known (e.g. `vitalik.eth`, `alice.gwei`). */
|
|
17
|
+
name: string | null;
|
|
18
|
+
/** The resolved address, when known (checksummed). */
|
|
19
|
+
address: Address | null;
|
|
20
|
+
/** Which system produced the result, or `null` if nothing resolved. */
|
|
21
|
+
system: NameSystem | null;
|
|
22
|
+
}
|
|
23
|
+
interface EthereumNamesConfig {
|
|
24
|
+
/**
|
|
25
|
+
* A viem `PublicClient` to read from. The client's chain must have ENS
|
|
26
|
+
* contracts configured (e.g. `mainnet`) for ENS resolution to work.
|
|
27
|
+
* If omitted, a mainnet client is created from `rpcUrl`/`chain`.
|
|
28
|
+
*/
|
|
29
|
+
client?: PublicClient;
|
|
30
|
+
/** RPC endpoint used when no `client` is supplied. Defaults to viem's public mainnet RPC. */
|
|
31
|
+
rpcUrl?: string;
|
|
32
|
+
/** Chain used when no `client` is supplied. Defaults to `mainnet`. */
|
|
33
|
+
chain?: Chain;
|
|
34
|
+
/** Override the GNS contract address. Defaults to the canonical deployment. */
|
|
35
|
+
gnsContract?: Address;
|
|
36
|
+
/** Order in which reverse lookups try each system. Defaults to `['ens', 'gns']`. */
|
|
37
|
+
reversePriority?: NameSystem[];
|
|
38
|
+
/**
|
|
39
|
+
* Forward-verify reverse lookups: after reading an address's primary name,
|
|
40
|
+
* resolve that name back and confirm it points to the same address before
|
|
41
|
+
* trusting it. Guards against spoofed reverse records (notably for ENS, whose
|
|
42
|
+
* reverse records are not self-validating). Defaults to `true`.
|
|
43
|
+
*/
|
|
44
|
+
verify?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface EthereumNames {
|
|
47
|
+
/** The underlying viem client. */
|
|
48
|
+
readonly client: PublicClient;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a name to an address. Accepts ENS names (`vitalik.eth`), GNS names
|
|
51
|
+
* (`alice.gwei` or the bare label `alice`), or an address (returned checksummed).
|
|
52
|
+
* Returns `null` when nothing resolves.
|
|
53
|
+
*/
|
|
54
|
+
resolve(nameOrAddress: string): Promise<Address | null>;
|
|
55
|
+
/** Reverse resolve an address to its primary name, trying each system in priority order. */
|
|
56
|
+
reverse(address: string): Promise<string | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Reverse resolve an address across *both* systems, returning every primary
|
|
59
|
+
* name found. Use this when an address may have both an ENS and a GNS name.
|
|
60
|
+
*/
|
|
61
|
+
reverseAll(address: string): Promise<ReverseNames>;
|
|
62
|
+
/** Resolve or reverse-resolve `input`, returning a rich {@link ResolvedName}. */
|
|
63
|
+
lookup(input: string): Promise<ResolvedName>;
|
|
64
|
+
/** Read the `avatar` for a name (ENS avatar record, or the GNS `avatar` text record). */
|
|
65
|
+
getAvatar(name: string): Promise<string | null>;
|
|
66
|
+
/** Read an arbitrary text record for a name. */
|
|
67
|
+
getText(name: string, key: string): Promise<string | null>;
|
|
68
|
+
/** Detect which system a name belongs to, without any network call. */
|
|
69
|
+
system(name: string): NameSystem | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a unified ENS + GNS name client.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { createEthereumNames } from '@1001-digital/ethereum-names'
|
|
78
|
+
*
|
|
79
|
+
* const names = createEthereumNames()
|
|
80
|
+
*
|
|
81
|
+
* await names.resolve('vitalik.eth') // ENS → 0x...
|
|
82
|
+
* await names.resolve('alice.gwei') // GNS → 0x...
|
|
83
|
+
* await names.reverse('0xd8dA...') // → 'vitalik.eth' | 'alice.gwei' | null
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* // Bring your own viem client
|
|
89
|
+
* import { createPublicClient, http } from 'viem'
|
|
90
|
+
* import { mainnet } from 'viem/chains'
|
|
91
|
+
*
|
|
92
|
+
* const client = createPublicClient({ chain: mainnet, transport: http() })
|
|
93
|
+
* const names = createEthereumNames({ client })
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare function createEthereumNames(config?: EthereumNamesConfig): EthereumNames;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Detect which name system an input belongs to, purely from its shape — no
|
|
100
|
+
* network calls.
|
|
101
|
+
*
|
|
102
|
+
* - `*.gwei` and bare labels (no dot) → `gns`
|
|
103
|
+
* - any other dotted name (`*.eth`, `*.box`, …) → `ens`
|
|
104
|
+
* - empty input → `null`
|
|
105
|
+
*/
|
|
106
|
+
declare function detectSystem(input: string): NameSystem | null;
|
|
107
|
+
|
|
108
|
+
/** The canonical GNS contract (same address on mainnet and Sepolia). */
|
|
109
|
+
declare const DEFAULT_GNS_CONTRACT: Address;
|
|
110
|
+
|
|
111
|
+
export { DEFAULT_GNS_CONTRACT, type EthereumNames, type EthereumNamesConfig, type NameSystem, type ResolvedName, type ReverseNames, createEthereumNames, detectSystem };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { GNS_CONTRACT, normalizeName, gnsAbi } from '@donnoh/gns-utils';
|
|
2
|
+
import { createPublicClient, http, isAddress, getAddress, isAddressEqual, zeroAddress } from 'viem';
|
|
3
|
+
import { mainnet } from 'viem/chains';
|
|
4
|
+
import { getEnsText, readContract, getEnsAvatar, getEnsAddress, getEnsName } from 'viem/actions';
|
|
5
|
+
import { normalize } from 'viem/ens';
|
|
6
|
+
|
|
7
|
+
// src/client.ts
|
|
8
|
+
function detectSystem(input) {
|
|
9
|
+
const value = input.trim().toLowerCase();
|
|
10
|
+
if (!value) return null;
|
|
11
|
+
if (value.endsWith(".gwei")) return "gns";
|
|
12
|
+
if (value.includes(".")) return "ens";
|
|
13
|
+
return "gns";
|
|
14
|
+
}
|
|
15
|
+
function safeNormalizeEns(name) {
|
|
16
|
+
try {
|
|
17
|
+
return normalize(name);
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/ens.ts
|
|
24
|
+
async function ensResolve(client, name) {
|
|
25
|
+
const normalized = safeNormalizeEns(name);
|
|
26
|
+
if (!normalized) return null;
|
|
27
|
+
return getEnsAddress(client, { name: normalized });
|
|
28
|
+
}
|
|
29
|
+
function ensReverse(client, address) {
|
|
30
|
+
return getEnsName(client, { address });
|
|
31
|
+
}
|
|
32
|
+
async function ensAvatar(client, name) {
|
|
33
|
+
const normalized = safeNormalizeEns(name);
|
|
34
|
+
if (!normalized) return null;
|
|
35
|
+
return getEnsAvatar(client, { name: normalized });
|
|
36
|
+
}
|
|
37
|
+
async function ensText(client, name, key) {
|
|
38
|
+
const normalized = safeNormalizeEns(name);
|
|
39
|
+
if (!normalized) return null;
|
|
40
|
+
return getEnsText(client, { name: normalized, key });
|
|
41
|
+
}
|
|
42
|
+
var DEFAULT_GNS_CONTRACT = GNS_CONTRACT;
|
|
43
|
+
function tokenId(client, contract, name) {
|
|
44
|
+
return readContract(client, {
|
|
45
|
+
address: contract,
|
|
46
|
+
abi: gnsAbi,
|
|
47
|
+
functionName: "computeId",
|
|
48
|
+
args: [normalizeName(name)]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async function gnsResolve(client, contract, name) {
|
|
52
|
+
const id = await tokenId(client, contract, name);
|
|
53
|
+
if (id === 0n) return null;
|
|
54
|
+
const addr = await readContract(client, {
|
|
55
|
+
address: contract,
|
|
56
|
+
abi: gnsAbi,
|
|
57
|
+
functionName: "resolve",
|
|
58
|
+
args: [id]
|
|
59
|
+
});
|
|
60
|
+
if (!addr || isAddressEqual(addr, zeroAddress)) return null;
|
|
61
|
+
return getAddress(addr);
|
|
62
|
+
}
|
|
63
|
+
async function gnsReverse(client, contract, address) {
|
|
64
|
+
const name = await readContract(client, {
|
|
65
|
+
address: contract,
|
|
66
|
+
abi: gnsAbi,
|
|
67
|
+
functionName: "reverseResolve",
|
|
68
|
+
args: [address]
|
|
69
|
+
});
|
|
70
|
+
return name || null;
|
|
71
|
+
}
|
|
72
|
+
async function gnsText(client, contract, name, key) {
|
|
73
|
+
const id = await tokenId(client, contract, name);
|
|
74
|
+
if (id === 0n) return null;
|
|
75
|
+
const value = await readContract(client, {
|
|
76
|
+
address: contract,
|
|
77
|
+
abi: gnsAbi,
|
|
78
|
+
functionName: "text",
|
|
79
|
+
args: [id, key]
|
|
80
|
+
});
|
|
81
|
+
return value || null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/client.ts
|
|
85
|
+
function createEthereumNames(config = {}) {
|
|
86
|
+
const client = config.client ?? createPublicClient({
|
|
87
|
+
chain: config.chain ?? mainnet,
|
|
88
|
+
transport: http(config.rpcUrl)
|
|
89
|
+
});
|
|
90
|
+
const gnsContract = config.gnsContract ?? DEFAULT_GNS_CONTRACT;
|
|
91
|
+
const reversePriority = config.reversePriority ?? ["ens", "gns"];
|
|
92
|
+
const verify = config.verify ?? true;
|
|
93
|
+
function canonical(name, system) {
|
|
94
|
+
if (system === "gns") return normalizeName(name);
|
|
95
|
+
if (system === "ens") return safeNormalizeEns(name);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
async function resolveName(name, system) {
|
|
99
|
+
try {
|
|
100
|
+
if (system === "ens") return await ensResolve(client, name);
|
|
101
|
+
return await gnsResolve(client, gnsContract, name);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function rawReverse(system, address) {
|
|
107
|
+
try {
|
|
108
|
+
if (system === "ens") return await ensReverse(client, address);
|
|
109
|
+
return await gnsReverse(client, gnsContract, address);
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function reverseFor(system, address) {
|
|
115
|
+
const name = await rawReverse(system, address);
|
|
116
|
+
if (!name) return null;
|
|
117
|
+
if (!verify) return name;
|
|
118
|
+
const forward = await resolveName(name, system);
|
|
119
|
+
return forward && isAddressEqual(forward, address) ? name : null;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
client,
|
|
123
|
+
system(name) {
|
|
124
|
+
return detectSystem(name);
|
|
125
|
+
},
|
|
126
|
+
async resolve(nameOrAddress) {
|
|
127
|
+
if (!nameOrAddress) return null;
|
|
128
|
+
if (isAddress(nameOrAddress)) return getAddress(nameOrAddress);
|
|
129
|
+
const system = detectSystem(nameOrAddress);
|
|
130
|
+
if (!system) return null;
|
|
131
|
+
return resolveName(nameOrAddress, system);
|
|
132
|
+
},
|
|
133
|
+
async reverse(address) {
|
|
134
|
+
if (!isAddress(address)) return null;
|
|
135
|
+
const checksummed = getAddress(address);
|
|
136
|
+
for (const system of reversePriority) {
|
|
137
|
+
const name = await reverseFor(system, checksummed);
|
|
138
|
+
if (name) return name;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
},
|
|
142
|
+
async reverseAll(address) {
|
|
143
|
+
if (!isAddress(address)) return { ens: null, gns: null };
|
|
144
|
+
const checksummed = getAddress(address);
|
|
145
|
+
const [ens, gns] = await Promise.all([
|
|
146
|
+
reverseFor("ens", checksummed),
|
|
147
|
+
reverseFor("gns", checksummed)
|
|
148
|
+
]);
|
|
149
|
+
return { ens, gns };
|
|
150
|
+
},
|
|
151
|
+
async lookup(input) {
|
|
152
|
+
if (isAddress(input)) {
|
|
153
|
+
const address2 = getAddress(input);
|
|
154
|
+
for (const system2 of reversePriority) {
|
|
155
|
+
const name = await reverseFor(system2, address2);
|
|
156
|
+
if (name) return { input, name, address: address2, system: system2 };
|
|
157
|
+
}
|
|
158
|
+
return { input, name: null, address: address2, system: null };
|
|
159
|
+
}
|
|
160
|
+
const system = detectSystem(input);
|
|
161
|
+
if (!system) return { input, name: null, address: null, system: null };
|
|
162
|
+
const address = await resolveName(input, system);
|
|
163
|
+
return { input, name: canonical(input, system), address, system };
|
|
164
|
+
},
|
|
165
|
+
async getAvatar(name) {
|
|
166
|
+
const system = detectSystem(name);
|
|
167
|
+
try {
|
|
168
|
+
if (system === "ens") return await ensAvatar(client, name);
|
|
169
|
+
if (system === "gns") return await gnsText(client, gnsContract, name, "avatar");
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
},
|
|
175
|
+
async getText(name, key) {
|
|
176
|
+
const system = detectSystem(name);
|
|
177
|
+
try {
|
|
178
|
+
if (system === "ens") return await ensText(client, name, key);
|
|
179
|
+
if (system === "gns") return await gnsText(client, gnsContract, name, key);
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { DEFAULT_GNS_CONTRACT, createEthereumNames, detectSystem };
|
|
189
|
+
//# sourceMappingURL=index.js.map
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/ens.ts","../src/gns.ts","../src/client.ts"],"names":["normalizeName","isAddressEqual","getAddress","address","system"],"mappings":"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","file":"index.js","sourcesContent":["import { normalize } from 'viem/ens'\nimport type { NameSystem } from './types.js'\n\n/**\n * Detect which name system an input belongs to, purely from its shape — no\n * network calls.\n *\n * - `*.gwei` and bare labels (no dot) → `gns`\n * - any other dotted name (`*.eth`, `*.box`, …) → `ens`\n * - empty input → `null`\n */\nexport function detectSystem(input: string): NameSystem | null {\n const value = input.trim().toLowerCase()\n if (!value) return null\n if (value.endsWith('.gwei')) return 'gns'\n if (value.includes('.')) return 'ens'\n // A bare label is treated as a `.gwei` name, matching @donnoh/gns-utils.\n return 'gns'\n}\n\n/** Normalize an ENS name (ENSIP-15), returning `null` if it is invalid. */\nexport function safeNormalizeEns(name: string): string | null {\n try {\n return normalize(name)\n } catch {\n return null\n }\n}\n","import type { Address, PublicClient } from 'viem'\nimport { getEnsAddress, getEnsAvatar, getEnsName, getEnsText } from 'viem/actions'\nimport { safeNormalizeEns } from './utils.js'\n\n/** Resolve an ENS name to an address via viem's universal resolver. */\nexport async function ensResolve(client: PublicClient, name: string): Promise<Address | null> {\n const normalized = safeNormalizeEns(name)\n if (!normalized) return null\n return getEnsAddress(client, { name: normalized })\n}\n\n/** Reverse resolve an address to its primary ENS name. */\nexport function ensReverse(client: PublicClient, address: Address): Promise<string | null> {\n return getEnsName(client, { address })\n}\n\n/** Read the ENS avatar record for a name. */\nexport async function ensAvatar(client: PublicClient, name: string): Promise<string | null> {\n const normalized = safeNormalizeEns(name)\n if (!normalized) return null\n return getEnsAvatar(client, { name: normalized })\n}\n\n/** Read an arbitrary ENS text record for a name. */\nexport async function ensText(\n client: PublicClient,\n name: string,\n key: string,\n): Promise<string | null> {\n const normalized = safeNormalizeEns(name)\n if (!normalized) return null\n return getEnsText(client, { name: normalized, key })\n}\n","import { GNS_CONTRACT, gnsAbi, normalizeName } from '@donnoh/gns-utils'\nimport { type Address, getAddress, isAddressEqual, zeroAddress } from 'viem'\nimport type { PublicClient } from 'viem'\nimport { readContract } from 'viem/actions'\n\n/** The canonical GNS contract (same address on mainnet and Sepolia). */\nexport const DEFAULT_GNS_CONTRACT = GNS_CONTRACT as Address\n\n/** Compute the on-chain token id for a `.gwei` name. */\nfunction tokenId(client: PublicClient, contract: Address, name: string): Promise<bigint> {\n return readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'computeId',\n args: [normalizeName(name)],\n }) as Promise<bigint>\n}\n\n/** Resolve a `.gwei` name to an address. */\nexport async function gnsResolve(\n client: PublicClient,\n contract: Address,\n name: string,\n): Promise<Address | null> {\n const id = await tokenId(client, contract, name)\n if (id === 0n) return null\n const addr = (await readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'resolve',\n args: [id],\n })) as Address\n if (!addr || isAddressEqual(addr, zeroAddress)) return null\n return getAddress(addr)\n}\n\n/** Reverse resolve an address to its primary `.gwei` name. */\nexport async function gnsReverse(\n client: PublicClient,\n contract: Address,\n address: Address,\n): Promise<string | null> {\n const name = (await readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'reverseResolve',\n args: [address],\n })) as string\n return name || null\n}\n\n/** Read a text record for a `.gwei` name. */\nexport async function gnsText(\n client: PublicClient,\n contract: Address,\n name: string,\n key: string,\n): Promise<string | null> {\n const id = await tokenId(client, contract, name)\n if (id === 0n) return null\n const value = (await readContract(client, {\n address: contract,\n abi: gnsAbi,\n functionName: 'text',\n args: [id, key],\n })) as string\n return value || null\n}\n","import { normalizeName } from '@donnoh/gns-utils'\nimport { http, type Address, createPublicClient, getAddress, isAddress, isAddressEqual } from 'viem'\nimport { mainnet } from 'viem/chains'\nimport { ensAvatar, ensResolve, ensReverse, ensText } from './ens.js'\nimport { DEFAULT_GNS_CONTRACT, gnsResolve, gnsReverse, gnsText } from './gns.js'\nimport type {\n EthereumNames,\n EthereumNamesConfig,\n NameSystem,\n ResolvedName,\n ReverseNames,\n} from './types.js'\nimport { detectSystem, safeNormalizeEns } from './utils.js'\n\n/**\n * Create a unified ENS + GNS name client.\n *\n * @example\n * ```ts\n * import { createEthereumNames } from '@1001-digital/ethereum-names'\n *\n * const names = createEthereumNames()\n *\n * await names.resolve('vitalik.eth') // ENS → 0x...\n * await names.resolve('alice.gwei') // GNS → 0x...\n * await names.reverse('0xd8dA...') // → 'vitalik.eth' | 'alice.gwei' | null\n * ```\n *\n * @example\n * ```ts\n * // Bring your own viem client\n * import { createPublicClient, http } from 'viem'\n * import { mainnet } from 'viem/chains'\n *\n * const client = createPublicClient({ chain: mainnet, transport: http() })\n * const names = createEthereumNames({ client })\n * ```\n */\nexport function createEthereumNames(config: EthereumNamesConfig = {}): EthereumNames {\n const client =\n config.client ??\n createPublicClient({\n chain: config.chain ?? mainnet,\n transport: http(config.rpcUrl),\n })\n\n const gnsContract = config.gnsContract ?? DEFAULT_GNS_CONTRACT\n const reversePriority: NameSystem[] = config.reversePriority ?? ['ens', 'gns']\n const verify = config.verify ?? true\n\n /** Canonical form of a name for a given system. */\n function canonical(name: string, system: NameSystem | null): string | null {\n if (system === 'gns') return normalizeName(name)\n if (system === 'ens') return safeNormalizeEns(name)\n return null\n }\n\n async function resolveName(name: string, system: NameSystem): Promise<Address | null> {\n try {\n if (system === 'ens') return await ensResolve(client, name)\n return await gnsResolve(client, gnsContract, name)\n } catch {\n return null\n }\n }\n\n async function rawReverse(system: NameSystem, address: Address): Promise<string | null> {\n try {\n if (system === 'ens') return await ensReverse(client, address)\n return await gnsReverse(client, gnsContract, address)\n } catch {\n return null\n }\n }\n\n /** Reverse resolve for a single system, forward-verifying the result when enabled. */\n async function reverseFor(system: NameSystem, address: Address): Promise<string | null> {\n const name = await rawReverse(system, address)\n if (!name) return null\n if (!verify) return name\n const forward = await resolveName(name, system)\n return forward && isAddressEqual(forward, address) ? name : null\n }\n\n return {\n client,\n\n system(name) {\n return detectSystem(name)\n },\n\n async resolve(nameOrAddress) {\n if (!nameOrAddress) return null\n if (isAddress(nameOrAddress)) return getAddress(nameOrAddress)\n const system = detectSystem(nameOrAddress)\n if (!system) return null\n return resolveName(nameOrAddress, system)\n },\n\n async reverse(address) {\n if (!isAddress(address)) return null\n const checksummed = getAddress(address)\n for (const system of reversePriority) {\n const name = await reverseFor(system, checksummed)\n if (name) return name\n }\n return null\n },\n\n async reverseAll(address): Promise<ReverseNames> {\n if (!isAddress(address)) return { ens: null, gns: null }\n const checksummed = getAddress(address)\n const [ens, gns] = await Promise.all([\n reverseFor('ens', checksummed),\n reverseFor('gns', checksummed),\n ])\n return { ens, gns }\n },\n\n async lookup(input): Promise<ResolvedName> {\n if (isAddress(input)) {\n const address = getAddress(input)\n for (const system of reversePriority) {\n const name = await reverseFor(system, address)\n if (name) return { input, name, address, system }\n }\n return { input, name: null, address, system: null }\n }\n\n const system = detectSystem(input)\n if (!system) return { input, name: null, address: null, system: null }\n const address = await resolveName(input, system)\n return { input, name: canonical(input, system), address, system }\n },\n\n async getAvatar(name) {\n const system = detectSystem(name)\n try {\n if (system === 'ens') return await ensAvatar(client, name)\n if (system === 'gns') return await gnsText(client, gnsContract, name, 'avatar')\n } catch {\n return null\n }\n return null\n },\n\n async getText(name, key) {\n const system = detectSystem(name)\n try {\n if (system === 'ens') return await ensText(client, name, key)\n if (system === 'gns') return await gnsText(client, gnsContract, name, key)\n } catch {\n return null\n }\n return null\n },\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@1001-digital/ethereum-names",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One clean API to resolve Ethereum names across ENS and the Gwei Name Service (GNS), powered by viem.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.cjs",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.cts",
|
|
20
|
+
"default": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"keywords": [
|
|
29
|
+
"ethereum",
|
|
30
|
+
"ens",
|
|
31
|
+
"gns",
|
|
32
|
+
"gwei",
|
|
33
|
+
"gwei-name-service",
|
|
34
|
+
"name-service",
|
|
35
|
+
"resolver",
|
|
36
|
+
"viem",
|
|
37
|
+
"web3"
|
|
38
|
+
],
|
|
39
|
+
"author": "jalil.eth",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/1001-digital/ethereum-names.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/1001-digital/ethereum-names/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/1001-digital/ethereum-names#readme",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@donnoh/gns-utils": "^0.2.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"viem": "^2.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@biomejs/biome": "^1.9.4",
|
|
57
|
+
"@changesets/changelog-github": "^0.5.1",
|
|
58
|
+
"@changesets/cli": "^2.30.0",
|
|
59
|
+
"@types/node": "^22.19.11",
|
|
60
|
+
"tsup": "^8.5.0",
|
|
61
|
+
"tsx": "^4.21.0",
|
|
62
|
+
"typescript": "~5.8.3",
|
|
63
|
+
"viem": "^2.45.3"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsup",
|
|
67
|
+
"dev": "tsup --watch",
|
|
68
|
+
"clean": "rm -rf dist",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"test": "tsx --test src/*.test.ts",
|
|
71
|
+
"lint": "biome check src",
|
|
72
|
+
"format": "biome check --write src",
|
|
73
|
+
"check": "biome check src && tsc --noEmit",
|
|
74
|
+
"changeset": "changeset",
|
|
75
|
+
"version": "changeset version",
|
|
76
|
+
"release": "changeset publish"
|
|
77
|
+
}
|
|
78
|
+
}
|