Skip to content

Latest commit

 

History

History
853 lines (670 loc) · 31.3 KB

File metadata and controls

853 lines (670 loc) · 31.3 KB

Ed4l02 — PBW 2026 — Récapitulatif technique

Dernière mise à jour : 11 avril 2026


Vision

Infrastructure de paiement on-chain pour entreprises et institutions sur le XRP Ledger, construite autour du protocole x402 (HTTP 402 Payment Required). Un marchand protège ses API en une ligne, ses clients paient en XRP/IOU/MPT, et les transactions sont loggées en SQLite avec export ISO 20022.


Architecture globale

┌─────────────────────────────────────────────────────────────────────┐
│                        Client (AI agent / browser)                  │
└──────────────────────────────────┬──────────────────────────────────┘
                                   │ 1. GET /resource (no header)
                                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│              Resource Server  (ed4l02-x402 middleware)              │
│  Express / Next.js / Hono / Cloudflare Workers                      │
│                                                                     │
│  app.get('/premium', xrpl402Express({ ...opts }), handler)          │
└──────────────────────────────────┬──────────────────────────────────┘
         2. 402 + X-Payment-Required │         │ 4. POST /settle
         ◄────────────────────────────         ▼
                                   ┌───────────────────────────────────┐
  3. sign tx + X-Payment-Signature │       ed4l02-facilitator           │
  ──────────────────────────────►  │                                   │
                                   │  verify → credential gate → submit│
                                   │  → SQLite log → { tx_hash }       │
                                   └───────────────────┬───────────────┘
                                                       │ 5. submitAndWait
                                                       ▼
                                               XRPL Ledger (testnet/mainnet)

Packages

ed4l02-x402 — middleware npm

Ajoute le gate x402 à n'importe quelle route en une ligne.

Fichiers clés

ed4l02-x402/src/
├── middleware.ts    # xrpl402() + xrpl402Express()
├── types.ts         # PaymentPayload, PaymentDetails, VatBreakdown, Xrpl402Options...
├── vat.ts           # computeVat(), VAT_RATES_BPS
└── index.ts         # exports publics

Usage Express

import { xrpl402Express } from 'ed4l02-x402';

app.get('/premium',
  xrpl402Express({
    facilitatorUrl: 'https://ed4l02.onrender.com',
    network: 'testnet',
    asset: 'XRP',
    amount: '1000000',   // 1 XRP en drops
    payTo: 'rVotreAdresse...',
    // Optionnel — credential gate XLS-70 (AND)
    requiredCredentials: [{ issuer: 'rIssuer...', credentialType: 'KYC_18' }],
    // Optionnel — domain gate XLS-80 (OR)
    requiredDomain: { domainId: 'ABCD1234...' },
    // Optionnel — TVA auto (XRP uniquement)
    vatConfig: { jurisdiction: 'FR', ht_amount: '1000000' }, // 1 XRP HT → 1.2 XRP TTC
  }),
  handler
);

Assets supportés

Asset Format amount Notes
XRP "1000000" (drops) 1 XRP = 1 000 000 drops
IOU { currency, issuer, value } Trust lines, RLUSD...
MPT { mpt_issuance_id, value } XLS-33 Multi-Purpose Token

Headers x402

  • X-Payment-Required — 402 → base64(JSON) des conditions de paiement
  • X-Payment-Signature — client → base64(PaymentPayload) avec le tx signé
  • X-Payment-Response — 200 → base64(SettleResult) avec le tx_hash

Bug fix majeur : requiredCredentials et requiredDomain n'étaient pas forwardés au facilitator → corrigé dans buildPaymentDetails() avec conditional spreads.


ed4l02-facilitator — serveur Express

Settlement backend. Déployé sur Render : https://ed4l02.onrender.com

Fichiers clés

