Skip to main content

Overview

The Settlement contract (identipay::settlement) is the core orchestrator for identiPay transactions. It implements atomic execution of commerce settlements, ensuring all operations succeed or fail together. Per the whitepaper (section 5), the settlement uses Programmable Transaction Blocks (PTBs) to atomically:
  1. Verify ZK eligibility proofs (if age-gated)
  2. Verify buyer’s intent signature
  3. Transfer payment to merchant
  4. Mint encrypted receipt to buyer’s stealth address
  5. Optionally mint warranty
  6. Emit settlement event

Source Code

Location: contracts/sources/settlement.move:12

Data Structures

SettlementState

Shared state for settlement replay protection.
id
UID
required
Sui object identifier
executed_intents
Table<vector<u8>, bool>
required
Maps intent hash to execution status. Prevents replay of signed intents.
public struct SettlementState has key {
    id: UID,
    executed_intents: Table<vector<u8>, bool>,
}

SettlementEvent

Emitted on successful settlement. Indexed by intent hash, not buyer identity.
intent_hash
vector<u8>
SHA3-256 hash of the canonicalized intent
merchant
address
Merchant’s Sui address receiving payment
amount
u64
Payment amount in smallest token units
receipt_id
ID
Object ID of the minted receipt
warranty_id
Option<ID>
Optional warranty object ID
buyer_stealth_address
address
One-time stealth address where artifacts were delivered
public struct SettlementEvent has copy, drop {
    intent_hash: vector<u8>,
    merchant: address,
    amount: u64,
    receipt_id: ID,
    warranty_id: Option<ID>,
    buyer_stealth_address: address,
}

Entry Functions

execute_commerce

Execute a full commerce settlement atomically with ZK proof verification. Function Signature:
entry fun execute_commerce<T>(
    state: &mut SettlementState,
    payment: &mut Coin<T>,
    amount: u64,
    merchant: address,
    buyer_stealth_addr: address,
    intent_sig: vector<u8>,
    intent_hash: vector<u8>,
    buyer_pubkey: vector<u8>,
    proposal_expiry: u64,
    zk_vk: &VerificationKey,
    zk_proof: vector<u8>,
    zk_public_inputs: vector<u8>,
    encrypted_payload: vector<u8>,
    payload_nonce: vector<u8>,
    ephemeral_pubkey: vector<u8>,
    encrypted_warranty_terms: vector<u8>,
    warranty_terms_nonce: vector<u8>,
    warranty_expiry: u64,
    warranty_transferable: bool,
    ctx: &mut TxContext,
)
state
&mut SettlementState
required
Mutable reference to the shared settlement state
payment
&mut Coin<T>
required
Coin to split payment from. Change remains with sender.
amount
u64
required
Payment amount to transfer to merchant
merchant
address
required
Merchant’s Sui address for payment delivery
buyer_stealth_addr
address
required
One-time stealth address for receipt/warranty delivery
intent_sig
vector<u8>
required
Ed25519 signature (64 bytes) over intent_hash
intent_hash
vector<u8>
required
SHA3-256 hash of canonicalized commerce proposal
buyer_pubkey
vector<u8>
required
Buyer’s Ed25519 public key (32 bytes)
proposal_expiry
u64
required
Proposal expiration timestamp (epoch ms). Must be in the future.
zk_vk
&VerificationKey
required
ZK verification key for eligibility proof
zk_proof
vector<u8>
required
Groth16 proof bytes
zk_public_inputs
vector<u8>
required
Public inputs for ZK circuit
encrypted_payload
vector<u8>
required
AES-256-GCM encrypted receipt payload
payload_nonce
vector<u8>
required
12-byte GCM nonce for receipt decryption
ephemeral_pubkey
vector<u8>
required
Ephemeral X25519 public key (32 bytes) for ECDH
encrypted_warranty_terms
vector<u8>
Encrypted warranty terms. Pass empty vector if no warranty.
warranty_terms_nonce
vector<u8>
12-byte GCM nonce for warranty decryption
warranty_expiry
u64
Warranty expiration timestamp (epoch ms)
warranty_transferable
bool
Whether warranty can be transferred
Errors:
  • EInvalidAmount (0): Amount is zero or exceeds payment coin value
  • EProposalExpired (1): Current time exceeds proposal_expiry
  • EIntentAlreadyExecuted (2): Intent hash was already settled
