Skip to main content

Overview

The Warranty contract (identipay::warranty) implements on-chain warranties as NFTs that are minted atomically with receipts during settlement. Warranties are:
  • Linked to receipts: Each warranty references its associated receipt
  • Encrypted: Warranty terms are AES-256-GCM ciphertext
  • Expirable: Plaintext expiry timestamp for on-chain enforcement
  • Optionally transferable: Can be marked transferable for resale scenarios

Source Code

Location: contracts/sources/warranty.move:6

Data Structures

WarrantyObject

On-chain warranty NFT minted during atomic settlement.
id
UID
required
Sui object identifier (unique warranty ID)
receipt_id
ID
required
Reference to the associated receipt object
merchant
address
required
Merchant who issued the warranty
intent_hash
vector<u8>
required
Intent hash binding this warranty to a specific transaction
encrypted_terms
vector<u8>
required
AES-256-GCM ciphertext of warranty terms (duration, exclusions, service endpoints)
terms_nonce
vector<u8>
required
12-byte GCM nonce for terms decryption
ephemeral_pubkey
vector<u8>
required
Ephemeral public key E = e*G (same as receipt, for ECDH decryption)
expiry
u64
required
Warranty expiry timestamp in epoch milliseconds (plaintext for on-chain enforcement)
transferable
bool
required
Whether this warranty can be transferred (e.g., on resale)
public struct WarrantyObject has key, store {
    id: UID,
    receipt_id: ID,
    merchant: address,
    intent_hash: vector<u8>,
    encrypted_terms: vector<u8>,
    terms_nonce: vector<u8>,
    ephemeral_pubkey: vector<u8>,
    expiry: u64,
    transferable: bool,
}

Events

WarrantyMinted

Emitted when a warranty is minted.
warranty_id
ID
Unique object ID of the warranty
receipt_id
ID
Associated receipt ID
merchant
address
Merchant who issued the warranty
intent_hash
vector<u8>
Intent hash
expiry
u64
Warranty expiration timestamp
transferable
bool
Whether warranty is transferable

WarrantyTransferred

Emitted when a warranty is transferred.
warranty_id
ID
Warranty object ID
from
address
Previous owner
to
address
New owner

Public Functions

mint_warranty

Mint a new warranty. Called by the settlement module during atomic execution. Function Signature:
public(package) fun mint_warranty(
    receipt_id: ID,
    merchant: address,
    intent_hash: vector<u8>,
    encrypted_terms: vector<u8>,
    terms_nonce: vector<u8>,
    ephemeral_pubkey: vector<u8>,
    expiry: u64,
    transferable: bool,
    ctx: &mut TxContext,
): WarrantyObject
receipt_id
ID
required
Object ID of the associated receipt
merchant
address
required
Merchant’s Sui address
intent_hash
vector<u8>
required
Intent hash
encrypted_terms
vector<u8>
required
AES-256-GCM ciphertext of warranty terms
terms_nonce
vector<u8>
required
12-byte GCM nonce (must be exactly 12 bytes)
ephemeral_pubkey
vector<u8>
required
Ephemeral X25519 public key (must be exactly 32 bytes)
expiry
u64
required
Warranty expiration timestamp (epoch ms, must be > 0)
transferable
bool
required
Whether the warranty can be transferred
warranty
WarrantyObject
The minted warranty object
Errors:
  • EInvalidNonceLength (2): Nonce is not 12 bytes
  • EInvalidEphemeralPubkey (3): Ephemeral pubkey is not 32 bytes
  • EEmptyTerms (4): Encrypted terms are empty
  • EInvalidExpiry (5): Expiry is 0
Visibility: public(package) — only callable by the settlement module. Location: warranty.move:65-103

transfer_warranty

Transfer a warranty to a new address. Only allowed if the warranty is marked as transferable and has not expired. Function Signature:
public fun transfer_warranty(
    warranty: WarrantyObject,
    recipient: address,
    ctx: &TxContext,
)
warranty
WarrantyObject
required
Warranty object to transfer (consumed by this function)
recipient
address
required
New owner’s address
Errors:
  • EWarrantyNotTransferable (0): Warranty has transferable = false
  • EWarrantyExpired (1): Current time exceeds warranty expiry
Location: warranty.move:107-122 Example:
import { TransactionBlock } from '@mysten/sui.js/transactions';

// User wants to transfer warranty when selling an item
const tx = new TransactionBlock();

tx.moveCall({
  target: `${PACKAGE_ID}::warranty::transfer_warranty`,
  arguments: [
    tx.object(warrantyObjectId),
    tx.pure(newOwnerAddress),
  ],
});

await wallet.signAndExecuteTransactionBlock({ transactionBlock: tx });

Accessors

receipt_id

public fun receipt_id(warranty: &WarrantyObject): ID
Returns the associated receipt ID.

merchant

public fun merchant(warranty: &WarrantyObject): address
Returns the merchant’s address.

expiry

public fun expiry(warranty: &WarrantyObject): u64
Returns the expiry timestamp.