ed4l02-facilitator/src/
├── index.ts         # Routes : /health, /verify, /settle, /transactions, /marketplace
├── verify.ts        # Vérification offline du tx_blob (sans RPC)
├── settle.ts        # Pipeline : verify → TVA gate → credential gate → submit → SQLite
├── credentials.ts   # XLS-70 gate + EDEL_AGENT + XLS-80 gate + checkMerchantVatCredential
├── marketplace.ts   # CRUD marchands + endpoints : /merchants/listing, /marketplace
├── db.ts            # SQLite (better-sqlite3, WAL) : insertTx, getTxsByAddress
├── client.ts        # Pool de clients xrpl.js par réseau
└── types.ts         # TxLog, PaymentPayload, PaymentDetails, VatBreakdown, SettleResponse...

Endpoints

GET /health

{ "status": "ok", "service": "ed4l02-facilitator", "version": "0.1.0" }

POST /verify — vérification offline (pas de RPC)

  • Vérifie scheme, network, Account, Destination, DestinationTag, montant exact
  • Retourne { valid: true } ou { valid: false, reason: "..." }

POST /settle — pipeline complet

  1. Déduplication — sha256(tx_blob) en cache mémoire (TTL 120s)
  2. Vérification offline
  3. Gate TVA — si paymentDetails.vatBreakdown présent : vérifie que le MARCHAND tient EDEL_TVA_FR ou EDEL_TVA_CH (émis par EDEL_TRUSTED_ISSUER)
  4. Gate XLS-70 (requiredCredentials) — AND logic
  5. Gate XLS-80 (requiredDomain) — OR logic
  6. submitAndWait(tx_blob) avec timeout configurable (défaut 60s)
  7. Vérification meta.TransactionResult === "tesSUCCESS"
  8. Log SQLite (inclut les colonnes TVA)

GET /transactions?address=r...&limit=50&offset=0

{
  "total": 5,
  "transactions": [
    {
      "tx_hash": "267B64A5...",
      "from_address": "rSender...",
      "to_address": "rMarchand...",
      "asset": "XRP",
      "amount": "1200000",
      "network": "testnet",
      "settled_at": "2026-04-11T16:44:08.964Z",
      "facilitator": "http://localhost:3000",
      "vat_jurisdiction": "FR",
      "vat_ht_amount": "1000000",
      "vat_vat_amount": "200000",
      "vat_ttc_amount": "1200000",
      "vat_rate_bps": 2000
    }
  ]
}

POST /merchants/listing — déclarer/mettre à jour un marchand sur le marketplace

  • Header x-wallet-address doit correspondre à merchant.wallet dans le JSON
  • Upsert par wallet_address — le fichier JSON est la source de vérité
  • Body :
{
  "merchant": {
    "name": "Acme Weather",
    "wallet": "rVotreAdresse...",
    "description": "Real-time weather data API",
    "category": "data"
  },
  "endpoints": [
    { "url": "https://acme.com/api/weather", "title": "Current Weather", "price": "10000", "asset": "XRP" }
  ]
}

GET /marketplace — liste tous les marchands publics

  • Query params optionnels : category=data|ai|finance|media|tools|other, max_price=<drops>

GET /marketplace/:merchant_id — détail d'un marchand

SQLite — ed4l02.db (dans DATA_DIR)

Le fichier ed4l02.db contient 3 tables. WAL mode activé pour des écritures rapides et lectures non-bloquantes.

Table tx_logs — toutes les tx settlées

Colonne Type Notes
tx_hash TEXT PRIMARY KEY Hash XRPL de la tx confirmée
from_address TEXT Wallet expéditeur
to_address TEXT Wallet marchand
asset TEXT XRP / IOU / MPT
amount TEXT Drops (XRP) ou value string (IOU/MPT)
currency TEXT IOU uniquement
issuer TEXT IOU uniquement
mpt_id TEXT MPT uniquement
network TEXT mainnet / testnet / devnet
settled_at TEXT ISO 8601
facilitator TEXT URL du facilitator
vat_jurisdiction TEXT "FR" / "CH" / NULL
vat_ht_amount TEXT Montant HT en drops / NULL
vat_vat_amount TEXT Montant TVA en drops / NULL
vat_ttc_amount TEXT Montant TTC en drops / NULL
vat_rate_bps INTEGER 2000 = 20%, 810 = 8.1% / NULL

