Build Your First DUAL Concept
A complete step-by-step tutorial using LuxeVerify (luxury goods authentication)
A complete step-by-step tutorial using LuxeVerify (luxury goods authentication)
Why LuxeVerify?
LuxeVerify is the ideal first DUAL concept because it:
- Zero regulatory overhead — no licensing, banking, or export controls required
- Immediate revenue — mint tokens and sell within minutes of authentication
- Simple state machine — 5 core states, straightforward transitions
- High margins — authentication fee + resale transaction fee per token
- Proven demand — $500B+ luxury counterfeit market annually
What You'll Build
By the end of this tutorial, you'll have a working platform that:
- Creates DUAL organizations and defines token templates
- Mints authenticated luxury items as cryptographically-signed tokens
- Manages a 5-state lifecycle: Authenticated → For_Sale → Sold → Received → Resale_Listed
- Tracks chain-of-custody through ownership transfers
- Executes state transitions with compliance validation
- Queries token history and builds analytics dashboards
Estimated time: 60 minutes. Prerequisites: curl or Postman, basic JSON knowledge, DUAL API credentials.
Prerequisites
Verify you have the following before starting:
| Requirement | Details |
|---|---|
| DUAL API Credentials | API key from https://api.dual.io with org management permissions |
| Funded Wallet | Wallet with 1000+ VEE for org creation fees (~$0.10-$5 depending on network) |
| HTTP Client | curl, Postman, or equivalent to make API calls |
| Text Editor | Keep notes of IDs, templates, and test data |
| Endpoint Access | https://api.dual.io/v1 (mainnet) or https://testnet.dual.io/v1 (testnet) |
Check Your Setup
export DUAL_API_KEY="sk_live_..."
export DUAL_ORG_FQDN="your-org.dual"
# Verify API connectivity
curl -H "Authorization: Bearer $DUAL_API_KEY" https://api.dual.io/v1/health
Define Your Token Schema
Understanding Token Structure
A DUAL token has three sections: Immutable, Mutable, and Compliance.
- Immutable fields are set at mint and cannot change (serial number, manufacturing date, authenticity proof hash)
- Mutable fields change as the token moves through its lifecycle (status, owner, listing price)
- Compliance fields track regulatory constraints (export restrictions, tax jurisdiction, anti-counterfeiting checks)
The LuxeVerify Token Schema
Immutable Section (Set at Mint)
{
"brand": "Hermès",
"model": "Birkin 30 Taurillon Clemence",
"serial_number": "SL2345XXXX9876",
"manufacturing_year": 2023,
"authentication_date": "2026-04-09T14:32:00Z",
"authenticator_signature": "sig:abc123...",
"authentication_report_hash": "sha256:def456...",
"condition_photos_hash": "sha256:ghi789..."
}
Mutable Section (Changes on Transitions)
{
"status": "authenticated",
"current_owner": "0xabc123...",
"condition_grade": "excellent",
"location_coordinates": { "lat": 37.7749, "lng": -122.4194 },
"listing_price_usd": 9500,
"previous_owners": ["0x789abc..."],
"last_transition_timestamp": "2026-04-09T14:32:00Z",
"days_since_authentication": 0
}
Compliance Section (Validation Rules)
{
"is_export_restricted": false,
"export_jurisdictions_blacklist": ["IR", "KP", "SY"],
"tax_jurisdiction": "US",
"anti_counterfeiting_check_passed": true,
"clone_detection_ai_confidence": 0.998,
"legal_ownership_verified": true,
"sanctions_check_timestamp": "2026-04-09T14:30:00Z"
}
Complete Template Definition
POST /v1/templates
{
"name": "LuxeVerify",
"description": "Authenticated luxury goods resale platform",
"object": {
"metadata": {
"name": "Authenticated Luxury Item",
"description": "Designer item authenticated and tokenized for secure resale",
"category": "luxury_goods"
},
"immutable": {
"brand": { "type": "string", "description": "Designer brand" },
"model": { "type": "string", "description": "Item model or style" },
"serial_number": { "type": "string", "description": "Unique identifier" },
"manufacturing_year": { "type": "integer" },
"authentication_date": { "type": "string", "format": "iso8601" },
"authenticator_signature": { "type": "string" },
"authentication_report_hash": { "type": "string" },
"condition_photos_hash": { "type": "string" }
},
"mutable": {
"status": { "type": "enum", "values": ["authenticated", "for_sale", "sold", "received", "resale_listed"] },
"current_owner": { "type": "string" },
"condition_grade": { "type": "enum", "values": ["mint", "excellent", "very_good", "good", "fair"] },
"listing_price_usd": { "type": "number" },
"previous_owners": { "type": "array", "items": { "type": "string" } },
"last_transition_timestamp": { "type": "string", "format": "iso8601" }
},
"compliance": {
"is_export_restricted": { "type": "boolean" },
"export_jurisdictions_blacklist": { "type": "array", "items": { "type": "string" } },
"tax_jurisdiction": { "type": "string" },
"anti_counterfeiting_check_passed": { "type": "boolean" },
"clone_detection_ai_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
"legal_ownership_verified": { "type": "boolean" },
"sanctions_check_timestamp": { "type": "string", "format": "iso8601" }
}
},
"state_transitions": {
"authenticated": ["for_sale"],
"for_sale": ["sold", "authenticated"],
"sold": ["received"],
"received": ["resale_listed"],
"resale_listed": ["sold", "received"]
}
}
Response
{
"id": "tmpl_luxeverify_v1",
"fqdn": "luxeverify.dual",
"name": "LuxeVerify",
"created_at": "2026-04-09T14:35:00Z",
"version": 1,
"status": "active"
}
Save this ID: You'll use tmpl_luxeverify_v1 for all subsequent operations.
Configure the State Machine
LuxeVerify State Transitions
State Descriptions
| State | Meaning | Allowed Transitions |
|---|---|---|
| Authenticated | Item verified, token minted, awaiting sale | → For_Sale |
| For_Sale | Listed on marketplace, awaiting buyer | → Sold, → Authenticated |
| Sold | Ownership transferred, item in transit | → Received |
| Received | Buyer confirmed physical receipt | → Resale_Listed |
| Resale_Listed | New owner relisted for resale | → Sold, → Received |
Compliance Rules by Transition
Before transitioning to For_Sale:
- Verify
anti_counterfeiting_check_passed == true - Verify
clone_detection_ai_confidence > 0.95 - Check
legal_ownership_verified == true
Before transitioning to Sold:
- Verify buyer not in OFAC sanctions list
- Verify
listing_price_usdwithin platform bounds - If
is_export_restricted, verify buyer not in blacklist jurisdictions
Before transitioning to Received:
- Verify buyer signature on receipt confirmation
- Log physical delivery timestamp
Mint Your First Token
What Minting Does
Minting creates a new token instance by executing the authenticate action. This:
- Cryptographically commits immutable data (serial number, authentication proof)
- Sets initial mutable state (status, owner, condition grade)
- Validates all compliance rules
- Creates an immutable audit log entry
- Assigns the token a unique object ID
Prepare Authentication Data
Generate Content Hashes
# Create authentication report (PDF)
openssl dgst -sha256 authentication_report.pdf
# a1b2c3d4e5f6...
# Zip and hash condition photos
zip condition_photos.zip *.jpg
openssl dgst -sha256 condition_photos.zip
# x9y8z7w6v5u4...
Execute the Authenticate Action
POST /v1/tokens/{template_id}/actions/authenticate
{
"immutable": {
"brand": "Hermès",
"model": "Birkin 30 Taurillon Clemence",
"serial_number": "SL2345XXXX9876",
"manufacturing_year": 2023,
"authentication_date": "2026-04-09T14:32:00Z",
"authenticator_signature": "sig:0x1a2b3c4d5e6f7g8h",
"authentication_report_hash": "a1b2c3d4e5f6g7h8",
"condition_photos_hash": "x9y8z7w6v5u4t3s2"
},
"mutable": {
"status": "authenticated",
"current_owner": "0xabc123def456abc",
"condition_grade": "excellent",
"listing_price_usd": 0,
"previous_owners": [],
"last_transition_timestamp": "2026-04-09T14:32:00Z"
},
"compliance": {
"is_export_restricted": false,
"export_jurisdictions_blacklist": ["IR", "KP", "SY"],
"tax_jurisdiction": "US",
"anti_counterfeiting_check_passed": true,
"clone_detection_ai_confidence": 0.998,
"legal_ownership_verified": true,
"sanctions_check_timestamp": "2026-04-09T14:30:00Z"
}
}
Response
{
"object_id": "obj_birkin_001",
"template_id": "tmpl_luxeverify_v1",
"action_executed": "authenticate",
"batch_id": "batch_auth_001",
"status": "processing",
"timestamp": "2026-04-09T14:32:15Z",
"immutable_hash": "0xabc123...",
"state_root": "0xdef456..."
}
Success! Your first token has been minted. Check status: GET /v1/objects/obj_birkin_001
Save: object_id = "obj_birkin_001"
Execute State Transitions
Transition 1: List for Sale
POST /v1/objects/obj_birkin_001/actions/list_for_sale
{
"mutable": {
"status": "for_sale",
"listing_price_usd": 9500,
"last_transition_timestamp": "2026-04-09T15:00:00Z"
}
}
Response
{
"object_id": "obj_birkin_001",
"status": "for_sale",
"batch_id": "batch_list_001",
"timestamp": "2026-04-09T15:00:15Z",
"state_root": "0xghi789..."
}
Transition 2: Sell to Buyer
POST /v1/objects/obj_birkin_001/actions/sell
{
"buyer_wallet": "0xbuyer123def456abc",
"sale_price_usd": 9500,
"mutable": {
"status": "sold",
"current_owner": "0xbuyer123def456abc",
"previous_owners": ["0xabc123def456abc"],
"last_transition_timestamp": "2026-04-09T16:00:00Z"
},
"compliance": {
"buyer_sanctions_check": true,
"buyer_sanctions_timestamp": "2026-04-09T15:55:00Z"
}
}
Response
{
"object_id": "obj_birkin_001",
"status": "sold",
"batch_id": "batch_sell_001",
"buyer": "0xbuyer123def456abc",
"timestamp": "2026-04-09T16:00:15Z",
"state_root": "0xjkl012..."
}
Transition 3: Buyer Confirms Receipt
POST /v1/objects/obj_birkin_001/actions/confirm_receipt
{
"buyer_signature": "sig:0xbuyer_confirms...",
"receipt_photos_hash": "receipt_abc123...",
"mutable": {
"status": "received",
"last_transition_timestamp": "2026-04-09T17:30:00Z"
}
}
Response
{
"object_id": "obj_birkin_001",
"status": "received",
"batch_id": "batch_receipt_001",
"timestamp": "2026-04-09T17:30:15Z",
"state_root": "0xmno345..."
}
Token has completed one full lifecycle. New owner can now relist via relist action.
Add Compliance Rules
Export Restriction Validation
Before executing transition to Sold, validate:
if token.is_export_restricted:
buyer_country = lookup_wallet_jurisdiction(buyer)
if buyer_country in token.export_jurisdictions_blacklist:
return error("Sale blocked: export restrictions in force")
Anti-Counterfeiting Re-Check
POST /v1/objects/obj_birkin_001/compliance/re_verify
{
"check_type": "anti_counterfeiting",
"verification_images": [
"ipfs://Qm123...",
"ipfs://Qm456..."
],
"ai_model_version": "clone_detect_v2_1"
}
Run compliance checks before every high-value transition. Use webhooks to automate.
Query Token History
Get Complete Token State
GET /v1/objects/obj_birkin_001
Returns: {
"id": "obj_birkin_001",
"template_id": "tmpl_luxeverify_v1",
"created_at": "2026-04-09T14:32:15Z",
"immutable": { ... },
"mutable": { ... },
"compliance": { ... },
"state_root": "0xmno345...",
"owner": "0xbuyer123def456abc",
"transitions_count": 4
}
Get Full Audit Trail
GET /v1/objects/obj_birkin_001/history
Returns: [
{
"timestamp": "2026-04-09T14:32:15Z",
"action": "authenticate",
"batch_id": "batch_auth_001",
"state_after": "authenticated",
"actor": "0xabc123...",
"immutable_hash": "0xabc123..."
},
{
"timestamp": "2026-04-09T15:00:15Z",
"action": "list_for_sale",
"batch_id": "batch_list_001",
"state_after": "for_sale",
"actor": "0xabc123...",
"mutable_changes": { "status": "for_sale", "listing_price_usd": 9500 }
},
...
]
Dashboard: List All Objects by Status
GET /v1/objects?template_id=tmpl_luxeverify_v1&filter.mutable.status=for_sale&limit=100
Returns: {
"count": 42,
"objects": [
{ "id": "obj_birkin_001", ... },
{ "id": "obj_rolex_001", ... },
...
]
}
Full audit transparency. Every state change immutably logged with actor, timestamp, and cryptographic proof.
Wire Webhooks for Real-Time Events
Create a Webhook Subscription
POST /v1/webhooks
{
"name": "luxeverify_sales_events",
"url": "https://api.luxeverify.io/webhooks/events",
"events": [
"action.executed:authenticate",
"action.executed:sell",
"action.executed:confirm_receipt"
],
"template_id": "tmpl_luxeverify_v1",
"signing_secret": "whsec_live_...",
"retry_policy": {
"max_attempts": 3,
"backoff_multiplier": 2
}
}
Response
{
"id": "hook_luxeverify_sales",
"status": "active",
"created_at": "2026-04-09T18:00:00Z"
}
Webhook Payload Format
{
"event_id": "evt_12345678",
"event_type": "action.executed",
"action_name": "sell",
"object_id": "obj_birkin_001",
"timestamp": "2026-04-09T16:00:15Z",
"actor": "0xabc123...",
"mutable_changes": {
"status": "sold",
"current_owner": "0xbuyer123..."
}
}
Example: Payment Trigger on Sale
// Node.js: Auto-trigger payment when webhook fires
app.post('/webhooks/events', (req, res) => {
const event = req.body;
if (event.action_name === 'sell') {
const seller = event.mutable_changes.previous_owners[0];
const price = event.sale_price_usd;
// Trigger payout to seller
stripe.charges.create({
amount: Math.round(price * 100),
currency: 'usd',
customer: seller,
description: `Sale: ${event.object_id}`
});
}
res.status(200).send('OK');
});
Always verify webhook signatures using HMAC in x-dual-signature header.
Next Steps
What to Build Next
- Admin Dashboard — Query objects by status, visualize pipeline
- Batch Minting — Authenticate 100+ items in parallel
- Role-Based Access — Separate authenticator, seller, buyer permissions
- Automated Compliance — OFAC checks, tax jurisdiction routing
- Secondary Marketplace — Integrate LuxeVerify tokens into frontend
Scaling for 100K+ Items
- Use batch minting APIs to create tokens in parallel
- Implement pagination for object queries (limit=100, next_cursor=...)
- Cache state roots and immutable_hash for instant verification
- Use webhooks instead of polling to reduce API load
Tutorial complete! You now have a production-ready luxury authentication platform. Next: invite authenticators, mint items, and process sales.