Integrate .bull names
Every .bull name is a Metaplex Core NFT minted by a Solana program — whoever holds the NFT owns the name. Names never expire and can never be rewritten. Resolution works three ways: a CORS-open HTTP API, a tiny TypeScript SDK, or raw on-chain reads.
Addresses
| Program (mainnet) | 2HQrqBW7wNNhcWuHb6Ub4dUkgEZ6pgwM8PYCXn38Tfj9 |
| Program (devnet) | BwLd96v6kAyjqgNnCQpWCp93yrDihqkAfbXyfgYCBh8h |
| Treasury (Squads vault) | BiVV9xiAyZfQqbbU7GmRWAxhXJsyrz3qempfXaLahYv8 |
| Metaplex Core | CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d |
HTTP API
CORS-open, edge-cached, no key required. Ownership is read live from the chain, so transfers and sales reflect within seconds.
GET https://www.bns.wtf/api/resolve/<name>
# registered
{
"name": "crispy.bull",
"registered": true,
"owner": "8wvq...Wteb", // controlling wallet
"asset": "FPrW...HVzx", // the Core NFT
"registeredAt": "2026-08-18T09:12:00.000Z",
"image": "https://img.bns.wtf/domains/crispy-1fe752c2.jpg",
"metadata": "https://www.bns.wtf/api/meta/crispy",
"listing": { "priceLamports": 500000000, "seller": "...", "listedAt": "..." } | null
}
# unregistered
{ "name": "free.bull", "registered": false, "registerPriceLamports": 200000000 }GET https://www.bns.wtf/api/wallet/<address> # reverse lookup
{
"address": "8wvq...Wteb",
"count": 2,
"domains": [
{ "name": "crispy.bull", "asset": "FPrW...", "image": "https://...", "listed": false },
{ "name": "moon.bull", "asset": "9aBc...", "image": "https://...", "listed": true, "priceLamports": 1000000000 }
]
}GET https://www.bns.wtf/api/directory # every domain + listings + recent sales
GET https://www.bns.wtf/api/meta/<name> # NFT metadata (name, image, attributes)TypeScript SDK
npm install bull-name-service @solana/web3.jsimport { Connection } from "@solana/web3.js";
import { resolve, getDomainState } from "bull-name-service";
const connection = new Connection("https://api.mainnet-beta.solana.com");
const owner = await resolve(connection, "crispy.bull");
// PublicKey | null — if listed for sale, this is the seller
const state = await getDomainState(connection, "crispy");
// { name, owner, asset, registeredAt, imageUri, listing }Also exported: getDomainKey, getDomainsByOwner, validateName, and DEVNET_PROGRAM_ID (pass { programId: DEVNET_PROGRAM_ID } to target devnet).
On-chain resolution (no dependencies)
A domain record is a PDA of the program with seeds ["domain", name] (name lowercase, no suffix). Account layout:
offset size field
0 8 discriminator [130,61,36,125,56,48,189,247]
8 4 name length (u32 LE)
12 n name (utf-8)
12+n 32 asset pubkey (the Core NFT)
44+n 8 registered_at (i64 LE, unix seconds)
52+n 1 bump
53+n 4 + m image_uri (u32 LE length + utf-8; empty = default art)The current holder is inside the Core asset account: bytes [1..33] are the owner pubkey. If the domain is listed on the native market the asset is held by the escrow PDA (seeds ["escrow"]); the listing PDA (seeds ["listing", name]) then records the real seller and price. Valid names: 1–32 chars of a-z 0-9 -, no leading/trailing hyphen.
Fees
Registration (one-time, forever): 5 SOL for 1–2 chars, 2 SOL for 3, 0.5 SOL for 4, 0.2 SOL for 5+. Native marketplace sales pay 90% to the seller, 10% to the treasury. Secondary sales anywhere carry a 5% royalty enforced by the Core Royalties plugin. Program upgrade authority and the treasury are a Squads 2-of-3 multisig.
Questions or integrating .bull somewhere? bns.wtf