Features Solutions Technology Tokenomics Docs About
Docs / Tutorials / Ownership Transfer

Ownership Transfer Tutorial

Implement secure, blockchain-verified ownership transfers in your application.

How Transfers Work

TAG IT Network uses a secure two-step transfer process to ensure both parties consent to ownership changes. This prevents unauthorized transfers and provides a clear audit trail on the blockchain.

The transfer flow consists of:

  1. Initiation - The current owner (seller) initiates the transfer
  2. Verification - The buyer's identity is verified via wallet signature
  3. Confirmation - The buyer confirms acceptance of the transfer
  4. Recording - The blockchain record is automatically updated
  5. Notification - Both parties receive confirmation via webhooks
Note

Transfer requests expire after 72 hours if not confirmed by the buyer. This prevents stale transfers from cluttering the system.

Step 1: Initiate Transfer

The seller (current owner) initiates the transfer by specifying the product and the buyer's wallet address:

import { TagIt } from '@tagit/sdk';

const tagit = new TagIt({
  apiKey: process.env.TAGIT_API_KEY,
  network: 'mainnet'
});

// Seller initiates the transfer
async function initiateTransfer(tagId, buyerAddress) {
  try {
    const transfer = await tagit.transfers.initiate({
      tagId: tagId,
      toAddress: buyerAddress,
      metadata: {
        salePrice: '2500.00',
        currency: 'USD',
        notes: 'Luxury watch sale - includes original box and papers'
      }
    });

    console.log('Transfer initiated:', transfer.transferId);
    console.log('Status:', transfer.status); // 'pending_acceptance'
    console.log('Expires at:', transfer.expiresAt);

    return transfer;
  } catch (error) {
    console.error('Failed to initiate transfer:', error.message);
    throw error;
  }
}

// Example usage
const transfer = await initiateTransfer(
  'NFC-TAG-001-ABC',
  '0x8Ba1f109551bD432803012645Ac136ddd64DBA72'
);

Step 2: Verify Buyer Identity

Before the buyer can accept the transfer, they must verify their wallet ownership by signing a message:

// Buyer verifies their identity
async function verifyBuyerIdentity(transferId) {
  // Generate a verification challenge
  const challenge = await tagit.transfers.getChallenge(transferId);

  console.log('Challenge message:', challenge.message);
  // "TAG IT Network Transfer Verification\nTransfer ID: xfer_abc123\nTimestamp: 1706123456"

  // Buyer signs the challenge with their wallet (e.g., using ethers.js)
  const signer = provider.getSigner();
  const signature = await signer.signMessage(challenge.message);

  // Submit the signature for verification
  const verification = await tagit.transfers.verifyIdentity({
    transferId: transferId,
    signature: signature
  });

  console.log('Identity verified:', verification.verified);
  console.log('Verified address:', verification.address);

  return verification;
}

// Example with MetaMask in browser
async function verifyWithMetaMask(transferId) {
  const accounts = await window.ethereum.request({
    method: 'eth_requestAccounts'
  });

  const challenge = await tagit.transfers.getChallenge(transferId);

  const signature = await window.ethereum.request({
    method: 'personal_sign',
    params: [challenge.message, accounts[0]]
  });

  return await tagit.transfers.verifyIdentity({
    transferId,
    signature
  });
}
Security Warning

Always verify the challenge message content before signing. Never sign messages from untrusted sources, as this could be used in phishing attacks.

Step 3: Confirm Transfer

Once identity is verified, the buyer confirms the transfer to complete the ownership change:

// Buyer confirms the transfer
async function confirmTransfer(transferId) {
  try {
    const result = await tagit.transfers.confirm({
      transferId: transferId,
      acceptTerms: true
    });

    console.log('Transfer confirmed!');
    console.log('New owner:', result.newOwner);
    console.log('Transaction hash:', result.txHash);
    console.log('Block number:', result.blockNumber);

    return result;
  } catch (error) {
    if (error.code === 'TRANSFER_EXPIRED') {
      console.error('Transfer has expired. Please request a new transfer.');
    } else if (error.code === 'IDENTITY_NOT_VERIFIED') {
      console.error('Please verify your identity first.');
    } else {
      console.error('Transfer confirmation failed:', error.message);
    }
    throw error;
  }
}

