Code Samples & Starter Repos

Production-ready examples and boilerplate to accelerate your DUAL integration.

Quick Start Snippets

Five essential TypeScript snippets using @dual/sdk to get you started:

Authentication

import { DualClient } from '@dual/sdk';

const client = new DualClient({
  apiKey: process.env.DUAL_API_KEY,
  orgId: process.env.DUAL_ORG_ID,
});

const wallet = await client.wallets.login({
  email: 'user@example.com',
  password: 'secure-password',
});

console.log('Logged in:', wallet.id);

Create Template

const template = await client.templates.create({
  name: 'io.example.product::v1',
  description: 'A product token template',
  private: {
    serial_number: '',
    manufacture_date: '',
    price_usd: 0,
  },
});

console.log('Template created:', template.id);

Mint Object

const object = await client.objects.create({
  template_id: template.id,
  owner_wallet_id: wallet.id,
  properties: {
    serial_number: 'SN-12345',
    manufacture_date: '2026-03-14',
    price_usd: 99.99,
  },
});

console.log('Object minted:', object.id);

Transfer Object

const action = await client.actions.execute({
  type: 'transfer',
  object_id: object.id,
  parameters: {
    recipient_wallet_id: 'wallet-recipient-id',
    notes: 'Product transferred',
  },
});

console.log('Action executed:', action.id);

Query Objects

const objects = await client.objects.search({
  template_id: template.id,
  filters: {
    owner_wallet_id: wallet.id,
    status: 'active',
  },
  limit: 50,
});

console.log('Found objects:', objects.length);

Express.js API Backend

A sample Express route file that wraps DUAL operations:

import express from 'express';
import { DualClient } from '@dual/sdk';

const router = express.Router();
const dual = new DualClient({
  apiKey: process.env.DUAL_API_KEY,
  orgId: process.env.DUAL_ORG_ID,
});

// Create asset endpoint
router.post('/assets', async (req, res) => {
  try {
    const { template_id, properties, owner_id } = req.body;

    const object = await dual.objects.create({
      template_id,
      properties,
      owner_wallet_id: owner_id,
    });

    res.json({ success: true, asset_id: object.id });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// List assets endpoint
router.get('/assets', async (req, res) => {
  try {
    const { owner_id, template_id } = req.query;

    const objects = await dual.objects.search({
      owner_wallet_id: owner_id,
      template_id,
      limit: 50,
    });

    res.json({ assets: objects });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Transfer asset endpoint
router.post('/assets/:id/transfer', async (req, res) => {
  try {
    const { id } = req.params;
    const { recipient_id } = req.body;

    const action = await dual.actions.execute({
      type: 'transfer',
      object_id: id,
      parameters: { recipient_wallet_id: recipient_id },
    });

    res.json({ success: true, action_id: action.id });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

export default router;

Next.js Full-Stack App

Architecture overview: Pages for listing objects, viewing details, and an admin dashboard. Key API route and React component:

API Route: pages/api/assets/[id].ts

import { DualClient } from '@dual/sdk';
import { NextApiRequest, NextApiResponse } from 'next';

const dual = new DualClient({
  apiKey: process.env.DUAL_API_KEY,
  orgId: process.env.DUAL_ORG_ID,
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { id } = req.query;

  if (req.method === 'GET') {
    try {
      const object = await dual.objects.get(id as string);
      const activity = await dual.objects.getActivity(id as string);

      res.status(200).json({ object, activity });
    } catch (error) {
      res.status(404).json({ error: 'Asset not found' });
    }
  }
}

React Component: components/AssetDetail.tsx

import { useEffect, useState } from 'react';

export default function AssetDetail({ assetId }: { assetId: string }) {
  const [asset, setAsset] = useState(null);
  const [activity, setActivity] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/assets/" + assetId)
      .then(res => res.json())
      .then(data => {
        setAsset(data.object);
        setActivity(data.activity);
        setLoading(false);
      });
  }, [assetId]);

  if (loading) return 
Loading...
; return (

{asset.template_id}

Owner: {asset.owner_wallet_id}

Created: {new Date(asset.created_at).toLocaleDateString()}

Activity Log

    {activity.map(act => (
  • {act.type} - {act.timestamp}
  • ))}
); }

Webhook Handler

Express middleware that validates and processes DUAL webhook payloads with signature verification:

import crypto from 'crypto';
import express from 'express';

const WEBHOOK_SECRET = process.env.DUAL_WEBHOOK_SECRET;

export function verifyWebhookSignature(
  payload: string,
  signature: string
): boolean {
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET!)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

export function webhookHandler(
  req: express.Request,
  res: express.Response
) {
  const signature = req.headers['x-dual-signature'] as string;
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event_type, data } = req.body;

  switch (event_type) {
    case 'object.created':
      console.log('Object created:', data.object_id);
      // Handle creation
      break;
    case 'action.executed':
      console.log('Action executed:', data.action_id);
      // Handle action
      break;
    case 'transfer.completed':
      console.log('Transfer complete:', data.object_id);
      // Update your database
      break;
  }

  res.status(200).json({ received: true });
}

CLI Scripting

Bash script for bulk minting 100 objects from a CSV file using the DUAL CLI:

#!/bin/bash
set -e

API_KEY="${DUAL_API_KEY}"
ORG_ID="${DUAL_ORG_ID}"
TEMPLATE_ID="io.example.product::v1"
CSV_FILE="products.csv"

echo "Starting bulk mint..."

while IFS=',' read -r name sku price; do
  if [ "$name" != "name" ]; then
    echo "Minting: $name"

    dual objects create       --template-id "$TEMPLATE_ID"       --property name="$name"       --property sku="$sku"       --property price_usd="$price"       --api-key "$API_KEY"       --org-id "$ORG_ID"

    # Rate limit: 10 per second
    sleep 0.1
  fi
done < "$CSV_FILE"

echo "Bulk mint complete!"

GitHub Starter Repos

Jump-start your development with these production-ready templates:

  • dual-starter-express — Full Express.js backend with DUAL integration, authentication, and webhook handling. View repo
  • dual-starter-nextjs — Next.js full-stack app with API routes, React components, and server-side rendering. View repo
  • dual-starter-python — Python Flask backend for non-Node environments. View repo
  • dual-sdk-examples — 20+ standalone examples covering all SDK features. View repo