Skip to content

Commit 31364f6

Browse files
jwahdatehaghclaude
andcommitted
Make bare-label resolution configurable, default ENS
Bare labels (no dot) are ambiguous now that GNS (.gwei) and WNS (.wei) both accept them and can point to different owners. Route them to a configurable `bareLabel` system instead of guessing. - New `bareLabel` option ('ens' | 'gns' | 'wns'), default 'ens' - detectSystem(input, bareLabel?) takes an optional second arg - Behavior change from 0.2.0: bare labels resolved as .gwei; now default to ENS (which has no bare-label namespace, so resolve('alice') is null unless bareLabel is set to 'gns'/'wns' or an explicit suffix is used) - Docs, tests, changeset Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b5111ea commit 31364f6

6 files changed

Lines changed: 55 additions & 14 deletions

File tree

.changeset/bare-label-system.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@1001-digital/ethereum-names": minor
3+
---
4+
5+
Make bare-label resolution configurable and default it to ENS.
6+
7+
Bare labels (no dot, e.g. `alice`) are ambiguous now that both GNS (`.gwei`) and WNS (`.wei`) accept them — and they can point to different owners. Rather than silently guess, `detectSystem` and the client now route bare labels to a configurable `bareLabel` system.
8+
9+
- New `bareLabel` option (`'ens' | 'gns' | 'wns'`), defaulting to `'ens'`. `detectSystem(input, bareLabel?)` takes an optional second argument.
10+
- **Behavior change:** in `0.2.0` a bare label resolved as `.gwei`; it now resolves against ENS by default. Since ENS has no bare-label namespace, `resolve('alice')` returns `null` unless you set `bareLabel: 'gns'` or `'wns'` (or pass an explicit `alice.gwei` / `alice.wei`).

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const names = createEthereumNames()
2727
await names.resolve('vitalik.eth') // ENS → '0xd8dA...' | null
2828
await names.resolve('alice.gwei') // GNS → '0x...' | null
2929
await names.resolve('alice.wei') // WNS → '0x...' | null
30-
await names.resolve('alice') // bare label → treated as alice.gwei
30+
await names.resolve('alice') // bare label → ENS by default (see bareLabel)
3131
await names.resolve('0xd8dA...') // address → returned checksummed
3232

3333
// Reverse: address → primary name (tries ENS, then GNS, then WNS)
@@ -58,10 +58,16 @@ names.system('foo.eth') // 'ens'
5858
| --------------------------- | ------ |
5959
| `*.wei` | WNS |
6060
| `*.gwei` | GNS |
61-
| bare label (no dot) | GNS |
6261
| any other dotted name `*.eth`, `*.box`, … | ENS |
62+
| bare label (no dot) | `bareLabel` option (default ENS) |
6363
| `0x…` address | passed through (checksummed) |
6464

65+
A bare label like `alice` is ambiguous — GNS (`.gwei`) and WNS (`.wei`) both accept bare
66+
labels, and they can point to different owners. Rather than guess, it resolves against the
67+
`bareLabel` system, which defaults to `ens`. Since ENS has no bare-label namespace, bare
68+
labels resolve to `null` by default; set `bareLabel: 'gns'` or `'wns'` to opt a label like
69+
`alice` into that registry.
70+
6571
Reverse lookups try each system in order (ENS first by default) and return the first
6672
match. Configure the order with `reversePriority`, or use `reverseAll` to get the primary
6773
name from **all** systems at once.
@@ -87,6 +93,10 @@ const quick = createEthereumNames({ rpcUrl: 'https://my-rpc' })
8793

