diff --git a/.env.master.example b/.env.master.example index b2c5586..57d2404 100644 --- a/.env.master.example +++ b/.env.master.example @@ -34,6 +34,10 @@ CLOUDFLARE_TUNNEL_ID_MIFOS_R630_02= CLOUDFLARE_TUNNEL_TOKEN_MIFOS_R630_02= CLOUDFLARE_ORIGIN_CA_KEY= CLOUDFLARE_ACCOUNT_ID= +# Turnstile (Captcha) for IRU marketplace inquiry — Dashboard → Turnstile; NOT the DNS API key +CLOUDFLARE_TURNSTILE_SECRET_KEY= +# dbis_core Vite marketplace: VITE_CLOUDFLARE_TURNSTILE_SITE_KEY= +# Sankofa portal Next.js (sibling repo): NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY= # --- ClouDNS --- CLOUDNS_AUTH_ID= @@ -100,10 +104,18 @@ AWS_S3_BUCKET= AZURE_STORAGE_CONNECTION_STRING= AZURE_STORAGE_CONTAINER= +# --- Pinata (IPFS pinning; token logos) --- +# Dashboard: https://app.pinata.cloud — API Keys → JWT or key/secret. +# scripts/upload-token-logos-to-ipfs.sh uses PINATA_JWT only (Bearer for pinFileToIPFS). +PINATA_JWT= +PINATA_API_KEY= +PINATA_API_SECRET= + # --- Blockchain / SMOM-DBIS-138 (use smom-dbis-138/.env for PRIVATE_KEY) --- PRIVATE_KEY= RPC_URL_138= RPC_URL_138_PUBLIC= +# Ethereum L1 — used for dual-anchor attestation with scripts/omnl/omnl-chain138-attestation-tx.sh (consumes ETH gas). Alias: RPC_URL_MAINNET. ETHEREUM_MAINNET_RPC= CHAIN_651940_RPC_URL= ETHERLINK_RPC_URL= @@ -122,6 +134,51 @@ MOONPAY_SECRET_KEY= RAMP_NETWORK_API_KEY= ONRAMPER_API_KEY= +# --- GRU Transport / cW hard-peg bridge controls (Chain 138 -> public chains) --- +# Canonical L1 bridge env used by the GRU transport overlay and token-aggregation. +CHAIN138_L1_BRIDGE= +# Legacy alias still used by some deployment helpers. +CW_L1_BRIDGE_CHAIN138= +CW_BRIDGE_MAINNET= +CW_BRIDGE_CRONOS= +CW_BRIDGE_BSC= +CW_BRIDGE_POLYGON= +CW_BRIDGE_GNOSIS= +CW_BRIDGE_AVALANCHE= +CW_BRIDGE_BASE= +CW_BRIDGE_ARBITRUM= +CW_BRIDGE_OPTIMISM= +CW_RESERVE_VERIFIER_CHAIN138= +CW_STABLECOIN_RESERVE_VAULT= +CW_RESERVE_SYSTEM= +CW_ATTACH_VERIFIER_TO_L1=1 +CW_REQUIRE_VAULT_BACKING= +CW_REQUIRE_RESERVE_SYSTEM_BALANCE= +CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT= +CW_CANONICAL_USDT= +CW_CANONICAL_USDC= +CW_USDT_RESERVE_ASSET= +CW_USDC_RESERVE_ASSET= +CW_MAX_OUTSTANDING_USDT_MAINNET= +CW_MAX_OUTSTANDING_USDC_MAINNET= +CW_MAX_OUTSTANDING_USDT_CRONOS= +CW_MAX_OUTSTANDING_USDC_CRONOS= +CW_MAX_OUTSTANDING_USDT_BSC= +CW_MAX_OUTSTANDING_USDC_BSC= +CW_MAX_OUTSTANDING_USDT_POLYGON= +CW_MAX_OUTSTANDING_USDC_POLYGON= +CW_MAX_OUTSTANDING_USDT_GNOSIS= +CW_MAX_OUTSTANDING_USDC_GNOSIS= +CW_MAX_OUTSTANDING_USDT_AVALANCHE= +CW_MAX_OUTSTANDING_USDC_AVALANCHE= +CW_MAX_OUTSTANDING_USDT_BASE= +CW_MAX_OUTSTANDING_USDC_BASE= +CW_MAX_OUTSTANDING_USDT_ARBITRUM= +CW_MAX_OUTSTANDING_USDC_ARBITRUM= +CW_MAX_OUTSTANDING_USDT_OPTIMISM= +CW_MAX_OUTSTANDING_USDC_OPTIMISM= +CW_FREEZE_AVAX_L2_CONFIG= + # --- Alerts & monitoring --- SLACK_WEBHOOK_URL= PAGERDUTY_INTEGRATION_KEY= @@ -129,6 +186,17 @@ EMAIL_ALERT_API_URL= EMAIL_ALERT_RECIPIENTS= SENTRY_DSN= +# --- dbis_core IRU / marketplace outbound mail (optional; Proxmox Mail Proxy VMID 100 = 192.168.11.32) --- +# EMAIL_PROVIDER=smtp +# SMTP_HOST=192.168.11.32 +# SMTP_PORT=587 +# SMTP_SECURE=false +# SMTP_USER= +# SMTP_PASSWORD= +# EMAIL_FROM= +# EMAIL_FROM_NAME=SolaceNet +# DBIS_SALES_EMAIL= + # --- Legal / e-signature --- E_SIGNATURE_BASE_URL= diff --git a/hybx_jurisdictional_cheat_sheets_implementation_roadmap.md b/hybx_jurisdictional_cheat_sheets_implementation_roadmap.md new file mode 100644 index 0000000..9c707e9 --- /dev/null +++ b/hybx_jurisdictional_cheat_sheets_implementation_roadmap.md @@ -0,0 +1,223 @@ +# HYBX Jurisdictional Cheat Sheets — Implementation Roadmap + +This document **operationalizes** the blueprint in [hybx_jurisdictional_cheat_sheets_technical_plan.md](hybx_jurisdictional_cheat_sheets_technical_plan.md). It defines build order, a **canonical schema v1**, API/service boundaries, integration contracts with the Compliance & Routing Sidecar and the routing graph model, and explicit non-goals. It does not replace the technical plan. + +--- + +## 1. Scope statement + +- **Inherits** the JIS vision: deterministic jurisdiction knowledge for banking, payments, liquidity, settlement, and regulatory execution (see technical plan Purpose and Core Objective). +- **This roadmap** only specifies: what to implement first, schema norms, MVP geography, read APIs, and how consumers (especially the sidecar) call JIS. +- **Out of roadmap detail**: full legal research, production data feeds, and UI design specs (covered at a high level only). + +--- + +## 2. Architecture placement + +JIS is a **read-mostly fact service**. It is **not** the Policy DSL: policies interpret facts; JIS supplies structured **facts** (permissions, rails, risk tiers, citations). + +```mermaid +flowchart LR + Composer[Transaction_Composer] + Orch[Orchestrator] + Sidecar[Compliance_Routing_Sidecar] + JIS[JIS_Service] + Policy[Policy_DSL_future] + Composer --> Orch + Orch --> Sidecar + Sidecar --> JIS + Sidecar --> Policy + JIS -.->|facts_only| Sidecar +``` + +**Flow (conceptual):** Transaction graph / compiled transaction enters the sidecar; sidecar performs jurisdiction lookup against JIS (by ids, currencies, corridor pairs); sidecar combines JIS outputs with internal rules and optional future Policy DSL to produce PASS/WARN/FAIL, routing constraints, and explainable decisions. + +--- + +## 3. Canonical schema v1 (normative for code) + +Aligns with the technical plan “Data Model Design / Core Structure” example. All profiles are JSON-serializable documents stored as versioned rows. + +### 3.1 Root envelope (every stored profile) + +| Field | MVP required | Full profile | Description | +|--------|--------------|--------------|-------------| +| `schemaVersion` | yes | yes | e.g. `"1.0.0"` for this spec. | +| `jurisdictionId` | yes | yes | Stable id (see section 4). | +| `profileVersion` | yes | yes | Semver or monotonic string for this jurisdiction’s content. | +| `effectiveFrom` | yes | yes | ISO-8601 date/time. | +| `effectiveTo` | no | optional | Null if current. | +| `dataQuality` | yes | yes | `complete` \| `partial` \| `stub` — MVP seeds often `partial` or `stub`. | +| `sourceRefs` | recommended | yes | Array of `{ "title", "url"?, "publisher"?, "retrievedAt"? }` per subsection or aggregated. | +| `core` | yes | yes | Registry fields (see 3.2). | +| `currencyRules` | yes | yes | Array or map by currency code; see 3.3. | +| `fxRules` | yes | yes | FX convertibility, controls, limits (may be stub). | +| `settlementSystems` | yes | yes | List of settlement rail summaries. | +| `licensingRules` | partial | yes | License types applicable in jurisdiction; MVP may be empty array with `dataQuality`. | +| `crossBorderRules` | partial | yes | Inbound/outbound, restricted jurisdictions. | +| `riskProfile` | yes | yes | Political/financial/sanctions tiers as scalars or enums. | +| `sanctions` | no | optional | Module from technical plan § Sanctions Intelligence. | +| `documentation` | no | optional | Required doc types for payments. | +| `feeGovernance` | no | optional | Max fees, disclosures (technical plan § Fee Governance). | + +### 3.2 `core` (jurisdiction registry) + +Minimum MVP fields: + +- `countryName`, `isoCountryCode` (ISO 3166-1 alpha-2) +- `primaryFinancialRegulator` (string) +- `legalSystemType` (enum or string) +- `centralBankName` (string) +- `primaryCurrency` (ISO 4217) +- `timeZones` (string[]) +- `politicalRiskTier`, `financialRiskTier` (string or number; align with technical plan Risk Intelligence) + +Full profile adds: region, capital, secondary regulators, languages, extended sanctions status fields as in technical plan. + +### 3.3 `currencyRules` (per currency or default) + +Each entry should support at least: + +- `currency` (ISO 4217) +- `convertibilityLevel` (enum: e.g. `fully_convertible`, `managed`, `restricted`) +- `settlementType` (string or enum) +- `liquidityAvailability` (enum or coarse string) +- `centralBankRestrictions` (string, optional) + +### 3.4 Versioning rules + +- **schemaVersion**: bump **minor** for backward-compatible new optional fields; **major** for breaking renames. +- **profileVersion**: independent per `jurisdictionId`; any material regulatory update increments profile version and sets `effectiveFrom`. + +--- + +## 4. Jurisdiction ID scheme + +- **Default:** ISO 3166-1 alpha-2 uppercase (`ID`, `US`, `SG`, `GB`). +- **Sub-national or zones:** use hyphenated suffixes, e.g. `US-NY` only if the product truly needs sub-national rules; otherwise keep national id and encode specificity in `core` or overlays. +- **EU:** prefer national ids (`DE`, `FR`) for MVP; introduce `EU` or `EU-DE` only when cross-EU harmonized profiles are maintained as first-class rows. +- **Special financial centers:** e.g. `HK`, `MO` as ISO codes; avoid custom ids unless they are registered in an internal enum table. + +Document every non-ISO id in a single **registry table** in the implementation repo. + +--- + +## 5. Phased delivery + +| Phase | Goal | Outcome | +|-------|------|--------| +| **0** | Schema + validation | JSON Schema (or TypeScript types) + validator; one sample fixture (e.g. `ID`) that validates. | +| **1** | JIS MVP service | `GET /jurisdiction/{id}` returns active profile by id; `POST /jurisdiction/query` with `{ "ids": string[] }` returns map id → profile; store in **Postgres** (prod) or **SQLite** (local dev); **no Elasticsearch**. | +| **2** | Pilot dataset | Seed **5–10** jurisdictions; **minimum** `ID`, `US`, `SG`, `GB`. Use `dataQuality: partial` or `stub` and `sourceRefs` where legal detail is incomplete. | +| **3** | Sidecar integration contract | Written contract (this section expanded in runbooks): request/response fields, caching, error handling; optional `constraintHints` for routing graph (see section 6). | +| **4** | Search (optional) | Add Elasticsearch or Postgres FTS **only** when query patterns exceed id/batch lookup. | +| **5** | Dashboard / visualization | Defer until API and seeds are stable (technical plan Visualization). | + +--- + +## 6. Integration with existing HYBX artifacts + +### 6.1 Compliance & Routing Sidecar + +- Reference: [hybx_compliance_routing_sidecar_technical_plan.md](hybx_compliance_routing_sidecar_technical_plan.md). +- Sidecar `POST /evaluate-transaction` (or equivalent) should resolve **all jurisdiction ids** present on the transaction graph, participant registry, or routing plan, then call JIS (batch) before or during compliance/routing engines. +- JIS returns **facts only**; the sidecar applies rules (AML/KYC/sanctions workflows may still use other services; JIS supplies jurisdiction-grounded permissions and tiers). + +### 6.2 Routing graph data model + +- Reference: [hybx_routing_graph_data_model.md](hybx_routing_graph_data_model.md) (jurisdiction overlays, `regulatoryPenalty`, transaction-level filters). +- **Mapping:** JIS `riskProfile`, `fxRules`, `crossBorderRules`, and `currencyRules` inform: + - vertex `jurisdiction` and `capabilities` validation; + - edge `jurisdictions[]` tags and suggested `costVector.regulatoryPenalty` (sidecar computes penalty from JIS facts + policy weights). +- JIS does **not** store the routing graph; it **constrains** how the graph may be traversed. + +### 6.3 Transaction Composer + +- Optional future: jurisdiction pickers on nodes, auto-tagging `jurisdictionId` on `TransactionNodeData`. **Out of scope** for phases 0–1 unless product prioritizes it. +- Composer continues to emit design-time graphs; orchestrator/sidecar enrich with registry ids. + +--- + +## 7. Sidecar integration contract (phase 3 deliverable detail) + +**Batch request (example):** + +```json +{ + "jurisdictionIds": ["ID", "US"], + "context": { + "currencies": ["USD", "IDR"], + "corridor": { "fromJurisdiction": "US", "toJurisdiction": "ID" }, + "effectiveAt": "2026-03-29T12:00:00Z" + } +} +``` + +**Batch response (example shape):** + +```json +{ + "profiles": { + "ID": { "schemaVersion": "1.0.0", "jurisdictionId": "ID", "profileVersion": "0.1.0", "dataQuality": "partial", "core": {}, "currencyRules": [], "fxRules": {}, "settlementSystems": [], "crossBorderRules": {}, "riskProfile": {} } + }, + "notFound": [] +} +``` + +**Errors:** HTTP 404 for unknown id on single GET; batch omits missing ids in `profiles` and lists them in `notFound` (or returns explicit errors—choose one behavior and document in OpenAPI). + +**Caching:** Sidecar or API gateway may cache by `(jurisdictionId, profileVersion)` with TTL aligned to technical plan (configurable; target lookup < 100 ms p95 at steady state with cache). + +--- + +## 8. Data acquisition and legal posture + +- **MVP:** Manually curated YAML/JSON seeds in-repo or loaded from migration; every non-trivial field should have `sourceRefs` where possible. +- **Production expansion:** Licensed legal/regulatory data providers and official publications (technical plan Data Acquisition Model). **No** commitment to automated scraping in MVP code paths. +- **Disclaimer:** Profiles are engineering aids; final legal interpretation remains with compliance officers and counsel. + +--- + +## 9. Non-goals (explicit) + +- Full **global coverage** on day one (technical plan Phase 1/2 expansion applies). +- **Real-time** regulatory crawling as a blocking dependency for MVP. +- **Multilingual** content in MVP (technical plan Localization deferred). +- **Graph database** for JIS core (optional in technical plan; not required for MVP). +- Replacing **sanctions screening vendors** or **KYC** systems—JIS complements them with jurisdiction facts. + +--- + +## 10. Acceptance criteria (“MVP ready”) + +1. At least **four** jurisdiction profiles (`ID`, `US`, `SG`, `GB`) validate against **schema v1**. +2. `GET /jurisdiction/{id}` and `POST /jurisdiction/query` implemented and documented (OpenAPI or equivalent). +3. With a **warm cache**, p95 read latency meets **< 100 ms** internal target under nominal load (align with technical plan Performance Targets). +4. **One worked example** documented end-to-end: transaction touching **USD→IDR** with participants in **US** and **ID**, showing which JIS fields the sidecar would read for FX and cross-border checks, and how results feed [hybx_routing_graph_data_model.md](hybx_routing_graph_data_model.md) overlays. + +### Worked example (narrative) + +- Transaction: remitter in **US**, beneficiary bank in **ID**, USD notionally converted to IDR. +- Sidecar resolves `US` and `ID` profiles; checks `fxRules` and `crossBorderRules` for both; reads `currencyRules` for USD/IDR; compares against transaction amounts and rails in the routing plan. +- If a rule indicates heightened friction, sidecar may set routing graph `regulatoryPenalty` on affected edges or return WARN with explanation references to `sourceRefs`. + +--- + +## 11. References + +| Document | Role | +|----------|------| +| [hybx_jurisdictional_cheat_sheets_technical_plan.md](hybx_jurisdictional_cheat_sheets_technical_plan.md) | Full JIS blueprint (subsystems, storage, visualization, global coverage). | +| [hybx_compliance_routing_sidecar_technical_plan.md](hybx_compliance_routing_sidecar_technical_plan.md) | Sidecar engines, API sketch, evaluation flow. | +| [hybx_routing_graph_data_model.md](hybx_routing_graph_data_model.md) | Routing vertices/edges, `costVector`, jurisdiction overlays. | +| [transaction-composer/](transaction-composer/) | Design-time transaction graph and compiler (optional future jurisdiction fields). | + +--- + +## Document control + +| Item | Value | +|------|--------| +| Roadmap version | 1.0 | +| Aligns with technical plan | As of repo snapshot; amend when blueprint changes | +| Next reviews | After phase 1 API freeze; after first pilot dataset sign-off | diff --git a/hybx_routing_graph_data_model.md b/hybx_routing_graph_data_model.md new file mode 100644 index 0000000..2e70a7d --- /dev/null +++ b/hybx_routing_graph_data_model.md @@ -0,0 +1,435 @@ +# HYBX Routing Graph Data Model + +**Purpose:** Define the canonical **routing graph** used by the Compliance & Routing Sidecar (see [hybx_compliance_routing_sidecar_technical_plan.md](hybx_compliance_routing_sidecar_technical_plan.md)) for graph-based pathfinding, liquidity resolution, fee path construction, and jurisdiction-aware routing. This is the mathematical backbone for nostro pathfinding, multi-bank routing, liquidity selection, and failover as **alternate paths** on the same graph. + +**Audience:** Engineers implementing the Routing Engine, orchestration adapters, and registry services. + +**Non-goals:** Policy DSL grammar, liquidity registry service APIs, explanation-engine templates, and failover algorithms are specified elsewhere. + +--- + +## 1. Purpose and scope + +The routing graph models the **operational payment network** relevant to a transaction: institutions, account corridors, liquidity pools, FX venues, settlement rails, fee agents, and beneficiary anchors, connected by **directed, weighted** relationships. + +- **In scope:** Vertex types, edge types, cost vectors, liquidity and fee metadata, jurisdiction overlays, serialization, versioning, and mapping from the Transaction Composer compile output. +- **Out of scope:** Specific shortest-path or MOCO (multi-objective) solver implementations; this document defines **inputs** those solvers consume. + +The Transaction Composer UI produces a **design-time pipeline graph** (user intent). The sidecar may **project** that into one or more **routing subgraphs** and enrich them with edges and weights that are not drawn in the UI (e.g. alternate correspondents, backup rails). + +--- + +## 2. Conceptual model + +Let **G = (V, E)** be a **directed graph** with: + +- **V**: typed vertices (routing nodes). +- **E**: typed directed edges with metadata and costs. + +### 2.1 Two layers + +| Layer | Meaning | Typical source of truth | +|--------|---------|-------------------------| +| **Topology** | Durable relationships: who may route to whom, which rails exist, static eligibility. | Reference data, contracts, registry | +| **Snapshot** | Time-bound overlays: available liquidity, current fee quotes, latency estimates, risk adjustments for **this evaluation**. | Pool registry, market data, sidecar snapshot builder | + +At evaluation time, the engine consumes a **materialized view**: topology ∪ snapshot overlays. The JSON envelope below supports both. + +### 2.2 Relationship to sidecar API + +The sidecar plan describes `POST /evaluate-transaction` with `transactionGraph` and responses including `routingPlan`. This document defines the **shape of the network graph** (and fragments thereof) that backs **routingPlan** generation—not the full HTTP schema. + +--- + +## 3. Vertex (node) types + +Vertices are discriminated by `vertexKind`. All vertices share a common base; extensions are kind-specific. + +### 3.1 Base fields (all kinds) + +| Field | Type | Required | Description | +|--------|------|----------|-------------| +| `id` | string | yes | Stable unique id in this graph (UUID or registry key). | +| `vertexKind` | enum | yes | One of the kinds below. | +| `displayName` | string | no | Human label. | +| `jurisdiction` | string | yes | ISO-3166 alpha-2, or internal jurisdiction code (e.g. `ID-JK`). | +| `identifiers` | object | no | `bic`, `lei`, `internalOrgId`, etc. | +| `capabilities` | object | no | See below. | +| `riskTier` | string \| number | no | Opaque tier for risk engine (e.g. `T1`–`T3`). | +| `metadata` | object | no | Opaque extensibility. | + +**Capabilities object (optional, common pattern):** + +```json +{ + "currenciesAllowed": ["USD", "IDR"], + "fxPermitted": true, + "settlementTypes": ["RTGS", "ACH"], + "maxSingleTransfer": { "amount": "1000000000", "currency": "USD" } +} +``` + +### 3.2 Vertex kind: `Institution` + +Legal or operational entity (bank, CB, PSP). Maps from composer participants and some operational nodes. + +### 3.3 Vertex kind: `AccountCorridor` + +Abstract **nostro/vostro or nostro-like** relationship endpoint—not necessarily one ledger row. Used for pathfinding between institutions. + +- Typical use: attach liquidity and correspondent edges to corridors. + +### 3.4 Vertex kind: `LiquidityPool` + +A **source of fungible liquidity** in a currency (or synthetic pool id). + +- Should align with sidecar liquidity sketch: currency, amount, provider, expiry (carried in snapshot overlay on the pool or on incident edges). + +### 3.5 Vertex kind: `FxVenue` + +FX conversion capability (desk, LP, internal book). + +### 3.6 Vertex kind: `SettlementRail` + +Logical settlement channel (RTGS system, chain leg, internal settlement batch). + +### 3.7 Vertex kind: `FeeAgent` + +Optional dedicated vertex when fees are not folded into `Institution` (e.g. third-party fee collector). + +### 3.8 Vertex kind: `BeneficiaryAnchor` + +Terminal or near-terminal node representing beneficiary credit location (could be institution + product, or a logical “credit to PT Parak at Bank Kanaya”). + +--- + +## 4. Edge types + +Edges are **directed**: `(sourceId, targetId)`. Each edge has: + +| Field | Type | Required | Description | +|--------|------|----------|-------------| +| `id` | string | yes | Unique edge id. | +| `sourceId` | string | yes | Vertex `id`. | +| `targetId` | string | yes | Vertex `id`. | +| `edgeKind` | enum | yes | See below. | +| `costVector` | object | yes | Normalized costs (section 5). | +| `jurisdictions` | string[] | no | Tags for crossing rules; default may inherit from endpoints. | +| `validFrom` / `validTo` | string (ISO-8601) | no | Validity window for this edge. | +| `policyRefs` | string[] | no | Opaque ids for future Policy DSL bindings. | +| `liquidityRef` | string | no | Pool or line id when edge represents funding. | +| `feeRef` | string | no | Link to fee schedule id. | +| `metadata` | object | no | Extensibility. | + +### 4.1 Edge kind: `correspondent` + +Bank-to-bank (or institution-to-institution) routing leg; may attach to `AccountCorridor` vertices or directly to `Institution` depending on model granularity. + +### 4.2 Edge kind: `liquidity_link` + +Connects a pool to a corridor, venue, or institution **consumption** point. + +### 4.3 Edge kind: `fx_quote` + +Connects currency/state A to B through an `FxVenue` (often modeled as two edges via the venue, or one bundled edge with pair metadata in `metadata`). + +### 4.4 Edge kind: `fee_hop` + +Explicit fee accrual or pass-through segment (supports building a **fee propagation tree** as a subgraph). + +### 4.5 Edge kind: `settlement_path` + +Connects to or from a `SettlementRail` or `BeneficiaryAnchor`. + +--- + +## 5. Weights and multi-criteria cost + +The sidecar Routing Engine (see technical plan) uses a **weighted directed graph** with dimensions including **fee cost**, **latency**, and **liquidity availability**. + +### 5.1 `costVector` (required on every edge) + +Recommended normalized fields (all optional numbers except at least one should be present for pathfinding): + +| Key | Meaning | Units / notes | +|-----|---------|----------------| +| `feeCost` | Expected monetary cost of traversing this edge | Normalized to a reference currency in snapshot builder, or raw with `feeCurrency` in `metadata` | +| `latencyMs` | Expected processing time | Milliseconds or representative score | +| `liquidityAvailability` | How “easy” it is to fund this hop | 0–1 score, or available notional on this hop | +| `regulatoryPenalty` | Additive penalty from policy | Non-negative; 0 if none | +| `reliability` | Historical success / health | 0–1, can be inverted by solver | + +Example: + +```json +"costVector": { + "feeCost": 1250.5, + "latencyMs": 800, + "liquidityAvailability": 0.92, + "regulatoryPenalty": 0, + "reliability": 0.995 +} +``` + +### 5.2 Aggregation strategy (normative intent, not algorithm) + +Path cost is a **multi-criteria** problem: + +1. **Hard constraints:** e.g. minimum liquidity availability, blocked jurisdictions, expired `validTo`. +2. **Scalarization:** weighted sum `w1*feeCost + w2*latencyMs + w3*(1-liquidityAvailability) + w4*regulatoryPenalty + w5*(1-reliability)` with configurable weights per product lane. +3. **Pareto / k-shortest:** optional second phase to present alternates for failover UX. + +Exact solver choice is implementation-defined; this document standardizes **what** is measured on each edge. + +--- + +## 6. Liquidity metadata + +Aligned with the sidecar liquidity model (currency, amount, provider, expiry): + +**On `LiquidityPool` vertex or `liquidity_link` edge (snapshot):** + +| Field | Description | +|--------|-------------| +| `poolId` | Registry identifier | +| `currency` | ISO-4217 | +| `availableAmount` | Decimal string recommended | +| `providerId` | Institution or LP id | +| `expiresAt` | ISO-8601 | +| `refreshTtlSeconds` | Optional hint for cache | + +--- + +## 7. Fee metadata + +Fees may be attached to `fee_hop` edges or embedded in `correspondent` / `settlement_path` via `feeRef`. + +**Suggested fields (snapshot or static):** + +| Field | Description | +|--------|-------------| +| `feeModel` | `percent`, `flat`, `tiered`, `conditional` | +| `bps` | Basis points if percent | +| `flatAmount` | If flat | +| `currency` | Fee currency | +| `conditionRef` | Opaque id for conditional rules (Policy DSL later) | + +**Fee propagation tree:** Derived by taking the subgraph induced by `fee_hop` edges (and optionally fee-bearing segments), orienting edges in flow direction, and interpreting parent/child as **payer → collector → onward**. The routing graph remains the single source of truth; the tree is a **view**, not a second graph. + +--- + +## 8. Jurisdiction overlays + +- **Node default:** Each vertex has a primary `jurisdiction`. +- **Edge override:** `jurisdictions[]` on an edge marks **crossing** or **rule buckets** (e.g. `US-OFAC`, `EU-PII`). +- **Transaction-level filter:** Evaluation request may supply `transactionJurisdictions` or `denyJurisdictionTags`; the pathfinder **prunes** edges violating hard policy (details in Policy DSL doc). + +Compliance outcomes (PASS / WARN / FAIL) are produced by the Compliance Engine; this model only supplies **tags and penalties** (`regulatoryPenalty`) consumed by routing. + +--- + +## 9. Mapping from Transaction Composer + +The Transaction Composer ([transaction-composer/](transaction-composer/)) compiles UI graphs into `CompiledTransaction` with buckets: `participants`, `nostroAccounts`, `liquidity`, `fx`, `fees`, `settlement`, plus `topology`. + +**Composer is a pipeline; routing graph is a network.** The table below is a **projection guide**, not a 1:1 id equality (routing vertices may be created per registry lookup). + +| Composer source | Compiler bucket / `NodeKind` | Typical routing vertex kind(s) | Notes | +|-----------------|--------------------------------|---------------------------------|--------| +| Central / commercial / remittance bank | `participants` (`centralBank`, `commercialBank`, `remittanceInstitution`) | `Institution` | Map `institution` string to registry id; `participantRole` informs source vs beneficiary anchor. | +| Nostro | `nostroAccounts` | `AccountCorridor`, `Institution` | Often one corridor per nostro relationship. | +| Liquidity | `liquidity` (`liquidityProvider`) | `LiquidityPool`, `Institution`, `liquidity_link` | Pool may be resolved via Liquidity Pool Registry. | +| FX | `fx` (`fxConversion`) | `FxVenue`, `fx_quote` edges | May expand to venue + pair legs. | +| Fees | `fees` (`feeRouter`) | `FeeAgent` or `Institution`, `fee_hop` | Multiple fee nodes become multiple hops or a small fee subgraph. | +| Settlement | `settlement` | `SettlementRail`, `BeneficiaryAnchor` | Beneficiary text may map to anchor + rail. | +| Topology edges | `topology.edges` | Mixed `edgeKind` | Composer edges imply **ordering**; routing edges add **weights** and **alternates**. | + +**Important:** The composer `topology.orderedNodeIds` defines **design order**. The routing engine may introduce **parallel paths** (e.g. two correspondent edges) not present in the UI graph. + +--- + +## 10. Serialization and versioning + +### 10.1 JSON envelope + +```json +{ + "schemaVersion": "1.0.0", + "graphId": "rg-2026-03-29-indonesia-demo", + "effectiveAt": "2026-03-29T12:00:00Z", + "vertices": [], + "edges": [], + "overlays": { + "liquiditySnapshotId": "optional-registry-pointer", + "feeScheduleSnapshotId": "optional", + "notes": "optional" + } +} +``` + +### 10.2 Stability rules + +- **Patch** (1.0.x): Add optional fields only; do not remove or rename required fields. +- **Minor** (1.x.0): Add new `vertexKind` / `edgeKind` values; old consumers ignore unknown kinds if possible. +- **Major** (x.0.0): Breaking renames or semantic changes. + +--- + +## 11. Worked example (minimal) + +Scenario aligned with composer demo intent: **OMNL → BNI nostro/liquidity → USD/IDR FX → fees → settlement at Bank Kanaya; beneficiary PT Parak International.** Includes one **alternate** `correspondent` edge as a failover hint (not expanded into full failover logic). + +```json +{ + "schemaVersion": "1.0.0", + "graphId": "example-omnl-bni-kanaya", + "effectiveAt": "2026-03-29T12:00:00Z", + "vertices": [ + { + "id": "v-omnl", + "vertexKind": "Institution", + "displayName": "OMNL", + "jurisdiction": "ID", + "identifiers": { "internalOrgId": "OMNL" }, + "capabilities": { "currenciesAllowed": ["USD"], "fxPermitted": false } + }, + { + "id": "v-bni", + "vertexKind": "Institution", + "displayName": "BNI", + "jurisdiction": "ID", + "identifiers": { "bic": "BNINIDJA" } + }, + { + "id": "v-nostro-bni-usd", + "vertexKind": "AccountCorridor", + "displayName": "BNI USD Nostro corridor", + "jurisdiction": "ID", + "identifiers": { "internalOrgId": "BNI", "currency": "USD" } + }, + { + "id": "v-pool-bni-usd", + "vertexKind": "LiquidityPool", + "displayName": "BNI USD pool", + "jurisdiction": "ID", + "metadata": { + "currency": "USD", + "availableAmount": "70000000000", + "providerId": "v-bni", + "expiresAt": "2026-03-29T18:00:00Z" + } + }, + { + "id": "v-fx-bni", + "vertexKind": "FxVenue", + "displayName": "BNI FX", + "jurisdiction": "ID", + "capabilities": { "currenciesAllowed": ["USD", "IDR"], "fxPermitted": true } + }, + { + "id": "v-rail-id", + "vertexKind": "SettlementRail", + "displayName": "ID domestic settlement", + "jurisdiction": "ID", + "capabilities": { "settlementTypes": ["RTGS"] } + }, + { + "id": "v-kanaya", + "vertexKind": "Institution", + "displayName": "Bank Kanaya", + "jurisdiction": "ID" + }, + { + "id": "v-parak", + "vertexKind": "BeneficiaryAnchor", + "displayName": "PT Parak International", + "jurisdiction": "ID", + "metadata": { "creditInstitutionId": "v-kanaya" } + }, + { + "id": "v-alt-correspondent", + "vertexKind": "Institution", + "displayName": "Alternate correspondent (failover candidate)", + "jurisdiction": "ID" + } + ], + "edges": [ + { + "id": "e-omnl-nostro", + "sourceId": "v-omnl", + "targetId": "v-nostro-bni-usd", + "edgeKind": "correspondent", + "costVector": { "feeCost": 0, "latencyMs": 200, "liquidityAvailability": 1, "regulatoryPenalty": 0, "reliability": 0.999 } + }, + { + "id": "e-nostro-pool", + "sourceId": "v-nostro-bni-usd", + "targetId": "v-pool-bni-usd", + "edgeKind": "liquidity_link", + "liquidityRef": "pool-bni-usd-001", + "costVector": { "feeCost": 0, "latencyMs": 50, "liquidityAvailability": 0.95, "regulatoryPenalty": 0, "reliability": 0.998 } + }, + { + "id": "e-pool-fx", + "sourceId": "v-pool-bni-usd", + "targetId": "v-fx-bni", + "edgeKind": "liquidity_link", + "costVector": { "feeCost": 0, "latencyMs": 100, "liquidityAvailability": 0.93, "regulatoryPenalty": 0, "reliability": 0.997 } + }, + { + "id": "e-fx-rail", + "sourceId": "v-fx-bni", + "targetId": "v-rail-id", + "edgeKind": "fx_quote", + "metadata": { "pair": "USD/IDR", "rateRef": "DEMO-15000" }, + "costVector": { "feeCost": 500, "latencyMs": 400, "liquidityAvailability": 0.9, "regulatoryPenalty": 0, "reliability": 0.996 } + }, + { + "id": "e-fee-compliance", + "sourceId": "v-rail-id", + "targetId": "v-kanaya", + "edgeKind": "fee_hop", + "feeRef": "fee-reg-omnl-bni", + "costVector": { "feeCost": 200, "latencyMs": 50, "liquidityAvailability": 1, "regulatoryPenalty": 0, "reliability": 0.999 } + }, + { + "id": "e-settle-parak", + "sourceId": "v-kanaya", + "targetId": "v-parak", + "edgeKind": "settlement_path", + "costVector": { "feeCost": 0, "latencyMs": 300, "liquidityAvailability": 1, "regulatoryPenalty": 0, "reliability": 0.998 } + }, + { + "id": "e-omnl-alt-nostro", + "sourceId": "v-omnl", + "targetId": "v-alt-correspondent", + "edgeKind": "correspondent", + "costVector": { "feeCost": 800, "latencyMs": 350, "liquidityAvailability": 0.7, "regulatoryPenalty": 0, "reliability": 0.99 }, + "metadata": { "role": "failover_candidate" } + } + ], + "overlays": { + "liquiditySnapshotId": "demo-snapshot-001", + "notes": "Demonstration only; ids and costs are illustrative." + } +} +``` + +--- + +## 12. Related work (next documents) + +| Next artifact | What this routing graph doc provides | +|---------------|--------------------------------------| +| **Policy DSL specification** | `policyRefs` on edges, `regulatoryPenalty`, jurisdiction tags, and constraint hooks for hard rejects. | +| **Liquidity pool registry model** | Canonical `poolId`, refresh TTL, and binding to `LiquidityPool` vertices / `liquidity_link` edges. | +| **Decision explanation engine** | Path trace over `vertices`/`edges` with human-readable labels and cost breakdowns from `costVector`. | +| **Failover routing strategy model** | k-shortest paths, edge_disjoint alternates, and scoring on top of this graph without redefining V/E types. | + +--- + +## References + +- [hybx_compliance_routing_sidecar_technical_plan.md](hybx_compliance_routing_sidecar_technical_plan.md) — sidecar architecture, Routing Engine, API sketch. +- [transaction-composer/src/types/nodeTypes.ts](transaction-composer/src/types/nodeTypes.ts) — `NodeKind`, `TransactionNodeData`. +- [transaction-composer/src/orchestration/transactionCompiler.ts](transaction-composer/src/orchestration/transactionCompiler.ts) — `CompiledTransaction` buckets and topology. diff --git a/info-defi-oracle-138/README.md b/info-defi-oracle-138/README.md new file mode 100644 index 0000000..7a3c202 --- /dev/null +++ b/info-defi-oracle-138/README.md @@ -0,0 +1,53 @@ +# info.defi-oracle.io — Chain 138 public hub + +Static SPA (Vite + React) for **https://info.defi-oracle.io**: compliant **c\*** tokens, **cW\*** registry (from `cross-chain-pmm-lps/config/deployment-status.json`), live **pools** and **quotes** from the token-aggregation API, **CCIP routing** tables, and wallet **swapExactIn** on `DODOPMMIntegration`. + +## Develop + +```bash +cd /path/to/proxmox +pnpm install +pnpm --filter info-defi-oracle-138 dev +``` + +Open http://localhost:5180 + +## Environment (optional) + +| Variable | Purpose | +|----------|---------| +| `VITE_TOKEN_AGGREGATION_API_BASE` | Token-aggregation origin (default `https://dbis-api.d-bis.org`). The client also tries the same host with `/token-aggregation` prefix and `https://explorer.d-bis.org` variants until one returns HTTP 200. | +| `VITE_RPC_URL_138` | Chain 138 RPC for wallet / reads (default `https://rpc.defi-oracle.io`) | + +Example: + +```bash +export VITE_TOKEN_AGGREGATION_API_BASE=https://dbis-api.d-bis.org +export VITE_RPC_URL_138=https://rpc.defi-oracle.io +pnpm --filter info-defi-oracle-138 build +``` + +## Build + +```bash +pnpm --filter info-defi-oracle-138 build +``` + +Output: `info-defi-oracle-138/dist/` — deploy as static files (any CDN, Nginx, object storage, or LXC). + +## Deploy `info.defi-oracle.io` + +1. **DNS:** Point `info.defi-oracle.io` (A/AAAA or CNAME) to your edge (Cloudflare, NPMplus host, etc.). +2. **TLS:** Issue certificate for `info.defi-oracle.io` (same pattern as `rpc.defi-oracle.io`). +3. **NPMplus (or Nginx):** Proxy host → upstream serving `dist/` with `try_files $uri $uri/ /index.html` for SPA routes. +4. **API:** Ensure `VITE_TOKEN_AGGREGATION_API_BASE` at build time points to a **public** token-aggregation URL that allows browser CORS (service already uses `cors()`). + +See also: `docs/04-configuration/E2E_ENDPOINTS_LIST.md` (dbis-api hosts), `docs/04-configuration/ALL_VMIDS_ENDPOINTS.md` (VMID targets). + +## Refreshing cW\* addresses + +cW\* tables are **bundled at build** from `cross-chain-pmm-lps/config/deployment-status.json`. Rebuild and redeploy after registry changes. + +## Canonical on-chain addresses + +Built-in constants follow `docs/11-references/CONTRACT_ADDRESSES_REFERENCE.md` (e.g. `DODOPMMIntegration`, cUSDT/cUSDC on 138). diff --git a/info-defi-oracle-138/index.html b/info-defi-oracle-138/index.html new file mode 100644 index 0000000..76fdf53 --- /dev/null +++ b/info-defi-oracle-138/index.html @@ -0,0 +1,23 @@ + + + + + + + + info.defi-oracle.io — Chain 138 + + + + + +
+ + + diff --git a/info-defi-oracle-138/package.json b/info-defi-oracle-138/package.json new file mode 100644 index 0000000..b53c028 --- /dev/null +++ b/info-defi-oracle-138/package.json @@ -0,0 +1,27 @@ +{ + "name": "info-defi-oracle-138", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -p tsconfig.json && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.90.21", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.3", + "viem": "^2.46.1", + "wagmi": "^2.19.5" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@types/react": "^18.3.28", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.7.0", + "typescript": "^5.9.3", + "vite": "^5.4.21" + } +} diff --git a/info-defi-oracle-138/public/favicon.svg b/info-defi-oracle-138/public/favicon.svg new file mode 100644 index 0000000..254322b --- /dev/null +++ b/info-defi-oracle-138/public/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/info-defi-oracle-138/src/App.tsx b/info-defi-oracle-138/src/App.tsx new file mode 100644 index 0000000..d5cf23d --- /dev/null +++ b/info-defi-oracle-138/src/App.tsx @@ -0,0 +1,25 @@ +import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; +import { Layout } from '@/components/Layout'; +import { HomePage } from '@/pages/HomePage'; +import { TokensPage } from '@/pages/TokensPage'; +import { PoolsPage } from '@/pages/PoolsPage'; +import { SwapPage } from '@/pages/SwapPage'; +import { RoutingPage } from '@/pages/RoutingPage'; + +export function App() { + return ( + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +} diff --git a/info-defi-oracle-138/src/abi/integration.ts b/info-defi-oracle-138/src/abi/integration.ts new file mode 100644 index 0000000..14674c2 --- /dev/null +++ b/info-defi-oracle-138/src/abi/integration.ts @@ -0,0 +1,44 @@ +export const dodopmmIntegrationAbi = [ + { + name: 'swapExactIn', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { name: 'pool', type: 'address' }, + { name: 'tokenIn', type: 'address' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'minAmountOut', type: 'uint256' }, + ], + outputs: [{ name: 'amountOut', type: 'uint256' }], + }, +] as const; + +export const erc20Abi = [ + { + name: 'approve', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { name: 'spender', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [{ type: 'bool' }], + }, + { + name: 'allowance', + type: 'function', + stateMutability: 'view', + inputs: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + ], + outputs: [{ type: 'uint256' }], + }, + { + name: 'decimals', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint8' }], + }, +] as const; diff --git a/info-defi-oracle-138/src/api/client.ts b/info-defi-oracle-138/src/api/client.ts new file mode 100644 index 0000000..52ca8a4 --- /dev/null +++ b/info-defi-oracle-138/src/api/client.ts @@ -0,0 +1,49 @@ +import { TOKEN_AGGREGATION_BASE } from '@/config'; + +export class ApiError extends Error { + constructor( + message: string, + public status: number, + ) { + super(message); + this.name = 'ApiError'; + } +} + +/** Try primary URL, then /token-aggregation prefix on same origin, then public explorer hosts. */ +function aggregationBases(): string[] { + const primary = TOKEN_AGGREGATION_BASE.replace(/\/$/, ''); + let origin = ''; + try { + origin = new URL(primary).origin; + } catch { + /* ignore */ + } + const list = [ + primary, + ...(origin ? [`${origin}/token-aggregation`] : []), + 'https://explorer.d-bis.org', + 'https://explorer.d-bis.org/token-aggregation', + ].filter(Boolean); + return [...new Set(list)]; +} + +export async function fetchApi(path: string): Promise { + const p = path.startsWith('/') ? path : `/${path}`; + let last: Error | null = null; + for (const base of aggregationBases()) { + const url = `${base}${p}`; + try { + const res = await fetch(url); + if (!res.ok) { + const text = await res.text().catch(() => ''); + last = new ApiError(text || res.statusText, res.status); + continue; + } + return res.json() as Promise; + } catch (e) { + last = e instanceof Error ? e : new Error(String(e)); + } + } + throw last || new Error('All token-aggregation API bases failed'); +} diff --git a/info-defi-oracle-138/src/api/types.ts b/info-defi-oracle-138/src/api/types.ts new file mode 100644 index 0000000..f686e71 --- /dev/null +++ b/info-defi-oracle-138/src/api/types.ts @@ -0,0 +1,61 @@ +export type TokenRow = { + address: string; + symbol?: string; + name?: string; + decimals?: number; + hasDodoPool?: boolean; + pmmPool?: string | null; + market?: { priceUsd?: string; volume24h?: string; tvl?: string }; +}; + +export type TokensResponse = { + tokens: TokenRow[]; + pagination: { limit: number; offset: number; count: number }; +}; + +export type PoolRow = { + address: string; + dex?: string; + token0?: { address: string; symbol?: string }; + token1?: { address: string; symbol?: string }; + reserves?: { reserve0?: string; reserve1?: string }; + tvl?: string; + volume24h?: string; + feeTier?: string; +}; + +export type PoolsResponse = { pools: PoolRow[] }; + +export type QuoteResponse = { + amountOut: string | null; + poolAddress?: string | null; + dexType?: string | null; + error?: string; +}; + +export type BridgeRoutesResponse = { + routes: { + weth9: Record; + weth10: Record; + trustless?: Record; + }; + chain138Bridges: { weth9: string; weth10: string; trustless?: string }; + tokenMappingApi?: unknown; +}; + +export type TokenMappingResponse = { + tokens?: string[]; + addressMapFromTo?: Record; + addressMapToFrom?: Record; +}; + +export type NetworksResponse = { + version: string; + networks: Array<{ + chainId: string; + chainIdDecimal: number; + chainName: string; + rpcUrls: string[]; + blockExplorerUrls?: string[]; + }>; +}; diff --git a/info-defi-oracle-138/src/chains.ts b/info-defi-oracle-138/src/chains.ts new file mode 100644 index 0000000..8350bee --- /dev/null +++ b/info-defi-oracle-138/src/chains.ts @@ -0,0 +1,17 @@ +import { defineChain } from 'viem'; + +export const chain138 = defineChain({ + id: 138, + name: 'DeFi Oracle Meta Mainnet', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { + default: { + http: [ + (import.meta.env.VITE_RPC_URL_138 as string | undefined) || 'https://rpc.defi-oracle.io', + ], + }, + }, + blockExplorers: { + default: { name: 'Blockscout', url: 'https://explorer.d-bis.org' }, + }, +}); diff --git a/info-defi-oracle-138/src/components/Layout.tsx b/info-defi-oracle-138/src/components/Layout.tsx new file mode 100644 index 0000000..5940e8f --- /dev/null +++ b/info-defi-oracle-138/src/components/Layout.tsx @@ -0,0 +1,50 @@ +import { NavLink, Outlet } from 'react-router-dom'; +import { CHAIN_ID, EXPLORER_BASE, TOKEN_AGGREGATION_BASE } from '@/config'; + +const linkClass = ({ isActive }: { isActive: boolean }) => (isActive ? 'active' : undefined); + +export function Layout() { + return ( +
+
+
+ info.defi-oracle.io + — Chain {CHAIN_ID} +
+ +
+ +
+

+ Live data from token-aggregation API:{' '} + {TOKEN_AGGREGATION_BASE} +
+ Explorer:{' '} + + {EXPLORER_BASE} + + {' · '} + Repo docs:{' '} + docs/04-configuration/DEX_AND_AGGREGATORS_CHAIN138_EXPLAINER.md,{' '} + docs/11-references/CW_STAR_CMC_COINGECKO_LISTING_STATUS.md +

+
+
+ ); +} diff --git a/info-defi-oracle-138/src/config.ts b/info-defi-oracle-138/src/config.ts new file mode 100644 index 0000000..08c69d0 --- /dev/null +++ b/info-defi-oracle-138/src/config.ts @@ -0,0 +1,18 @@ +/** Chain 138 hub — align with docs/11-references/CONTRACT_ADDRESSES_REFERENCE.md */ +export const CHAIN_ID = 138; + +export const EXPLORER_BASE = 'https://explorer.d-bis.org'; + +export const DODOPMM_INTEGRATION = + '0x5BDc62f1ae7D630c37A8B363a1d49845356Ee72d' as const; + +export const TOKEN_AGGREGATION_BASE = + (import.meta.env.VITE_TOKEN_AGGREGATION_API_BASE as string | undefined)?.replace(/\/$/, '') || + 'https://dbis-api.d-bis.org'; + +export const RPC_URL_138 = + (import.meta.env.VITE_RPC_URL_138 as string | undefined) || 'https://rpc.defi-oracle.io'; + +/** Canonical cUSDT / cUSDC on Chain 138 (see EXPLORER_TOKEN_LIST_CROSSCHECK) */ +export const CANONICAL_CUSDT = '0x93E66202A11B1772E55407B32B44e5Cd8eda7f22'; +export const CANONICAL_CUSDC = '0xf22258f57794CC8E06237084b353Ab30fFfa640b'; diff --git a/info-defi-oracle-138/src/data/deployment-status.ts b/info-defi-oracle-138/src/data/deployment-status.ts new file mode 100644 index 0000000..7ae86c9 --- /dev/null +++ b/info-defi-oracle-138/src/data/deployment-status.ts @@ -0,0 +1,34 @@ +/** + * Bridged cW* registry (public EVM chains). Source: cross-chain-pmm-lps/config/deployment-status.json + * Re-import or sync when addresses change. + */ +import raw from '../../../cross-chain-pmm-lps/config/deployment-status.json'; + +export type DeploymentStatus = typeof raw; + +export const deploymentStatus: DeploymentStatus = raw; + +export function listCwChains(): Array<{ + chainId: string; + name: string; + tokens: Array<{ symbol: string; address: string }>; +}> { + const out: Array<{ + chainId: string; + name: string; + tokens: Array<{ symbol: string; address: string }>; + }> = []; + const chains = deploymentStatus.chains as Record< + string, + { name: string; cwTokens: Record } + >; + for (const [chainId, row] of Object.entries(chains)) { + const tokens = Object.entries(row.cwTokens || {}).map(([symbol, address]) => ({ + symbol, + address, + })); + if (tokens.length === 0) continue; + out.push({ chainId, name: row.name, tokens }); + } + return out.sort((a, b) => Number(a.chainId) - Number(b.chainId)); +} diff --git a/info-defi-oracle-138/src/main.tsx b/info-defi-oracle-138/src/main.tsx new file mode 100644 index 0000000..ffda733 --- /dev/null +++ b/info-defi-oracle-138/src/main.tsx @@ -0,0 +1,33 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { WagmiProvider, createConfig, http } from 'wagmi'; +import { injected } from 'wagmi/connectors'; +import { chain138 } from '@/chains'; +import { RPC_URL_138 } from '@/config'; +import { App } from '@/App'; +import '@/styles/global.css'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: 1, refetchOnWindowFocus: false }, + }, +}); + +const wagmiConfig = createConfig({ + chains: [chain138], + connectors: [injected()], + transports: { + [chain138.id]: http(RPC_URL_138), + }, +}); + +createRoot(document.getElementById('root')!).render( + + + + + + + , +); diff --git a/info-defi-oracle-138/src/pages/HomePage.tsx b/info-defi-oracle-138/src/pages/HomePage.tsx new file mode 100644 index 0000000..b5055f4 --- /dev/null +++ b/info-defi-oracle-138/src/pages/HomePage.tsx @@ -0,0 +1,85 @@ +import { Link } from 'react-router-dom'; +import { CHAIN_ID, EXPLORER_BASE, RPC_URL_138 } from '@/config'; +import { useQuery } from '@tanstack/react-query'; +import { fetchApi } from '@/api/client'; +import type { NetworksResponse } from '@/api/types'; + +export function HomePage() { + const { data, isError } = useQuery({ + queryKey: ['networks'], + queryFn: () => fetchApi('/api/v1/networks'), + staleTime: 60_000, + }); + + const n138 = data?.networks?.find((x) => x.chainIdDecimal === CHAIN_ID); + + return ( + <> +

DeFi Oracle Meta Mainnet

+

+ Public hub for compliant c* tokens on Chain {CHAIN_ID}, the{' '} + cW* bridged registry on partner networks, live PMM pools, bridge routing, and + single-hop atomic swaps via DODOPMMIntegration. +

+ +
+ +

c* & cW*

+

Canonical Chain {CHAIN_ID} tokens from the API plus bridged cW* addresses per chain.

+ + +

Pools

+

Indexed liquidity from token-aggregation (refreshes with indexer).

+ + +

Atomic swap

+

Quote + wallet: approve and call swapExactIn on the integration contract.

+ + +

Routing

+

CCIP WETH bridge table and cross-chain token mapping for 138.

+ +
+ +

Network

+ {isError && ( +

+ Could not load /api/v1/networks — check API base URL and CORS. + Default RPC below still works for wallets. +

+ )} +
+ + + + + + + + + + + + + + + {n138 && ( + <> + + + + + + + + + + )} + +
Chain ID{CHAIN_ID}
RPC (fallback){RPC_URL_138}
Explorer + {EXPLORER_BASE} +
Name (API){n138.chainName}
RPC (API){n138.rpcUrls?.[0] ?? '—'}
+
+ + ); +} diff --git a/info-defi-oracle-138/src/pages/PoolsPage.tsx b/info-defi-oracle-138/src/pages/PoolsPage.tsx new file mode 100644 index 0000000..4fb0f10 --- /dev/null +++ b/info-defi-oracle-138/src/pages/PoolsPage.tsx @@ -0,0 +1,116 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchApi } from '@/api/client'; +import type { PoolsResponse, TokensResponse } from '@/api/types'; +import { CHAIN_ID, EXPLORER_BASE } from '@/config'; + +function explorerAddress(addr: string) { + return `${EXPLORER_BASE}/address/${addr}`; +} + +async function fetchPoolsForToken(address: string): Promise { + try { + const res = await fetchApi( + `/api/v1/tokens/${address}/pools?chainId=${CHAIN_ID}`, + ); + return res.pools || []; + } catch { + return []; + } +} + +export function PoolsPage() { + const tokensQuery = useQuery({ + queryKey: ['tokens-pools', CHAIN_ID], + queryFn: () => + fetchApi(`/api/v1/tokens?chainId=${CHAIN_ID}&limit=150&includeDodoPool=true`), + staleTime: 30_000, + }); + + const poolAggQuery = useQuery({ + queryKey: ['aggregate-pools', tokensQuery.data?.tokens?.map((t) => t.address).join(',')], + enabled: Boolean(tokensQuery.data?.tokens?.length), + queryFn: async () => { + const tokens = tokensQuery.data!.tokens.filter((t) => + (t.symbol || '').toLowerCase().startsWith('c'), + ); + const slice = tokens.slice(0, 48); + const rows: Array<{ + token: string; + symbol?: string; + pool: PoolsResponse['pools'][0]; + }> = []; + const batchSize = 8; + for (let i = 0; i < slice.length; i += batchSize) { + const batch = slice.slice(i, i + batchSize); + const results = await Promise.all( + batch.map(async (t) => { + const pools = await fetchPoolsForToken(t.address); + return pools.map((pool) => ({ token: t.address, symbol: t.symbol, pool })); + }), + ); + for (const r of results) rows.push(...r); + } + return rows; + }, + staleTime: 45_000, + }); + + return ( + <> +

Liquidity pools

+

+ Pools are loaded from the token-aggregation indexer for Chain {CHAIN_ID} compliant (c*) + tokens (sampled up to 48 symbols). TVL and volume update with the service refresh cycle. +

+ + {tokensQuery.isError && ( +
{(tokensQuery.error as Error).message}
+ )} + {poolAggQuery.isFetching &&

Loading pool details…

} + {poolAggQuery.isError && ( +
{(poolAggQuery.error as Error).message}
+ )} + + {poolAggQuery.data && ( +
+ + + + + + + + + + + + {poolAggQuery.data.length === 0 && ( + + + + )} + {poolAggQuery.data.map(({ pool, token, symbol }) => ( + + + + + + + + ))} + +
PoolDEXPairTVLVol 24h
+ No pools returned for sampled tokens (API unreachable or indexer empty). +
+ + {pool.address.slice(0, 8)}…{pool.address.slice(-6)} + + {pool.dex || '—'} + {symbol || '?'}{' '} + {pool.token0?.symbol || '?'} / {pool.token1?.symbol || '?'} + {pool.tvl ?? '—'}{pool.volume24h ?? '—'}
+
+ )} + + ); +} diff --git a/info-defi-oracle-138/src/pages/RoutingPage.tsx b/info-defi-oracle-138/src/pages/RoutingPage.tsx new file mode 100644 index 0000000..91a4ea5 --- /dev/null +++ b/info-defi-oracle-138/src/pages/RoutingPage.tsx @@ -0,0 +1,148 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchApi } from '@/api/client'; +import type { BridgeRoutesResponse, TokenMappingResponse } from '@/api/types'; +import { CHAIN_ID, EXPLORER_BASE } from '@/config'; +import bridgeRoutesFallback from '@repo-config/bridge-routes-chain138-default.json'; + +const PAIRS: Array<{ from: number; to: number; label: string }> = [ + { from: CHAIN_ID, to: 1, label: `${CHAIN_ID} → Ethereum` }, + { from: 1, to: CHAIN_ID, label: `Ethereum → ${CHAIN_ID}` }, + { from: CHAIN_ID, to: 651940, label: `${CHAIN_ID} → ALL (651940)` }, +]; + +export function RoutingPage() { + const routesQ = useQuery({ + queryKey: ['bridge-routes'], + queryFn: async (): Promise => { + try { + return await fetchApi('/api/v1/bridge/routes'); + } catch { + return bridgeRoutesFallback as BridgeRoutesResponse; + } + }, + staleTime: 120_000, + }); + + const mappingQs = useQuery({ + queryKey: ['token-mappings', PAIRS], + queryFn: async () => { + const out: Array<{ label: string; data: TokenMappingResponse | null; error?: string }> = []; + for (const p of PAIRS) { + try { + const data = await fetchApi( + `/api/v1/token-mapping?fromChain=${p.from}&toChain=${p.to}`, + ); + out.push({ label: p.label, data }); + } catch (e) { + out.push({ + label: p.label, + data: null, + error: (e as Error).message, + }); + } + } + return out; + }, + staleTime: 120_000, + }); + + return ( + <> +

Routing & bridges

+

+ CCIP WETH9/WETH10 bridge addresses and cross-chain token mapping for building swap → bridge → + swap flows. Single-hop quotes remain on the Swap tab. +

+ +

Chain {CHAIN_ID} bridge contracts

+ {routesQ.isError &&
{(routesQ.error as Error).message}
} +

+ Bridge table uses the live API when any configured host returns 200; if all bases fail, the UI + falls back to config/bridge-routes-chain138-default.json. +

+ {routesQ.data?.chain138Bridges && ( +
+ + + + + + + + + + + {routesQ.data.chain138Bridges.trustless && ( + + + + + )} + +
WETH9 bridge (138) + + {routesQ.data.chain138Bridges.weth9} + +
WETH10 bridge (138) + + {routesQ.data.chain138Bridges.weth10} + +
Trustless Lockbox (138) + + {routesQ.data.chain138Bridges.trustless} + +
+
+ )} + +

Destination routers (WETH9)

+ {routesQ.data?.routes?.weth9 && ( +
+ + + + + + + + + {Object.entries(routesQ.data.routes.weth9).map(([name, addr]) => ( + + + + + ))} + +
DestinationBridge / router address
{name}{addr}
+
+ )} + +

Token mapping snapshots

+ {mappingQs.isFetching &&

Loading mappings…

} + {mappingQs.data?.map((block) => ( +
+

{block.label}

+ {block.error &&
{block.error}
} + {block.data?.tokens && ( +

+ {block.data.tokens.slice(0, 24).join(', ')} + {block.data.tokens.length > 24 ? '…' : ''} +

+ )} +
+ ))} + + ); +} diff --git a/info-defi-oracle-138/src/pages/SwapPage.tsx b/info-defi-oracle-138/src/pages/SwapPage.tsx new file mode 100644 index 0000000..3902cf4 --- /dev/null +++ b/info-defi-oracle-138/src/pages/SwapPage.tsx @@ -0,0 +1,282 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { + useAccount, + useConnect, + useDisconnect, + useReadContract, + useWriteContract, + useWaitForTransactionReceipt, +} from 'wagmi'; +import { injected } from 'wagmi/connectors'; +import { parseUnits, formatUnits } from 'viem'; +import { fetchApi } from '@/api/client'; +import type { QuoteResponse, TokensResponse } from '@/api/types'; +import { + CHAIN_ID, + DODOPMM_INTEGRATION, + EXPLORER_BASE, + CANONICAL_CUSDT, + CANONICAL_CUSDC, +} from '@/config'; +import { dodopmmIntegrationAbi, erc20Abi } from '@/abi/integration'; + +export function SwapPage() { + const { address, isConnected, chainId } = useAccount(); + const { connect } = useConnect(); + const { disconnect } = useDisconnect(); + + const tokensQuery = useQuery({ + queryKey: ['tokens-swap', CHAIN_ID], + queryFn: () => + fetchApi(`/api/v1/tokens?chainId=${CHAIN_ID}&limit=200&includeDodoPool=true`), + staleTime: 60_000, + }); + + const fallbackTokens = useMemo( + () => [ + { address: CANONICAL_CUSDT, symbol: 'cUSDT', decimals: 6, name: 'Tether USD (Compliant)' }, + { address: CANONICAL_CUSDC, symbol: 'cUSDC', decimals: 6, name: 'USD Coin (Compliant)' }, + ], + [], + ); + + const cTokens = useMemo(() => { + const fromApi = (tokensQuery.data?.tokens || []).filter((t) => + (t.symbol || '').toLowerCase().startsWith('c'), + ); + return fromApi.length > 0 ? fromApi : fallbackTokens; + }, [tokensQuery.data, fallbackTokens]); + + const [tokenIn, setTokenIn] = useState(CANONICAL_CUSDT); + const [tokenOut, setTokenOut] = useState(CANONICAL_CUSDC); + const [amountInHuman, setAmountInHuman] = useState('100'); + + const tokenInMeta = cTokens.find( + (t) => t.address.toLowerCase() === tokenIn.toLowerCase(), + ); + const tokenOutMeta = cTokens.find( + (t) => t.address.toLowerCase() === tokenOut.toLowerCase(), + ); + const decimalsIn = tokenInMeta?.decimals ?? 6; + const decimalsOut = tokenOutMeta?.decimals ?? 6; + + const amountInWei = useMemo(() => { + try { + return parseUnits(amountInHuman || '0', decimalsIn); + } catch { + return 0n; + } + }, [amountInHuman, decimalsIn]); + + const quoteQuery = useQuery({ + queryKey: ['quote', tokenIn, tokenOut, amountInHuman, decimalsIn], + enabled: amountInWei > 0n && tokenIn !== tokenOut, + queryFn: () => + fetchApi( + `/api/v1/quote?chainId=${CHAIN_ID}&tokenIn=${tokenIn}&tokenOut=${tokenOut}&amountIn=${amountInWei.toString()}`, + ), + staleTime: 15_000, + }); + + const amountOutWei = useMemo(() => { + const raw = quoteQuery.data?.amountOut; + if (!raw) return 0n; + try { + return BigInt(raw); + } catch { + return 0n; + } + }, [quoteQuery.data?.amountOut]); + + const minOut = useMemo(() => (amountOutWei * 99n) / 100n, [amountOutWei]); + const poolAddress = quoteQuery.data?.poolAddress as `0x${string}` | undefined; + + const { data: allowance, refetch: refetchAllowance } = useReadContract({ + address: tokenIn as `0x${string}`, + abi: erc20Abi, + functionName: 'allowance', + args: address ? [address, DODOPMM_INTEGRATION as `0x${string}`] : undefined, + query: { enabled: Boolean(address && tokenIn) }, + }); + + const needsApprove = + address && allowance !== undefined && amountInWei > 0n && allowance < amountInWei; + + const { writeContract: writeApprove, data: approveHash, isPending: approvePending } = + useWriteContract(); + const approveReceipt = useWaitForTransactionReceipt({ hash: approveHash }); + + const { writeContract: writeSwap, data: swapHash, isPending: swapPending } = useWriteContract(); + const swapReceipt = useWaitForTransactionReceipt({ hash: swapHash }); + + useEffect(() => { + if (approveReceipt.isSuccess) void refetchAllowance(); + }, [approveReceipt.isSuccess, refetchAllowance]); + + const wrongChain = isConnected && chainId !== CHAIN_ID; + + return ( + <> +

Atomic swap (single hop)

+

+ Fetches a quote from the aggregation API, then (on Chain {CHAIN_ID}) approves{' '} + DODOPMMIntegration and calls{' '} + swapExactIn for the returned pool. Multi-hop routing is not + available in the API; use sequential swaps if needed. +

+ +
+
+ {!isConnected ? ( + + ) : ( + <> + {address} + + + )} +
+ {wrongChain && ( +
Switch your wallet to Chain {CHAIN_ID} (DeFi Oracle Meta).
+ )} + +
+ + +
+
+ +
+ + {quoteQuery.isFetching &&

Fetching quote…

} + {quoteQuery.error && ( +
{(quoteQuery.error as Error).message}
+ )} + {quoteQuery.data?.error &&
{quoteQuery.data.error}
} + + {quoteQuery.data && !quoteQuery.data.error && ( +
+
+ Est. out:{' '} + + {amountOutWei > 0n + ? formatUnits(amountOutWei, decimalsOut) + : quoteQuery.data.amountOut || '—'} + {' '} + (raw minOut with 1% slip: {minOut.toString()}) +
+
+ Pool:{' '} + {poolAddress ? ( + + {poolAddress} + + ) : ( + '—' + )} +
+
+ Integration: {DODOPMM_INTEGRATION} +
+
+ )} + + {isConnected && + !wrongChain && + poolAddress && + amountInWei > 0n && + amountOutWei > 0n && + allowance !== undefined && ( +
+ {needsApprove ? ( + + ) : ( + + )} +
+ )} + + {approveHash && ( +

+ Approve tx:{' '} + + {approveHash} + {' '} + {approveReceipt.isSuccess ? '✓' : approveReceipt.isLoading ? '…' : ''} +

+ )} + {swapHash && ( +

+ Swap tx:{' '} + + {swapHash} + {' '} + {swapReceipt.isSuccess ? '✓' : swapReceipt.isLoading ? '…' : ''} +

+ )} +
+ + ); +} diff --git a/info-defi-oracle-138/src/pages/TokensPage.tsx b/info-defi-oracle-138/src/pages/TokensPage.tsx new file mode 100644 index 0000000..2aefcf5 --- /dev/null +++ b/info-defi-oracle-138/src/pages/TokensPage.tsx @@ -0,0 +1,106 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchApi } from '@/api/client'; +import type { TokensResponse } from '@/api/types'; +import { CHAIN_ID, EXPLORER_BASE } from '@/config'; +import { listCwChains } from '@/data/deployment-status'; + +function explorerToken(addr: string) { + return `${EXPLORER_BASE}/token/${addr}`; +} + +export function TokensPage() { + const { data, isLoading, error } = useQuery({ + queryKey: ['tokens', CHAIN_ID], + queryFn: () => + fetchApi(`/api/v1/tokens?chainId=${CHAIN_ID}&limit=200&includeDodoPool=true`), + staleTime: 30_000, + }); + + const cwChains = listCwChains(); + + const cStar = + data?.tokens?.filter((t) => (t.symbol || '').toLowerCase().startsWith('c')) || []; + + return ( + <> +

c* & cW* tokens

+

+ c* Compliant tokens on Chain {CHAIN_ID} (from indexer). +
+ cW* Bridged compliant-wrapped tokens on public EVM chains + (from repo deployment-status.json, rebuild site to refresh). +

+ +

+ Chain {CHAIN_ID} — c* (live API){' '} + {isLoading && Loading…} +

+ {error && ( +
{(error as Error).message}
+ )} + {!isLoading && !error && ( +
+ + + + + + + + + + + {cStar.map((t) => ( + + + + + + + ))} + +
SymbolNameAddressPMM
+ {t.symbol || '?'} + {t.name || '—'} + + {t.address.slice(0, 10)}…{t.address.slice(-6)} + + {t.hasDodoPool || t.pmmPool ? 'Yes' : '—'}
+
+ )} + +

Bridged cW* (registry)

+

+ Addresses are maintained in cross-chain-pmm-lps/config/deployment-status.json. +

+ {cwChains.map(({ chainId, name, tokens }) => ( +
+

+ {name}{' '} + ({chainId}) +

+
+ + + + + + + + + {tokens.map((row) => ( + + + + + ))} + +
SymbolAddress
+ {row.symbol} + {row.address}
+
+
+ ))} + + ); +} diff --git a/info-defi-oracle-138/src/styles/global.css b/info-defi-oracle-138/src/styles/global.css new file mode 100644 index 0000000..4d28cc7 --- /dev/null +++ b/info-defi-oracle-138/src/styles/global.css @@ -0,0 +1,257 @@ +:root { + --bg: #070b14; + --surface: #0f1629; + --surface2: #151d35; + --border: rgba(148, 163, 184, 0.18); + --text: #e2e8f0; + --muted: #94a3b8; + --accent: #38bdf8; + --accent2: #a78bfa; + --ok: #4ade80; + --warn: #fbbf24; + --err: #f87171; + --radius: 12px; + --font-sans: 'DM Sans', system-ui, sans-serif; + --font-display: 'Instrument Serif', Georgia, serif; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + margin: 0; + min-height: 100%; +} + +body { + font-family: var(--font-sans); + background: radial-gradient(1200px 600px at 10% -10%, rgba(56, 189, 248, 0.12), transparent), + radial-gradient(800px 400px at 90% 0%, rgba(167, 139, 250, 0.1), transparent), var(--bg); + color: var(--text); + line-height: 1.5; +} + +a { + color: var(--accent); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +code, +.mono { + font-family: ui-monospace, monospace; + font-size: 0.85em; +} + +.layout { + max-width: 1120px; + margin: 0 auto; + padding: 1.25rem 1.25rem 3rem; +} + +header.site { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + padding-bottom: 1.5rem; + border-bottom: 1px solid var(--border); + margin-bottom: 2rem; +} + +.brand { + font-family: var(--font-display); + font-size: 1.75rem; + font-weight: 400; + letter-spacing: -0.02em; +} +.brand span { + color: var(--muted); + font-size: 1rem; +} + +nav.site a { + margin-right: 1rem; + color: var(--muted); + font-weight: 600; + font-size: 0.9rem; + text-decoration: none; +} +nav.site a:hover { + color: var(--accent); +} +nav.site a.active { + color: var(--text); +} + +h1 { + font-family: var(--font-display); + font-size: 2.25rem; + font-weight: 400; + margin: 0 0 0.5rem; +} + +h2 { + font-size: 1.15rem; + font-weight: 700; + margin: 2rem 0 0.75rem; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +p.lead { + color: var(--muted); + max-width: 52ch; + margin: 0 0 1.5rem; +} + +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 1rem; +} + +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.1rem 1.25rem; +} +.card h3 { + margin: 0 0 0.35rem; + font-size: 1rem; +} +.card p { + margin: 0; + font-size: 0.9rem; + color: var(--muted); +} + +.table-wrap { + overflow-x: auto; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--surface); +} + +table.data { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} +table.data th, +table.data td { + text-align: left; + padding: 0.65rem 0.85rem; + border-bottom: 1px solid var(--border); +} +table.data th { + color: var(--muted); + font-weight: 600; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} +table.data tr:last-child td { + border-bottom: none; +} + +.badge { + display: inline-block; + padding: 0.15rem 0.45rem; + border-radius: 6px; + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; +} +.badge-c { + background: rgba(56, 189, 248, 0.15); + color: var(--accent); +} +.badge-cw { + background: rgba(167, 139, 250, 0.15); + color: var(--accent2); +} + +.form-row { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: flex-end; + margin-bottom: 1rem; +} +label { + display: flex; + flex-direction: column; + gap: 0.25rem; + font-size: 0.8rem; + color: var(--muted); + font-weight: 600; +} +input, +select, +button { + font: inherit; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--surface2); + color: var(--text); + padding: 0.5rem 0.65rem; +} +button.primary { + background: linear-gradient(135deg, #0ea5e9, #6366f1); + border: none; + color: white; + font-weight: 700; + cursor: pointer; + padding: 0.55rem 1.1rem; +} +button.primary:disabled { + opacity: 0.45; + cursor: not-allowed; +} +button.ghost { + background: transparent; + border-color: var(--border); + color: var(--muted); + cursor: pointer; +} + +.err-box { + background: rgba(248, 113, 113, 0.1); + border: 1px solid rgba(248, 113, 113, 0.35); + color: var(--err); + padding: 0.75rem 1rem; + border-radius: var(--radius); + font-size: 0.9rem; +} + +.ok-box { + background: rgba(74, 222, 128, 0.08); + border: 1px solid rgba(74, 222, 128, 0.25); + color: var(--ok); + padding: 0.75rem 1rem; + border-radius: var(--radius); + font-size: 0.9rem; +} + +.muted { + color: var(--muted); + font-size: 0.9rem; +} + +footer.site { + margin-top: 3rem; + padding-top: 1.5rem; + border-top: 1px solid var(--border); + font-size: 0.85rem; + color: var(--muted); +} diff --git a/info-defi-oracle-138/src/vite-env.d.ts b/info-defi-oracle-138/src/vite-env.d.ts new file mode 100644 index 0000000..817b9bf --- /dev/null +++ b/info-defi-oracle-138/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_TOKEN_AGGREGATION_API_BASE: string; + readonly VITE_RPC_URL_138: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/info-defi-oracle-138/tsconfig.json b/info-defi-oracle-138/tsconfig.json new file mode 100644 index 0000000..5c894bc --- /dev/null +++ b/info-defi-oracle-138/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@repo-config/*": ["../config/*"] + }, + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/info-defi-oracle-138/tsconfig.node.json b/info-defi-oracle-138/tsconfig.node.json new file mode 100644 index 0000000..d02c37d --- /dev/null +++ b/info-defi-oracle-138/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/info-defi-oracle-138/vite.config.ts b/info-defi-oracle-138/vite.config.ts new file mode 100644 index 0000000..94369a7 --- /dev/null +++ b/info-defi-oracle-138/vite.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + '@repo-config': path.resolve(__dirname, '../config'), + }, + }, + server: { + port: 5180, + }, + build: { + outDir: 'dist', + sourcemap: true, + }, +}); diff --git a/output/omnl-discovery/clients.json b/output/omnl-discovery/clients.json index e9d553e..00b189a 100644 --- a/output/omnl-discovery/clients.json +++ b/output/omnl-discovery/clients.json @@ -1 +1 @@ -{"totalFilteredRecords":2,"pageItems":[{"id":1,"accountNo":"348952249","externalId":"2db4be95-1eeb-4caa-b6ce-a646205f06fc","status":{"id":300,"code":"clientStatusType.active","value":"Active"},"subStatus":{"active":false,"mandatory":false},"active":true,"activationDate":[2026,2,10],"fullname":"Organisation Mondiale du Numérique L.P.B.C","displayName":"Organisation Mondiale du Numérique L.P.B.C","gender":{"active":false,"mandatory":false},"clientType":{"active":false,"mandatory":false},"clientClassification":{"active":false,"mandatory":false},"isStaff":false,"officeId":2,"officeName":"SHAMRAYAN ENTERPRISES","timeline":{"submittedOnDate":[2026,2,10],"submittedByUsername":"app.omnl","submittedByFirstname":"App","submittedByLastname":"Administrator","activatedOnDate":[2026,2,10],"activatedByUsername":"app.omnl","activatedByFirstname":"App","activatedByLastname":"Administrator"},"legalForm":{"id":2,"code":"legalFormType.entity","value":"Entity"},"clientNonPersonDetails":{"constitution":{"id":16,"name":"Colorado Business Corporation Act","active":false,"mandatory":false},"incorpNumber":"98450070C57395F6B906","mainBusinessLine":{"active":false,"mandatory":false}}},{"id":2,"accountNo":"811204797","externalId":"c9f3b153-a1b7-403e-bd9a-676fe18492f7","status":{"id":300,"code":"clientStatusType.active","value":"Active"},"subStatus":{"active":false,"mandatory":false},"active":true,"activationDate":[2026,2,10],"fullname":"SHAMRAYAN ENTERPRISES","displayName":"SHAMRAYAN ENTERPRISES","gender":{"active":false,"mandatory":false},"clientType":{"active":false,"mandatory":false},"clientClassification":{"active":false,"mandatory":false},"isStaff":false,"officeId":1,"officeName":"Head Office","timeline":{"submittedOnDate":[2026,2,10],"submittedByUsername":"app.omnl","submittedByFirstname":"App","submittedByLastname":"Administrator","activatedOnDate":[2026,2,10],"activatedByUsername":"app.omnl","activatedByFirstname":"App","activatedByLastname":"Administrator"},"legalForm":{"id":2,"code":"legalFormType.entity","value":"Entity"},"clientNonPersonDetails":{"constitution":{"id":21,"name":"Companies Act, 2012","active":false,"mandatory":false},"incorpNumber":"80020002760051","mainBusinessLine":{"active":false,"mandatory":false}}}]} +{"totalFilteredRecords":0,"pageItems":[]} diff --git a/output/omnl-discovery/offices.json b/output/omnl-discovery/offices.json index 3d15422..4225cf8 100644 --- a/output/omnl-discovery/offices.json +++ b/output/omnl-discovery/offices.json @@ -1 +1 @@ -[{"id":1,"name":"Head Office","nameDecorated":"Head Office","externalId":"1","openingDate":[2009,1,1],"hierarchy":"."},{"id":2,"name":"SHAMRAYAN ENTERPRISES","nameDecorated":"....SHAMRAYAN ENTERPRISES","externalId":"80020002760051","openingDate":[2026,2,2],"hierarchy":".2.","parentId":1,"parentName":"Head Office"}] +[{"id":1,"name":"OMNL Head Office (DBIS) – Central Bank","nameDecorated":"OMNL Head Office (DBIS) – Central Bank","externalId":"1","openingDate":[2026,1,1],"hierarchy":"."},{"id":10,"name":"Alpha Omega Holdings","nameDecorated":"....Alpha Omega Holdings","externalId":"OMNL-10","openingDate":[2026,1,1],"hierarchy":".10.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":11,"name":"SGI Capital","nameDecorated":"....SGI Capital","externalId":"OMNL-11","openingDate":[2026,1,1],"hierarchy":".11.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":12,"name":"Titan Financial","nameDecorated":"....Titan Financial","externalId":"OMNL-12","openingDate":[2026,1,1],"hierarchy":".12.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":13,"name":"Roy Walker PLLC","nameDecorated":"....Roy Walker PLLC","externalId":"OMNL-13","openingDate":[2026,1,1],"hierarchy":".13.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":14,"name":"SGI Partners LLC","nameDecorated":"....SGI Partners LLC","externalId":"OMNL-14","openingDate":[2026,1,1],"hierarchy":".14.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":15,"name":"Tsunami Holdings AG","nameDecorated":"....Tsunami Holdings AG","externalId":"OMNL-15","openingDate":[2026,1,1],"hierarchy":".15.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":16,"name":"Anakatech LLC","nameDecorated":"....Anakatech LLC","externalId":"OMNL-16","openingDate":[2026,1,1],"hierarchy":".16.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":17,"name":"Anema Cameron Walker Global","nameDecorated":"....Anema Cameron Walker Global","externalId":"OMNL-17","openingDate":[2026,1,1],"hierarchy":".17.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":18,"name":"NEPAL RASTRA BANK","nameDecorated":"....NEPAL RASTRA BANK","externalId":"OMNL-18","openingDate":[2026,1,1],"hierarchy":".18.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":19,"name":"SANIMA BANK LIMITED","nameDecorated":"....SANIMA BANK LIMITED","externalId":"OMNL-19","openingDate":[2026,1,1],"hierarchy":".19.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":2,"name":"Shamrayan Enterprises","nameDecorated":"....Shamrayan Enterprises","externalId":"OMNL-2","openingDate":[2026,1,1],"hierarchy":".2.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":20,"name":"Samama Group LLC - Azerbaijan","nameDecorated":"....Samama Group LLC - Azerbaijan","externalId":"SAMAMA-AZ-1703722701","openingDate":[2024,1,10],"hierarchy":".20.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":21,"name":"Bank Kanaya (Indonesia)","nameDecorated":"....Bank Kanaya (Indonesia)","externalId":"BANK-KANAYA-ID","openingDate":[2026,1,1],"hierarchy":".21.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":3,"name":"HYBX","nameDecorated":"....HYBX","externalId":"OMNL-3","openingDate":[2026,1,1],"hierarchy":".3.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":4,"name":"TAJ Private Single Family Office","nameDecorated":"....TAJ Private Single Family Office","externalId":"OMNL-4","openingDate":[2026,1,1],"hierarchy":".4.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":5,"name":"Aseret Mortgage Bank","nameDecorated":"....Aseret Mortgage Bank","externalId":"OMNL-5","openingDate":[2026,1,1],"hierarchy":".5.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":6,"name":"Mann Li Family Offices","nameDecorated":"....Mann Li Family Offices","externalId":"OMNL-6","openingDate":[2026,1,1],"hierarchy":".6.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":7,"name":"Sovereign Order of Malta OSJ","nameDecorated":"....Sovereign Order of Malta OSJ","externalId":"OMNL-7","openingDate":[2026,1,1],"hierarchy":".7.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":8,"name":"Alltra Mainnet","nameDecorated":"....Alltra Mainnet","externalId":"OMNL-8","openingDate":[2026,1,1],"hierarchy":".8.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"},{"id":9,"name":"FIDIS","nameDecorated":"....FIDIS","externalId":"OMNL-9","openingDate":[2026,1,1],"hierarchy":".9.","parentId":1,"parentName":"OMNL Head Office (DBIS) – Central Bank"}] diff --git a/output/omnl-discovery/savingsaccounts.json b/output/omnl-discovery/savingsaccounts.json index 1ca1262..00b189a 100644 --- a/output/omnl-discovery/savingsaccounts.json +++ b/output/omnl-discovery/savingsaccounts.json @@ -1 +1 @@ -{"totalFilteredRecords":2,"pageItems":[{"id":1,"accountNo":"166963551","depositType":{"id":100,"code":"depositAccountType.savingsDeposit","value":"Savings"},"externalId":"53c467f6-9023-435a-b6cd-11c875393b61","clientId":1,"clientName":"Organisation Mondiale du Numérique L.P.B.C","savingsProductId":1,"savingsProductName":"USD Wallet","fieldOfficerId":0,"status":{"id":300,"code":"savingsAccountStatusType.active","value":"Active","submittedAndPendingApproval":false,"approved":false,"rejected":false,"withdrawnByApplicant":false,"active":true,"closed":false,"prematureClosed":false,"transferInProgress":false,"transferOnHold":false,"matured":false},"subStatus":{"id":0,"code":"SavingsAccountSubStatusEnum.none","value":"None","none":true,"inactive":false,"dormant":false,"escheat":false,"block":false,"blockCredit":false,"blockDebit":false},"timeline":{"submittedOnDate":[2026,2,10],"submittedByUsername":"app.omnl","submittedByFirstname":"App","submittedByLastname":"Administrator","approvedOnDate":[2026,2,10],"approvedByUsername":"app.omnl","approvedByFirstname":"App","approvedByLastname":"Administrator","activatedOnDate":[2026,2,10],"activatedByUsername":"app.omnl","activatedByFirstname":"App","activatedByLastname":"Administrator"},"currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"},"nominalAnnualInterestRate":0.000000,"interestCompoundingPeriodType":{"id":1,"code":"savings.interest.period.savingsCompoundingInterestPeriodType.daily","value":"Daily"},"interestPostingPeriodType":{"id":4,"code":"savings.interest.posting.period.savingsPostingInterestPeriodType.monthly","value":"Monthly"},"interestCalculationType":{"id":1,"code":"savingsInterestCalculationType.dailybalance","value":"Daily Balance"},"interestCalculationDaysInYearType":{"id":365,"code":"savingsInterestCalculationDaysInYearType.days365","value":"365 Days"},"withdrawalFeeForTransfers":false,"allowOverdraft":false,"enforceMinRequiredBalance":false,"lienAllowed":false,"withHoldTax":false,"lastActiveTransactionDate":[2026,2,10],"isDormancyTrackingActive":false,"summary":{"currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"},"totalInterestPosted":0,"accountBalance":0.000000,"totalOverdraftInterestDerived":0,"interestNotPosted":0,"availableBalance":0.000000}},{"id":2,"accountNo":"277420915","depositType":{"id":100,"code":"depositAccountType.savingsDeposit","value":"Savings"},"externalId":"c4a8ce37-ee04-457e-823a-18ad685e8be0","clientId":1,"clientName":"Organisation Mondiale du Numérique L.P.B.C","savingsProductId":2,"savingsProductName":"EUR Wallet","fieldOfficerId":0,"status":{"id":300,"code":"savingsAccountStatusType.active","value":"Active","submittedAndPendingApproval":false,"approved":false,"rejected":false,"withdrawnByApplicant":false,"active":true,"closed":false,"prematureClosed":false,"transferInProgress":false,"transferOnHold":false,"matured":false},"subStatus":{"id":0,"code":"SavingsAccountSubStatusEnum.none","value":"None","none":true,"inactive":false,"dormant":false,"escheat":false,"block":false,"blockCredit":false,"blockDebit":false},"timeline":{"submittedOnDate":[2026,2,10],"submittedByUsername":"app.omnl","submittedByFirstname":"App","submittedByLastname":"Administrator","approvedOnDate":[2026,2,10],"approvedByUsername":"app.omnl","approvedByFirstname":"App","approvedByLastname":"Administrator","activatedOnDate":[2026,2,10],"activatedByUsername":"app.omnl","activatedByFirstname":"App","activatedByLastname":"Administrator"},"currency":{"code":"EUR","name":"Euro","decimalPlaces":2,"displaySymbol":"€","nameCode":"currency.EUR","displayLabel":"Euro (€)"},"nominalAnnualInterestRate":0.000000,"interestCompoundingPeriodType":{"id":1,"code":"savings.interest.period.savingsCompoundingInterestPeriodType.daily","value":"Daily"},"interestPostingPeriodType":{"id":4,"code":"savings.interest.posting.period.savingsPostingInterestPeriodType.monthly","value":"Monthly"},"interestCalculationType":{"id":1,"code":"savingsInterestCalculationType.dailybalance","value":"Daily Balance"},"interestCalculationDaysInYearType":{"id":365,"code":"savingsInterestCalculationDaysInYearType.days365","value":"365 Days"},"withdrawalFeeForTransfers":false,"allowOverdraft":false,"enforceMinRequiredBalance":false,"lienAllowed":false,"withHoldTax":false,"lastActiveTransactionDate":[2026,2,10],"isDormancyTrackingActive":false,"summary":{"currency":{"code":"EUR","name":"Euro","decimalPlaces":2,"displaySymbol":"€","nameCode":"currency.EUR","displayLabel":"Euro (€)"},"totalInterestPosted":0,"accountBalance":0.000000,"totalOverdraftInterestDerived":0,"interestNotPosted":0,"availableBalance":0.000000}}]} +{"totalFilteredRecords":0,"pageItems":[]} diff --git a/output/omnl-discovery/savingsproducts.json b/output/omnl-discovery/savingsproducts.json index f252a3c..fe51488 100644 --- a/output/omnl-discovery/savingsproducts.json +++ b/output/omnl-discovery/savingsproducts.json @@ -1 +1 @@ -[{"id":1,"name":"USD Wallet","shortName":"USD","currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"},"nominalAnnualInterestRate":0.000000,"interestCompoundingPeriodType":{"id":1,"code":"savings.interest.period.savingsCompoundingInterestPeriodType.daily","value":"Daily"},"interestPostingPeriodType":{"id":4,"code":"savings.interest.posting.period.savingsPostingInterestPeriodType.monthly","value":"Monthly"},"interestCalculationType":{"id":1,"code":"savingsInterestCalculationType.dailybalance","value":"Daily Balance"},"interestCalculationDaysInYearType":{"id":365,"code":"savingsInterestCalculationDaysInYearType.days365","value":"365 Days"},"withdrawalFeeForTransfers":false,"allowOverdraft":false,"overdraftLimit":0.000000,"minRequiredBalance":0.000000,"enforceMinRequiredBalance":false,"maxAllowedLienLimit":0.000000,"lienAllowed":false,"nominalAnnualInterestRateOverdraft":0.000000,"minOverdraftForInterestCalculation":0.000000,"withHoldTax":false,"accountingRule":{"id":1,"code":"accountingRuleType.none","value":"NONE"},"isDormancyTrackingActive":false},{"id":2,"name":"EUR Wallet","shortName":"EUR","currency":{"code":"EUR","name":"Euro","decimalPlaces":2,"displaySymbol":"€","nameCode":"currency.EUR","displayLabel":"Euro (€)"},"nominalAnnualInterestRate":0.000000,"interestCompoundingPeriodType":{"id":1,"code":"savings.interest.period.savingsCompoundingInterestPeriodType.daily","value":"Daily"},"interestPostingPeriodType":{"id":4,"code":"savings.interest.posting.period.savingsPostingInterestPeriodType.monthly","value":"Monthly"},"interestCalculationType":{"id":1,"code":"savingsInterestCalculationType.dailybalance","value":"Daily Balance"},"interestCalculationDaysInYearType":{"id":365,"code":"savingsInterestCalculationDaysInYearType.days365","value":"365 Days"},"withdrawalFeeForTransfers":false,"allowOverdraft":false,"overdraftLimit":0.000000,"minRequiredBalance":0.000000,"enforceMinRequiredBalance":false,"maxAllowedLienLimit":0.000000,"lienAllowed":false,"nominalAnnualInterestRateOverdraft":0.000000,"minOverdraftForInterestCalculation":0.000000,"withHoldTax":false,"accountingRule":{"id":1,"code":"accountingRuleType.none","value":"NONE"},"isDormancyTrackingActive":false}] +[] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57257d2..cfb99c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -555,7 +555,7 @@ importers: version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^4.7.0 - version: 4.7.0(vite@5.4.21(@types/node@25.2.3)) + version: 4.7.0(vite@5.4.21(@types/node@20.19.33)) '@vitest/ui': specifier: ^1.6.1 version: 1.6.1(vitest@1.6.1) @@ -576,19 +576,19 @@ importers: version: 23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) ts-jest: specifier: ^29.4.6 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)))(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 vite: specifier: ^5.4.21 - version: 5.4.21(@types/node@25.2.3) + version: 5.4.21(@types/node@20.19.33) vite-plugin-node-polyfills: specifier: ^0.24.0 - version: 0.24.0(rollup@4.57.1)(vite@5.4.21(@types/node@25.2.3)) + version: 0.24.0(rollup@4.57.1)(vite@5.4.21(@types/node@20.19.33)) vitest: specifier: ^1.6.1 - version: 1.6.1(@types/node@25.2.3)(@vitest/ui@1.6.1)(jsdom@23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 1.6.1(@types/node@20.19.33)(@vitest/ui@1.6.1)(jsdom@23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) smom-dbis-138/services/token-aggregation: dependencies: @@ -681,6 +681,46 @@ importers: specifier: ^5.9.3 version: 5.9.3 + smom-dbis-138/test/emoney/api: + dependencies: + ajv: + specifier: ^8.12.0 + version: 8.18.0 + ajv-formats: + specifier: ^2.1.1 + version: 2.1.1(ajv@8.18.0) + axios: + specifier: ^1.6.2 + version: 1.13.5 + graphql: + specifier: ^16.8.1 + version: 16.13.2 + graphql-request: + specifier: ^6.1.0 + version: 6.1.0(graphql@16.13.2) + js-yaml: + specifier: ^4.1.0 + version: 4.1.1 + devDependencies: + '@types/jest': + specifier: ^29.5.11 + version: 29.5.14 + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 + '@types/node': + specifier: ^20.10.0 + version: 20.19.33 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) + ts-jest: + specifier: ^29.1.1 + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@29.7.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)))(typescript@5.9.3) + typescript: + specifier: ^5.3.0 + version: 5.9.3 + transaction-composer: dependencies: '@xstate/react': @@ -2081,6 +2121,11 @@ packages: resolution: {integrity: sha512-5umyLoD5vMxlSVQwtmUXeNCNWs9dzmWykGm1qrHe/pCYrj/1lyJIgJRw+IxoMNodGqtcHEtfDhdNjRDM9yo/TA==} engines: {node: '>=6.0.0'} + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + '@grpc/grpc-js@1.14.3': resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} engines: {node: '>=12.10.0'} @@ -4750,6 +4795,9 @@ packages: '@types/jest@29.5.14': resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -7938,6 +7986,15 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql-request@6.1.0: + resolution: {integrity: sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==} + peerDependencies: + graphql: 14 - 16 + + graphql@16.13.2: + resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} @@ -14478,6 +14535,10 @@ snapshots: lit: 2.8.0 three: 0.146.0 + '@graphql-typed-document-node/core@3.2.0(graphql@16.13.2)': + dependencies: + graphql: 16.13.2 + '@grpc/grpc-js@1.14.3': dependencies: '@grpc/proto-loader': 0.8.0 @@ -14645,7 +14706,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -14654,7 +14715,7 @@ snapshots: '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 jest-message-util: 30.2.0 jest-util: 30.2.0 @@ -14667,14 +14728,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -14695,7 +14756,7 @@ snapshots: - supports-color - ts-node - '@jest/core@30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3))': + '@jest/core@30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -14703,14 +14764,14 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.4.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -14737,14 +14798,14 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-mock: 29.7.0 '@jest/environment@30.2.0': dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-mock: 30.2.0 '@jest/expect-utils@29.7.0': @@ -14773,7 +14834,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -14782,7 +14843,7 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 @@ -14809,7 +14870,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-regex-util: 30.0.1 '@jest/reporters@29.7.0': @@ -14820,7 +14881,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit: 0.1.2 @@ -14849,7 +14910,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -14969,7 +15030,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -14979,7 +15040,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -18860,22 +18921,22 @@ snapshots: '@types/bn.js@4.11.6': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/bn.js@5.2.0': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.2.0 '@types/keyv': 3.1.4 - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/responselike': 1.0.3 '@types/caseless@0.12.5': {} @@ -18892,7 +18953,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/cookie-parser@1.4.10(@types/express@4.17.25)': dependencies: @@ -18957,7 +19018,7 @@ snapshots: '@types/express-serve-static-core@4.19.8': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -18971,7 +19032,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/hast@2.3.10': dependencies: @@ -19000,6 +19061,8 @@ snapshots: expect: 29.7.0 pretty-format: 29.7.0 + '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -19011,7 +19074,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/lodash@4.17.23': {} @@ -19049,7 +19112,7 @@ snapshots: '@types/pbkdf2@3.1.2': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/pg@8.16.0': dependencies: @@ -19081,33 +19144,33 @@ snapshots: '@types/request@2.48.13': dependencies: '@types/caseless': 0.12.5 - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/tough-cookie': 4.0.5 form-data: 2.5.5 '@types/responselike@1.0.3': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/secp256k1@4.0.7': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/semver@7.7.1': {} '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/send@1.2.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/send': 0.17.6 '@types/stack-utils@2.0.3': {} @@ -19136,11 +19199,11 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 22.19.15 + '@types/node': 20.19.33 '@types/ws@8.18.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@types/yargs-parser@21.0.3': {} @@ -19388,6 +19451,18 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@20.19.33))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@20.19.33) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.15))': dependencies: '@babel/core': 7.29.0 @@ -19400,18 +19475,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@25.2.3))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 5.4.21(@types/node@25.2.3) - transitivePeerDependencies: - - supports-color - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.2.3)(jiti@1.21.7)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 @@ -19514,7 +19577,7 @@ snapshots: pathe: 1.1.2 picocolors: 1.1.1 sirv: 2.0.4 - vitest: 1.6.1(@types/node@25.2.3)(@vitest/ui@1.6.1)(jsdom@23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + vitest: 1.6.1(@types/node@20.19.33)(@vitest/ui@1.6.1)(jsdom@23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@vitest/utils@1.6.1': dependencies: @@ -24829,6 +24892,16 @@ snapshots: graphemer@1.4.0: {} + graphql-request@6.1.0(graphql@16.13.2): + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2) + cross-fetch: 3.2.0 + graphql: 16.13.2 + transitivePeerDependencies: + - encoding + + graphql@16.13.2: {} + gtoken@7.1.0: dependencies: gaxios: 6.7.1 @@ -25456,7 +25529,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.1(babel-plugin-macros@3.1.0) @@ -25482,7 +25555,7 @@ snapshots: '@jest/expect': 30.2.0 '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.1(babel-plugin-macros@3.1.0) @@ -25521,15 +25594,15 @@ snapshots: - supports-color - ts-node - jest-cli@30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): + jest-cli@30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -25571,38 +25644,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)): - dependencies: - '@babel/core': 7.29.0 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0(babel-plugin-macros@3.1.0) - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 25.2.3 - ts-node: 10.9.2(@types/node@20.19.33)(typescript@5.9.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 '@jest/get-type': 30.1.0 @@ -25629,8 +25671,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 25.2.3 - ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) + '@types/node': 20.19.33 + ts-node: 10.9.2(@types/node@20.19.33)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -25678,7 +25720,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -25687,7 +25729,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-mock: 30.2.0 jest-util: 30.2.0 jest-validate: 30.2.0 @@ -25698,7 +25740,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 25.2.3 + '@types/node': 20.19.33 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -25713,7 +25755,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -25776,13 +25818,13 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-util: 29.7.0 jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-util: 30.2.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -25841,7 +25883,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -25867,7 +25909,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -25896,7 +25938,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.3 @@ -25923,7 +25965,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 cjs-module-lexer: 2.2.0 collect-v8-coverage: 1.0.3 @@ -25995,7 +26037,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -26004,7 +26046,7 @@ snapshots: jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 chalk: 4.1.2 ci-info: 4.4.0 graceful-fs: 4.2.11 @@ -26032,7 +26074,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 20.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -26043,7 +26085,7 @@ snapshots: dependencies: '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -26052,14 +26094,14 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@30.2.0: dependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@ungap/structured-clone': 1.3.0 jest-util: 30.2.0 merge-stream: 2.0.0 @@ -26077,12 +26119,12 @@ snapshots: - supports-color - ts-node - jest@30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): + jest@30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + '@jest/core': 30.2.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + jest-cli: 30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -27956,7 +27998,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.2.3 + '@types/node': 20.19.33 long: 5.3.2 proxy-addr@2.0.7: @@ -29429,12 +29471,12 @@ snapshots: babel-jest: 30.2.0(@babel/core@7.29.0) jest-util: 30.2.0 - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + jest: 30.2.0(@types/node@20.19.33)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -29485,25 +29527,6 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 25.2.3 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optional: true - ts-pattern@5.9.0: {} tsconfig-paths@3.15.0: @@ -29980,13 +30003,13 @@ snapshots: - utf-8-validate - zod - vite-node@1.6.1(@types/node@25.2.3): + vite-node@1.6.1(@types/node@20.19.33): dependencies: cac: 6.7.14 debug: 4.4.3 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.4.21(@types/node@25.2.3) + vite: 5.4.21(@types/node@20.19.33) transitivePeerDependencies: - '@types/node' - less @@ -30019,14 +30042,23 @@ snapshots: - tsx - yaml - vite-plugin-node-polyfills@0.24.0(rollup@4.57.1)(vite@5.4.21(@types/node@25.2.3)): + vite-plugin-node-polyfills@0.24.0(rollup@4.57.1)(vite@5.4.21(@types/node@20.19.33)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.57.1) node-stdlib-browser: 1.3.1 - vite: 5.4.21(@types/node@25.2.3) + vite: 5.4.21(@types/node@20.19.33) transitivePeerDependencies: - rollup + vite@5.4.21(@types/node@20.19.33): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.57.1 + optionalDependencies: + '@types/node': 20.19.33 + fsevents: 2.3.3 + vite@5.4.21(@types/node@22.19.15): dependencies: esbuild: 0.21.5 @@ -30036,15 +30068,6 @@ snapshots: '@types/node': 22.19.15 fsevents: 2.3.3 - vite@5.4.21(@types/node@25.2.3): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.6 - rollup: 4.57.1 - optionalDependencies: - '@types/node': 25.2.3 - fsevents: 2.3.3 - vite@6.4.1(@types/node@25.2.3)(jiti@1.21.7)(yaml@2.8.2): dependencies: esbuild: 0.25.12 @@ -30073,7 +30096,7 @@ snapshots: jiti: 1.21.7 yaml: 2.8.2 - vitest@1.6.1(@types/node@25.2.3)(@vitest/ui@1.6.1)(jsdom@23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)): + vitest@1.6.1(@types/node@20.19.33)(@vitest/ui@1.6.1)(jsdom@23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)): dependencies: '@vitest/expect': 1.6.1 '@vitest/runner': 1.6.1 @@ -30092,11 +30115,11 @@ snapshots: strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.21(@types/node@25.2.3) - vite-node: 1.6.1(@types/node@25.2.3) + vite: 5.4.21(@types/node@20.19.33) + vite-node: 1.6.1(@types/node@20.19.33) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 20.19.33 '@vitest/ui': 1.6.1(vitest@1.6.1) jsdom: 23.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8bea2cc..3627896 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,6 +14,7 @@ packages: - rpc-translator-138 - smom-dbis-138/frontend-dapp - smom-dbis-138/services/token-aggregation + - smom-dbis-138/test/emoney/api - info-defi-oracle-138 ignoredBuiltDependencies: diff --git a/reports/path_b_execution_order_test_first_2026-03-04.md b/reports/path_b_execution_order_test_first_2026-03-04.md index a2d1b83..9201095 100644 --- a/reports/path_b_execution_order_test_first_2026-03-04.md +++ b/reports/path_b_execution_order_test_first_2026-03-04.md @@ -8,7 +8,7 @@ Objective: complete Mainnet Path B by first using already deployed contracts, va - Generated gas report: `reports/deployer_needed_chain_gas_balances_2026-03-04.md` (all required chains `ok`). - Read-only verification status: - `scripts/verify/check-deployer-balance-blockscout-vs-rpc.sh https://rpc-http-pub.d-bis.org https://explorer.d-bis.org/api/v2` -> passed, balances match. - - `scripts/verify/check-contracts-on-chain-138.sh http://192.168.11.211:8545` -> passed, 59/59 addresses present (check-contracts-on-chain-138.sh). + - `scripts/verify/check-contracts-on-chain-138.sh http://192.168.11.211:8545` -> passed, 59/59 addresses present **at this report date** — **current script list: 64/64** (ISO20022Router added 2026-03-30; see `docs/00-meta/INTEGRATION_GAPS_AND_NEXT_STEPS_2026-03-30.md`). - `smom-dbis-138/scripts/verify-bridge-setup-checklist.sh` -> passed when using `FORCE_RPC_URL=https://rpc-http-pub.d-bis.org`. - `smom-dbis-138/scripts/verify-bridge-prerequisites.sh 0.001` -> passed when using `FORCE_RPC_URL=https://rpc-http-pub.d-bis.org`. - E2E route health: diff --git a/smom-dbis-138-proxmox/README_HYPERLEDGER.md b/smom-dbis-138-proxmox/README_HYPERLEDGER.md index 5f9e047..0a09a32 100644 --- a/smom-dbis-138-proxmox/README_HYPERLEDGER.md +++ b/smom-dbis-138-proxmox/README_HYPERLEDGER.md @@ -1,6 +1,6 @@ # Hyperledger Services Deployment Guide -Complete deployment guide for Hyperledger services (Firefly, Cacti, Fabric, Indy) on Proxmox VE. +Complete deployment guide for Hyperledger services (Firefly, Cacti, Fabric, Indy, Aries / AnonCreds, Caliper) on Proxmox VE. ## 📋 Overview @@ -9,6 +9,16 @@ This document covers deployment of additional Hyperledger blockchain frameworks - **Cacti**: Blockchain integration platform - **Fabric**: Permissioned blockchain framework - **Indy**: Self-sovereign identity ledger +- **Aries / AnonCreds**: ACA-Py based identity-agent runtime +- **Caliper**: Benchmark harness and workload workspace + +Current verified primaries in this workspace: +- **Cacti** on `5200` +- **Fabric** on `6000` +- **Firefly** on `6200` +- **Indy** on `6400` +- **Aries / AnonCreds** on `6500` +- **Caliper** on `6600` ## 🚀 Quick Deployment @@ -27,7 +37,15 @@ DEPLOY_FIREFLY=true DEPLOY_CACTI=false DEPLOY_FABRIC=false DEPLOY_INDY=false \ ./scripts/deployment/deploy-hyperledger-services.sh # Deploy only Cacti -DEPLOY_FIREFLY=false DEPLOY_CACTI=true DEPLOY_FABRIC=false DEPLOY_INDY=false \ +DEPLOY_FIREFLY=false DEPLOY_CACTI=true DEPLOY_FABRIC=false DEPLOY_INDY=false DEPLOY_ARIES=false DEPLOY_CALIPER=false \ + ./scripts/deployment/deploy-hyperledger-services.sh + +# Deploy only Aries / AnonCreds +DEPLOY_FIREFLY=false DEPLOY_CACTI=false DEPLOY_FABRIC=false DEPLOY_INDY=false DEPLOY_ARIES=true DEPLOY_CALIPER=false \ + ./scripts/deployment/deploy-hyperledger-services.sh + +# Deploy only Caliper +DEPLOY_FIREFLY=false DEPLOY_CACTI=false DEPLOY_FABRIC=false DEPLOY_INDY=false DEPLOY_ARIES=false DEPLOY_CALIPER=true \ ./scripts/deployment/deploy-hyperledger-services.sh ``` @@ -35,103 +53,155 @@ DEPLOY_FIREFLY=false DEPLOY_CACTI=true DEPLOY_FABRIC=false DEPLOY_INDY=false \ ### Hyperledger Firefly -**VMID Range**: 150-159 -**Default VMID**: 150 +**VMID Range**: 6200-6201 +**Default VMID**: 6200 **Container**: `firefly-1` **After deployment**: 1. Configure Besu connection: ```bash -pct exec 150 -- bash -c "cd /opt/firefly && \ - sed -i 's|FF_BLOCKCHAIN_RPC=.*|FF_BLOCKCHAIN_RPC=http://192.168.11.250:8545|' docker-compose.yml && \ - sed -i 's|FF_BLOCKCHAIN_WS=.*|FF_BLOCKCHAIN_WS=ws://192.168.11.250:8546|' docker-compose.yml" +pct exec 6200 -- bash -c "cd /opt/firefly && \ + sed -i 's|FF_BLOCKCHAIN_RPC=.*|FF_BLOCKCHAIN_RPC=http://192.168.11.211:8545|' docker-compose.yml && \ + sed -i 's|FF_BLOCKCHAIN_WS=.*|FF_BLOCKCHAIN_WS=ws://192.168.11.211:8546|' docker-compose.yml" ``` 2. Start service: ```bash -pct exec 150 -- systemctl start firefly +pct exec 6200 -- systemctl start firefly ``` 3. Access API: ```bash -curl http://10.3.1.60:5000/api/v1/status +curl http://192.168.11.35:5000/api/v1/status ``` --- ### Hyperledger Cacti -**VMID Range**: 150-159 -**Default VMID**: 151 +**VMID Range**: 5200-5202 +**Default VMID**: 5200 **Container**: `cacti-1` **After deployment**: -1. Configure Besu connection: +1. Configure Besu connection if you need a non-default endpoint: ```bash -pct exec 151 -- bash -c "cd /opt/cacti && \ - sed -i 's|BESU_RPC_URL=.*|BESU_RPC_URL=http://192.168.11.250:8545|' docker-compose.yml && \ - sed -i 's|BESU_WS_URL=.*|BESU_WS_URL=ws://192.168.11.250:8546|' docker-compose.yml" +pct exec 5200 -- bash -c "cd /opt/cacti && \ + sed -i 's|http://192.168.11.211:8545|http://192.168.11.211:8545|' docker-compose.yml && \ + sed -i 's|ws://192.168.11.211:8546|ws://192.168.11.211:8546|' docker-compose.yml" ``` 2. Start service: ```bash -pct exec 151 -- systemctl start cacti +pct exec 5200 -- systemctl start cacti ``` -3. Access API: +3. Wait for the first plugin install, then access API: ```bash -curl http://10.3.1.61:4000/api/v1/api-server/healthcheck +curl http://192.168.11.80:4000/api/v1/api-server/healthcheck ``` --- ### Hyperledger Fabric -**VMID Range**: 150-159 -**Default VMID**: 152 +**VMID Range**: 6000-6002 +**Default VMID**: 6000 **Container**: `fabric-1` **After deployment**: -1. Generate crypto materials (customize for your network): +1. Start the official sample network: ```bash -pct exec 152 -- bash -cd /opt/fabric -# Generate crypto materials -cryptogen generate --config=./config/crypto-config.yaml -# Generate genesis block -configtxgen -profile TestNetwork -channelID test-channel -outputBlock ./network/genesis.block +pct exec 6000 -- bash -lc "/usr/local/bin/fabric-up-sample-network.sh" ``` -2. Configure network in `docker-compose.yml` (requires manual setup) +2. Verify the network: +```bash +pct exec 6000 -- bash -lc "docker ps --format 'table {{.Names}}\t{{.Status}}'" +pct exec 6000 -- bash -lc "cd /opt/fabric/fabric-samples/test-network && \ + export FABRIC_CFG_PATH=/opt/fabric/fabric-samples/config && \ + . scripts/envVar.sh && setGlobals 1 && peer channel getinfo -c mychannel && \ + setGlobals 2 && peer channel getinfo -c mychannel" +``` -3. Start Fabric network +3. Expected listeners: +```bash +pct exec 6000 -- ss -ltnp | grep -E ':7050\\b|:7051\\b|:9051\\b|:9443\\b|:9444\\b|:9445\\b' +``` --- ### Hyperledger Indy -**VMID Range**: 150-159 -**Default VMID**: 153 +**VMID Range**: 6400-6402 +**Default VMID**: 6400 **Container**: `indy-1` **After deployment**: 1. Generate genesis transactions: ```bash -pct exec 153 -- bash -cd /opt/indy -# Generate pool transactions (requires pool configuration) -# See Indy documentation for pool setup +pct exec 6400 -- bash -lc "/usr/local/bin/indy-generate-pool.sh" ``` -2. Configure pool name: +2. Start the four-node pool: ```bash -pct exec 153 -- bash -c "cd /opt/indy && \ - sed -i 's|NETWORK_NAME=.*|NETWORK_NAME=smom-dbis-138|' docker-compose.yml" +pct exec 6400 -- systemctl start indy ``` -3. Start service: +3. Verify the pool: ```bash -pct exec 153 -- systemctl start indy +pct exec 6400 -- bash -lc "docker ps --format 'table {{.Names}}\t{{.Status}}' | grep indy" +pct exec 6400 -- ss -ltnp | grep -E ':970[1-8]\\b' +``` + +--- + +### Hyperledger Aries / AnonCreds + +**VMID Range**: 6500 +**Default VMID**: 6500 +**Container**: `aries-1` + +**After deployment**: +1. Copy the Indy genesis file from the primary Indy CT: +```bash +pct exec 6400 -- cat /var/lib/indy/sandbox/pool_transactions_genesis > /root/indy-pool_transactions_genesis +pct push 6500 /root/indy-pool_transactions_genesis /opt/aries/ledger/pool_transactions_genesis +``` + +2. Set the real endpoint and start the ACA-Py service: +```bash +pct exec 6500 -- bash -lc "sed -i 's|^ACAPY_ENDPOINT=.*|ACAPY_ENDPOINT=http://192.168.11.88:8030|' /opt/aries/.env && systemctl start aries" +``` + +3. Verify the agent: +```bash +pct exec 6500 -- bash -lc "systemctl is-active aries && curl -fsS http://127.0.0.1:8031/status/live" +pct exec 6500 -- bash -lc "docker inspect acapy-agent --format '{{json .Config.Cmd}}'" +``` + +--- + +### Hyperledger Caliper + +**VMID Range**: 6600 +**Default VMID**: 6600 +**Container**: `caliper-1` + +**After deployment**: +1. Verify the workspace and Besu binding: +```bash +pct exec 6600 -- bash -lc "/usr/local/bin/caliper-chain138-doctor.sh" +``` + +2. Confirm the installed packages: +```bash +pct exec 6600 -- bash -lc "cd /opt/caliper/workspace && npm ls --depth=0 @hyperledger/caliper-cli web3" +``` + +3. Confirm Chain 138 RPC reachability from the benchmark CT: +```bash +pct exec 6600 -- bash -lc "curl -fsS http://192.168.11.211:8545 -H 'Content-Type: application/json' --data '{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[],\"id\":1}'" ``` ## 🔗 Integration @@ -161,46 +231,68 @@ pct exec 153 -- systemctl start indy | Service | CPU | RAM | Disk | VMID | |---------|-----|-----|------|------| -| Firefly | 2 | 4GB | 50GB | 150 | -| Cacti | 2 | 4GB | 50GB | 151 | -| Fabric | 4 | 8GB | 100GB | 152 | -| Indy | 4 | 8GB | 100GB | 153 | +| Firefly | 2 | 4GB | 50GB | 6200 | +| Cacti | 2 | 4GB | 50GB | 5200 | +| Fabric | 4 | 8GB | 100GB | 6000 | +| Indy | 4 | 8GB | 100GB | 6400 | +| Aries / AnonCreds | 2 | 4GB | 50GB | 6500 | +| Caliper | 2 | 4GB | 50GB | 6600 | -**Total Additional Resources**: ~12 cores, 24GB RAM, 300GB disk +**Total Additional Resources**: ~16 cores, 32GB RAM, 400GB disk ## 🆘 Troubleshooting ### Firefly Issues ```bash # Check logs -pct exec 150 -- journalctl -u firefly -f +pct exec 6200 -- journalctl -u firefly -f # Check Docker containers -pct exec 150 -- docker ps -pct exec 150 -- docker-compose -f /opt/firefly/docker-compose.yml logs +pct exec 6200 -- docker ps +pct exec 6200 -- docker-compose -f /opt/firefly/docker-compose.yml logs ``` ### Cacti Issues ```bash # Check logs -pct exec 151 -- journalctl -u cacti -f -pct exec 151 -- docker-compose -f /opt/cacti/docker-compose.yml logs +pct exec 5200 -- journalctl -u cacti -f +pct exec 5200 -- docker-compose -f /opt/cacti/docker-compose.yml logs ``` ### Fabric Issues ```bash # Check Docker containers -pct exec 152 -- docker ps -# Review network logs -pct exec 152 -- docker logs +pct exec 6000 -- docker ps +# Re-run the official sample network helper +pct exec 6000 -- bash -lc "/usr/local/bin/fabric-up-sample-network.sh" ``` ### Indy Issues ```bash +# Regenerate pool artifacts +pct exec 6400 -- bash -lc "/usr/local/bin/indy-generate-pool.sh" # Check all node logs -pct exec 153 -- docker-compose -f /opt/indy/docker-compose.yml logs +pct exec 6400 -- docker-compose -f /opt/indy/docker-compose.yml logs # Check individual node -pct exec 153 -- docker logs indy-node-1 +pct exec 6400 -- docker logs indy-node-1 +``` + +### Aries / AnonCreds Issues +```bash +# Check service status +pct exec 6500 -- systemctl status aries +# Check agent logs +pct exec 6500 -- docker logs acapy-agent +# Check admin API +pct exec 6500 -- curl -fsS http://127.0.0.1:8031/status/live +``` + +### Caliper Issues +```bash +# Re-run the workspace doctor +pct exec 6600 -- bash -lc "/usr/local/bin/caliper-chain138-doctor.sh" +# Inspect workspace dependencies +pct exec 6600 -- bash -lc "cd /opt/caliper/workspace && npm ls --depth=0" ``` ## 📚 Additional Resources @@ -208,5 +300,6 @@ pct exec 153 -- docker logs indy-node-1 - [Firefly Documentation](https://hyperledger.github.io/firefly/) - [Cacti Documentation](https://github.com/hyperledger/cacti) - [Fabric Documentation](https://hyperledger-fabric.readthedocs.io/) +- [ACA-Py Documentation](https://aca-py.org/) +- [Caliper Documentation](https://hyperledger-caliper.github.io/caliper/) - [Indy Documentation](https://hyperledger-indy.readthedocs.io/) - diff --git a/smom-dbis-138-proxmox/install/aries-install.sh b/smom-dbis-138-proxmox/install/aries-install.sh new file mode 100644 index 0000000..0f0d1c2 --- /dev/null +++ b/smom-dbis-138-proxmox/install/aries-install.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +# Installation script for Hyperledger Aries / AnonCreds via ACA-Py in an LXC container + +set -euo pipefail + +source /dev/stdin <<<"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func 2>/dev/null || echo '')" + +ACAPY_IMAGE="${ACAPY_IMAGE:-ghcr.io/openwallet-foundation/acapy-agent:py3.12-1.3-lts}" +ARIES_USER="${ARIES_USER:-aries}" +ARIES_GROUP="${ARIES_GROUP:-aries}" +ARIES_HOME="/opt/aries" +ARIES_DATA="/var/lib/aries" +ARIES_LEDGER_DIR="$ARIES_HOME/ledger" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[✓]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARNING]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +log_info "Updating system packages..." +export LC_ALL=C +export LANG=C +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get upgrade -y -qq +apt-get install -y -qq \ + docker.io \ + docker-compose \ + curl \ + wget \ + jq \ + ca-certificates \ + gnupg \ + lsb-release + +log_success "System packages updated" + +systemctl enable docker +systemctl start docker + +log_info "Creating aries user..." +if ! id -u "$ARIES_USER" &>/dev/null; then + useradd -r -s /bin/bash -d "$ARIES_HOME" -m "$ARIES_USER" + usermod -aG docker "$ARIES_USER" + log_success "Created aries user" +else + log_info "Aries user already exists" + usermod -aG docker "$ARIES_USER" +fi + +log_info "Pulling ACA-Py image..." +docker pull "$ACAPY_IMAGE" + +log_info "Creating Aries directories..." +mkdir -p "$ARIES_HOME" "$ARIES_DATA" "$ARIES_LEDGER_DIR" +chown -R "$ARIES_USER:$ARIES_GROUP" "$ARIES_HOME" "$ARIES_DATA" + +cat > "$ARIES_HOME/.env" <<'EOF' +ACAPY_IMAGE=ghcr.io/openwallet-foundation/acapy-agent:py3.12-1.3-lts +ACAPY_ADMIN_PORT=8031 +ACAPY_ENDPOINT_PORT=8030 +ACAPY_ENDPOINT=http://localhost:8030 +ACAPY_LABEL=dbis-aries-1 +ACAPY_WALLET_TYPE=askar-anoncreds +ACAPY_WALLET_NAME=dbis-aries-wallet +ACAPY_WALLET_KEY=dbis-aries-wallet-key-change-me +ACAPY_LOG_LEVEL=info +EOF + +cat > "$ARIES_HOME/docker-compose.yml" <<'EOF' +version: '3.8' + +services: + acapy: + image: ${ACAPY_IMAGE:-ghcr.io/openwallet-foundation/acapy-agent:py3.12-1.3-lts} + container_name: acapy-agent + command: > + start + --label ${ACAPY_LABEL:-dbis-aries-1} + --inbound-transport http 0.0.0.0 ${ACAPY_ENDPOINT_PORT:-8030} + --outbound-transport http + --admin 0.0.0.0 ${ACAPY_ADMIN_PORT:-8031} + --admin-insecure-mode + --endpoint ${ACAPY_ENDPOINT:-http://localhost:8030} + --wallet-type ${ACAPY_WALLET_TYPE:-askar-anoncreds} + --wallet-name ${ACAPY_WALLET_NAME:-dbis-aries-wallet} + --wallet-key ${ACAPY_WALLET_KEY:-dbis-aries-wallet-key-change-me} + --auto-provision + --genesis-file /opt/aries/ledger/pool_transactions_genesis + --auto-accept-invites + --auto-accept-requests + --auto-ping-connection + --public-invites + --emit-new-didcomm-prefix + --log-level ${ACAPY_LOG_LEVEL:-info} + env_file: + - .env + ports: + - "${ACAPY_ENDPOINT_PORT:-8030}:${ACAPY_ENDPOINT_PORT:-8030}" + - "${ACAPY_ADMIN_PORT:-8031}:${ACAPY_ADMIN_PORT:-8031}" + volumes: + - aries-data:/home/aries/.local/share/aries_cloudagent + - ./ledger:/opt/aries/ledger + restart: unless-stopped + +volumes: + aries-data: +EOF + +cat > "$ARIES_LEDGER_DIR/README.txt" <<'EOF' +Place the Indy pool genesis file at: +/opt/aries/ledger/pool_transactions_genesis + +The live DBIS setup copies this file from the Indy primary: +/var/lib/indy/sandbox/pool_transactions_genesis +EOF + +cat > /usr/local/bin/aries-healthcheck.sh <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +curl -fsS http://127.0.0.1:8031/status/live | jq . +EOF +chmod +x /usr/local/bin/aries-healthcheck.sh + +cat > /etc/systemd/system/aries.service </dev/null || echo '')" -CACTI_VERSION="${CACTI_VERSION:-latest}" +CACTI_IMAGE="${CACTI_IMAGE:-ghcr.io/hyperledger/cactus-connector-besu:2024-07-04-8c030ae}" CACTI_USER="${CACTI_USER:-cacti}" CACTI_GROUP="${CACTI_GROUP:-cacti}" CACTI_HOME="/opt/cacti" @@ -62,8 +62,8 @@ log_info "Creating directories..." mkdir -p "$CACTI_HOME" "$CACTI_DATA" chown -R "$CACTI_USER:$CACTI_GROUP" "$CACTI_HOME" "$CACTI_DATA" -# Download Cacti docker-compose -log_info "Downloading Cacti configuration..." +# Create Cacti runtime configuration +log_info "Creating Cacti configuration..." cd "$CACTI_HOME" # Create docker-compose.yml @@ -72,39 +72,28 @@ version: '3.8' services: cactus-api: - image: ghcr.io/hyperledger/cactus-cmd-api-server:${CACTI_VERSION:-latest} + image: ${CACTI_IMAGE:-ghcr.io/hyperledger/cactus-connector-besu:2024-07-04-8c030ae} container_name: cactus-api + security_opt: + - apparmor=unconfined ports: - "4000:4000" - "4001:4001" environment: - - CACTUS_NODE_ID=cactus-node-1 - - CACTUS_LOG_LEVEL=info - volumes: - - cacti-data:/var/lib/cactus + - AUTHORIZATION_PROTOCOL=${AUTHORIZATION_PROTOCOL:-NONE} + - AUTHORIZATION_CONFIG_JSON=${AUTHORIZATION_CONFIG_JSON:-{}} + - GRPC_TLS_ENABLED=${GRPC_TLS_ENABLED:-false} + - API_TLS_ENABLED=${API_TLS_ENABLED:-false} + - HTTP_PORT=${HTTP_PORT:-4000} + - WS_PORT=${WS_PORT:-4001} + - API_HOST=${API_HOST:-0.0.0.0} + - CACTUS_NODE_ID=${CACTUS_NODE_ID:-cactus-node-1} + - CACTUS_LOG_LEVEL=${CACTUS_LOG_LEVEL:-info} + - PLUGINS=[{"packageName":"@hyperledger/cactus-plugin-ledger-connector-besu","type":"org.hyperledger.cactus.plugin_import_type.LOCAL","action":"org.hyperledger.cactus.plugin_import_action.INSTALL","options":{"rpcApiHttpHost":"${BESU_RPC_URL:-http://192.168.11.211:8545}","rpcApiWsHost":"${BESU_WS_URL:-ws://192.168.11.211:8546}","instanceId":"${CACTUS_BESU_INSTANCE_ID:-besu-chain-138-connector}"}}] restart: unless-stopped networks: - cacti-network - besu-connector: - image: ghcr.io/hyperledger/cactus-plugin-ledger-connector-besu:${CACTI_VERSION:-latest} - container_name: cactus-besu-connector - depends_on: - - cactus-api - environment: - - BESU_RPC_URL=${BESU_RPC_URL:-http://192.168.11.250:8545} - - BESU_WS_URL=${BESU_WS_URL:-ws://192.168.11.250:8546} - - CHAIN_ID=${CHAIN_ID:-138} - - CACTUS_NODE_ID=cactus-node-1 - ports: - - "4100:4100" - restart: unless-stopped - networks: - - cacti-network - -volumes: - cacti-data: - networks: cacti-network: driver: bridge @@ -143,8 +132,7 @@ log_success "Systemd service created" log_success "Cacti installation completed!" log_info "Next steps:" -log_info "1. Edit $CACTI_HOME/docker-compose.yml to set BESU_RPC_URL and other configs" -log_info "2. Set environment variables in /etc/systemd/system/cacti.service or .env file" -log_info "3. Start service: systemctl start cacti" -log_info "4. Cacti API will be available at: http://localhost:4000" - +log_info "1. Review $CACTI_HOME/docker-compose.yml if you need non-default Besu RPC settings" +log_info "2. Start service: systemctl start cacti" +log_info "3. Wait for the first plugin install to complete inside the container" +log_info "4. Cacti API healthcheck: http://localhost:4000/api/v1/api-server/healthcheck" diff --git a/smom-dbis-138-proxmox/install/caliper-install.sh b/smom-dbis-138-proxmox/install/caliper-install.sh new file mode 100644 index 0000000..1b3c293 --- /dev/null +++ b/smom-dbis-138-proxmox/install/caliper-install.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# Installation script for Hyperledger Caliper benchmark workspace in an LXC container + +set -euo pipefail + +source /dev/stdin <<<"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func 2>/dev/null || echo '')" + +CALIPER_VERSION="${CALIPER_VERSION:-0.6.0}" +CALIPER_NODE_MAJOR="${CALIPER_NODE_MAJOR:-20}" +CALIPER_USER="${CALIPER_USER:-caliper}" +CALIPER_GROUP="${CALIPER_GROUP:-caliper}" +CALIPER_HOME="/opt/caliper" +CALIPER_WORKSPACE="$CALIPER_HOME/workspace" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[✓]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARNING]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +log_info "Updating system packages..." +export LC_ALL=C +export LANG=C +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get upgrade -y -qq +apt-get install -y -qq \ + curl \ + wget \ + jq \ + ca-certificates \ + gnupg \ + lsb-release \ + git \ + make \ + g++ \ + python3 + +log_info "Installing Node.js ${CALIPER_NODE_MAJOR}.x..." +install -d -m 0755 /etc/apt/keyrings +curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ + | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg +echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${CALIPER_NODE_MAJOR}.x nodistro main" \ + > /etc/apt/sources.list.d/nodesource.list +apt-get update -qq +apt-get install -y -qq nodejs + +log_success "System packages updated" + +log_info "Creating caliper user..." +if ! id -u "$CALIPER_USER" &>/dev/null; then + useradd -r -s /bin/bash -d "$CALIPER_HOME" -m "$CALIPER_USER" + log_success "Created caliper user" +else + log_info "Caliper user already exists" +fi + +log_info "Creating Caliper workspace..." +mkdir -p \ + "$CALIPER_WORKSPACE/networks/besu" \ + "$CALIPER_WORKSPACE/benchmarks/read-only" \ + "$CALIPER_WORKSPACE/workloads" + +cat > "$CALIPER_WORKSPACE/package.json" < "$CALIPER_WORKSPACE/networks/besu/chain138.example.json" <<'EOF' +{ + "caliper": { + "blockchain": "ethereum" + }, + "ethereum": { + "url": "ws://192.168.11.211:8546", + "transactionConfirmationBlocks": 1, + "contracts": {} + } +} +EOF + +cat > "$CALIPER_WORKSPACE/benchmarks/read-only/config.yaml" <<'EOF' +test: + name: chain138-read-only-placeholder + description: Placeholder benchmark config for the DBIS Chain 138 Caliper workspace. + workers: + number: 1 + rounds: [] +EOF + +cat > "$CALIPER_WORKSPACE/README.md" <<'EOF' +# DBIS Caliper workspace + +This workspace is pinned to the official Hyperledger Caliper CLI flow: + +1. `npm install --only=prod @hyperledger/caliper-cli@0.6.0` +2. `npx caliper bind --caliper-bind-sut besu:1.4` +3. `npx caliper launch manager ...` + +The shipped `chain138.example.json` is a starter network file only. Add funded account material and real benchmark configs before write-heavy runs. +EOF + +cat > /usr/local/bin/caliper-chain138-doctor.sh <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +cd /opt/caliper/workspace +npx caliper --version +npx caliper bind --caliper-bind-sut besu:1.4 --caliper-bind-cwd /opt/caliper/workspace >/tmp/caliper-bind.log +echo "Bound Besu adapter into /opt/caliper/workspace" +echo "Network template: /opt/caliper/workspace/networks/besu/chain138.example.json" +echo "Bench template: /opt/caliper/workspace/benchmarks/read-only/config.yaml" +EOF +chmod +x /usr/local/bin/caliper-chain138-doctor.sh + +chown -R "$CALIPER_USER:$CALIPER_GROUP" "$CALIPER_HOME" + +log_info "Installing Hyperledger Caliper CLI ${CALIPER_VERSION}..." +su -s /bin/bash - "$CALIPER_USER" -c "cd $CALIPER_WORKSPACE && npm install --only=prod @hyperledger/caliper-cli@${CALIPER_VERSION}" +su -s /bin/bash - "$CALIPER_USER" -c "cd $CALIPER_WORKSPACE && npx caliper bind --caliper-bind-sut besu:1.4 --caliper-bind-cwd $CALIPER_WORKSPACE" + +log_success "Caliper installation completed!" +log_info "Next steps:" +log_info "1. Review $CALIPER_WORKSPACE/networks/besu/chain138.example.json" +log_info "2. Add funded account and benchmark artifacts before write tests" +log_info "3. Run /usr/local/bin/caliper-chain138-doctor.sh for a quick workspace check" diff --git a/smom-dbis-138-proxmox/install/fabric-install.sh b/smom-dbis-138-proxmox/install/fabric-install.sh index 43481c4..bdfa637 100755 --- a/smom-dbis-138-proxmox/install/fabric-install.sh +++ b/smom-dbis-138-proxmox/install/fabric-install.sh @@ -48,9 +48,6 @@ apt-get install -y -qq \ log_success "System packages updated" -# Install Docker Compose v2 -log_info "Installing Docker Compose..." -apt-get install -y -qq docker-compose-plugin systemctl enable docker systemctl start docker @@ -64,62 +61,76 @@ else usermod -aG docker "$FABRIC_USER" fi -# Install Fabric binaries -log_info "Installing Hyperledger Fabric binaries..." +# Install Docker Compose v2 when available +log_info "Installing Docker Compose plugin when available..." +apt-get install -y -qq docker-compose-plugin || true + +# Install Fabric binaries, samples, and Docker images +log_info "Installing Hyperledger Fabric binaries, samples, and images..." +rm -rf /tmp/fabric-samples /tmp/bin /tmp/config cd /tmp -curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh | bash -s -- binary - -# Move binaries to system location -mkdir -p /usr/local/bin/fabric -mv fabric-samples/bin/* /usr/local/bin/fabric/ -chmod +x /usr/local/bin/fabric/* -ln -sf /usr/local/bin/fabric/* /usr/local/bin/ - -log_success "Fabric binaries installed" +curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh | bash -s -- binary samples docker # Create directories log_info "Creating directories..." -mkdir -p "$FABRIC_HOME" "$FABRIC_DATA" -chown -R "$FABRIC_USER:$FABRIC_GROUP" "$FABRIC_HOME" "$FABRIC_DATA" +mkdir -p "$FABRIC_HOME" "$FABRIC_DATA" "$FABRIC_HOME/config" "$FABRIC_HOME/network" "$FABRIC_HOME/scripts" -# Download Fabric samples (optional, for reference) -log_info "Setting up Fabric environment..." -cd "$FABRIC_HOME" +# Stage Fabric samples in the canonical location +if [[ -d /tmp/fabric-samples ]]; then + rm -rf "$FABRIC_HOME/fabric-samples" + mv /tmp/fabric-samples "$FABRIC_HOME/fabric-samples" +fi -# Create configuration directory -mkdir -p config network scripts +# Move binaries to system location +mkdir -p /usr/local/bin/fabric +if [[ -d "$FABRIC_HOME/fabric-samples/bin" ]]; then + cp -a "$FABRIC_HOME/fabric-samples/bin/." /usr/local/bin/fabric/ +fi +chmod +x /usr/local/bin/fabric/* 2>/dev/null || true +for fabric_bin in /usr/local/bin/fabric/*; do + ln -sf "$fabric_bin" /usr/local/bin/"$(basename "$fabric_bin")" +done -log_info "Creating basic network configuration..." -cat > config/configtx.yaml.template <<'EOF' -# Configtx template for Fabric network -# This is a template - customize for your network -Organizations: - - &OrdererOrg - Name: OrdererOrg - ID: OrdererMSP - MSPDir: crypto-config/ordererOrganizations/example.com/msp +# Copy the official sample configuration so peer/orderer tooling works in-place +if [[ -d "$FABRIC_HOME/fabric-samples/config" ]]; then + cp -a "$FABRIC_HOME/fabric-samples/config/." "$FABRIC_HOME/config/" +elif [[ -d /tmp/config ]]; then + cp -a /tmp/config/. "$FABRIC_HOME/config/" +fi +ln -sfn "$FABRIC_HOME/config" "$FABRIC_HOME/fabric-samples/config" - - &Org1 - Name: Org1MSP - ID: Org1MSP - MSPDir: crypto-config/peerOrganizations/org1.example.com/msp +# Nested LXC needs docker run calls to opt out of AppArmor confinement +cat > /usr/local/bin/docker <<'EOF' +#!/usr/bin/env bash +set -euo pipefail -Profiles: - TestNetwork: - Orderer: - <<: *OrdererOrg - Consortiums: - SampleConsortium: - Organizations: - - <<: *Org1 +if [[ "${1:-}" == "run" ]]; then + exec /usr/bin/docker run --security-opt apparmor=unconfined "${@:2}" +fi + +exec /usr/bin/docker "$@" EOF +chmod +x /usr/local/bin/docker + +# Provide a reproducible helper for the official sample network +cat > /usr/local/bin/fabric-up-sample-network.sh <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +export FABRIC_CFG_PATH=/opt/fabric/fabric-samples/config + +cd /opt/fabric/fabric-samples/test-network +./network.sh up createChannel +EOF +chmod +x /usr/local/bin/fabric-up-sample-network.sh + +log_success "Fabric binaries and sample network assets installed" chown -R "$FABRIC_USER:$FABRIC_GROUP" "$FABRIC_HOME" log_success "Fabric installation completed!" log_info "Next steps:" -log_info "1. Configure Fabric network in $FABRIC_HOME/config/" -log_info "2. Generate crypto materials using cryptogen or CA" -log_info "3. Create channel configuration" -log_info "4. Start Fabric network using docker-compose" - +log_info "1. Start the official sample network with /usr/local/bin/fabric-up-sample-network.sh" +log_info "2. Verify peers and orderer with docker ps inside the container" +log_info "3. Verify channel health with peer channel getinfo -c mychannel" diff --git a/smom-dbis-138-proxmox/install/indy-install.sh b/smom-dbis-138-proxmox/install/indy-install.sh index 15e5d7a..82bf4db 100755 --- a/smom-dbis-138-proxmox/install/indy-install.sh +++ b/smom-dbis-138-proxmox/install/indy-install.sh @@ -11,6 +11,9 @@ INDY_USER="${INDY_USER:-indy}" INDY_GROUP="${INDY_GROUP:-indy}" INDY_HOME="/opt/indy" INDY_DATA="/var/lib/indy" +INDY_LOG_DIR="/var/log/indy" +INDY_IMAGE="${INDY_IMAGE:-hyperledgerlabs/indy-node:latest}" +INDY_NETWORK_NAME="${INDY_NETWORK_NAME:-sandbox}" RED='\033[0;31m' GREEN='\033[0;32m' @@ -38,15 +41,7 @@ apt-get install -y -qq \ ca-certificates \ gnupg \ lsb-release \ - python3 \ - python3-pip \ - python3-dev \ - libssl-dev \ - libffi-dev \ - build-essential \ - pkg-config \ - libzmq5 \ - libzmq3-dev + python3 log_success "System packages updated" @@ -64,120 +59,162 @@ else usermod -aG docker "$INDY_USER" fi -# Install Python dependencies -log_info "Installing Python dependencies..." -pip3 install --break-system-packages -q \ - python3-indy \ - indy-plenum \ - indy-node \ - aiohttp +# Pull the published Indy node image that carries the working runtime bits +log_info "Pulling published Indy node image..." +docker pull "$INDY_IMAGE" -log_success "Python dependencies installed" - -# Create directories +# Create directories and config log_info "Creating directories..." -mkdir -p "$INDY_HOME" "$INDY_DATA" "$INDY_DATA/data" "$INDY_DATA/logs" "$INDY_DATA/keys" -chown -R "$INDY_USER:$INDY_GROUP" "$INDY_HOME" "$INDY_DATA" +mkdir -p \ + "$INDY_HOME" \ + "$INDY_DATA" \ + "$INDY_DATA/backup" \ + "$INDY_DATA/plugins" \ + "$INDY_LOG_DIR" \ + /etc/indy +chown -R "$INDY_USER:$INDY_GROUP" "$INDY_HOME" "$INDY_DATA" "$INDY_LOG_DIR" -# Download Indy pool configuration log_info "Setting up Indy configuration..." -cd "$INDY_HOME" +cat > /etc/indy/indy_config.py < docker-compose.yml <<'EOF' +cat > "$INDY_HOME/docker-compose.yml" <<'EOF' version: '3.8' services: indy-node-1: - image: indy-node:${INDY_VERSION:-1.18.1} + image: hyperledgerlabs/indy-node:latest container_name: indy-node-1 + network_mode: host + security_opt: + - apparmor=unconfined environment: - - NODE_NAME=Node1 - - NODE_IP=0.0.0.0 - - NODE_PORT=9701 - - CLIENT_IP=0.0.0.0 - - CLIENT_PORT=9702 - - NETWORK_NAME=${NETWORK_NAME:-sandbox} + - INDY_NETWORK_NAME=sandbox + - INDY_NODE_NAME=Node1 + - INDY_NODE_IP=0.0.0.0 + - INDY_NODE_PORT=9701 + - INDY_CLIENT_IP=0.0.0.0 + - INDY_CLIENT_PORT=9702 volumes: - - indy-data:/var/lib/indy - - indy-logs:/var/log/indy - ports: - - "9701:9701" - - "9702:9702" + - /var/lib/indy/sandbox:/var/lib/indy/sandbox + - /var/lib/indy/keys:/var/lib/indy/keys + - /var/lib/indy/backup:/var/lib/indy/backup + - /var/lib/indy/plugins:/var/lib/indy/plugins + - /var/log/indy:/var/log/indy + - /etc/indy/indy_config.py:/etc/indy/indy_config.py restart: unless-stopped - networks: - - indy-network indy-node-2: - image: indy-node:${INDY_VERSION:-1.18.1} + image: hyperledgerlabs/indy-node:latest container_name: indy-node-2 + network_mode: host + security_opt: + - apparmor=unconfined environment: - - NODE_NAME=Node2 - - NODE_IP=0.0.0.0 - - NODE_PORT=9703 - - CLIENT_IP=0.0.0.0 - - CLIENT_PORT=9704 - - NETWORK_NAME=${NETWORK_NAME:-sandbox} + - INDY_NETWORK_NAME=sandbox + - INDY_NODE_NAME=Node2 + - INDY_NODE_IP=0.0.0.0 + - INDY_NODE_PORT=9703 + - INDY_CLIENT_IP=0.0.0.0 + - INDY_CLIENT_PORT=9704 volumes: - - indy-data:/var/lib/indy - - indy-logs:/var/log/indy - ports: - - "9703:9703" - - "9704:9704" + - /var/lib/indy/sandbox:/var/lib/indy/sandbox + - /var/lib/indy/keys:/var/lib/indy/keys + - /var/lib/indy/backup:/var/lib/indy/backup + - /var/lib/indy/plugins:/var/lib/indy/plugins + - /var/log/indy:/var/log/indy + - /etc/indy/indy_config.py:/etc/indy/indy_config.py restart: unless-stopped - networks: - - indy-network indy-node-3: - image: indy-node:${INDY_VERSION:-1.18.1} + image: hyperledgerlabs/indy-node:latest container_name: indy-node-3 + network_mode: host + security_opt: + - apparmor=unconfined environment: - - NODE_NAME=Node3 - - NODE_IP=0.0.0.0 - - NODE_PORT=9705 - - CLIENT_IP=0.0.0.0 - - CLIENT_PORT=9706 - - NETWORK_NAME=${NETWORK_NAME:-sandbox} + - INDY_NETWORK_NAME=sandbox + - INDY_NODE_NAME=Node3 + - INDY_NODE_IP=0.0.0.0 + - INDY_NODE_PORT=9705 + - INDY_CLIENT_IP=0.0.0.0 + - INDY_CLIENT_PORT=9706 volumes: - - indy-data:/var/lib/indy - - indy-logs:/var/log/indy - ports: - - "9705:9705" - - "9706:9706" + - /var/lib/indy/sandbox:/var/lib/indy/sandbox + - /var/lib/indy/keys:/var/lib/indy/keys + - /var/lib/indy/backup:/var/lib/indy/backup + - /var/lib/indy/plugins:/var/lib/indy/plugins + - /var/log/indy:/var/log/indy + - /etc/indy/indy_config.py:/etc/indy/indy_config.py restart: unless-stopped - networks: - - indy-network indy-node-4: - image: indy-node:${INDY_VERSION:-1.18.1} + image: hyperledgerlabs/indy-node:latest container_name: indy-node-4 + network_mode: host + security_opt: + - apparmor=unconfined environment: - - NODE_NAME=Node4 - - NODE_IP=0.0.0.0 - - NODE_PORT=9707 - - CLIENT_IP=0.0.0.0 - - CLIENT_PORT=9708 - - NETWORK_NAME=${NETWORK_NAME:-sandbox} + - INDY_NETWORK_NAME=sandbox + - INDY_NODE_NAME=Node4 + - INDY_NODE_IP=0.0.0.0 + - INDY_NODE_PORT=9707 + - INDY_CLIENT_IP=0.0.0.0 + - INDY_CLIENT_PORT=9708 volumes: - - indy-data:/var/lib/indy - - indy-logs:/var/log/indy - ports: - - "9707:9707" - - "9708:9708" + - /var/lib/indy/sandbox:/var/lib/indy/sandbox + - /var/lib/indy/keys:/var/lib/indy/keys + - /var/lib/indy/backup:/var/lib/indy/backup + - /var/lib/indy/plugins:/var/lib/indy/plugins + - /var/log/indy:/var/log/indy + - /etc/indy/indy_config.py:/etc/indy/indy_config.py restart: unless-stopped - networks: - - indy-network - -volumes: - indy-data: - indy-logs: - -networks: - indy-network: - driver: bridge EOF -chown -R "$INDY_USER:$INDY_GROUP" "$INDY_HOME" +cat > /usr/local/bin/indy-generate-pool.sh <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +export PATH=/usr/local/bin:/usr/bin:/bin + +host_ip="${INDY_HOST_IP:-$(hostname -I | cut -d' ' -f1)}" +ips="${host_ip},${host_ip},${host_ip},${host_ip}" + +rm -rf /var/lib/indy/sandbox /var/lib/indy/keys +mkdir -p /var/lib/indy /var/lib/indy/backup /var/lib/indy/plugins /var/log/indy + +docker pull hyperledgerlabs/indy-node:latest >/dev/null +docker run --rm \ + -v /var/lib/indy:/var/lib/indy \ + -v /var/log/indy:/var/log/indy \ + -v /etc/indy:/etc/indy \ + --entrypoint bash \ + hyperledgerlabs/indy-node:latest \ + -lc "generate_indy_pool_transactions --nodes=4 --clients=1 --nodeNum 1 2 3 4 --ips=${ips} --network=sandbox" + +for node in Node1 Node2 Node3 Node4; do + mkdir -p "/var/lib/indy/keys/sandbox/keys/${node}/public_keys" "/var/lib/indy/keys/sandbox/keys/${node}/verif_keys" + cp -f "/var/lib/indy/sandbox/keys/${node}/public_keys/${node}.key" "/var/lib/indy/keys/sandbox/keys/${node}/public_keys/${node}.key.bootstrap" + cp -f "/var/lib/indy/sandbox/keys/${node}/verif_keys/${node}.key" "/var/lib/indy/keys/sandbox/keys/${node}/verif_keys/${node}.key.bootstrap" +done + +chown -R indy:indy /var/lib/indy /var/log/indy +echo "Generated Indy pool artifacts for ${host_ip}" +EOF +chmod +x /usr/local/bin/indy-generate-pool.sh + +chown -R "$INDY_USER:$INDY_GROUP" "$INDY_HOME" "$INDY_DATA" "$INDY_LOG_DIR" log_success "Indy configuration created" @@ -210,9 +247,6 @@ log_success "Systemd service created" log_success "Indy installation completed!" log_info "Next steps:" -log_info "1. Edit $INDY_HOME/docker-compose.yml to configure network name and nodes" -log_info "2. Generate genesis transactions for the pool" -log_info "3. Configure node keys and certificates" -log_info "4. Start service: systemctl start indy" -log_info "5. Indy pool will run 4 nodes for consensus" - +log_info "1. Run /usr/local/bin/indy-generate-pool.sh" +log_info "2. Start service: systemctl start indy" +log_info "3. Verify listeners: ss -ltnp | grep -E ':970[1-8]\\b'" diff --git a/smom-dbis-138-proxmox/scripts/deployment/deploy-hyperledger-services.sh b/smom-dbis-138-proxmox/scripts/deployment/deploy-hyperledger-services.sh index 4ed45c8..92a406e 100755 --- a/smom-dbis-138-proxmox/scripts/deployment/deploy-hyperledger-services.sh +++ b/smom-dbis-138-proxmox/scripts/deployment/deploy-hyperledger-services.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Deploy Hyperledger Services (Firefly, Cacti, Fabric, Indy) on Proxmox VE LXC containers +# Deploy Hyperledger Services (Firefly, Cacti, Fabric, Indy, Aries, Caliper) on Proxmox VE LXC containers set -euo pipefail @@ -19,12 +19,16 @@ DEPLOY_FIREFLY="${DEPLOY_FIREFLY:-true}" DEPLOY_CACTI="${DEPLOY_CACTI:-true}" DEPLOY_FABRIC="${DEPLOY_FABRIC:-false}" DEPLOY_INDY="${DEPLOY_INDY:-false}" +DEPLOY_ARIES="${DEPLOY_ARIES:-false}" +DEPLOY_CALIPER="${DEPLOY_CALIPER:-false}" # VMID ranges -VMID_FIREFLY_START="${VMID_FIREFLY_START:-150}" -VMID_CACTI_START="${VMID_CACTI_START:-151}" -VMID_FABRIC_START="${VMID_FABRIC_START:-152}" -VMID_INDY_START="${VMID_INDY_START:-153}" +VMID_FIREFLY_START="${VMID_FIREFLY_START:-6200}" +VMID_CACTI_START="${VMID_CACTI_START:-5200}" +VMID_FABRIC_START="${VMID_FABRIC_START:-6000}" +VMID_INDY_START="${VMID_INDY_START:-6400}" +VMID_ARIES_START="${VMID_ARIES_START:-6500}" +VMID_CALIPER_START="${VMID_CALIPER_START:-6600}" log_info "Starting Hyperledger services deployment..." @@ -42,7 +46,7 @@ ensure_os_template "${CONTAINER_OS_TEMPLATE:-local:vztmpl/ubuntu-22.04-standard_ # Function to create and configure Hyperledger service create_hyperledger_service() { - local service_type="$1" # firefly, cacti, fabric, indy + local service_type="$1" # firefly, cacti, fabric, indy, aries, caliper local vmid="$2" local hostname="$3" local ip_address="$4" @@ -153,6 +157,46 @@ if [[ "$DEPLOY_FIREFLY" == "true" ]]; then log_success "Deployed Firefly service" fi +# Deploy Aries / AnonCreds +if [[ "$DEPLOY_ARIES" == "true" ]]; then + log_info "Deploying Aries / AnonCreds service..." + vmid=$VMID_ARIES_START + hostname="aries-1" + ip_octet=64 + ip_address="${SERVICES_SUBNET:-10.3.1}.${ip_octet}" + + aries_info=$(create_hyperledger_service \ + "aries" \ + "$vmid" \ + "$hostname" \ + "$ip_address" \ + "${ARIES_MEMORY:-4096}" \ + "${ARIES_CORES:-2}" \ + "${ARIES_DISK:-50}") + + log_success "Deployed Aries / AnonCreds service" +fi + +# Deploy Caliper +if [[ "$DEPLOY_CALIPER" == "true" ]]; then + log_info "Deploying Caliper service..." + vmid=$VMID_CALIPER_START + hostname="caliper-1" + ip_octet=65 + ip_address="${SERVICES_SUBNET:-10.3.1}.${ip_octet}" + + caliper_info=$(create_hyperledger_service \ + "caliper" \ + "$vmid" \ + "$hostname" \ + "$ip_address" \ + "${CALIPER_MEMORY:-4096}" \ + "${CALIPER_CORES:-2}" \ + "${CALIPER_DISK:-50}") + + log_success "Deployed Caliper service" +fi + # Deploy Cacti if [[ "$DEPLOY_CACTI" == "true" ]]; then log_info "Deploying Cacti service..." @@ -245,7 +289,18 @@ EOF echo "INDY_${hostname//-/}_VMID=$vmid" >> "$INVENTORY_FILE" echo "INDY_${hostname//-/}_IP=$ip" >> "$INVENTORY_FILE" fi + + if [[ -n "${aries_info:-}" ]]; then + IFS=':' read -r vmid hostname ip <<< "$aries_info" + echo "ARIES_${hostname//-/}_VMID=$vmid" >> "$INVENTORY_FILE" + echo "ARIES_${hostname//-/}_IP=$ip" >> "$INVENTORY_FILE" + fi + + if [[ -n "${caliper_info:-}" ]]; then + IFS=':' read -r vmid hostname ip <<< "$caliper_info" + echo "CALIPER_${hostname//-/}_VMID=$vmid" >> "$INVENTORY_FILE" + echo "CALIPER_${hostname//-/}_IP=$ip" >> "$INVENTORY_FILE" + fi fi log_success "Hyperledger services deployment completed!" - diff --git a/token-lists/README.md b/token-lists/README.md index f8ffc89..d13524f 100644 --- a/token-lists/README.md +++ b/token-lists/README.md @@ -38,7 +38,8 @@ node scripts/verify-on-chain.js lists/dbis-138.tokenlist.json token-lists/ ├── lists/ │ └── dbis-138.tokenlist.json # Main token list -├── logos/ # Token logos (future) +├── logos/ +│ └── gru/ # Canonical GRU SVG logos for c* assets and Chain 138 list branding ├── scripts/ │ ├── validate-token-list.js # Schema & validation │ ├── checksum-addresses.js # EIP-55 checksum validation @@ -58,10 +59,19 @@ token-lists/ ## Token List Contents -Current version: **1.2.0** +Current version: **1.7.0** + +Canonical currency and lifecycle state are also tracked in [`../config/gru-iso4217-currency-manifest.json`](../config/gru-iso4217-currency-manifest.json). The token list is the wallet/explorer-facing projection of that broader GRU asset inventory. Canonical GRU SVG logos now live under [`logos/gru/`](logos/gru) and are referenced through raw HTTPS URLs so wallets and explorers can use the same source artwork. ### Tokens +Current token count: **18** + +Notable current state: +- `cUSDT` and `cUSDC` each have both V1 and staged V2 entries on Chain 138. +- Duplicate symbols for V1/V2 are intentional during coexistence and cutover. +- Non-USD GRU currencies currently covered in the list are `EUR`, `GBP`, `AUD`, `JPY`, `CHF`, `CAD`, and `XAU`. + 1. **ETH/USD Price Feed** (Oracle) - Address: `0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6` - Decimals: 8 @@ -82,16 +92,30 @@ Current version: **1.2.0** - Decimals: 18 - Category: DeFi, Oracle, CCIP -5. **cUSDT** (Compliant Tether USD) +5. **cUSDT** (Compliant Tether USD, V1) - Address: `0x93E66202A11B1772E55407B32B44e5Cd8eda7f22` - Decimals: 6 - Category: Stablecoin, DeFi, Compliant -6. **cUSDC** (Compliant USD Coin) +6. **cUSDT** (Compliant Tether USD V2, staged) + - Address: `0x8d342d321DdEe97D0c5011DAF8ca0B59DA617D29` + - Decimals: 6 + - Category: Stablecoin, DeFi, Compliant, V2, x402-ready + +7. **cUSDC** (Compliant USD Coin, V1) - Address: `0xf22258f57794CC8E06237084b353Ab30fFfa640b` - Decimals: 6 - Category: Stablecoin, DeFi, Compliant +8. **cUSDC** (Compliant USD Coin V2, staged) + - Address: `0x1ac3F4942a71E86A9682D91837E1E71b7BACdF99` + - Decimals: 6 + - Category: Stablecoin, DeFi, Compliant, V2, x402-ready + +9. **Additional GRU currencies** + - `cEURC`, `cEURT`, `cGBPC`, `cGBPT`, `cAUDC`, `cJPYC`, `cCHFC`, `cCADC`, `cXAUC`, `cXAUT` + - See `lists/dbis-138.tokenlist.json` for the full current set. + --- ## Validation @@ -104,6 +128,7 @@ All token lists are validated against: - Duplicate detection (addresses and symbols) - Logo URL accessibility - On-chain contract verification +- Expected duplicate-symbol warnings during V1/V2 coexistence ### Running Validations @@ -351,4 +376,3 @@ The chain configuration includes: --- **Last Updated**: 2025-12-22 - diff --git a/token-lists/lists/dbis-138.tokenlist.json b/token-lists/lists/dbis-138.tokenlist.json index da92c4a..c48e55f 100644 --- a/token-lists/lists/dbis-138.tokenlist.json +++ b/token-lists/lists/dbis-138.tokenlist.json @@ -1 +1,328 @@ -{"name":"DBIS Chain 138 Token List","version":{"major":1,"minor":6,"patch":1},"timestamp":"2026-02-28T00:00:00.000Z","keywords":["dbis","chain138","defi oracle meta"],"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tokens":[{"chainId":138,"address":"0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6","name":"ETH/USD Price Feed","symbol":"ETH-USD","decimals":8,"logoURI":"https://ipfs.io/ipfs/QmPZuycjyJEe2otREuQ5HirvPJ8X6Yc6MBtwz1VhdD79pY","tags":["oracle","pricefeed"]},{"chainId":138,"address":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","name":"Wrapped Ether","symbol":"WETH","decimals":18,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["defi","wrapped"]},{"chainId":138,"address":"0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F","name":"Wrapped Ether v10","symbol":"WETH10","decimals":18,"logoURI":"https://ipfs.io/ipfs/QmanDFPHxnbKd6SSNzzXHf9GbpL9dLXSphxDZSPPYE6ds4","tags":["defi","wrapped"]},{"chainId":138,"address":"0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03","name":"Chainlink Token","symbol":"LINK","decimals":18,"logoURI":"https://ipfs.io/ipfs/QmenWcmfNGfssz4HXvrRV912eZDiKqLTt6z2brRYuTGz9A","tags":["defi","oracle","ccip"]},{"chainId":138,"address":"0x93E66202A11B1772E55407B32B44e5Cd8eda7f22","name":"Compliant Tether USD","symbol":"cUSDT","decimals":6,"logoURI":"https://ipfs.io/ipfs/QmRfhPs9DcyFPpGjKwF6CCoVDWUHSxkQR34n9NK7JSbPCP","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0xf22258f57794CC8E06237084b353Ab30fFfa640b","name":"Compliant USD Coin","symbol":"cUSDC","decimals":6,"logoURI":"https://ipfs.io/ipfs/QmNPq4D5JXzurmi9jAhogVMzhAQRk1PZ1r9H3qQUV9gjDm","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0x8085961F9cF02b4d800A3c6d386D31da4B34266a","name":"Euro Coin (Compliant)","symbol":"cEURC","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0xdf4b71c61E5912712C1Bdd451416B9aC26949d72","name":"Tether EUR (Compliant)","symbol":"cEURT","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0x003960f16D9d34F2e98d62723B6721Fb92074aD2","name":"Pound Sterling (Compliant)","symbol":"cGBPC","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0x350f54e4D23795f86A9c03988c7135357CCaD97c","name":"Tether GBP (Compliant)","symbol":"cGBPT","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0xD51482e567c03899eecE3CAe8a058161FD56069D","name":"Australian Dollar (Compliant)","symbol":"cAUDC","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0xEe269e1226a334182aace90056EE4ee5Cc8A6770","name":"Japanese Yen (Compliant)","symbol":"cJPYC","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0x873990849DDa5117d7C644f0aF24370797C03885","name":"Swiss Franc (Compliant)","symbol":"cCHFC","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0x54dBd40cF05e15906A2C21f600937e96787f5679","name":"Canadian Dollar (Compliant)","symbol":"cCADC","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["stablecoin","defi","compliant"]},{"chainId":138,"address":"0x290E52a8819A4fbD0714E517225429aA2B70EC6b","name":"Gold (Compliant)","symbol":"cXAUC","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["defi","compliant"],"extensions":{"unitOfAccount":"troy_ounce","unitDescription":"1 full token (10^decimals base units) = 1 troy oz fine gold"}},{"chainId":138,"address":"0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E","name":"Tether XAU (Compliant)","symbol":"cXAUT","decimals":6,"logoURI":"https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong","tags":["defi","compliant"],"extensions":{"unitOfAccount":"troy_ounce","unitDescription":"1 full token (10^decimals base units) = 1 troy oz fine gold"}}],"tags":{"defi":{"name":"DeFi","description":"Decentralized Finance tokens"},"wrapped":{"name":"Wrapped","description":"Wrapped tokens representing native assets"},"oracle":{"name":"Oracle","description":"Oracle price feed tokens"},"pricefeed":{"name":"Price Feed","description":"Price feed oracle contracts"},"stablecoin":{"name":"Stablecoin","description":"Stable value tokens pegged to fiat currencies"},"compliant":{"name":"Compliant","description":"Regulatory compliant tokens with compliance features"},"ccip":{"name":"CCIP","description":"Cross Chain Interoperability Protocol tokens"}}} \ No newline at end of file +{ + "name": "DBIS Chain 138 Token List", + "version": { + "major": 1, + "minor": 7, + "patch": 0 + }, + "timestamp": "2026-03-31T00:00:00.000Z", + "keywords": [ + "dbis", + "chain138", + "defi oracle meta" + ], + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/chain138-list.svg", + "tokens": [ + { + "chainId": 138, + "address": "0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6", + "name": "ETH/USD Price Feed", + "symbol": "ETH-USD", + "decimals": 8, + "logoURI": "https://ipfs.io/ipfs/QmPZuycjyJEe2otREuQ5HirvPJ8X6Yc6MBtwz1VhdD79pY", + "tags": [ + "oracle", + "pricefeed" + ] + }, + { + "chainId": 138, + "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "name": "Wrapped Ether", + "symbol": "WETH", + "decimals": 18, + "logoURI": "https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong", + "tags": [ + "defi", + "wrapped" + ] + }, + { + "chainId": 138, + "address": "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F", + "name": "Wrapped Ether v10", + "symbol": "WETH10", + "decimals": 18, + "logoURI": "https://ipfs.io/ipfs/QmanDFPHxnbKd6SSNzzXHf9GbpL9dLXSphxDZSPPYE6ds4", + "tags": [ + "defi", + "wrapped" + ] + }, + { + "chainId": 138, + "address": "0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03", + "name": "Chainlink Token", + "symbol": "LINK", + "decimals": 18, + "logoURI": "https://ipfs.io/ipfs/QmenWcmfNGfssz4HXvrRV912eZDiKqLTt6z2brRYuTGz9A", + "tags": [ + "defi", + "oracle", + "ccip" + ] + }, + { + "chainId": 138, + "address": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22", + "name": "Compliant Tether USD", + "symbol": "cUSDT", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cUSDT.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ], + "extensions": { + "currencyCode": "USD", + "gruVersion": "v1", + "forwardCanonical": true, + "x402Ready": false + } + }, + { + "chainId": 138, + "address": "0x8d342d321DdEe97D0c5011DAF8ca0B59DA617D29", + "name": "Compliant Tether USD V2", + "symbol": "cUSDT", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cUSDT.svg", + "tags": [ + "stablecoin", + "defi", + "compliant", + "v2", + "x402", + "staged" + ], + "extensions": { + "currencyCode": "USD", + "gruVersion": "v2", + "forwardCanonical": false, + "x402Ready": true, + "explorerLabel": "Chain 138 cUSDT V2 (staged)" + } + }, + { + "chainId": 138, + "address": "0xf22258f57794CC8E06237084b353Ab30fFfa640b", + "name": "Compliant USD Coin", + "symbol": "cUSDC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cUSDC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ], + "extensions": { + "currencyCode": "USD", + "gruVersion": "v1", + "forwardCanonical": true, + "x402Ready": false + } + }, + { + "chainId": 138, + "address": "0x1ac3F4942a71E86A9682D91837E1E71b7BACdF99", + "name": "Compliant USD Coin V2", + "symbol": "cUSDC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cUSDC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant", + "v2", + "x402", + "staged" + ], + "extensions": { + "currencyCode": "USD", + "gruVersion": "v2", + "forwardCanonical": false, + "x402Ready": true, + "explorerLabel": "Chain 138 cUSDC V2 (staged)" + } + }, + { + "chainId": 138, + "address": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a", + "name": "Euro Coin (Compliant)", + "symbol": "cEURC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cEURC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0xdf4b71c61E5912712C1Bdd451416B9aC26949d72", + "name": "Tether EUR (Compliant)", + "symbol": "cEURT", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cEURT.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0x003960f16D9d34F2e98d62723B6721Fb92074aD2", + "name": "Pound Sterling (Compliant)", + "symbol": "cGBPC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cGBPC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0x350f54e4D23795f86A9c03988c7135357CCaD97c", + "name": "Tether GBP (Compliant)", + "symbol": "cGBPT", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cGBPT.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0xD51482e567c03899eecE3CAe8a058161FD56069D", + "name": "Australian Dollar (Compliant)", + "symbol": "cAUDC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cAUDC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0xEe269e1226a334182aace90056EE4ee5Cc8A6770", + "name": "Japanese Yen (Compliant)", + "symbol": "cJPYC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cJPYC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0x873990849DDa5117d7C644f0aF24370797C03885", + "name": "Swiss Franc (Compliant)", + "symbol": "cCHFC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cCHFC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0x54dBd40cF05e15906A2C21f600937e96787f5679", + "name": "Canadian Dollar (Compliant)", + "symbol": "cCADC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cCADC.svg", + "tags": [ + "stablecoin", + "defi", + "compliant" + ] + }, + { + "chainId": 138, + "address": "0x290E52a8819A4fbD0714E517225429aA2B70EC6b", + "name": "Gold (Compliant)", + "symbol": "cXAUC", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cXAUC.svg", + "tags": [ + "defi", + "compliant" + ], + "extensions": { + "unitOfAccount": "troy_ounce", + "unitDescription": "1 full token = 1 troy oz fine gold" + } + }, + { + "chainId": 138, + "address": "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E", + "name": "Tether XAU (Compliant)", + "symbol": "cXAUT", + "decimals": 6, + "logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cXAUT.svg", + "tags": [ + "defi", + "compliant" + ], + "extensions": { + "unitOfAccount": "troy_ounce", + "unitDescription": "1 full token = 1 troy oz fine gold" + } + } + ], + "tags": { + "defi": { + "name": "DeFi", + "description": "Decentralized Finance tokens" + }, + "wrapped": { + "name": "Wrapped", + "description": "Wrapped tokens representing native assets" + }, + "oracle": { + "name": "Oracle", + "description": "Oracle price feed tokens" + }, + "pricefeed": { + "name": "Price Feed", + "description": "Price feed oracle contracts" + }, + "stablecoin": { + "name": "Stablecoin", + "description": "Stable value tokens pegged to fiat currencies" + }, + "compliant": { + "name": "Compliant", + "description": "Regulatory compliant tokens with compliance features" + }, + "ccip": { + "name": "CCIP", + "description": "Cross Chain Interoperability Protocol tokens" + }, + "v2": { + "name": "Version 2", + "description": "Next generation GRU token contracts" + }, + "x402": { + "name": "x402 Ready", + "description": "Permit or authorization capable payment tokens" + }, + "staged": { + "name": "Staged", + "description": "Deployed and discoverable, not yet canonical" + } + } +} diff --git a/token-lists/scripts/validate-logos.js b/token-lists/scripts/validate-logos.js index 0374b45..12802ac 100755 --- a/token-lists/scripts/validate-logos.js +++ b/token-lists/scripts/validate-logos.js @@ -4,16 +4,57 @@ * Validates that all logoURI URLs are accessible and return image content */ -import { readFileSync } from 'fs'; +import { readFileSync, existsSync, statSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +const PROJECT_ROOT = resolve(__dirname, '../..'); const MAX_LOGO_SIZE = 500 * 1024; // 500KB const IMAGE_MIME_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml', 'image/webp', 'image/gif']; +function inferContentTypeFromPath(filePath) { + if (filePath.endsWith('.svg')) return 'image/svg+xml'; + if (filePath.endsWith('.png')) return 'image/png'; + if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg'; + if (filePath.endsWith('.webp')) return 'image/webp'; + if (filePath.endsWith('.gif')) return 'image/gif'; + return null; +} + +function resolveLocalRepoAsset(logoURI) { + try { + const url = new URL(logoURI); + if (url.protocol !== 'https:') return null; + + if (url.hostname === 'raw.githubusercontent.com') { + const segments = url.pathname.split('/').filter(Boolean); + if (segments.length < 4) return null; + const relativePath = segments.slice(3).join('/'); + const candidate = resolve(PROJECT_ROOT, relativePath); + return existsSync(candidate) ? candidate : null; + } + + if (url.hostname === 'gitea.d-bis.org') { + const marker = '/raw/branch/'; + const markerIndex = url.pathname.indexOf(marker); + if (markerIndex === -1) return null; + const afterMarker = url.pathname.slice(markerIndex + marker.length); + const pathSegments = afterMarker.split('/').filter(Boolean); + if (pathSegments.length < 2) return null; + const relativePath = pathSegments.slice(1).join('/'); + const candidate = resolve(PROJECT_ROOT, relativePath); + return existsSync(candidate) ? candidate : null; + } + } catch { + return null; + } + + return null; +} + async function validateLogo(logoURI, tokenInfo) { const issues = []; @@ -24,6 +65,24 @@ async function validateLogo(logoURI, tokenInfo) { // For HTTPS URLs, validate accessibility if (logoURI.startsWith('https://')) { + const localAsset = resolveLocalRepoAsset(logoURI); + if (localAsset) { + try { + const size = statSync(localAsset).size; + const contentType = inferContentTypeFromPath(localAsset); + if (!contentType || !IMAGE_MIME_TYPES.includes(contentType)) { + issues.push(`Local repo asset has unsupported extension: ${localAsset}`); + } + if (size > MAX_LOGO_SIZE) { + issues.push(`Local repo asset too large: ${(size / 1024).toFixed(2)}KB (max ${MAX_LOGO_SIZE / 1024}KB)`); + } + return issues; + } catch (error) { + issues.push(`Failed to stat local repo asset: ${error.message}`); + return issues; + } + } + try { const response = await fetch(logoURI, { method: 'HEAD' }); @@ -132,4 +191,3 @@ validateLogos(filePath).then(exitCode => { console.error('Unexpected error:', error); process.exit(1); }); - diff --git a/token-lists/scripts/validate-token-list.js b/token-lists/scripts/validate-token-list.js index d24cb15..1fe34ed 100755 --- a/token-lists/scripts/validate-token-list.js +++ b/token-lists/scripts/validate-token-list.js @@ -38,7 +38,7 @@ async function getSchema() { return tokenLists.schema; } } catch (error) { - console.log('⚠️ @uniswap/token-lists package not available, fetching schema from URL...\n'); + console.log('ℹ️ Using fallback token list schema source\n'); } // Fallback: fetch schema from Uniswap @@ -69,10 +69,38 @@ function isChecksummed(address) { function enhancedValidation(tokenList) { const errors = []; const warnings = []; + const infos = []; const seenAddresses = new Set(); - const seenSymbols = new Map(); // chainId -> Set of symbols + const seenSymbols = new Map(); // chainId -> Map let detectedChainId = null; + function isAllowedGruVersionDuplicate(existingToken, nextToken) { + if (!existingToken?.extensions || !nextToken?.extensions) return false; + + const existingVersion = existingToken.extensions.gruVersion; + const nextVersion = nextToken.extensions.gruVersion; + const existingCurrencyCode = existingToken.extensions.currencyCode; + const nextCurrencyCode = nextToken.extensions.currencyCode; + + if (typeof existingVersion !== 'string' || typeof nextVersion !== 'string') { + return false; + } + + if (existingVersion === nextVersion) { + return false; + } + + if (typeof existingCurrencyCode !== 'string' || typeof nextCurrencyCode !== 'string') { + return false; + } + + if (existingCurrencyCode !== nextCurrencyCode) { + return false; + } + + return true; + } + // Required fields if (!tokenList.name || typeof tokenList.name !== 'string') { errors.push('Missing or invalid "name" field'); @@ -154,13 +182,27 @@ function enhancedValidation(tokenList) { // Symbol uniqueness per chainId const chainId = token.chainId || 0; if (!seenSymbols.has(chainId)) { - seenSymbols.set(chainId, new Set()); + seenSymbols.set(chainId, new Map()); } - const symbolSet = seenSymbols.get(chainId); - if (symbolSet.has(token.symbol.toUpperCase())) { - warnings.push(`${prefix}: Duplicate symbol "${token.symbol}" on chainId ${chainId}`); + const symbolMap = seenSymbols.get(chainId); + const symbolKey = token.symbol.toUpperCase(); + const existingTokens = symbolMap.get(symbolKey) || []; + if (existingTokens.length > 0) { + const duplicateAllowed = existingTokens.every(existingToken => + isAllowedGruVersionDuplicate(existingToken, token) + ); + + if (duplicateAllowed) { + infos.push( + `${prefix}: Allowed staged GRU duplicate symbol "${token.symbol}" on chainId ${chainId} ` + + `(${existingTokens.map(existingToken => existingToken.extensions?.gruVersion).join(', ')} -> ${token.extensions?.gruVersion})` + ); + } else { + warnings.push(`${prefix}: Duplicate symbol "${token.symbol}" on chainId ${chainId}`); + } } - symbolSet.add(token.symbol.toUpperCase()); + existingTokens.push(token); + symbolMap.set(symbolKey, existingTokens); } if (typeof token.decimals !== 'number' || token.decimals < 0 || token.decimals > 255) { @@ -181,7 +223,7 @@ function enhancedValidation(tokenList) { } }); - return { errors, warnings, valid: errors.length === 0 }; + return { errors, warnings, infos, valid: errors.length === 0 }; } async function validateTokenList(filePath) { @@ -226,6 +268,7 @@ async function validateTokenList(filePath) { validationResult = { errors: [...schemaErrors, ...enhanced.errors], warnings: enhanced.warnings, + infos: enhanced.infos, valid: false }; } @@ -275,6 +318,14 @@ async function validateTokenList(filePath) { }); console.log(''); } + + if (validationResult.infos.length > 0) { + console.log('ℹ️ Notes:'); + validationResult.infos.forEach(info => { + console.log(` - ${info}`); + }); + console.log(''); + } process.exit(0); } else { @@ -295,6 +346,14 @@ async function validateTokenList(filePath) { }); console.log(''); } + + if (validationResult.infos.length > 0) { + console.log('Notes:'); + validationResult.infos.forEach(info => { + console.log(` ℹ️ ${info}`); + }); + console.log(''); + } process.exit(1); } @@ -318,4 +377,3 @@ validateTokenList(filePath).catch(error => { console.error('Unexpected error:', error); process.exit(1); }); - diff --git a/transaction-composer b/transaction-composer new file mode 160000 index 0000000..5b8e3fc --- /dev/null +++ b/transaction-composer @@ -0,0 +1 @@ +Subproject commit 5b8e3fc5f93272355295240f87acbf413d4d6f00