Table merchants — profils marchands du marketplace

Colonne Type Notes
id TEXT PRIMARY KEY UUID
wallet_address TEXT UNIQUE Adresse XRPL
name TEXT Nom du marchand
description TEXT Description
category TEXT data / ai / finance / media / tools / other
listed INTEGER 1 = visible sur le marketplace
created_at TEXT ISO 8601
updated_at TEXT ISO 8601

Table merchant_endpoints — routes x402 listées par marchand

Colonne Type Notes
id INTEGER PRIMARY KEY Auto-increment
merchant_id TEXT FK → merchants.id
url TEXT URL de l'endpoint protégé
title TEXT Titre affiché sur le marketplace
description TEXT Description
price_amount TEXT Prix en drops
price_asset TEXT XRP / IOU / MPT

Variables d'environnement

Variable Défaut Description
PORT 3000 Port HTTP
SETTLE_TIMEOUT_MS 60000 Timeout confirmation ledger (ms)
DATA_DIR ./data Dossier SQLite — le fichier ed4l02.db est créé ici
EDEL_TRUSTED_ISSUER Adresse XRPL de l'émetteur des credentials TVA/KYC
FACILITATOR_URL http://localhost:3000 URL publique du facilitator (affiché dans /marketplace)
XRPL_MAINNET_URL wss://xrplcluster.com Node mainnet
XRPL_TESTNET_URL wss://testnet.xrpl-labs.com Node testnet
XRPL_DEVNET_URL wss://s.devnet.rippletest.net:51233 Node devnet

Flows d'authentification / gating

Flow basique (XRP sans gate)

Client                Resource Server           Facilitator         XRPL
  │                        │                        │                 │
  │── GET /resource ──────▶│                        │                 │
  │◀── 402 + conditions ───│                        │                 │
  │   sign Payment tx      │                        │                 │
  │── GET /resource ──────▶│── POST /settle ───────▶│                 │
  │   X-Payment-Signature  │                        │── submitAndWait▶│
  │                        │                        │◀── tesSUCCESS ──│
  │◀── 200 + tx_hash ──────│◀── { success, hash } ──│                 │

Flow XLS-70 — credential gate (AND logic)

Le marchand configure requiredCredentials: [{ issuer, credentialType }].

À la réception du /settle, le facilitator :

  1. Appelle account_objects(sender, type: credential) sur le ledger validé
  2. Pour chaque RequiredCredential, vérifie que le sender possède un Credential objet avec :
    • Issuer correspondant
    • CredentialType en hex uppercase correspondant
    • Flags & lsfAccepted (0x00010000) activé
    • Expiration absent ou dans le futur (epoch Ripple)
  3. Toutes les credentials doivent être présentes (AND) → sinon rejet avant submit

Flow XLS-80 — PermissionedDomain gate (OR logic)

Le marchand configure requiredDomain: { domainId }.

  1. ledger_entry(domainId) → récupère le PermissionedDomain et sa liste AcceptedCredentials
  2. account_objects(sender, credential) → récupère les credentials du sender
  3. Le sender doit posséder au moins une credential de la liste du domaine (OR) → sinon rejet

Flow TVA auto — vérification credential marchand

Le marchand configure vatConfig: { jurisdiction: 'FR' | 'CH', ht_amount: '...' } sur sa route.

Ce que fait le middleware (ed4l02-x402)

  • Calcule TTC = HT × (1 + taux) en BigInt basis points (pas de flottant)
    • FR : 2000n bps → 20% ; CH : 810n bps → 8.1%
  • Override amount avec le TTC
  • Embed vatBreakdown: { jurisdiction, rate_bps, ht_amount, vat_amount, ttc_amount } dans PaymentDetails

Ce que fait le facilitator

  • Étape 3 du pipeline (avant gates credential/domain)
  • Vérifie que le MARCHAND (pay_to) tient EDEL_TVA_FR ou EDEL_TVA_CH émis par EDEL_TRUSTED_ISSUER
  • Si OK → poursuit le pipeline et logue les colonnes vat_* en SQLite
  • Si KO → rejet 402 "Merchant does not hold a valid EDEL_TVA_FR credential"

