chore: pnpm lockfile, info-defi-oracle-138 app, token-lists, OMNL discovery output

- Refresh pnpm-lock.yaml / workspace after prior merge
- Add Chain 138 info hub SPA (info-defi-oracle-138)
- Token list and validation script tweaks; path_b report; Hyperledger proxmox install notes
- HYBX implementation roadmap and routing graph data model

Note: transaction-composer is a nested git repo — convert to submodule before tracking.
Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-31 22:32:15 -07:00
parent 7ac74f432b
commit 6c5fdcfd62
45 changed files with 3601 additions and 408 deletions

View File

@@ -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=

View File

@@ -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 jurisdictions 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 **510** 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 01 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 |

View File

@@ -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 | 01 score, or available notional on this hop |
| `regulatoryPenalty` | Additive penalty from policy | Non-negative; 0 if none |
| `reliability` | Historical success / health | 01, 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.

View File

@@ -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).

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Chain 138 (DeFi Oracle Meta Mainnet): compliant c* tokens, bridged cW* registry, live pools, routing, and atomic swaps."
/>
<title>info.defi-oracle.io — Chain 138</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,600;0,9..40,700;1,9..40,400&family=Instrument+Serif:ital@0;1&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -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"
}
}

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<rect width="32" height="32" rx="6" fill="#0f172a"/>
<path d="M8 22V10l4 6 4-6v12" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="22" cy="16" r="4" stroke="#a78bfa" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@@ -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 (
<BrowserRouter>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<HomePage />} />
<Route path="/chain-138" element={<Navigate to="/" replace />} />
<Route path="/tokens" element={<TokensPage />} />
<Route path="/pools" element={<PoolsPage />} />
<Route path="/swap" element={<SwapPage />} />
<Route path="/routing" element={<RoutingPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</BrowserRouter>
);
}

View File

@@ -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;

View File

@@ -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<T>(path: string): Promise<T> {
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<T>;
} catch (e) {
last = e instanceof Error ? e : new Error(String(e));
}
}
throw last || new Error('All token-aggregation API bases failed');
}

View File

@@ -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<string, string>;
weth10: Record<string, string>;
trustless?: Record<string, string>;
};
chain138Bridges: { weth9: string; weth10: string; trustless?: string };
tokenMappingApi?: unknown;
};
export type TokenMappingResponse = {
tokens?: string[];
addressMapFromTo?: Record<string, string>;
addressMapToFrom?: Record<string, string>;
};
export type NetworksResponse = {
version: string;
networks: Array<{
chainId: string;
chainIdDecimal: number;
chainName: string;
rpcUrls: string[];
blockExplorerUrls?: string[];
}>;
};

View File

@@ -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' },
},
});

View File

@@ -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 (
<div className="layout">
<header className="site">
<div className="brand">
info.defi-oracle.io
<span> Chain {CHAIN_ID}</span>
</div>
<nav className="site">
<NavLink to="/" end className={linkClass}>
Overview
</NavLink>
<NavLink to="/tokens" className={linkClass}>
c* &amp; cW*
</NavLink>
<NavLink to="/pools" className={linkClass}>
Pools
</NavLink>
<NavLink to="/swap" className={linkClass}>
Swap
</NavLink>
<NavLink to="/routing" className={linkClass}>
Routing
</NavLink>
</nav>
</header>
<Outlet />
<footer className="site">
<p>
Live data from token-aggregation API:{' '}
<span className="mono">{TOKEN_AGGREGATION_BASE}</span>
<br />
Explorer:{' '}
<a href={EXPLORER_BASE} target="_blank" rel="noreferrer">
{EXPLORER_BASE}
</a>
{' · '}
Repo docs:{' '}
<span className="mono">docs/04-configuration/DEX_AND_AGGREGATORS_CHAIN138_EXPLAINER.md</span>,{' '}
<span className="mono">docs/11-references/CW_STAR_CMC_COINGECKO_LISTING_STATUS.md</span>
</p>
</footer>
</div>
);
}

View File

@@ -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';

View File

@@ -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<string, string> }
>;
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));
}

View File

@@ -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(
<StrictMode>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</WagmiProvider>
</StrictMode>,
);

View File

