Skip to main content

Overview

The names API provides a privacy-preserving name system for stealth addresses. Users can register human-readable names (e.g., “alice”) that resolve to meta-address public keys.
Name resolution returns ONLY spendPubkey and viewPubkey, never a Sui address. This preserves privacy.

Endpoints

Resolve Name

Resolve a name to meta-address public keys.
GET /api/identipay/v1/names/:name

Path Parameters

name
string
required
Name to resolve (3-20 characters)

Response

name
string
The resolved name
spendPubkey
string
Spend public key (hex string)
viewPubkey
string
View public key (hex string)
Sui addresses are NEVER returned to preserve privacy.

Example Request

curl https://api.identipay.com/api/identipay/v1/names/alice

Example Response

{
  "name": "alice",
  "spendPubkey": "a1b2c3d4e5f6...",
  "viewPubkey": "f6e5d4c3b2a1..."
}

Error Responses


Register Name

Register a new name with stealth meta-address and ZK identity proof.
POST /api/identipay/v1/names/register
The backend builds the registration transaction, sponsors gas, and submits it on-chain.

Request Body

name
string
required
Name to register (3-20 characters)
spendPubkey
string
required
Spend public key (hex string)
viewPubkey
string
required
View public key (hex string)
identityCommitment
string
required
Identity commitment (32 bytes hex)
zkProof
string
required
Groth16 ZK proof (256 bytes hex)
zkPublicInputs
string
required
ZK public inputs (N * 32 bytes hex)

Response

txDigest
string
Sui transaction digest of the registration

Example Request

curl -X POST https://api.identipay.com/api/identipay/v1/names/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "alice",
    "spendPubkey": "a1b2c3d4e5f6...",
    "viewPubkey": "f6e5d4c3b2a1...",
    "identityCommitment": "c0mm1tm3nt...",
    "zkProof": "pr00f...",
    "zkPublicInputs": "1nput5..."
  }'

Example Response

{
  "txDigest": "9x4Kb2FgH8..."
}

Error Responses

Implementation Details

Name Resolution Flow

When resolving a name (see routes/names.ts:14-61):
  1. Check Cache: Query database for cached name
  2. Return Cached: If found, return spendPubkey and viewPubkey
  3. On-Chain Lookup: If not cached, call suiService.resolveName()
  4. Store Cache: Cache result in database
  5. Return Result: Return public keys only (no Sui address)

Privacy Invariants

The backend NEVER returns Sui addresses, even though they exist on-chain.
This ensures:
  • Senders cannot link names to blockchain addresses
  • Name lookups don’t reveal user balances
  • On-chain activity remains unlinkable to names

Registration Flow

When registering a name (see routes/names.ts:66-104):
  1. Validate Input: Check name format, public key formats, ZK proof
  2. Build Transaction: Construct registration transaction with ZK proof
  3. Submit On-Chain: Backend signs with admin key (gas sponsor) and submits
  4. Cache Result: Store name and public keys in database
  5. Return Digest: Return Sui transaction digest
The backend builds and submits the transaction. Wallets don’t need to sign (privacy benefit).

ZK Identity Proof

Name registration requires a zero-knowledge proof that:
  • The registrant owns a valid identity credential
  • The credential’s commitment matches identityCommitment
  • The proof doesn’t reveal any identity attributes
This prevents spam/sybil attacks while preserving anonymity.

Caching Strategy

Names are cached in the database after first resolution:
await db
  .insert(names)
  .values({
    name,
    spendPubkey,
    viewPubkey,
    identityCommitment
  })
  .onConflictDoUpdate({
    target: names.name,
    set: {
      spendPubkey,
      viewPubkey,
      updatedAt: new Date()
    }
  });
Cache updates:
  • On first resolution from on-chain
  • On registration
  • On conflict resolution (upsert)

Database Schema

The names table stores:
CREATE TABLE names (
  name TEXT PRIMARY KEY,
  spend_pubkey TEXT NOT NULL,
  view_pubkey TEXT NOT NULL,
  identity_commitment TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
No Sui address is stored, preserving privacy.

Use Cases

Payment Request

A merchant can create a payment request using a name:
// Merchant creates request
const request = await createPayRequest({
  recipientName: 'alice',
  amount: '10.00',
  currency: 'USDC'
});

// Payer's wallet resolves name
const { spendPubkey, viewPubkey } = await resolveName('alice');

// Payer computes stealth address
const stealthAddr = computeStealthAddress(spendPubkey, viewPubkey);

// Payer sends to stealth address
await sendToStealth(stealthAddr, ...);

Direct Send

Users can send directly to a name:
// Resolve recipient name
const metaAddr = await fetch('/api/identipay/v1/names/bob').then(r => r.json());

// Generate stealth address
const { stealthAddr, ephemeralPubkey, viewTag } = generateStealthAddress(
  metaAddr.spendPubkey,
  metaAddr.viewPubkey
);

// Build and send transaction
const { txBytes } = await buildSponsoredSend({
  type: 'send',
  recipient: stealthAddr,
  amount: '1000000000',
  ephemeralPubkey,
  viewTag
});

Build docs developers (and LLMs) love