Skip to main content

Overview

The Trust Registry contract (identipay::trust_registry) maintains a shared on-chain database of verified merchants. Wallets query this registry to verify merchant legitimacy before signing payment intents. Per the whitepaper section 6, the registry stores each merchant’s:
  • Decentralized Identifier (DID)
  • Human-readable name
  • Sui payment address
  • X25519 public key for ECDH encryption
  • Hostname for URI resolution
  • Active/inactive status

Source Code

Location: contracts/sources/trust_registry.move:5

Data Structures

TrustRegistry

Shared registry object holding all merchant records.
id
UID
required
Sui object identifier
admin
address
required
Admin address authorized to register merchants
merchants
Table<String, MerchantEntry>
required
Maps DID to merchant entry
public struct TrustRegistry has key {
    id: UID,
    admin: address,
    merchants: Table<String, MerchantEntry>,
}

MerchantEntry

Represents a registered merchant’s verified identity.
did
String
required
Decentralized Identifier (e.g., “did:identipay:shop.example.com:merchant123”)
name
String
required
Human-readable merchant name for wallet display
sui_address
address
required
Merchant’s Sui address for receiving payments
public_key
vector<u8>
required
X25519 public key (32 bytes) for ECDH artifact encryption
hostname
String
required
Merchant’s hostname for URI resolution
active
bool
required
Whether the merchant is currently active
public struct MerchantEntry has store, copy, drop {
    did: String,
    name: String,
    sui_address: address,
    public_key: vector<u8>,
    hostname: String,
    active: bool,
}

Events

MerchantRegistered

Emitted when a new merchant is registered.
did
String
Merchant’s DID
name
String
Merchant’s display name
sui_address
address
Payment address
hostname
String
Merchant’s hostname

MerchantDeactivated

Emitted when a merchant is deactivated.
did
String
Deactivated merchant’s DID

Entry Functions

register_merchant

Register a new merchant in the trust registry. Only callable by admin. Function Signature:
entry fun register_merchant(
    registry: &mut TrustRegistry,
    did: String,
    name: String,
    sui_address: address,
    public_key: vector<u8>,
    hostname: String,
    ctx: &TxContext,
)
registry
&mut TrustRegistry
required
Mutable reference to the trust registry
did
String
required
Unique decentralized identifier for the merchant
name
String
required
Human-readable merchant name
sui_address
address
required
Merchant’s Sui address for receiving payments
public_key
vector<u8>
required
X25519 public key (must be exactly 32 bytes)
hostname
String
required
Merchant’s hostname
Errors:
  • ENotAdmin (0): Caller is not the registry admin
  • EEmptyDID (4): DID is empty string
  • EEmptyName (5): Name is empty string
  • EInvalidPublicKeyLength (3): Public key is not 32 bytes
  • EMerchantAlreadyExists (1): DID already registered
Location: trust_registry.move:84-116 Example:
import { TransactionBlock } from '@mysten/sui.js/transactions';

const tx = new TransactionBlock();

tx.moveCall({
  target: `${PACKAGE_ID}::trust_registry::register_merchant`,
  arguments: [
    tx.object(REGISTRY_ID),
    tx.pure('did:identipay:shop.example.com:merchant123'),
    tx.pure('Example Shop'),
    tx.pure('0x1234...'), // merchant sui address
    tx.pure(Array.from(publicKeyBytes)), // 32 bytes
    tx.pure('shop.example.com'),
  ],
});

// Admin signs and executes
await adminWallet.signAndExecuteTransactionBlock({
  transactionBlock: tx,
});

deactivate_merchant

Deactivate a merchant (soft delete). Only callable by admin. Function Signature:
entry fun deactivate_merchant(
    registry: &mut TrustRegistry,
    did: String,
    ctx: &TxContext,
)
registry
&mut TrustRegistry
required
Mutable reference to the trust registry
did
String
required
DID of merchant to deactivate
Errors:
  • ENotAdmin (0): Caller is not the registry admin
  • EMerchantNotFound (2): DID not found in registry
Location: trust_registry.move:119-131

update_merchant_key

Rotate a merchant’s public key. Only callable by admin. Function Signature:
entry fun update_merchant_key(
    registry: &mut TrustRegistry,
    did: String,
    new_public_key: vector<u8>,
    ctx: &TxContext,
)
registry
&mut TrustRegistry
required
Mutable reference to the trust registry
did
String
required
DID of merchant to update
new_public_key
vector<u8>
required
New X25519 public key (must be exactly 32 bytes)
Errors:
  • ENotAdmin (0): Caller is not the registry admin
  • EInvalidPublicKeyLength (3): Public key is not 32 bytes
  • EMerchantNotFound (2): DID not found in registry
Location: trust_registry.move:134-146

Public Functions

lookup_merchant

Look up a merchant by DID. Aborts if not found.
public fun lookup_merchant(
    registry: &TrustRegistry,
    did: String
): &MerchantEntry
registry
&TrustRegistry
required
Reference to the trust registry
did
String
required
Merchant’s DID
entry
&MerchantEntry
Reference to the merchant’s entry
Location: trust_registry.move:151-154

is_merchant_active

Check if a merchant exists and is active.
public fun is_merchant_active(
    registry: &TrustRegistry,
    did: String
): bool
registry
&TrustRegistry
required
Reference to the trust registry
did
String
required
Merchant’s DID
active
bool
Returns true if merchant exists and is active
Location: trust_registry.move:157-163

MerchantEntry Accessors

The following functions extract fields from a MerchantEntry:
public fun merchant_did(entry: &MerchantEntry): String
public fun merchant_name(entry: &MerchantEntry): String
public fun merchant_sui_address(entry: &MerchantEntry): address
public fun merchant_public_key(entry: &MerchantEntry): vector<u8>
public fun merchant_hostname(entry: &MerchantEntry): String
public fun merchant_active(entry: &MerchantEntry): bool
Location: trust_registry.move:167-172

Usage Example

import { identiPayClient } from '@identipay/sdk';

// User scans merchant QR code
const proposal = await fetchProposal(qrCode);

// Wallet verifies merchant is registered and active
const merchant = await identiPayClient.lookupMerchant(
  proposal.merchantDID
);

if (!merchant.active) {
  throw new Error('Merchant is not active');
}

// Display merchant info to user
showMerchantInfo({
  name: merchant.name,
  hostname: merchant.hostname,
  did: merchant.did,
});

// User approves, wallet encrypts receipt using merchant.publicKey
const sharedSecret = deriveECDH(
  ephemeralPrivateKey,
  merchant.publicKey
);

const encryptedReceipt = aesGcmEncrypt(
  receiptPayload,
  sharedSecret
);

// Proceed with settlement...

Security Considerations

Admin Centralization: The current implementation uses a single admin address. Production deployments should use a multisig or DAO for merchant registration decisions.
DID Format: The DID format is not enforced on-chain. Off-chain systems should validate DID structure before registration.
Key Rotation: Merchants can rotate public keys via update_merchant_key. Wallets should fetch fresh merchant data before each transaction to ensure they use the latest key.
Deactivation vs Deletion: Deactivated merchants remain in the registry with active = false. This preserves historical data while preventing new transactions.

DID Format

identiPay uses the following DID format:
did:identipay:<hostname>:<merchant-id>
Examples:
  • did:identipay:shop.example.com:merchant123
  • did:identipay:acme.corp:store-sf-downtown
  • did:identipay:coffee.local:barista-alice
The DID serves as a globally unique identifier that resolves to on-chain merchant data.

Settlement

Uses merchant address for payment routing

Receipt

Uses merchant public key for ECDH encryption

Build docs developers (and LLMs) love