Credential types (hex)

  • EDEL_TVA_FR = 4544454C5F5456415F4652
  • EDEL_TVA_CH = 4544454C5F5456415F4348

Flow EDEL_AGENT — délégation AI agent ←→ humain KYC

Permet à un AI agent de payer au nom d'un humain KYC'd sans que l'humain expose ses credentials.

Setup (plateforme Ed4l02)

issuer ──CredentialCreate──▶ human  (type: KYC_18)
issuer ──CredentialCreate──▶ agent  (type: EDEL_AGENT, URI: hex(human_address))

human ──CredentialAccept──▶ ledger
agent ──CredentialAccept──▶ ledger

Flow au moment du paiement

agent.sign(Payment → merchant)
     ↓
facilitator.POST /settle  (from_address = agent)
     ↓
checkCredentials(agent, [{ issuer, KYC_18 }])
  → agent n'a pas KYC_18 directement
  → fallback: checkAgentDelegation()
       1. Trouve EDEL_AGENT sur agent (même issuer, accepté, non expiré)
       2. Décode URI hex → UTF-8 → adresse XRPL de l'humain
       3. isValidClassicAddress() sur l'adresse décodée
       4. account_objects(human) → vérifie que l'humain a KYC_18
       5. OK → log "[credentials] delegation accepted: agent X acting for Y"
     ↓
submit tx → 200 ✓

