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
1

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.

2

Configure the State Machine

LuxeVerify State Transitions

[*] --> Authenticated Authenticated --> For_Sale: list_for_sale() For_Sale --> Sold: sell(buyer) For_Sale --> Authenticated: delisted Sold --> Received: confirm_receipt() Received --> Resale_Listed: relist() Resale_Listed --> Sold: sell(buyer) Resale_Listed --> Received: delisted

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_usd within 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
3

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"

4

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.

5

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.

6

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.

7

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.

8

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.