This document describes the SuiNS integration in the project: every creator automatically receives a subname like alice.patreon.sui so they can be found by name instead of a raw 0x… address.
- What is SuiNS?
- How It Works — Overview
- Architecture in the Project
- Detailed Flow (Code)
- Configuration
- Smart Contract Side
- Usage in the App
- Troubleshooting
- References
-
SuiNS (Sui Name Service) maps human-readable names to Sui addresses, similar to ENS on Ethereum.
-
Names end in
.sui(e.g.alice.sui). -
Subnames are children of a parent name (e.g.
alice.patreon.suiis a subname ofpatreon.sui). -
In this project, we own the parent name
patreon.suiand automatically create subnames for every creator who registers on the platform. -
Official docs: docs.suins.io
-
TS SDK:
@mysten/suins
- User registers as a creator (profile creation via the sponsored Enoki flow).
- Backend (admin) creates a node subname
<normalised-name>.patreon.suion SuiNS, signed by the admin wallet that ownspatreon.sui. The resultingSubDomainRegistrationNFT is transferred to the creator. - Frontend (creator signs) calls the Move function
set_suins_name(via Enoki sponsored tx) to record the subname inside the on-chainServiceobject. - Result: the creator's profile displays their SuiNS name; the search bar can match on it.
The subname creation is non-blocking: if it fails, the creator profile is still created — the SuiNS name can be retried later.
Frontend (app/)
├── hooks/
│ └── useAutoRegister.ts ← Calls /api/suins/create-subname then buildSetSuinsName
├── app/api/suins/
│ └── create-subname/route.ts ← POST: admin creates node subname on SuiNS
├── lib/
│ ├── contract-constants.ts ← SUINS_PARENT_NAME, SUINS_PARENT_NFT_ID
│ └── contract.ts ← buildSetSuinsName(), buildRemoveSuinsName()
└── components/
└── creator/ ← Displays creator.suinsName in cards & about tabs
| Layer | Who signs | What |
|---|---|---|
API route (create-subname) |
Admin wallet (via SUINS_ADMIN_SECRET_KEY) |
Creates the subname on SuiNS and transfers the NFT to the creator |
Smart contract (set_suins_name) |
Creator (via Enoki sponsored tx) | Links the subname string to the on-chain Service object |
After a successful create_creator_profile transaction, the hook:
// Step 2: Create the node subname on SuiNS (server-side, admin signs)
const suinsRes = await fetch("/api/suins/create-subname", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ creatorAddress: currentAccount.address }),
});If suinsRes.ok, it proceeds to link in the contract:
// Step 3: Record the subname in the contract (creator signs)
const setSuinsTx = buildSetSuinsName(newServiceId, suinsName);
await sponsorAndExecute(setSuinsTx);Request body: { creatorAddress: string }
The route performs the following:
- Validate that
creatorAddressowns an activeServiceon the platform. - Read the creator's name from the
Serviceobject. - Guard: check the
Servicedoesn't already have asuins_nameset (HTTP 409 if it does). - Normalise the name: lowercase, trim, replace spaces with hyphens, strip non-alphanumeric characters.
- Check idempotency: if the subname already exists on SuiNS (e.g. from a previous partial attempt), return success without re-creating it.
- Fetch parent NFT expiration (node subnames must expire ≤ parent).
- Build + sign + execute the creation transaction using
SuinsTransaction:
const suinsTx = new SuinsTransaction(suinsClient, tx);
const subNameNft = suinsTx.createSubName({
parentNft: SUINS_PARENT_NFT_ID,
name: fullSubname, // e.g. "alice.patreon.sui"
expirationTimestampMs: Number(parentExpirationMs),
allowChildCreation: false,
allowTimeExtension: true,
});
suinsTx.setTargetAddress({
nft: subNameNft,
address: creatorAddress,
isSubname: true,
});
tx.transferObjects([subNameNft], tx.pure.address(creatorAddress));- Return
{ success, suinsName, normalisedName, txDigest }.
After the API route creates the SuiNS subname, the creator signs (via Enoki) a call to:
service::set_suins_name(Service, Platform, suins_name)— stores the name string in theServiceon-chain object.service::remove_suins_name(Service, Platform)— clears it.
These functions must be called by the Service owner (service.creator == sender).
| Variable | Side | Description |
|---|---|---|
SUINS_ADMIN_SECRET_KEY |
Server | Base64-encoded private key of the wallet owning patreon.sui. Used by the API route. Never expose on the client. |
The admin key is used exclusively in app/api/suins/create-subname/route.ts.
| Constant | Value | Purpose |
|---|---|---|
SUINS_PARENT_NAME |
"patreon.sui" |
Parent name under which subnames are created |
SUINS_PARENT_NFT_ID |
0x91ea… |
Object ID of the SuinsRegistration NFT for patreon.sui |
The following SuiNS-related entry points must appear in ALLOWED_MOVE_CALL_TARGETS:
service::set_suins_nameservice::remove_suins_name
(They are already configured in contract-constants.ts as part of the general list, but ensure they also appear in the Enoki dashboard if required.)
The Service Move struct contains an optional field:
suins_name: Option<String>
set_suins_nameassertsservice.creator == tx_context::sender()and writes the name.remove_suins_nameasserts ownership and sets it back tooption::none().
The subname itself is not verified on-chain — it is a plain string. The security model relies on the API route (which is the only entry point for creating subnames) being the source of truth, and the admin wallet being the only signer able to create children of patreon.sui.
No manual action is needed. When a user creates their creator profile, useAutoRegister handles the full flow:
create_creator_profile(sponsored tx)POST /api/suins/create-subname(server-side admin call)set_suins_name(sponsored tx, creator signs)
- Creator cards show
creator.suinsNameas a badge (e.g.alice.patreon.sui). - About tab displays the SuiNS name.
- Search matches against
creator.suinsName.
Call buildRemoveSuinsName(serviceObjectId) via sponsorAndExecute to unlink the name from the Service. (The SuiNS NFT remains in the creator's wallet; only the Service field is cleared.)
| Issue | What to check |
|---|---|
SUINS_ADMIN_SECRET_KEY is not set |
Ensure the env var is defined in .env.local. It must be the base64 private key of the wallet owning patreon.sui. |
No active creator profile found (403) |
The address has no on-chain Service object. The user must create a profile first. |
This creator already has a SuiNS name (409) |
The Service.suins_name field is already set. Use remove_suins_name first if you want to reassign. |
Creator name results in an empty subname (422) |
The creator's name contains only special characters that are stripped during normalisation. |
| Subname created but not shown | The SuiNS subname exists, but set_suins_name (Step 3) may have failed. Retry manually or check the console for set_suins_name failed warnings. |
Failed to read parent NFT object |
The SUINS_PARENT_NFT_ID constant may be outdated. Verify the NFT still exists on-chain and is owned by the admin wallet. |
- SuiNS — Documentation
@mysten/suins— npm- SUI — Name Service
- Enoki integration (this project) — Sponsored transactions used for
set_suins_name