Credential EDEL_AGENT

  • CredentialType = 4544454C5F4147454E54 (hex de "EDEL_AGENT")
  • URI = hex(adresse XRPL de l'humain délégant) — ex: 724E4365633137... pour rNCEc17...
  • Émis par la plateforme EdelPay (même issuer que les credentials KYC)

Test d'intégration

9 steps (A–I) — fichier : ed4l02-facilitator/test-integration/run-test.ts

npx tsx test-integration/run-test.ts
Step Description Attendu
A GET /protected sans header 402
B Signature tx (sender → recipient, 1 XRP) tx signé
C GET /protected avec X-Payment-Signature 200 + tx_hash
D GET /credential-protected sans header 402
E Agent paye /credential-protected (délégation EDEL_AGENT → KYC_18 humain) 200 + tx_hash
F Sender sans credentials essaie /credential-protected 402 "missing credential"
G Issuer émet EDEL_TVA_FR sur le marchand (recipient), marchand accepte credential accepté
H Sender paye /vat-protected (1 XRP HT → 1.2 XRP TTC, TVA FR 20%) 200 + tx_hash
I Sender paye /vat-protected-no-cred (marchand sans EDEL_TVA_FR) 402 "does not hold EDEL_TVA_FR"

Le test :

  1. Démarre le facilitator en subprocess (avec EDEL_TRUSTED_ISSUER = adresse issuer)
  2. Connecte au testnet XRPL, fund 5 wallets (recipient, sender, issuer, human, agent)
  3. Émet KYC_18 (issuer→human) + EDEL_AGENT (issuer→agent, URI=hex(human)) on-chain
  4. Lance les steps A–F (basic + delegation flows)
  5. Émet EDEL_TVA_FR (issuer→recipient/marchand) on-chain — Step G
  6. Lance les steps H et I (TVA flow)

Frontend — frontend/

Dashboard Next.js pour les marchands + pages publiques marketplace + flow utilisateur.

Stack : Next.js 16 · React 18 · Tailwind · xrpl-connect · Space Grotesk

Navigation

Home | Marketplace | Merchant | Transactions | User
  • "List my API" n'est plus dans la nav globale — accessible depuis le dashboard marchand uniquement (feature réservée aux marchands onboardés)
  • "User" (/user) remplace l'ancien onglet "KYC" — flow KYC/identité utilisateur

Pages marchand

/merchant — Dashboard

  • 5 KPI cards : Total Revenue · This Week · Avg Payment · Unique Senders · TVA Collected
    • TVA Collected = somme des vat_amount sur toutes les tx + nombre de tx avec TVA
  • Bar chart SVG — revenus des 7 derniers jours (pur SVG, pas de lib externe)
  • Bouton "List my API →" dans le header de page → /merchant/marketplace-listing
  • Recent Payments — 8 derniers paiements, montant en XRP, sender raccourci, lien explorer au hover

/merchant/transactions — Historique complet

  • 4 KPI cards avec sparklines SVG (tendance haussière/baissière)
  • Bar chart SVG — 14 jours
  • Filtres :
    • Recherche texte (adresse sender ou montant)
    • Période : All / 7d / 30d / 90d
    • Asset : All / XRP / IOU / MPT
    • TVA : All / With TVA / Without TVA
    • Montant min/max en XRP
  • Table avec checkboxes multi-sélection
    • Avatar initiales coloré par adresse
    • Sender cliquable → copie dans le presse-papier
    • Montant en XRP avec badge TVA sous le montant quand applicable :
      +1.2 XRP
      [TVA FR 20%]  HT 1 · TVA 0.2
      
    • Date & heure complètes
    • Lien "View ↗" vers testnet.xrpl.org
  • Barre de sélection contextuelle avec export des sélectionnés
  • Bouton ISO 20022 — export CAMT.053 des tx visibles ou sélectionnées

/merchant/marketplace-listing — Lister son API

Formulaire JSON pour déclarer ses endpoints sur le marketplace Ed4l02.

/marketplace — Annuaire public des APIs x402

Catalogue des marchands ayant déclaré leurs endpoints. Filtrable par catégorie et prix.

/marketplace/[id] — Détail marchand

Affiche les endpoints, prix, et description d'un marchand.

/user — Dashboard utilisateur (KYC)

Flow d'onboarding KYC en 5 étapes : Wallet → Identity Type → Scan QR → Accept Credential → Complete.

API route proxy

frontend/app/api/merchants/[id]/transactions/route.js

Proxy vers le facilitator SQLite — map les champs TVA :

GET /api/merchants/me/transactions
  → GET http://localhost:3000/transactions?address=<walletAddress>
  → map TxLog → frontend shape :
      drops → XRP, vatBreakdown { jurisdiction, rate_bps, ht_amount, vat_amount, ttc_amount }

Variable : FACILITATOR_URL=http://localhost:3000 dans .env.local

Export ISO 20022 — CAMT.053

frontend/lib/camt053.js

Génère un XML camt.053.001.02 (Bank-to-Customer Statement) conforme ISO 20022 :

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
  <BkToCstmrStmt>
    <GrpHdr>
      <MsgId>EDELPAY-1744389848000</MsgId>
      <CreDtTm>2026-04-11T16:44:08Z</CreDtTm>
    </GrpHdr>
    <Stmt>
      <Acct>
        <Id><Othr><Id>rMarchand...</Id></Othr></Id>
        <Ccy>XRP</Ccy>
      </Acct>
      <TxsSummry>
        <TtlCdtNtries><NbOfNtries>5</NbOfNtries><Sum>26.000000</Sum></TtlCdtNtries>
      </TxsSummry>
      <Ntry>
        <Amt Ccy="XRP">5.000000</Amt>
        <CdtDbtInd>CRDT</CdtDbtInd>
        <Sts>BOOK</Sts>
        <AcctSvcrRef>267B64A5...</AcctSvcrRef>
        <BkTxCd><Domn><Cd>PMNT</Cd>...</Domn><Prtry><Cd>x402-xrpl-v1</Cd></Prtry></BkTxCd>
        <NtryDtls><TxDtls>
          <RltdPties><Dbtr>...</Dbtr><Cdtr>...</Cdtr></RltdPties>
          <RmtInf><Ustrd>x402 payment · network: testnet</Ustrd></RmtInf>
        </TxDtls></NtryDtls>
      </Ntry>
    </Stmt>
  </BkToCstmrStmt>
</Document>
  • Généré client-side dans le navigateur (pas besoin de serveur)
  • Nom de fichier : camt053-2026-04-01-2026-04-11.xml
  • Compatible SAP, Sage, QuickBooks et tout outil comptable ISO 20022

Structure du projet

Ed4l02/
├── ed4l02-x402/              # npm package — middleware x402
│   ├── src/
│   │   ├── middleware.ts     # xrpl402() + xrpl402Express() + buildPaymentDetails (vatConfig)
│   │   ├── vat.ts            # computeVat(), VAT_RATES_BPS (BigInt basis points)
│   │   ├── types.ts          # PaymentPayload, PaymentDetails, VatBreakdown, VatConfig...
│   │   └── index.ts          # exports publics
│   ├── package.json
│   └── README.md
│
├── ed4l02-facilitator/       # Express server — settlement backend
│   ├── src/
│   │   ├── index.ts          # Routes : /health /verify /settle /transactions /marketplace
│   │   ├── verify.ts         # Vérification offline
│   │   ├── settle.ts         # Pipeline : verify → TVA gate → cred gate → submit → SQLite
│   │   ├── credentials.ts    # XLS-70 + EDEL_AGENT + XLS-80 + checkMerchantVatCredential
│   │   ├── marketplace.ts    # CRUD marchands — /merchants/listing + /marketplace
│   │   ├── db.ts             # SQLite (WAL) : tx_logs, merchants, merchant_endpoints
│   │   ├── client.ts         # Pool xrpl.js par réseau
│   │   └── types.ts          # TxLog (avec colonnes vat_*), VatBreakdown...
│   ├── test-integration/
│   │   └── run-test.ts       # 9-step integration test (A–I)
│   ├── scripts/
│   │   └── send-txs.ts       # Script envoi de tx de test
│   └── data/
│       └── ed4l02.db         # SQLite (gitignore en prod)
│
├── frontend/                 # Next.js — dashboard marchand + marketplace + user
│   ├── app/
│   │   ├── merchant/
│   │   │   ├── page.js                         # Dashboard KPI (incl. TVA Collected)
│   │   │   ├── transactions/page.js            # Historique + filtres TVA + export
│   │   │   └── marketplace-listing/page.js     # Formulaire listing API
│   │   ├── marketplace/
│   │   │   ├── page.js                         # Annuaire public
│   │   │   └── [id]/page.js                    # Détail marchand
│   │   ├── user/
│   │   │   └── page.js                         # Flow KYC utilisateur (ex /onboarding/kyc)
│   │   └── api/merchants/[id]/
│   │       └── transactions/route.js           # Proxy → facilitator SQLite (avec champs TVA)
│   ├── components/
│   │   └── Header.js                           # Nav : Home Marketplace Merchant Transactions User
│   ├── hooks/
│   │   └── useMerchant.js                      # fetchTransactions, fetchMerchant, saveMerchant
│   ├── lib/
│   │   └── camt053.js                          # Générateur ISO 20022 CAMT.053
│   └── README.md
│
├── RECAP.md                  # Ce fichier
├── BRIEFING.md               # Briefing condensé pour Claude.ai web
├── Credentialinfos.md        # Spec XLS-70 credentials
└── PermissionDomain.md       # Spec XLS-80 PermissionedDomain

Déploiement du facilitator en production (VPS)

Instructions pour déployer ed4l02-facilitator sur un VPS Linux (Infomaniak, OVH, Hetzner…) avec SQLite persistant, PM2 comme gestionnaire de process, et Nginx en reverse proxy.

Pré-requis sur le VPS

# Node.js 20 LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# PM2 (gestionnaire de process)
sudo npm install -g pm2

# Nginx
sudo apt install -y nginx

# Git (si pas déjà installé)
sudo apt install -y git

Vérifier :

node -v    # v20.x.x
npm -v     # 10.x.x
pm2 -v     # 5.x.x
nginx -v

1. Cloner et builder le projet

# Cloner le repo (ou uploader les fichiers via scp/rsync)
git clone https://github.com/<ton-org>/ed4l02.git /srv/ed4l02
cd /srv/ed4l02/ed4l02-facilitator

# Installer les dépendances
npm install

# Builder TypeScript → dist/
npm run build

Si le repo est privé ou pas encore sur GitHub, utiliser scp :

# Depuis ta machine locale
scp -r ./ed4l02-facilitator user@ip-vps:/srv/ed4l02/

2. Créer le fichier .env

nano /srv/ed4l02/ed4l02-facilitator/.env

Contenu :

PORT=3000
SETTLE_TIMEOUT_MS=60000

# Dossier de données SQLite — le fichier ed4l02.db sera créé ici automatiquement
DATA_DIR=/srv/ed4l02/data

# Adresse XRPL de l'émetteur des credentials TVA (EDEL_TVA_FR, EDEL_TVA_CH) et KYC
EDEL_TRUSTED_ISSUER=rIssuerAddressXRPL...

# URL publique du facilitator (affiché dans /marketplace)
FACILITATOR_URL=https://facilitator.tondomaine.com

# Optionnel — surcharger les nodes XRPL
# XRPL_MAINNET_URL=wss://xrplcluster.com
# XRPL_TESTNET_URL=wss://testnet.xrpl-labs.com
# XRPL_DEVNET_URL=wss://s.devnet.rippletest.net:51233

Créer le dossier de données :

mkdir -p /srv/ed4l02/data
# Donner les droits à l'utilisateur qui fera tourner le process
chown -R $USER:$USER /srv/ed4l02/data

Point clé SQLite persistant : DATA_DIR doit pointer vers un dossier hors du code (dist/, repo). Comme ça, git pull + rebuild ne touche jamais la base. /srv/ed4l02/data/ est l'emplacement recommandé.


3. Lancer avec PM2

cd /srv/ed4l02/ed4l02-facilitator

# Lancer le process compilé
pm2 start dist/index.js --name "ed4l02-facilitator" --env production

# Sauvegarder la config PM2 pour redémarrage automatique au reboot
pm2 save
pm2 startup    # → copier-coller la commande sudo qu'il affiche

Commandes utiles PM2 :

pm2 status                        # voir les process
pm2 logs ed4l02-facilitator       # logs en temps réel
pm2 logs ed4l02-facilitator --lines 200  # 200 dernières lignes
pm2 restart ed4l02-facilitator    # redémarrer
pm2 stop ed4l02-facilitator       # stopper
pm2 delete ed4l02-facilitator     # supprimer du registre PM2

Vérifier que le facilitator tourne :

curl http://localhost:3000/health
# → {"status":"ok","service":"ed4l02-facilitator","version":"0.1.0"}

4. Configurer Nginx en reverse proxy

sudo nano /etc/nginx/sites-available/ed4l02-facilitator

Contenu (remplacer facilitator.tondomaine.com par ton vrai domaine ou l'IP) :

server {
    listen 80;
    server_name facilitator.tondomaine.com;

    # Logs
    access_log /var/log/nginx/ed4l02-facilitator.access.log;
    error_log  /var/log/nginx/ed4l02-facilitator.error.log;

    location / {
        proxy_pass         http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Headers standard pour proxy
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeouts (le settle peut prendre jusqu'à 60s sur le ledger)
        proxy_connect_timeout 10s;
        proxy_read_timeout    90s;
        proxy_send_timeout    90s;

        # Taille max du body (les tx_blob ne dépassent pas quelques Ko)
        client_max_body_size 1m;
    }
}

Activer et recharger Nginx :

sudo ln -s /etc/nginx/sites-available/ed4l02-facilitator /etc/nginx/sites-enabled/
sudo nginx -t          # vérifier la config — doit afficher "syntax is ok"
sudo systemctl reload nginx

Tester depuis l'extérieur :

curl http://facilitator.tondomaine.com/health

5. HTTPS avec Let's Encrypt (recommandé)

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d facilitator.tondomaine.com
# → suivre les instructions, choisir "redirect HTTP to HTTPS"

# Renouvellement automatique (déjà configuré par certbot, vérifier)
sudo systemctl status certbot.timer

Après ça, le facilitator est accessible en HTTPS :

https://facilitator.tondomaine.com/health
https://facilitator.tondomaine.com/settle
https://facilitator.tondomaine.com/transactions?address=r...

6. Mettre à jour le code (workflow de redéploiement)

cd /srv/ed4l02/ed4l02-facilitator

# Récupérer les changements
git pull origin main   # ou scp si pas de git

# Reinstaller les dépendances si package.json a changé
npm install

# Rebuilder
npm run build

# Redémarrer le process (la DB SQLite dans /srv/ed4l02/data/ n'est PAS touchée)
pm2 restart ed4l02-facilitator

# Vérifier
pm2 logs ed4l02-facilitator --lines 20
curl http://localhost:3000/health

7. Pointer le frontend vers le nouveau facilitator

Dans frontend/.env.local (ou les variables d'environnement du déploiement frontend) :

FACILITATOR_URL=https://facilitator.tondomaine.com

Et dans tous les appels xrpl402Express() du resource server, mettre à jour facilitatorUrl :

xrpl402Express({
  facilitatorUrl: 'https://facilitator.tondomaine.com',
  // ...
})

Checklist de déploiement

[ ] Node.js 20 + PM2 + Nginx installés
[ ] Code copié dans /srv/ed4l02/ed4l02-facilitator/
[ ] npm install && npm run build — sans erreur
[ ] .env créé avec DATA_DIR=/srv/ed4l02/data et EDEL_TRUSTED_ISSUER=r...
[ ] Dossier /srv/ed4l02/data/ créé avec les bons droits
[ ] pm2 start dist/index.js --name "ed4l02-facilitator"
[ ] curl localhost:3000/health → {"status":"ok"}
[ ] Config Nginx créée et activée
[ ] nginx -t → syntax ok
[ ] curl http://facilitator.tondomaine.com/health → ok
[ ] HTTPS avec certbot (optionnel mais recommandé)
[ ] pm2 save + pm2 startup (redémarrage auto au reboot)
[ ] FACILITATOR_URL mis à jour dans le frontend (.env.local)

Dépannage fréquent

Symptôme Cause probable Solution
pm2 logs : EACCES: permission denied, open '...ed4l02.db' Le dossier DATA_DIR n'existe pas ou mauvais owner mkdir -p /srv/ed4l02/data && chown $USER /srv/ed4l02/data
pm2 logs : Cannot find module 'dist/index.js' Build pas fait ou échoué npm run build dans le bon dossier
Nginx : 502 Bad Gateway Le facilitator ne tourne pas pm2 status + pm2 logs
Nginx : 504 Gateway Timeout proxy_read_timeout trop court Vérifier que proxy_read_timeout 90s est bien dans la config Nginx
/settle timeout SETTLE_TIMEOUT_MS trop court ou node XRPL lent Augmenter SETTLE_TIMEOUT_MS=90000 dans .env
WebSocket XRPL se déconnecte Idle timeout sur le VPS Normal, le client.ts se reconnecte automatiquement
ed4l02.db vide après redéploiement DATA_DIR pointe dans dist/ Mettre DATA_DIR=/srv/ed4l02/data dans .env

TODO / Next steps

  • Déploiement VPS Infomaniak avec SQLite persistant (DATA_DIR persisté hors du code)
  • Publier ed4l02-x402 sur npm
  • API key authentication sur le facilitator (header X-Api-Key)
  • Test e2e IOU (RLUSD) et MPT
  • Vraie implémentation CAMT.053 avec totaux d'ouverture/clôture (opening/closing balance)
  • Webhook marchand : POST vers une URL configurée à chaque tx settlée
  • Dashboard : pagination sur /transactions pour les gros volumes
  • Refresh automatique du dashboard toutes les N secondes
  • TVA CH — test e2e EDEL_TVA_CH (8.1%)
  • Marketplace : frontend pagination + filtres côté client (/marketplace)
  • Onboarding marchand guidé : émettre EDEL_TVA_FR/EDEL_TVA_CH depuis l'interface admin

Comptes de test (testnet)

Voir le fichier de seeds dans le répertoire projet (ne pas commiter en prod).

Facilitator local : http://localhost:3000
Frontend local : http://localhost:3001
Facilitator Render (devnet) : https://ed4l02.onrender.com