// Complete transfer flow for buyer
async function acceptTransfer(transferId) {
  // Step 1: Verify identity
  await verifyBuyerIdentity(transferId);

  // Step 2: Confirm the transfer
  const result = await confirmTransfer(transferId);

  console.log('Ownership transfer complete!');
  return result;
}

Step 4: Update Blockchain Record

After confirmation, the blockchain record is automatically updated. You can query the updated product information:

// Query updated product ownership
async function getProductOwnership(tagId) {
  const product = await tagit.products.get(tagId);

  console.log('Current owner:', product.currentOwner);
  console.log('Transfer count:', product.transferHistory.length);

  // Get full transfer history
  const history = await tagit.products.getTransferHistory(tagId);

  history.forEach((transfer, index) => {
    console.log(`Transfer ${index + 1}:`);
    console.log(`  From: ${transfer.from}`);
    console.log(`  To: ${transfer.to}`);
    console.log(`  Date: ${transfer.timestamp}`);
    console.log(`  TX: ${transfer.txHash}`);
  });

  return product;
}

// Verify transfer on-chain directly
async function verifyOnChain(tagId, expectedOwner) {
  const verification = await tagit.products.verifyOwnership({
    tagId: tagId,
    address: expectedOwner
  });

  console.log('Is owner:', verification.isOwner);
  console.log('Verified at block:', verification.blockNumber);

  return verification;
}
// Example transfer history response
{
  "tagId": "NFC-TAG-001-ABC",
  "currentOwner": "0x8Ba1f109551bD432803012645Ac136ddd64DBA72",
  "transferHistory": [
    {
      "transferId": "xfer_001",
      "from": "0x0000000000000000000000000000000000000000",
      "to": "0x742d35Cc6634C0532925a3b844Bc9e7595f5c9E8",
      "timestamp": "2024-01-15T10:30:00Z",
      "txHash": "0xabc123...",
      "type": "mint"
    },
    {
      "transferId": "xfer_002",
      "from": "0x742d35Cc6634C0532925a3b844Bc9e7595f5c9E8",
      "to": "0x8Ba1f109551bD432803012645Ac136ddd64DBA72",
      "timestamp": "2024-06-20T14:45:00Z",
      "txHash": "0xdef456...",
      "type": "transfer",
      "metadata": {
        "salePrice": "2500.00",
        "currency": "USD"
      }
    }
  ]
}

Step 5: Send Notifications

Configure webhooks to receive real-time notifications about transfer events:

// Configure webhook endpoint in your dashboard or via API
async function configureWebhook() {
  const webhook = await tagit.webhooks.create({
    url: 'https://your-app.com/api/webhooks/tagit',
    events: [
      'transfer.initiated',
      'transfer.verified',
      'transfer.confirmed',
      'transfer.cancelled',
      'transfer.expired'
    ],
    secret: process.env.WEBHOOK_SECRET
  });

  console.log('Webhook configured:', webhook.id);
  return webhook;
}

// Handle incoming webhooks (Express.js example)
import express from 'express';
import crypto from 'crypto';

const app = express();

app.post('/api/webhooks/tagit', express.raw({ type: 'application/json' }), (req, res) => {
  // Verify webhook signature
  const signature = req.headers['x-tagit-signature'];
  const expectedSignature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (signature !== `sha256=${expectedSignature}`) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);

  switch (event.type) {
    case 'transfer.initiated':
      console.log('Transfer initiated:', event.data.transferId);
      // Notify buyer about pending transfer
      notifyBuyer(event.data);
      break;

    case 'transfer.confirmed':
      console.log('Transfer confirmed:', event.data.transferId);
      // Update your database, send confirmation emails
      updateOwnershipRecord(event.data);
      sendConfirmationEmails(event.data);
      break;

    case 'transfer.expired':
      console.log('Transfer expired:', event.data.transferId);
      // Clean up pending transfers
      handleExpiredTransfer(event.data);
      break;
  }

  res.status(200).send('OK');
});