8894
// Prefer GNS names on reverse lookups
8995
const gnsFirst = createEthereumNames({ reversePriority: ['gns', 'ens', 'wns'] })
96+
97+
// Treat bare labels (e.g. `alice`) as `.gwei` names
98+
const gwei = createEthereumNames({ bareLabel: 'gns' })
99+
await gwei.resolve('alice') // → resolves alice.gwei
90100
```
91101

92102
| Option | Type | Description |
@@ -96,6 +106,7 @@ const gnsFirst = createEthereumNames({ reversePriority: ['gns', 'ens', 'wns'] })
96106
| `chain` | `Chain` | Chain used when no `client` is given. Defaults to `mainnet`. |
97107
| `gnsContract` | `Address` | Override the GNS contract address. |
98108
| `wnsContract` | `Address` | Override the WNS contract address. |
109+
| `bareLabel` | `'ens' \| 'gns' \| 'wns'` | System a bare label (no dot) resolves against. Defaults to `'ens'`. |
99110
| `reversePriority` | `('ens' \| 'gns' \| 'wns')[]` | Order reverse lookups try each system. Defaults to `['ens', 'gns', 'wns']`. |
100111
| `verify` | `boolean` | Forward-verify reverse lookups before trusting them. Defaults to `true`. |
101112

src/client.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function createEthereumNames(config: EthereumNamesConfig = {}): EthereumN
5353

5454
const reversePriority: NameSystem[] = config.reversePriority ?? ['ens', 'gns', 'wns']
5555
const verify = config.verify ?? true
56+
const bareLabel: NameSystem = config.bareLabel ?? 'ens'
5657

5758
/** GNS and WNS share a registry interface — only the contract and suffix differ. */
5859
const registries = {
@@ -99,13 +100,13 @@ export function createEthereumNames(config: EthereumNamesConfig = {}): EthereumN
99100
client,
100101

101102
system(name) {
102-
return detectSystem(name)
103+
return detectSystem(name, bareLabel)
103104
},
104105

105106
async resolve(nameOrAddress) {
106107
if (!nameOrAddress) return null
107108
if (isAddress(nameOrAddress)) return getAddress(nameOrAddress)
108-
const system = detectSystem(nameOrAddress)
109+
const system = detectSystem(nameOrAddress, bareLabel)
109110
if (!system) return null
110111
return resolveName(nameOrAddress, system)
111112
},
@@ -141,14 +142,14 @@ export function createEthereumNames(config: EthereumNamesConfig = {}): EthereumN
141142
return { input, name: null, address, system: null }
142143
}
143144

144-
const system = detectSystem(input)
145+
const system = detectSystem(input, bareLabel)
145146
if (!system) return { input, name: null, address: null, system: null }
146147
const address = await resolveName(input, system)
147148
return { input, name: canonical(input, system), address, system }
148149
},
149150

150151
async getAvatar(name) {
151-
const system = detectSystem(name)
152+
const system = detectSystem(name, bareLabel)
152153
try {
153154
if (system === 'ens') return await ensAvatar(client, name)
154155
if (system === 'gns' || system === 'wns') {
@@ -162,7 +163,7 @@ export function createEthereumNames(config: EthereumNamesConfig = {}): EthereumN
162163
},
163164

164165
async getText(name, key) {
165-
const system = detectSystem(name)
166+
const system = detectSystem(name, bareLabel)
166167
try {
167168
if (system === 'ens') return await ensText(client, name, key)
168169
if (system === 'gns' || system === 'wns') {

src/ethereum-names.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,26 @@ test('detectSystem routes names to the right system', () => {
1313
assert.equal(detectSystem('ALICE.WEI'), 'wns')
1414
// .gwei must not be mistaken for .wei
1515
assert.equal(detectSystem('gm.gwei'), 'gns')
16-
// bare labels are treated as .gwei
17-
assert.equal(detectSystem('alice'), 'gns')
16+
// bare labels default to ENS, and route to the configured system otherwise
17+
assert.equal(detectSystem('alice'), 'ens')
18+
assert.equal(detectSystem('alice', 'gns'), 'gns')
19+
assert.equal(detectSystem('alice', 'wns'), 'wns')
20+
// suffixed names ignore the bareLabel override
21+
assert.equal(detectSystem('alice.wei', 'gns'), 'wns')
1822
assert.equal(detectSystem(''), null)
1923
assert.equal(detectSystem(' '), null)
2024
})
2125

22-
test('system() mirrors detectSystem without a network call', () => {
26+
test('system() mirrors detectSystem, honoring bareLabel config', () => {
2327
const names = createEthereumNames()
2428
assert.equal(names.system('vitalik.eth'), 'ens')
2529
assert.equal(names.system('alice.gwei'), 'gns')
2630
assert.equal(names.system('alice.wei'), 'wns')
31+
// bare label defaults to ENS
32+
assert.equal(names.system('alice'), 'ens')
33+
// …and follows the bareLabel option
34+
assert.equal(createEthereumNames({ bareLabel: 'gns' }).system('alice'), 'gns')
35+
assert.equal(createEthereumNames({ bareLabel: 'wns' }).system('alice'), 'wns')
2736
})
2837

2938
test('resolve passes addresses through, checksummed', async () => {

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ export interface EthereumNamesConfig {
4040
gnsContract?: Address
4141
/** Override the WNS contract address. Defaults to the canonical deployment. */
4242
wnsContract?: Address
43+
/**
44+
* Which system a bare label (no dot, e.g. `alice`) is resolved against.
45+
* Bare labels are ambiguous — GNS (`.gwei`) and WNS (`.wei`) both accept
46+
* them — so this picks one deterministically instead of guessing an owner.
47+
* Defaults to `'ens'`; note ENS has no bare-label namespace, so bare labels
48+
* resolve to `null` unless you set this to `'gns'` or `'wns'`.
49+
*/
50+
bareLabel?: NameSystem
4351
/** Order in which reverse lookups try each system. Defaults to `['ens', 'gns', 'wns']`. */
4452
reversePriority?: NameSystem[]
4553
/**

src/utils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ import type { NameSystem } from './types.js'
66
* network calls.
77
*
88
* - `*.wei` → `wns`
9-
* - `*.gwei` and bare labels (no dot) → `gns`
9+
* - `*.gwei` → `gns`
1010
* - any other dotted name (`*.eth`, `*.box`, …) → `ens`
11+
* - a bare label (no dot) → `bareLabel` (default `'ens'`), since it is
12+
* ambiguous between the GNS/WNS bare-label namespaces
1113
* - empty input → `null`
1214
*/
13-
export function detectSystem(input: string): NameSystem | null {
15+
export function detectSystem(input: string, bareLabel: NameSystem = 'ens'): NameSystem | null {
1416
const value = input.trim().toLowerCase()
1517
if (!value) return null
1618
if (value.endsWith('.wei')) return 'wns'
1719
if (value.endsWith('.gwei')) return 'gns'
1820
if (value.includes('.')) return 'ens'
19-
// A bare label is treated as a `.gwei` name, matching the GNS convention.
20-
return 'gns'
21+
// A bare label (no dot) is ambiguous across systems; route it per config.
22+
return bareLabel
2123
}
2224

2325
/** Normalize an ENS name (ENSIP-15), returning `null` if it is invalid. */

0 commit comments

Comments
 (0)