@@ -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<NetworksResponse>('/api/v1/networks'),
staleTime: 60_000,
});
const n138 = data?.networks?.find((x) => x.chainIdDecimal === CHAIN_ID);
return (
<>
<h1>DeFi Oracle Meta Mainnet</h1>
<p className="lead">
Public hub for compliant <strong>c*</strong> tokens on Chain {CHAIN_ID}, the{' '}
<strong>cW*</strong> bridged registry on partner networks, live PMM pools, bridge routing, and
single-hop atomic swaps via DODOPMMIntegration.
</p>
<div className="card-grid">
<Link to="/tokens" className="card" style={{ textDecoration: 'none', color: 'inherit' }}>
<h3>c* &amp; cW*</h3>
<p>Canonical Chain {CHAIN_ID} tokens from the API plus bridged cW* addresses per chain.</p>
</Link>
<Link to="/pools" className="card" style={{ textDecoration: 'none', color: 'inherit' }}>
<h3>Pools</h3>
<p>Indexed liquidity from token-aggregation (refreshes with indexer).</p>
</Link>
<Link to="/swap" className="card" style={{ textDecoration: 'none', color: 'inherit' }}>
<h3>Atomic swap</h3>
<p>Quote + wallet: approve and call swapExactIn on the integration contract.</p>
</Link>
<Link to="/routing" className="card" style={{ textDecoration: 'none', color: 'inherit' }}>
<h3>Routing</h3>
<p>CCIP WETH bridge table and cross-chain token mapping for 138.</p>
</Link>
</div>
<h2>Network</h2>
{isError && (
<p className="muted">
Could not load <span className="mono">/api/v1/networks</span> check API base URL and CORS.
Default RPC below still works for wallets.
</p>
)}
<div className="table-wrap">
<table className="data">
<tbody>
<tr>
<th>Chain ID</th>
<td className="mono">{CHAIN_ID}</td>
</tr>
<tr>
<th>RPC (fallback)</th>
<td className="mono">{RPC_URL_138}</td>
</tr>
<tr>
<th>Explorer</th>
<td>
<a href={EXPLORER_BASE}>{EXPLORER_BASE}</a>
</td>
</tr>
{n138 && (
<>
<tr>
<th>Name (API)</th>
<td>{n138.chainName}</td>
</tr>
<tr>
<th>RPC (API)</th>
<td className="mono">{n138.rpcUrls?.[0] ?? '—'}</td>
</tr>
</>
)}
</tbody>
</table>
</div>
</>
);
}

View File

