Saltar al contenido

Run a notification gateway

A trade moves on a timer: a reservation expires, a payment gets marked sent, an escrow releases. People who are not staring at the app need telling. Notification gateways do that — you register the channels you can deliver on, deliver what a wallet has opted into, and report back what happened to each attempt.

Hito 6 de 6 · Run the infrastructure

Antes de empezar

  • An account with whatever you deliver through — an SMTP or email API, an SMS provider, a Telegram bot, a push service — the protocol supplies none of these
  • A publicly reachable endpoint for applications to send you delivery requests, if you register a webhook channel
  • Reachable access to an OpenFiat node's JSON-RPC endpoint, your own or someone else's
  • A persistent wallet.json for the gateway's identity — delivery reports are signed with it and your operational reputation accrues to it

Pasos

  1. 01

    Create the gateway's identity

    A gateway is identified by an Ed25519 keypair in Solana CLI wallet.json format, the same as every other provider. Generate it once and keep it: every delivery report you publish is signed with it, and §18's provider reputation is derived from those reports.

    shell
    solana-keygen new --outfile /etc/openfiat/gateway-wallet.json
    chmod 600 /etc/openfiat/gateway-wallet.json
  2. 02

    Register the channels you deliver on

    Notification gateways have no registration event of their own — you register through the ordinary service registry (OFS-1500) with a Notifications service type, naming one channel: Email, Telegram, Sms, Push or Webhook. `endpoints` is where applications actually reach you, so for a webhook gateway that is the URL you serve. Nobody approves the registration; you publish it and the network gossips it.

    Rust
    use openfiat_registry::Registration;
    use openfiat_sdk::{Client, ClientConfig};
    use openfiat_types::{NotificationChannel, ServiceId, ServiceType, Timestamp};
    
    let client = Client::new(ClientConfig {
        endpoint: "http://localhost:7080".to_string(),
        ..ClientConfig::default()
    });
    
    let registration = Registration {
        service_id: ServiceId::new("my-notification-gateway-1"),
        service_type: ServiceType::Notifications(NotificationChannel::Webhook),
        provider: provider_peer_id.clone(),
        provider_public_key: provider.public_key(),
        // Where applications send you delivery requests.
        endpoints: vec!["https://notify.example.com/openfiat".to_string()],
        supported_ofs: vec![1500, 6000],
        region: None,
        capabilities: vec!["Webhook".to_string()],
        pricing: None,
        timestamp: Timestamp::now(),
    };
    client.send_provider_register(registration, &provider).await?;
    TypeScript
    import { Client, providers, toBytes, type Registration } from "@openfiat/sdk";
    
    const client = new Client({ endpoint: "http://localhost:7080", timeoutMs: 30_000 });
    
    const registration: Registration = {
      service_id: "my-notification-gateway-1",
      service_type: { Notifications: "Webhook" },
      provider: toBytes(providerId),
      provider_public_key: toBytes(provider.publicKey),
      endpoints: ["https://notify.example.com/openfiat"],
      supported_ofs: [1500, 6000],
      region: null,
      capabilities: ["Webhook"],
      pricing: null,
      timestamp: Date.now(),
    };
    await providers.sendProviderRegister(client, registration, provider);
  3. 03

    Deliver only what a wallet asked for

    Subscriptions belong to the wallet, not to you — a wallet publishes one signed record listing the categories it wants, and the latest one fully replaces the previous. There are five categories (Trading, Marketplace, Disputes, Governance, Infrastructure) and sixteen triggers grouped under them, so a wallet that enabled Trading gets ReservationCreated through TradeCompleted, and nothing from Governance. Read the subscription before delivering rather than assuming.

    Rust
    // What has this wallet opted into?
    let subscription = client.get_subscription(&recipient_wallet).await?;
    
    // `Subscription::wants` maps a trigger to its category for you.
    if let Some(subscription) = subscription {
        if subscription.wants(NotificationTrigger::TradeCompleted) {
            // ... deliver
        }
    }
  4. 04

    Write the channel adapter

    This is the part you supply. `openfiat-notifications` defines the plugin interface and ships no adapters for any channel — email, SMS, Telegram and the rest are all expected to be implemented externally against the trait. Two rules constrain what you write: a gateway never creates protocol events, it only delivers ones already verified upstream (§17); and it receives only what delivery requires — a destination and rendered content, never the trade details that produced it, balances, or dispute evidence (§19).

    Rust
    use openfiat_notifications::{NotificationPayload, NotificationProvider};
    use openfiat_notifications::NotificationError;
    use openfiat_types::NotificationChannel;
    
    struct EmailGateway { /* your SMTP or API client */ }
    
    impl NotificationProvider for EmailGateway {
        fn channel(&self) -> NotificationChannel {
            NotificationChannel::Email
        }
    
        fn send(&self, payload: &NotificationPayload) -> Result<(), NotificationError> {
            // `destination` is the address; `subject` and `body` are already
            // rendered. Nothing else about the trade is available here, by design.
            self.deliver(&payload.destination, &payload.subject, &payload.body)
        }
    }
  5. 05

    Report what happened

    Every attempt gets a signed delivery report. The status is one of Queued, Sent, Delivered, Read, Failed, Retried or Expired, and each maps to its own gossip event — reporting a Failed honestly is what lets applications route around you, and it is how §18 provider reputation stays meaningful. Reports are keyed by notification id and the latest one wins, so you can report Sent and later Delivered for the same id.

    Rust
    use openfiat_notifications::events::DeliveryReport;
    use openfiat_notifications::{DeliveryStatus, NotificationId, NotificationTrigger};
    
    let report = DeliveryReport {
        notification_id: NotificationId::new("notification-1"),
        service_id: service_id.clone(),
        provider: provider_peer_id.clone(),
        provider_public_key: provider.public_key(),
        recipient_wallet: recipient_wallet.clone(),
        trigger: NotificationTrigger::TradeCompleted,
        status: DeliveryStatus::Delivered,
        timestamp: Timestamp::now(),
    };
    client.send_delivery_report(report, &provider).await?;
    TypeScript
    import { notifications, type DeliveryReport } from "@openfiat/sdk";
    
    const report: DeliveryReport = {
      notification_id: "notification-1",
      service_id: serviceId,
      provider: toBytes(providerId),
      provider_public_key: toBytes(provider.publicKey),
      recipient_wallet: toBytes(walletId),
      trigger: "TradeCompleted",
      status: "Delivered",
      timestamp: Date.now(),
    };
    await notifications.sendDeliveryReport(client, report, provider);
  6. 06

    Check the receipts came back

    Reading receipts back for a wallet is the quickest confirmation that your reports were accepted and propagated. Applications choose providers on reputation, latency, proximity, channel and cost — the protocol deliberately does not mandate a routing algorithm — so a clean, honest receipt history is the whole of your visible track record.

    TypeScript
    const receipts = await notifications.getDeliveryReceiptsByWallet(client, walletId);
    console.log(`delivery receipts for this wallet: ${receipts.length}`);
  7. 07

    What the stake does, and what it does not

    The stake minimum is real: the staking program enforces 5,000 OPEN for the NotificationProvider role, on the deployed devnet config, and rejects a stake or an unstake that would leave you below it. What it does not do is gate anything else — nothing in the registry or the notification crates reads your stake, so registering and delivering work whether or not you have posted it. The earnings half is not built at all: no notification fee exists anywhere in the protocol, so there is nothing to take a share of. Contact-handle verification is the same story — the identity crate defines the claim and its Verified status, but there is no protocol OTP flow, so the wallet application runs that externally and publishes the result.