Secondary Market Integration

Integrate with marketplace APIs to enable secondary market sales with authenticated products:

// List a product for sale on supported marketplaces
async function listForSale(tagId, listingDetails) {
  // First, verify you own the product
  const verification = await tagit.products.verifyOwnership({
    tagId: tagId,
    address: await signer.getAddress()
  });

  if (!verification.isOwner) {
    throw new Error('You must own the product to list it for sale');
  }

  // Create marketplace listing
  const listing = await tagit.marketplace.createListing({
    tagId: tagId,
    price: listingDetails.price,
    currency: listingDetails.currency,
    description: listingDetails.description,
    images: listingDetails.images,
    shippingOptions: listingDetails.shipping,
    acceptedPaymentMethods: ['crypto', 'card']
  });

  console.log('Listing created:', listing.listingId);
  console.log('Marketplace URL:', listing.url);

  return listing;
}

// Handle purchase from marketplace
async function handleMarketplacePurchase(listingId, buyerAddress) {
  // Marketplace handles payment, then initiates transfer
  const purchase = await tagit.marketplace.completePurchase({
    listingId: listingId,
    buyerAddress: buyerAddress
  });

  // Transfer is automatically initiated
  console.log('Transfer initiated:', purchase.transferId);
  console.log('Buyer should confirm at:', purchase.confirmationUrl);

  return purchase;
}

// Query marketplace activity for a product
async function getMarketplaceHistory(tagId) {
  const history = await tagit.marketplace.getHistory(tagId);

  history.sales.forEach(sale => {
    console.log(`Sale on ${sale.date}: ${sale.price} ${sale.currency}`);
    console.log(`  Platform: ${sale.marketplace}`);
    console.log(`  Verified: ${sale.authenticated}`);
  });

  return history;
}

Escrow Transfers

For high-value items, use escrow transfers to protect both buyer and seller:

// Create an escrow transfer for high-value items
async function createEscrowTransfer(tagId, buyerAddress, terms) {
  const escrow = await tagit.transfers.createEscrow({
    tagId: tagId,
    toAddress: buyerAddress,
    amount: terms.price,
    currency: terms.currency,
    escrowPeriod: terms.inspectionDays * 24 * 60 * 60, // Convert days to seconds
    conditions: {
      requirePhysicalDelivery: true,
      requireInspectionApproval: true,
      disputeResolutionMethod: 'arbitration'
    }
  });

  console.log('Escrow created:', escrow.escrowId);
  console.log('Escrow address:', escrow.escrowContractAddress);
  console.log('Buyer must deposit:', escrow.requiredDeposit);

  return escrow;
}

// Buyer funds the escrow
async function fundEscrow(escrowId) {
  const funding = await tagit.escrow.fund({
    escrowId: escrowId,
    // Payment handled via connected wallet
  });

  console.log('Escrow funded:', funding.txHash);
  console.log('Status:', funding.status); // 'awaiting_delivery'

  return funding;
}

// Confirm delivery and release funds
async function confirmDeliveryAndRelease(escrowId) {
  // Buyer confirms they received the item in good condition
  const release = await tagit.escrow.confirmAndRelease({
    escrowId: escrowId,
    inspectionPassed: true,
    rating: 5,
    feedback: 'Item exactly as described, fast shipping!'
  });

  console.log('Escrow released:', release.txHash);
  console.log('Seller received:', release.sellerPayout);
  console.log('Ownership transferred:', release.transferComplete);

  return release;
}

// Handle dispute if item not as described
async function initiateDispute(escrowId, reason) {
  const dispute = await tagit.escrow.initiateDispute({
    escrowId: escrowId,
    reason: reason,
    evidence: [
      { type: 'image', url: 'https://...' },
      { type: 'description', text: 'Item has damage not shown in listing' }
    ]
  });

  console.log('Dispute created:', dispute.disputeId);
  console.log('Arbitration deadline:', dispute.deadline);

  return dispute;
}
Best Practice

For items valued over $1,000, we recommend using escrow transfers to protect both parties and ensure a smooth transaction.

Edit this page on GitHub
Type to search documentation...