@@ -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<PoolsResponse['pools']> {
try {
const res = await fetchApi<PoolsResponse>(
`/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<TokensResponse>(`/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 (
<>
<h1>Liquidity pools</h1>
<p className="lead">
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.
</p>
{tokensQuery.isError && (
<div className="err-box">{(tokensQuery.error as Error).message}</div>
)}
{poolAggQuery.isFetching && <p className="muted">Loading pool details</p>}
{poolAggQuery.isError && (
<div className="err-box">{(poolAggQuery.error as Error).message}</div>
)}
{poolAggQuery.data && (
<div className="table-wrap">
<table className="data">
<thead>
<tr>
<th>Pool</th>
<th>DEX</th>
<th>Pair</th>
<th>TVL</th>
<th>Vol 24h</th>
</tr>
</thead>
<tbody>
{poolAggQuery.data.length === 0 && (
<tr>
<td colSpan={5} className="muted">
No pools returned for sampled tokens (API unreachable or indexer empty).
</td>
</tr>
)}
{poolAggQuery.data.map(({ pool, token, symbol }) => (
<tr key={`${pool.address}-${token}`}>
<td className="mono">
<a href={explorerAddress(pool.address)} target="_blank" rel="noreferrer">
{pool.address.slice(0, 8)}{pool.address.slice(-6)}
</a>
</td>
<td>{pool.dex || '—'}</td>
<td>
<span className="badge badge-c">{symbol || '?'}</span>{' '}
{pool.token0?.symbol || '?'} / {pool.token1?.symbol || '?'}
</td>
<td>{pool.tvl ?? '—'}</td>
<td>{pool.volume24h ?? '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</>
);
}

View File

@@ -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<BridgeRoutesResponse> => {
try {
return await fetchApi<BridgeRoutesResponse>('/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<TokenMappingResponse>(
`/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 (
<>
<h1>Routing &amp; bridges</h1>
<p className="lead">
CCIP WETH9/WETH10 bridge addresses and cross-chain token mapping for building swap bridge
swap flows. Single-hop quotes remain on the <a href="/swap">Swap</a> tab.
</p>
<h2>Chain {CHAIN_ID} bridge contracts</h2>
{routesQ.isError && <div className="err-box">{(routesQ.error as Error).message}</div>}
<p className="muted" style={{ marginBottom: '0.75rem' }}>
Bridge table uses the live API when any configured host returns 200; if all bases fail, the UI
falls back to <span className="mono">config/bridge-routes-chain138-default.json</span>.
</p>
{routesQ.data?.chain138Bridges && (
<div className="table-wrap">
<table className="data">
<tbody>
<tr>
<th>WETH9 bridge (138)</th>
<td className="mono">
<a
href={`${EXPLORER_BASE}/address/${routesQ.data.chain138Bridges.weth9}`}
target="_blank"
rel="noreferrer"
>
{routesQ.data.chain138Bridges.weth9}
</a>
</td>
</tr>
<tr>
<th>WETH10 bridge (138)</th>
<td className="mono">
<a
href={`${EXPLORER_BASE}/address/${routesQ.data.chain138Bridges.weth10}`}
target="_blank"
rel="noreferrer"
>
{routesQ.data.chain138Bridges.weth10}
</a>
</td>
</tr>
{routesQ.data.chain138Bridges.trustless && (
<tr>
<th>Trustless Lockbox (138)</th>
<td className="mono">
<a
href={`${EXPLORER_BASE}/address/${routesQ.data.chain138Bridges.trustless}`}
target="_blank"
rel="noreferrer"
>
{routesQ.data.chain138Bridges.trustless}
</a>
</td>
</tr>
)}
</tbody>
</table>
</div>
)}
<h2>Destination routers (WETH9)</h2>
{routesQ.data?.routes?.weth9 && (
<div className="table-wrap">
<table className="data">
<thead>
<tr>
<th>Destination</th>
<th>Bridge / router address</th>
</tr>
</thead>
<tbody>
{Object.entries(routesQ.data.routes.weth9).map(([name, addr]) => (
<tr key={name}>
<td>{name}</td>
<td className="mono">{addr}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<h2>Token mapping snapshots</h2>
{mappingQs.isFetching && <p className="muted">Loading mappings</p>}
{mappingQs.data?.map((block) => (
<div key={block.label} style={{ marginBottom: '1.25rem' }}>
<h3 style={{ fontSize: '1rem', marginBottom: '0.35rem' }}>{block.label}</h3>
{block.error && <div className="err-box">{block.error}</div>}
{block.data?.tokens && (
<p className="muted mono" style={{ fontSize: '0.8rem' }}>
{block.data.tokens.slice(0, 24).join(', ')}
{block.data.tokens.length > 24 ? '…' : ''}
</p>
)}
</div>
))}
</>
);
}

View File

@@ -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<TokensResponse>(`/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<QuoteResponse>(
`/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 (
<>
<h1>Atomic swap (single hop)</h1>
<p className="lead">
Fetches a quote from the aggregation API, then (on Chain {CHAIN_ID}) approves{' '}
<span className="mono">DODOPMMIntegration</span> and calls{' '}
<span className="mono">swapExactIn</span> for the returned pool. Multi-hop routing is not
available in the API; use sequential swaps if needed.
</p>
<div className="card" style={{ maxWidth: 520 }}>
<div className="form-row">
{!isConnected ? (
<button
type="button"
className="primary"
onClick={() => connect({ connector: injected() })}
>
Connect wallet
</button>
) : (
<>
<span className="mono muted">{address}</span>
<button type="button" className="ghost" onClick={() => disconnect()}>
Disconnect
</button>
</>
)}
</div>
{wrongChain && (
<div className="err-box">Switch your wallet to Chain {CHAIN_ID} (DeFi Oracle Meta).</div>
)}
<div className="form-row">
<label>
Token in
<select value={tokenIn} onChange={(e) => setTokenIn(e.target.value)}>
{cTokens.map((t) => (
<option key={t.address} value={t.address}>
{t.symbol} {t.address.slice(0, 8)}
</option>
))}
</select>
</label>
<label>
Token out
<select value={tokenOut} onChange={(e) => setTokenOut(e.target.value)}>
{cTokens.map((t) => (
<option key={t.address} value={t.address}>
{t.symbol} {t.address.slice(0, 8)}
</option>
))}
</select>
</label>
</div>
<div className="form-row">
<label>
Amount in ({tokenInMeta?.symbol || 'token'})
<input
value={amountInHuman}
onChange={(e) => setAmountInHuman(e.target.value)}
inputMode="decimal"
/>
</label>
</div>
{quoteQuery.isFetching && <p className="muted">Fetching quote</p>}
{quoteQuery.error && (
<div className="err-box">{(quoteQuery.error as Error).message}</div>
)}
{quoteQuery.data?.error && <div className="err-box">{quoteQuery.data.error}</div>}
{quoteQuery.data && !quoteQuery.data.error && (
<div className="ok-box" style={{ marginBottom: '1rem' }}>
<div>
Est. out:{' '}
<strong>
{amountOutWei > 0n
? formatUnits(amountOutWei, decimalsOut)
: quoteQuery.data.amountOut || '—'}
</strong>{' '}
(raw minOut with 1% slip: {minOut.toString()})
</div>
<div className="muted" style={{ marginTop: '0.35rem' }}>
Pool:{' '}
{poolAddress ? (
<a href={`${EXPLORER_BASE}/address/${poolAddress}`} target="_blank" rel="noreferrer">
{poolAddress}
</a>
) : (
'—'
)}
</div>
<div className="muted mono" style={{ marginTop: '0.35rem', fontSize: '0.75rem' }}>
Integration: {DODOPMM_INTEGRATION}
</div>
</div>
)}
{isConnected &&
!wrongChain &&
poolAddress &&
amountInWei > 0n &&
amountOutWei > 0n &&
allowance !== undefined && (
<div className="form-row">
{needsApprove ? (
<button
type="button"
className="primary"
disabled={approvePending}
onClick={() =>
writeApprove({
address: tokenIn as `0x${string}`,
abi: erc20Abi,
functionName: 'approve',
args: [DODOPMM_INTEGRATION as `0x${string}`, amountInWei],
})
}
>
{approvePending ? 'Approving…' : 'Approve token'}
</button>
) : (
<button
type="button"
className="primary"
disabled={swapPending}
onClick={() =>
writeSwap({
address: DODOPMM_INTEGRATION,
abi: dodopmmIntegrationAbi,
functionName: 'swapExactIn',
args: [poolAddress, tokenIn as `0x${string}`, amountInWei, minOut],
})
}
>
{swapPending ? 'Swapping…' : 'Swap (swapExactIn)'}
</button>
)}
</div>
)}
{approveHash && (
<p className="muted mono" style={{ fontSize: '0.8rem' }}>
Approve tx:{' '}
<a href={`${EXPLORER_BASE}/tx/${approveHash}`} target="_blank" rel="noreferrer">
{approveHash}
</a>{' '}
{approveReceipt.isSuccess ? '✓' : approveReceipt.isLoading ? '…' : ''}
</p>
)}
{swapHash && (
<p className="muted mono" style={{ fontSize: '0.8rem' }}>
Swap tx:{' '}
<a href={`${EXPLORER_BASE}/tx/${swapHash}`} target="_blank" rel="noreferrer">
{swapHash}
</a>{' '}
{swapReceipt.isSuccess ? '✓' : swapReceipt.isLoading ? '…' : ''}
</p>
)}
</div>
</>
);
}

View File

@@ -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<TokensResponse>(`/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 (
<>
<h1>c* &amp; cW* tokens</h1>
<p className="lead">
<span className="badge badge-c">c*</span> Compliant tokens on Chain {CHAIN_ID} (from indexer).
<br />
<span className="badge badge-cw">cW*</span> Bridged compliant-wrapped tokens on public EVM chains
(from repo <span className="mono">deployment-status.json</span>, rebuild site to refresh).
</p>
<h2>
Chain {CHAIN_ID} c* (live API){' '}
{isLoading && <span className="muted">Loading</span>}
</h2>
{error && (
<div className="err-box">{(error as Error).message}</div>
)}
{!isLoading && !error && (
<div className="table-wrap">
<table className="data">
<thead>
<tr>
<th>Symbol</th>
<th>Name</th>
<th>Address</th>
<th>PMM</th>
</tr>
</thead>
<tbody>
{cStar.map((t) => (
<tr key={t.address}>
<td>
<span className="badge badge-c">{t.symbol || '?'}</span>
</td>
<td>{t.name || '—'}</td>
<td className="mono">
<a href={explorerToken(t.address)} target="_blank" rel="noreferrer">
{t.address.slice(0, 10)}{t.address.slice(-6)}
</a>
</td>
<td>{t.hasDodoPool || t.pmmPool ? 'Yes' : '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<h2>Bridged cW* (registry)</h2>
<p className="muted">
Addresses are maintained in <span className="mono">cross-chain-pmm-lps/config/deployment-status.json</span>.
</p>
{cwChains.map(({ chainId, name, tokens }) => (
<div key={chainId} style={{ marginBottom: '1.5rem' }}>
<h3 style={{ fontSize: '1rem', margin: '0 0 0.5rem' }}>
{name}{' '}
<span className="muted mono">({chainId})</span>
</h3>
<div className="table-wrap">
<table className="data">
<thead>
<tr>
<th>Symbol</th>
<th>Address</th>
</tr>
</thead>
<tbody>
{tokens.map((row) => (
<tr key={row.symbol}>
<td>
<span className="badge badge-cw">{row.symbol}</span>
</td>
<td className="mono">{row.address}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
))}
</>
);
}

View File

@@ -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);
}

10
info-defi-oracle-138/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_TOKEN_AGGREGATION_API_BASE: string;
readonly VITE_RPC_URL_138: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -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"]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
},
"include": ["vite.config.ts"]
}

View File

@@ -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,
},
});

View File

@@ -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":[]}

View File

@@ -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"}]

View File

@@ -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":[]}

View File

@@ -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}]
[]

319
pnpm-lock.yaml generated
View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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 <container-name>
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/)

View File

@@ -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 <<EOF
[Unit]
Description=Hyperledger Aries / AnonCreds (ACA-Py)
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=$ARIES_HOME
User=$ARIES_USER
Group=$ARIES_GROUP
ExecStart=/usr/bin/docker-compose -f $ARIES_HOME/docker-compose.yml up -d
ExecStop=/usr/bin/docker-compose -f $ARIES_HOME/docker-compose.yml down
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable aries.service
chown -R "$ARIES_USER:$ARIES_GROUP" "$ARIES_HOME" "$ARIES_DATA"
log_success "Aries / AnonCreds installation completed!"
log_info "Next steps:"
log_info "1. Copy the Indy genesis file to $ARIES_LEDGER_DIR/pool_transactions_genesis"
log_info "2. Update $ARIES_HOME/.env so ACAPY_ENDPOINT matches the container IP or DNS name"
log_info "3. Start service: systemctl start aries"
log_info "4. Verify admin API: curl http://localhost:8031/status/live"

View File

@@ -6,7 +6,7 @@ set -euo pipefail
source /dev/stdin <<<"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func 2>/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"

View File

@@ -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" <<EOF
{
"name": "dbis-chain138-caliper",
"private": true,
"version": "1.0.0",
"description": "Hyperledger Caliper workspace for DBIS Chain 138 and auxiliary Hyperledger benchmarks",
"license": "UNLICENSED"
}
EOF
cat > "$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"

View File

@@ -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"

View File

@@ -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 <<EOF
NETWORK_NAME = '${INDY_NETWORK_NAME}'
enableStdOutLogging = False
LEDGER_DIR = '/var/lib/indy'
LOG_DIR = '/var/log/indy'
KEYS_DIR = '/var/lib/indy'
GENESIS_DIR = '/var/lib/indy'
BACKUP_DIR = '/var/lib/indy/backup'
PLUGINS_DIR = '/var/lib/indy/plugins'
NODE_INFO_DIR = '/var/lib/indy'
baseDir = '/var/lib/indy'
logDir = '/var/log/indy'
EOF
# Create docker-compose.yml for Indy nodes
cat > 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'"

View File

@@ -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!"

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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);
});

View File

@@ -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<symbol, token[]>
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);
});

1
transaction-composer Submodule

Submodule transaction-composer added at 5b8e3fc5f9