is_transferable

public fun is_transferable(warranty: &WarrantyObject): bool
Returns whether the warranty is transferable.

is_expired

public fun is_expired(warranty: &WarrantyObject, ctx: &TxContext): bool
Returns whether the warranty has expired. Location: warranty.move:149-155

Warranty Terms Format

The encrypted terms are a JSON object:
{
  "version": "1.0",
  "warranty": {
    "type": "manufacturer",
    "duration": "90 days",
    "coverage": [
      "Manufacturing defects",
      "Material defects",
      "Workmanship issues"
    ],
    "exclusions": [
      "Water damage",
      "Accidental damage",
      "Normal wear and tear"
    ],
    "serviceProcess": {
      "claimEmail": "[email protected]",
      "claimPhone": "+1-800-WARRANTY",
      "claimPortal": "https://example.com/warranty/claim",
      "requiredDocuments": [
        "Original receipt",
        "Photos of defect",
        "Product serial number"
      ]
    },
    "merchant": {
      "name": "Example Electronics",
      "supportEmail": "[email protected]"
    }
  }
}

Usage Example

import { aesGcmDecrypt, hkdf } from '@identipay/crypto';
import { sha256 } from '@noble/hashes/sha256';

// Fetch warranty from chain
const warranty = await suiClient.getObject({
  id: warrantyId,
  options: { showContent: true },
});

const {
  encrypted_terms,
  terms_nonce,
  ephemeral_pubkey,
  expiry,
  transferable,
} = warranty.data.content.fields;

// Check if expired
if (expiry <= Date.now()) {
  console.log('Warranty has expired');
  return;
}

// Derive shared secret (same as receipt decryption)
const sharedSecret = deriveECDH(
  stealthPrivateKey,
  merchantPublicKeyFromRegistry
);

// Derive AES key
const aesKey = hkdf(
  sha256,
  sharedSecret,
  'identipay-warranty',
  '',
  32
);

// Decrypt terms
const ciphertext = encrypted_terms.slice(0, -16);
const tag = encrypted_terms.slice(-16);

const plaintext = aesGcmDecrypt(
  aesKey,
  terms_nonce,
  ciphertext,
  tag
);

const warrantyTerms = JSON.parse(plaintext);
console.log('Warranty coverage:', warrantyTerms.warranty.coverage);
console.log('How to claim:', warrantyTerms.warranty.serviceProcess);

Transferability Scenarios

Non-Transferable Warranties (Soulbound)

For services or items that shouldn’t be resold:
// Merchant issues non-transferable warranty
await executeSettlement({
  // ...
  warranty_transferable: false,
});

// Later, if user tries to transfer:
try {
  await transferWarranty(warranty, newOwner);
} catch (e) {
  // Fails with EWarrantyNotTransferable
  console.error('This warranty cannot be transferred');
}
Use cases:
  • Software licenses (personal, non-transferable)
  • Service warranties tied to original purchaser
  • Medical products with liability concerns

Transferable Warranties

For physical goods that may be resold:
// Merchant issues transferable warranty
await executeSettlement({
  // ...
  warranty_transferable: true,
});

// User can transfer when selling the item
await transferWarranty(warranty, newOwnerAddress);
Use cases:
  • Electronics (phones, laptops)
  • Appliances (refrigerators, washing machines)
  • Vehicles
  • Collectibles with authenticity guarantees

Warranty Claims

Warranty claims happen off-chain using the decrypted terms:
1

Buyer Decrypts

Buyer decrypts warranty terms to find claim process (email, portal, phone).
2

Submit Claim

Buyer submits claim via merchant’s service portal with receipt proof.
3

Merchant Verifies

Merchant verifies:
  • Warranty has not expired (check expiry on-chain)
  • Receipt is authentic (check receipt_id matches on-chain)
  • Claim falls under coverage (check decrypted terms)
4

Resolution

Merchant processes claim (repair, replacement, refund) per terms.

Privacy Guarantees

Warranty terms (coverage, exclusions, process) are encrypted on-chain. Only buyer and merchant can decrypt.
Expiry is plaintext for on-chain enforcement. This prevents expired warranties from being transferred.
Transferability flag is plaintext so buyers know if they can resell the item with warranty.
Warranty is owned by a stealth address. On-chain observers cannot link it to buyer identity.

Security Considerations

Expiry Enforcement: The transfer_warranty function checks expiry on-chain. Expired warranties cannot be transferred, preventing fraud.
Receipt Linkage: Each warranty references its receipt via receipt_id. This enables verification that the warranty is authentic and corresponds to a real purchase.
Merchant Decryption: Merchants can decrypt all warranties they issued (for claim processing). This is necessary for warranty fulfillment.
Transfer Events: Warranty transfers emit events. Merchants can track transfers to update their claim databases.

Settlement

Mints warranties during atomic settlement

Receipt

Associated receipt linked via receipt_id

Trust Registry

Provides merchant public key for encryption

Build docs developers (and LLMs) love