I’m a self-taught builder, and this week I built a small thing that made a concept finally click for me: a .night domain profile viewer. You type a name like tomin.night, it resolves on-chain, and it shows you the profile. This is the walkthrough — what a .night name actually is, the one SDK call that resolves it, the two gotchas that cost me time, and how I wrapped it into a tiny dApp.
Repo: https://github.com/tomiin/midnames-profile-viewer
What a .night name actually is
On Midnight, .night domains are managed by Midnames. The important part for a developer: the name registry is a Compact smart contract deployed on-chain. The @midnames/sdk package literally ships the compiled contract (it exports Contract, ledger, witnesses, and a MANAGED_DIR of Compact artifacts).
So “look up a domain” doesn’t mean “call a company’s REST API.” It means read contract state. The domain, its owner, where it points, and its profile fields all live on the chain. That’s the whole appeal: a .night name is a portable, on-chain identity you own.
The one call that does the work
Install the SDK (Node ≥ 22, ESM):
bash
npm install @midnames/sdk
npm pkg set type=module
Resolving a domain is two lines:
ts
import { createDefaultProvider, getDomainProfile } from “@midnames/sdk”;
const provider = createDefaultProvider({ networkId: “mainnet” });
const result = await getDomainProfile(“tomin.night”, { provider });
if (result.success) {
console.dir(result.data, { depth: null });
} else {
console.error(result.error); // e.g. DomainNotFoundError
}
The SDK returns a result object with a success flag — no try/catch gymnastics for the “domain doesn’t exist” case, which is nice.
What comes back
Here’s the real shape of result.data for my domain (trimmed):
js
{
fullDomain: ‘tomin.night’,
info: {
id: 142n,
owner: ‘0e3240f66410…a9f36a6694’,
ownerAddress: ‘mn_addr1vglt…w63s8cp7sd’,
target: {
type: ‘shielded’,
address: ‘mn_shield-cpk1…nvxdys3gwdtn’
},
targetLocked: false
},
fields: Map(4) {
‘epk’ => ‘mn_shield-epk1…’,
‘profile_type’ => ‘personal’,
‘discord’ => ‘Uetto’,
‘Zealy’ => ‘tminus1sec’
},
settings: { coinColor: Uint8Array(32) […], costs: {…}, buyEnabled: false }
}
Two things jump out:
target — the address the name points to, like a DNS record but for wallets. Mine resolves to a shielded address. So a wallet could let you “pay tomin.night” and quietly turn it into that address, instead of anyone copying 60 hex characters.
fields — arbitrary key/value profile data (discord, socials, a profile_type, whatever you set). This is the identity layer.
Gotcha #1: which network is your domain on?
This one cost me real time. The Midnames SDK supports two networks, mainnet and preprod. I first ran a script against preprod and got:
DomainNotFoundError: Domain not found: tomin.night
The domain wasn’t missing — it was registered on mainnet, and I was querying preprod. The tell is in the addresses: a mainnet owner address looks like mn_addr1…, while preprod is mn_addr_preprod1…. So point the provider at the network your domain actually lives on:
ts
const provider = createDefaultProvider({ networkId: “mainnet” }); // not “preprod”
Gotcha #2: the SDK returns real JS types, not JSON
Look again at that result: id is a BigInt (142n), coinColor is a Uint8Array, and fields is a Map. If you JSON.stringify that to send it to a browser, it throws on the BigInt and mangles the Map. So I wrote a small serializer:
js
function serialize(v) {
if (typeof v === ‘bigint’) return v.toString();
if (v instanceof Uint8Array) return Buffer.from(v).toString(‘hex’);
if (v instanceof Map) return Object.fromEntries([…v].map(([k, val]) => [k, serialize(val)]));
if (Array.isArray(v)) return v.map(serialize);
if (v && typeof v === ‘object’) {
const o = {};
for (const [k, val] of Object.entries(v)) o[k] = serialize(val);
return o;
}
return v;
}
Wrapping it in a dApp
I kept the architecture boring on purpose: the SDK runs in a tiny Node/Express backend (where it’s happiest — no browser polyfills), and a plain HTML/JS page calls it and renders the card.
js
import express from ‘express’;
import { createDefaultProvider, getDomainProfile } from ‘@midnames/sdk’;
const app = express();
app.use(express.static(‘public’));
const providers = {};
const providerFor = (net) => (providers[net] ??= createDefaultProvider({ networkId: net }));
app.get(‘/api/resolve’, async (req, res) => {
const domain = String(req.query.domain || ”).toLowerCase();
const network = String(req.query.network || ‘mainnet’).toLowerCase();
const result = await getDomainProfile(domain, { provider: providerFor(network) });
if (!result.success) return res.status(404).json({ error: result.error?.message });
res.json({ domain, network, data: serialize(result.data) });
});
app.listen(5173, () => console.log(‘http://localhost:5173′));
The front end is just an input, a network toggle, and a fetch to /api/resolve that renders the owner, the resolved target, and the profile fields. No wallet connect, no login — resolving a name is a public read.
Run it
bash
git clone https://github.com/tomiin/midnames-profile-viewer
cd midnames-profile-viewer
npm install
npm start
open http://localhost:5173
Takeaways
A .night name is an entry in an on-chain Compact contract you own — resolvable to an address plus a bag of profile fields. It’s a genuinely nice identity primitive.
Reading it is one SDK call; the friction is all around it: pick the right network, and serialize the rich JS types (BigInt / Uint8Array / Map) before they hit a browser.
Code: https://github.com/tomiin/midnames-profile-viewer
답글 남기기