← Back to console

CometENS — Upstream API

Machine-to-machine API for upstream applications (e.g. apps that want to register ENS subdomains on behalf of users). All endpoints are under /api/v1/.

Server config required: the server must have UPSTREAM_ALLOWED_SIGNERS set (comma-separated Ethereum addresses of allowed callers) and VITE_ROOT_DOMAIN configured.

Authentication

Each request must be signed by a registered upstream application key. The server maintains a whitelist of allowed signer addresses in UPSTREAM_ALLOWED_SIGNERS.

Signing protocol

Use eth_sign / personal_sign on the canonical message string:

CometENS:register:{label}:{owner}:{timestamp}

Example (viem)

import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'

const account = privateKeyToAccount('0xYOUR_UPSTREAM_PRIVATE_KEY')
const wallet  = createWalletClient({ account, chain: mainnet, transport: http() })

const label     = 'alice'
const owner     = '0xYourUserAddress'
const timestamp = Math.floor(Date.now() / 1000)
const message   = `CometENS:register:${label}:${owner}:${timestamp}`

const signature = await wallet.signMessage({ account, message })

Example (ethers.js v6)

import { Wallet } from 'ethers'

const signer    = new Wallet('0xYOUR_UPSTREAM_PRIVATE_KEY')
const label     = 'alice'
const owner     = '0xYourUserAddress'
const timestamp = Math.floor(Date.now() / 1000)
const message   = `CometENS:register:${label}:${owner}:${timestamp}`

const signature = await signer.signMessage(message)

Endpoints

POST /api/v1/register POST

Register a subdomain under the configured root domain. Atomically creates the node and sets the ETH address record in one L2 transaction.

FieldTypeDescription
labelstringSubdomain label (1-63 chars, lowercase alphanumeric + hyphen)
owneraddressEthereum address of the new subdomain owner
addraddress (optional)Address to set as the ETH record (defaults to owner)
timestampnumberUnix seconds; must be within ±60s of server time
signaturehex stringpersonal_sign over the canonical message

Request example

const timestamp = Math.floor(Date.now() / 1000)
const label     = 'alice'
const owner     = '0x1234...abcd'
const message   = `CometENS:register:${label}:${owner}:${timestamp}`
const signature = await wallet.signMessage({ account, message })

const response = await fetch('https://your-cometens-server/api/v1/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ label, owner, timestamp, signature }),
})

const result = await response.json()
// { ok: true, name: "alice.aastar.eth", node: "0x...", txHash: "0x..." }

Success response (200)

{
  "ok": true,
  "name": "alice.aastar.eth",
  "node": "0xabc123...",
  "txHash": "0xdef456..."
}

Error responses

StatusErrorMeaning
400Invalid label / Missing signature / etc.Bad request — check field values
401Signer X is not in the allowed listYour signing key is not whitelisted
401Timestamp drift too largeClocks out of sync; use a fresh timestamp
503UPSTREAM_ALLOWED_SIGNERS not configuredServer is not configured for upstream access

Server Setup

Add to your .env.local:

# Comma-separated Ethereum addresses allowed to call /api/v1/*
UPSTREAM_ALLOWED_SIGNERS=0xYourUpstreamSignerAddress1,0xYourUpstreamSignerAddress2

Then restart the dev server (pnpm dev).

Read-only: resolving records

Any ENS-compatible library works for reads — no API key needed.

viem

import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const client = createPublicClient({ chain: mainnet, transport: http('YOUR_L1_RPC') })

// Resolve ETH address (via CCIP-Read — automatic)
const address = await client.getEnsAddress({ name: 'alice.aastar.eth' })

// Resolve text record
const twitter = await client.getEnsText({ name: 'alice.aastar.eth', key: 'com.twitter' })

ethers.js v6

import { JsonRpcProvider } from 'ethers'

const provider = new JsonRpcProvider('YOUR_L1_SEPOLIA_RPC')
const address  = await provider.resolveName('alice.aastar.eth')
const twitter  = await provider.getResolver('alice.aastar.eth')
                   .then(r => r?.getText('com.twitter'))

Shell (curl)

bash scripts/resolve-testnet.sh alice.aastar.eth

Test checklist

  1. Add your signing address to UPSTREAM_ALLOWED_SIGNERS and restart the server
  2. Call POST /api/v1/register with a fresh timestamp; expect {"ok":true,"txHash":"0x..."}
  3. Wait ~5s for the L2 transaction to confirm
  4. Run bash scripts/resolve-testnet.sh {label}.aastar.eth — should print the owner address
  5. Or use viem.getEnsAddress() with a Sepolia L1 RPC — should return the registered address