Intent

Reference documentation for the Zypp intent data model — the core type that flows through the entire pipeline.

Intent Structure

pub struct Intent {
    pub id: uuid::Uuid,              // Stable identity for retry correlation
    pub content_hash: u64,           // FNV-1a fingerprint of the serialized payload
    pub intent_type: IntentType,     // Payment | Ticket | Action
    pub payload: IntentPayload,      // Typed payload (see below)
    pub trust_tier: TrustTier,       // Unsigned | Signed
    pub envelope: Option<CryptographicEnvelope>,  // Signature + nonce
}

Fields

FieldTypeDescription
idUuidStable identity for retry correlation and idempotency keys
content_hashu64FNV-1a 64-bit hash of the serialized payload. Used for local dedup — equal hash = same logical action. Computed automatically in Intent::new()
intent_typeIntentTypeClosed enum — Payment, Ticket, or Action
payloadIntentPayloadVariant-specific payload (see below)
trust_tierTrustTierSecurity tier: Unsigned (fingerprint-only) or Signed (signature + nonce)
envelopeOption<CryptographicEnvelope>Present when trust_tier == Signed. Bundles signature, public key, and per-key monotonic nonce

IntentType

pub enum IntentType {
    Payment,
    Ticket,
    Action,
}

A closed enum — no arbitrary string passthrough. Adding a new variant requires an SDK version bump.

IntentPayload

pub enum IntentPayload {
    Payment(PaymentPayload),                           // Fixed schema
    Ticket { payload: FreeFormPayload, signatures: DualSignatureEnvelope },  // Dual-signature
    Action(FreeFormPayload),                           // Free-form
}

PaymentPayload (SDK-fixed)

pub struct PaymentPayload {
    pub amount: u64,         // lamports or smallest token unit
    pub asset: Vec<u8>,      // Mint account address (32 bytes)
    pub recipient: Vec<u8>,  // Recipient wallet address (32 bytes)
}

Fixed schema owned strictly by the SDK to guarantee payment safety.

FreeFormPayload (dev-owned)

pub struct FreeFormPayload {
    pub kind: String,   // e.g. "move_player", "concert_vip_redeem"
    pub data: Vec<u8>,  // Serialized application-specific fields
}

The SDK does not parse or validate data. The developer defines the schema.

TrustTier

pub enum TrustTier {
    Unsigned,  // No cryptographic signature; tracked via device/session fingerprint
    Signed,    // Cryptographically signed; automatically bundles replay protection
}
  • Unsigned: No signature required. Suitable for low-stakes actions like move_avatar or ping.
  • Signed: Ed25519 signature with per-key monotonic nonce. Mandatory for Payment intents. Recommended for high-stakes actions.

CryptographicEnvelope

pub struct CryptographicEnvelope {
    pub signature: Vec<u8>,  // Ed25519 signature bytes
    pub public_key: Vec<u8>, // Signer's public key
    pub nonce: u64,          // Per-key monotonically increasing counter
}

This envelope is verified by the sync engine before allowing the intent into the sync pipeline. The nonce is scoped to the public key — two distinct devices sharing a counter value would be mutually replayable, so per-key monotonicity prevents cross-device replay.

DualSignatureEnvelope (Ticket-specific)

pub struct DualSignatureEnvelope {
    pub issuer_signature: Vec<u8>,      // Proof of legitimacy
    pub holder_signature: Option<Vec<u8>>,  // Proof of execution
    pub nonce: u64,
}

Tickets carry two separate signature fields:

  • Issuer signature: Proves the ticket was legitimately created by the event issuer
  • Holder signature: Proves the device performed the redemption

Signing order is enforced by guards:

  1. Issuer signature must be set first
  2. Holder signature requires non-empty issuer signature

Content Hash

Every intent gets a deterministic FNV-1a 64-bit content fingerprint computed from the serialized payload only:

// types.rs
let serialized = bincode::serde::encode_to_vec(&payload, bincode::config::standard()).unwrap();
let content_hash = fnv1a(&serialized);

The hash covers only IntentPayload — not intent_type, trust_tier, envelope, or id. This is by design: dedup keys on the logical action, not on signature material. Two intents with identical payloads but different trust tiers produce the same content_hash.

Intent Lifecycle

// Create an Intent
let intent = Intent::new(
    uuid::Uuid::new_v4(),
    IntentType::Action,
    IntentPayload::Action(FreeFormPayload {
        kind: "swap_tokens".into(),
        data: bincode::serde::encode_to_vec(&swap_params, bincode::config::standard()).unwrap(),
    }),
    TrustTier::Signed,
    None, // envelope completed downstream
);
// content_hash is computed automatically from the payload

QueueEntry

pub struct QueueEntry {
    pub intent: Intent,
    pub status: QueueStatus,
    pub created_at: u64,
    pub updated_at: u64,
}

Each intent in the queue is wrapped in a QueueEntry that tracks its lifecycle status and timestamps. Status transitions are enforced at the type level:

impl QueueEntry {
    pub fn mark_provisional(&mut self);  // Pending → SyncedProvisional
    pub fn finalize(&mut self);          // SyncedProvisional → SyncedFinalized
    pub fn fail(&mut self, stage, reason); // Any → Failed (except SyncedFinalized)
}

FailureStage

pub enum FailureStage {
    IntentCreation,
    IntegrityCheck,
    DeliveryValidation,
    Settlement,
}

RejectionReason

pub enum RejectionReason {
    DuplicateInLocalQueue,
    SignatureInvalid,
    TamperedPayload,
    ValidationError { code: u32, message: String, details: Option<Vec<u8>> },
    L1Reversion,
}

Next Steps

  • Understand the Queue for lifecycle and state transitions
  • Configure Settlement routing
  • See the type design document in rust-core/TYPES_DESIGN.md for detailed rationale