Location: settlement.move:77-167

execute_commerce_no_zk

Execute a commerce settlement without ZK proof verification. For transactions without age gates or other constraints. Function Signature:
entry fun execute_commerce_no_zk<T>(
    state: &mut SettlementState,
    payment: &mut Coin<T>,
    amount: u64,
    merchant: address,
    buyer_stealth_addr: address,
    intent_sig: vector<u8>,
    intent_hash: vector<u8>,
    buyer_pubkey: vector<u8>,
    proposal_expiry: u64,
    encrypted_payload: vector<u8>,
    payload_nonce: vector<u8>,
    ephemeral_pubkey: vector<u8>,
    encrypted_warranty_terms: vector<u8>,
    warranty_terms_nonce: vector<u8>,
    warranty_expiry: u64,
    warranty_transferable: bool,
    ctx: &mut TxContext,
)
Parameters are identical to execute_commerce except ZK-related parameters are omitted. Location: settlement.move:171-248

Public Functions

is_intent_executed

Check if an intent hash has already been executed.
public fun is_intent_executed(
    state: &SettlementState,
    intent_hash: vector<u8>
): bool
state
&SettlementState
required
Reference to settlement state
intent_hash
vector<u8>
required
Intent hash to check
result
bool
Returns true if intent was already executed
Location: settlement.move:253-255

Usage Example

import { TransactionBlock } from '@mysten/sui.js/transactions';
import { identiPayClient } from '@identipay/sdk';

// Buyer wallet constructs settlement PTB
const tx = new TransactionBlock();

// Prepare intent signature
const intentHash = await computeIntentHash(proposal);
const intentSig = await wallet.signIntent(intentHash);

// Generate stealth address
const stealthAddr = await deriveStealthAddress(
  merchantMetaAddress,
  ephemeralPrivateKey
);

// Encrypt receipt payload
const { ciphertext, nonce } = await encryptPayload(
  receiptData,
  merchantPublicKey,
  ephemeralPrivateKey
);

// Call settlement
tx.moveCall({
  target: `${PACKAGE_ID}::settlement::execute_commerce`,
  typeArguments: [USDC_TYPE],
  arguments: [
    tx.object(SETTLEMENT_STATE_ID),
    tx.object(paymentCoinId),
    tx.pure(amount),
    tx.pure(merchantAddress),
    tx.pure(stealthAddr),
    tx.pure(intentSig),
    tx.pure(intentHash),
    tx.pure(buyerPublicKey),
    tx.pure(proposalExpiry),
    tx.object(zkVerificationKeyId),
    tx.pure(zkProof),
    tx.pure(zkPublicInputs),
    tx.pure(ciphertext),
    tx.pure(nonce),
    tx.pure(ephemeralPubkey),
    tx.pure([]), // no warranty
    tx.pure([]),
    tx.pure(0),
    tx.pure(false),
  ],
});

const result = await wallet.signAndExecuteTransactionBlock({
  transactionBlock: tx,
});

Security Considerations

Replay Protection: The contract stores executed intent hashes indefinitely. In production, consider implementing a time-based cleanup mechanism for very old intents to manage storage costs.
Atomicity: All operations execute in a single PTB. If any step fails (ZK proof invalid, signature invalid, insufficient balance), the entire transaction reverts with no state changes.
Generic Tokens: The contract is generic over <T> and works with any Sui coin type. Most deployments use Coin<USDC> on testnet.

Intent

Signature verification logic

Receipt

Receipt minting and structure

Warranty

Warranty minting and transfers

ZK Verifier

Groth16 proof verification

Build docs developers (and LLMs) love