From 13c0eed50e269ee56365fbd990940621f3727fc3 Mon Sep 17 00:00:00 2001 From: JuanCasimiro Date: Sun, 22 Mar 2026 18:00:22 -0300 Subject: [PATCH 1/5] fix: update console logging format and README for clarity - Refactor console.log statements for signed integers to a single line format - Update README to reflect changes in expected output format - Ensure consistency in logging for price-related outputs --- .../inventory-edge-protocol/.env.example | 32 + .../inventory-edge-protocol/.gitignore | 6 + .../cardano/inventory-edge-protocol/PITCH.md | 47 + .../cardano/inventory-edge-protocol/README.md | 171 + .../inventory-edge-protocol/lib/blueprint.ts | 37 + .../lib/datum_codec.ts | 106 + .../lib/evolution_client.ts | 49 + .../inventory-edge-protocol/lib/feeds.ts | 209 + .../lib/judge_store.ts | 408 ++ .../lib/mint_shadow.ts | 103 + .../inventory-edge-protocol/lib/paths.ts | 11 + .../lib/pool_datum_decode.ts | 20 + .../lib/pool_onchain.ts | 202 + .../inventory-edge-protocol/lib/pyth.ts | 32 + .../lib/pyth_quotes.ts | 136 + .../lib/transactions.ts | 387 ++ .../lib/vault_address.ts | 21 + .../lib/vault_datum_decode.ts | 77 + .../lib/wallet_shadow_nfts.ts | 142 + .../onchain/.gitignore | 6 + .../onchain/aiken.lock | 27 + .../onchain/aiken.toml | 23 + .../onchain/plutus.json | 246 ++ .../onchain/validators/liquidity_pool.ak | 26 + .../onchain/validators/vault.ak | 162 + .../inventory-edge-protocol/package-lock.json | 3815 +++++++++++++++++ .../inventory-edge-protocol/package.json | 40 + .../scripts/mock_assets.ts | 98 + .../scripts/run_apply_hedge.ts | 21 + .../scripts/run_liquidate.ts | 23 + .../scripts/run_open_vault.ts | 35 + .../inventory-edge-protocol/server/index.ts | 973 +++++ .../inventory-edge-protocol/tsconfig.json | 13 + .../inventory-edge-protocol/web/index.html | 18 + .../inventory-edge-protocol/web/src/App.tsx | 1654 +++++++ .../inventory-edge-protocol/web/src/index.css | 578 +++ .../inventory-edge-protocol/web/src/main.tsx | 11 + .../web/src/vault_display.tsx | 266 ++ .../inventory-edge-protocol/web/tsconfig.json | 15 + .../web/vite.config.ts | 40 + 40 files changed, 10286 insertions(+) create mode 100644 lazer/cardano/inventory-edge-protocol/.env.example create mode 100644 lazer/cardano/inventory-edge-protocol/.gitignore create mode 100644 lazer/cardano/inventory-edge-protocol/PITCH.md create mode 100644 lazer/cardano/inventory-edge-protocol/README.md create mode 100644 lazer/cardano/inventory-edge-protocol/lib/blueprint.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/datum_codec.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/feeds.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/judge_store.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/paths.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/pool_datum_decode.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/pyth.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/pyth_quotes.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/transactions.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/vault_address.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/vault_datum_decode.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts create mode 100644 lazer/cardano/inventory-edge-protocol/onchain/.gitignore create mode 100644 lazer/cardano/inventory-edge-protocol/onchain/aiken.lock create mode 100644 lazer/cardano/inventory-edge-protocol/onchain/aiken.toml create mode 100644 lazer/cardano/inventory-edge-protocol/onchain/plutus.json create mode 100644 lazer/cardano/inventory-edge-protocol/onchain/validators/liquidity_pool.ak create mode 100644 lazer/cardano/inventory-edge-protocol/onchain/validators/vault.ak create mode 100644 lazer/cardano/inventory-edge-protocol/package-lock.json create mode 100644 lazer/cardano/inventory-edge-protocol/package.json create mode 100644 lazer/cardano/inventory-edge-protocol/scripts/mock_assets.ts create mode 100644 lazer/cardano/inventory-edge-protocol/scripts/run_apply_hedge.ts create mode 100644 lazer/cardano/inventory-edge-protocol/scripts/run_liquidate.ts create mode 100644 lazer/cardano/inventory-edge-protocol/scripts/run_open_vault.ts create mode 100644 lazer/cardano/inventory-edge-protocol/server/index.ts create mode 100644 lazer/cardano/inventory-edge-protocol/tsconfig.json create mode 100644 lazer/cardano/inventory-edge-protocol/web/index.html create mode 100644 lazer/cardano/inventory-edge-protocol/web/src/App.tsx create mode 100644 lazer/cardano/inventory-edge-protocol/web/src/index.css create mode 100644 lazer/cardano/inventory-edge-protocol/web/src/main.tsx create mode 100644 lazer/cardano/inventory-edge-protocol/web/src/vault_display.tsx create mode 100644 lazer/cardano/inventory-edge-protocol/web/tsconfig.json create mode 100644 lazer/cardano/inventory-edge-protocol/web/vite.config.ts diff --git a/lazer/cardano/inventory-edge-protocol/.env.example b/lazer/cardano/inventory-edge-protocol/.env.example new file mode 100644 index 00000000..41c319b3 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/.env.example @@ -0,0 +1,32 @@ +# Optional: judge API (default 8787) +# JUDGE_API_PORT=8787 + +# Pyth Lazer API key +ACCESS_TOKEN= + +# 24-word seed for PreProd (Lucid + Evolution txs) +CARDANO_MNEMONIC= + +# Lucid mint + Evolution vault txs (adjust / close / liquidar / …) — una de: +# Sin esto, Evolution usa Koios y evaluateTx suele fallar ("Koios evaluateTx failed"). +BLOCKFROST_PROJECT_ID= +# MAESTRO_API_KEY= +# Opcional: si solo usás Koios con plan que expone ogmios +# KOIOS_TOKEN= + +# Depósitos on-chain al script liquidity_pool (dejar margen para fees) +# POOL_DEPOSIT_RESERVE_LOVELACE=4000000 + +# After mock-assets, pick one line printed to the console: +# SHADOW_POLICY_ID= +# SHADOW_NAME_HEX= +# Optional: XAU_USD | WTI_USD | BTC_USD +# SHADOW_ASSET=XAU_USD + +# Vault demo (openVault) +# VAULT_DEBT_LOVELACE=1000000000 +# VAULT_COLLATERAL_QTY=1000000 + +# applyHedge demo +# HEDGE_STRIKE_RAW=2500000 +# HEDGE_PAYOUT_LOVELACE=5000000 diff --git a/lazer/cardano/inventory-edge-protocol/.gitignore b/lazer/cardano/inventory-edge-protocol/.gitignore new file mode 100644 index 00000000..cb80eedf --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.env +data/ +onchain/build/ +onchain/.aiken/ +web/dist/ diff --git a/lazer/cardano/inventory-edge-protocol/PITCH.md b/lazer/cardano/inventory-edge-protocol/PITCH.md new file mode 100644 index 00000000..b9f01e2c --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/PITCH.md @@ -0,0 +1,47 @@ +# Inventory-Edge Protocol — RealFi MVP (Hackathon) + +## The missing primitive on Cardano + +Institutional **RealFi** needs a standard pattern to (1) represent an inventory or RWA position on-chain, (2) attach **real-time valuation**, and (3) enforce **risk rules** (loan health, insurance strikes) in the same transaction graph as the asset. Cardano’s eUTxO model is excellent for auditable state transitions, but teams still lack a **small, composable vault** that is explicitly designed around **oracle-verified** prices and **Lazer-style** asset metadata—not just a generic “lock tokens” script. + +**Inventory-Edge** is that primitive: a single spending validator (`vault`) that: + +- Locks a **shadow RWA NFT** (demo mint via Lucid; metadata carries Pyth Lazer feed hints). +- Tracks **synthetic debt** and a **collateral quantity** (off-chain scaling documented next to the datum). +- Supports **Adjust** (owner), **ApplyHedge** (parametric insurance strike / payout fields), **Close** (debt-free unwind), **Liquidate** (Pyth pull model + underwater check), and **ClaimInsurance** (Pyth pull model + strike breach). + +Judges can read `onchain/validators/vault.ak` in one sitting: it is intentionally smaller than a production protocol, but it is **not** a toy—it calls `pyth.get_updates` exactly like the audited Pyth Lazer Cardano integration. + +## High-frequency risk with Pyth Lazer + +We use **Pyth Lazer** off-chain to fetch **Solana-binary** price updates (the format shared with Cardano), then attach a **reference input** to the on-chain **Pyth state** and perform a **zero-lovelace withdrawal** against Pyth’s script so the **payload is verified on-chain** before our vault logic runs. + +That is the same pull-model pattern proven in the reference `lazer-rwa` project: the oracle is not “trusted JSON in metadata”; it is **cryptographically checked** in the transaction. + +For **liquidation**, we compare integer-rounded collateral value against **110% of debt** using on-chain integer arithmetic (`price * qty * 100 < debt * 110` in the MVP). For **insurance**, `ClaimInsurance` requires the owner’s signature and checks `spot < strike` using the same verified feed. + +## Synergy with Lazer-RWA + +**Lazer-RWA** (reference: `pyth-examples/lazer/cardano/lazer-rwa`) shows how to align **off-chain streaming**, **datum design**, and **Pyth state resolution** on **PreProd**. We treat that repo as the **design north star**, not a dependency to fork blindly: + +- **On-chain**: same `pyth-network/pyth-lazer-cardano` dependency and PreProd **policy id** as the reference integration. +- **Off-chain**: we keep **Lucid** for the **asset demo** (CIP-25 metadata + native mint) where the UX wins, and **Evolution SDK** for **Pyth-withdraw + Plutus v3 spends**, because Lucid 0.10 does not expose Plutus v3 attach paths—an honest trade-off documented here rather than hidden in the code. + +The three **shadow** assets (gold / WTI / BTC) map to **Pyth Lazer feed ids** in `lib/feeds.ts` (XAU is aligned with the reference; BTC/WTI are **placeholders** until you confirm ids from the Lazer symbols API—this keeps the demo honest). + +## Limitations (MVP scope) + +- **Composition**: `Liquidate` and `ClaimInsurance` are implemented as **single transactions** that combine Pyth verification with vault redemption in the Evolution builder. If your cluster parameters or coin-selection edge cases differ, fall back to the two-step flow described in the original sprint plan (verify Pyth on-chain, then spend vault) and keep iterating. +- **Insurance payout**: the on-chain check authorizes the transition when `price < strike`; **exact payout wiring** to outputs is left as a product layer (the datum already stores `payout_lovelace` for demos and future tightening). +- **Lucid vs Evolution**: Lucid is **deprecated** on npm; we still use it for **Preprod Blockfrost mints** because it is the fastest way to get CIP-25 NFTs for judges. Production should migrate mints to Evolution or another maintained stack. + +## How to demo (operators) + +1. `npm run build:onchain` +2. Set `BLOCKFROST_PROJECT_ID`, `CARDANO_MNEMONIC`, `ACCESS_TOKEN` in `.env` (see `.env.example`). +3. `npm run mock-assets` → copy `SHADOW_POLICY_ID` / `SHADOW_NAME_HEX` for one NFT. +4. `npm run tx:open-vault` → locks NFT + vault datum at the script. +5. Optional: `npm run tx:hedge` → sets insurance fields. +6. `npm run tx:liquidate` → Pyth payload + `Liquidate` when the vault is underwater. + +This is **PreProd-only** MVP code: do not reuse keys or mnemonics from demos on mainnet. diff --git a/lazer/cardano/inventory-edge-protocol/README.md b/lazer/cardano/inventory-edge-protocol/README.md new file mode 100644 index 00000000..9164a4f9 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/README.md @@ -0,0 +1,171 @@ +# Inventory-Edge Protocol — Judge Briefing + +RealFi / RWA hackathon MVP: a **Cardano vault** that locks a demo NFT, tracks synthetic debt, supports **parametric insurance** (strike in datum), and uses **Pyth Lazer** (pull model) for **liquidation** and **insurance** checks. + +**Narrative deck (why it matters):** see [PITCH.md](./PITCH.md). + +--- + +## 1. What on-chain contracts did we ship? + +| Artifact | Role | +|----------|------| +| **`vault` (Aiken)** | Single Plutus **v3** spending validator: [`onchain/validators/vault.ak`](onchain/validators/vault.ak). | +| **`vault.vault.spend`** | Entry judges should read: all business logic (datum/redeemer, Pyth, NFT continuity). | +| **`vault.vault.else`** | Aiken-generated companion artifact in [`onchain/plutus.json`](onchain/plutus.json) (same validator hash; not a separate product contract). | + +**Compiled blueprint:** `onchain/plutus.json` (generated by `aiken build`). + +**Validator script hash (blake2b-224, hex)** — used for the vault **enterprise** address on **PreProd** (network id 0): + +```text +39e330f0020708cecb4cf7dd09c3912000c9f30b31c80cb7c2ad21a1 +``` + +We do **not** deploy a separate Pyth contract. We integrate the **existing Pyth Lazer deployment on Cardano PreProd**: + +| Constant | Value | +|----------|--------| +| **Pyth Lazer governance `PolicyId` (PreProd, hex)** | `d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6` | + +That matches the reference integration in [`pyth-examples/lazer/cardano/lazer-rwa`](https://github.com/pyth-network/pyth-examples/tree/main/lazer/cardano/lazer-rwa). + +**Shadow NFTs** are **native assets** minted off-chain (Lucid + signing policy), **not** minted by this Aiken project. Metadata is **CIP-25** label `721` with fields such as `pyth_lazer_feed_id` and `inventory_edge_class` (see `scripts/mock_assets.ts`). + +--- + +## 2. On-chain state machine (auditor view) + +### Datum — `VaultDatum` + +| Field | Meaning | +|-------|---------| +| `owner` | `VerificationKeyHash` — must sign owner-only actions. | +| `pyth_policy` | Pyth Lazer policy id (PreProd constant above). | +| `nft_policy` / `nft_name` | Locked “shadow” NFT. | +| `debt_lovelace` | Synthetic debt (demo integer, lovelace units). | +| `collateral_qty` | Integer chosen off-chain so `price_raw * qty` is comparable to debt. | +| `feed_id` | Pyth Lazer numeric feed id (e.g. **346** = XAU/USD in our demo). | +| `hedge` | `Option HedgeParams { strike_raw, payout_lovelace }` for parametric insurance. | + +### Redeemer — `VaultRedeemer` + +| Variant | Who | Pyth required? | Summary | +|---------|-----|----------------|---------| +| `Adjust { new_debt }` | Owner | No | Updates debt; NFT must stay in continuing vault output. | +| `ApplyHedge { strike_raw, payout_lovelace }` | Owner | No | Sets `hedge` to `Some`; NFT preserved. | +| `Close` | Owner | No | `debt_lovelace == 0`, no continuing vault output. | +| `Liquidate` | Anyone | **Yes** | `pyth.get_updates`; underwater if `price * collateral_qty * 100 < debt_lovelace * 110`. | +| `ClaimInsurance` | Owner | **Yes** | Requires `hedge` = `Some` and `price < strike_raw`. | + +**Pull model:** `Liquidate` / `ClaimInsurance` call `pyth.get_updates(pyth_policy, tx)` which expects the transaction to include the **Pyth state reference input** and the **zero-withdrawal** witness with the **Solana-format** update payload (same pattern as the official Cardano + Lazer examples). + +--- + +## 3. Off-chain stack (why two libraries) + +| Layer | Tech | Used for | +|-------|------|----------| +| Mint + metadata | **Lucid** + **Blockfrost** (or **Maestro**) | `npm run mock-assets` or judge UI mint — PreProd NFT demo. | +| Vault txs + Pyth witness | **Evolution SDK** + **Blockfrost o Maestro** (misma prioridad que Lucid; Koios solo si no hay ninguno) | `openVault`, `applyHedge`, `adjustDebt`, `closeVault`, `liquidate`, `claimInsurance` en [`lib/transactions.ts`](lib/transactions.ts). | +| Pool de liquidez (tADA) | **Aiken** [`onchain/validators/liquidity_pool.ak`](onchain/validators/liquidity_pool.ak) + **Lucid** (depósito) + **Evolution** (retiro Plutus V3) [`lib/pool_onchain.ts`](lib/pool_onchain.ts) | Datum = tu payment key hash; solo vos podés gastar. API: `POST /api/pool/onchain/deposit-percent`, `GET /api/pool/onchain/positions`, `POST /api/pool/onchain/withdraw-all`. | +| Judge UI | **Vite + React** + **Express** (local API) | `npm run demo` — mint, vault, pool on-chain/mock, hedge, Pyth risk, liquidate / claim. | +| Oracle payload | **@pythnetwork/pyth-lazer-sdk** | Fetch latest update in Solana binary encoding. | +| Pyth state resolution | **@pythnetwork/pyth-lazer-cardano-js** | `getPythState` / `getPythScriptHash`. | + +**Lucid 0.10** does not support attaching **Plutus V3** spend scripts in a maintainable way; Evolution handles **Plutus V3** vault spends and **Pyth withdrawals** together. This split is intentional and documented in [PITCH.md](./PITCH.md). + +--- + +## 4. Repository layout + +```text +inventory-edge-protocol/ +├── README.md ← This file (judges) +├── PITCH.md ← Product / thesis (judges) +├── .env.example +├── package.json +├── lib/ +│ ├── feeds.ts ← Pyth Pro ids + canal (`Metal.XAU/USD` 346, `Crypto.BTC/USD` 1, WTI futuro `WTIK6` 2694) +│ ├── pyth.ts +│ ├── transactions.ts ← openVault, applyHedge, liquidate +│ └── ... +├── server/ +│ └── index.ts ← Local judge API (`.env` signing) +├── web/ ← Vite React judge UI +│ ├── index.html +│ └── src/ +├── scripts/ +│ ├── mock_assets.ts ← Mint 3 shadow NFTs +│ ├── run_open_vault.ts +│ ├── run_apply_hedge.ts +│ └── run_liquidate.ts +└── onchain/ + ├── aiken.toml + ├── plutus.json ← Blueprint (commit after build) + └── validators/ + └── vault.ak ← Core contract +``` + +--- + +## 5. Build & run (operators) + +**Prerequisites:** Node 20+, [Aiken](https://aiken-lang.org) 1.1+, PreProd **tADA**, **Pyth Lazer** `ACCESS_TOKEN`, and **Blockfrost Preprod** `BLOCKFROST_PROJECT_ID` or **Maestro** `MAESTRO_API_KEY`. + +```bash +cd inventory-edge-protocol +npm install +npm run build:onchain # produces onchain/plutus.json +npx tsc # optional typecheck +``` + +**Environment:** copy `.env.example` → `.env` and fill secrets (never commit `.env`). + +| Variable | Purpose | +|----------|---------| +| `CARDANO_MNEMONIC` | 24-word PreProd wallet | +| `ACCESS_TOKEN` | Pyth Lazer API key | +| `BLOCKFROST_PROJECT_ID` or `MAESTRO_API_KEY` | Lucid chain access for mint | +| `SHADOW_POLICY_ID` / `SHADOW_NAME_HEX` | After mint, to open vault | +| `SHADOW_ASSET` | `XAU_USD` \| `WTI_USD` \| `BTC_USD` (feeds in `lib/feeds.ts`) | + +**Suggested demo sequence:** + +```bash +npm run mock-assets +# Export SHADOW_* from script output, then: +npm run tx:open-vault +npm run tx:hedge # optional +npm run tx:liquidate # needs underwater economics + ACCESS_TOKEN +``` + +**Judge UI (browser):** with the same `.env`, start API + Vite together: + +```bash +npm run demo +``` + +Open [http://127.0.0.1:5173](http://127.0.0.1:5173). The UI proxies `/api` to `http://127.0.0.1:8787` (override with `JUDGE_API_PORT`). Signing stays server-side via `CARDANO_MNEMONIC` — suitable for hackathon booths, not for production custody. + +--- + +## 6. External references (for comparison) + +- **Pyth Lazer Cardano + Aiken:** [`pyth-lazer-cardano`](https://github.com/pyth-network/pyth-lazer-cardano) (dependency in `onchain/aiken.toml`). +- **Reference app in this repo:** `lazer/cardano/lazer-rwa` — minimal Pyth verify tx + `rwa_threshold.ak` threshold pattern. + +--- + +## 7. Honest MVP limits (read before scoring) + +- **BTC / WTI feed ids** in `lib/feeds.ts` may be **placeholders**; **XAU (346)** matches the reference app. Confirm ids via Pyth Lazer symbols API before claiming production accuracy. +- **Insurance payout:** on-chain we **authorize** `ClaimInsurance` when price < strike; routing exact **payout ADA** to outputs is left as a product-layer refinement (datum already stores `payout_lovelace` for demos). +- **No dedicated CLI** for `ClaimInsurance` in `package.json` — redeemer exists on-chain; add a script if you need a live demo of that path. +- **PreProd only** — do not reuse demo mnemonics or API keys on mainnet. + +--- + +## 8. License + +On-chain project license: **Apache-2.0** (see `onchain/aiken.toml` preamble). diff --git a/lazer/cardano/inventory-edge-protocol/lib/blueprint.ts b/lazer/cardano/inventory-edge-protocol/lib/blueprint.ts new file mode 100644 index 00000000..c8e2b3d1 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/blueprint.ts @@ -0,0 +1,37 @@ +import { readFileSync } from "node:fs"; +import { PLUTUS_JSON } from "./paths.js"; + +export type BlueprintValidator = { + title: string; + compiledCode: string; + hash: string; +}; + +export type Blueprint = { + validators: BlueprintValidator[]; +}; + +export function loadBlueprint(): Blueprint { + const raw = readFileSync(PLUTUS_JSON, "utf8"); + return JSON.parse(raw) as Blueprint; +} + +export function vaultSpendValidator(blueprint: Blueprint): BlueprintValidator { + const v = blueprint.validators.find((x) => x.title === "vault.vault.spend"); + if (!v) throw new Error("vault.vault.spend not found in plutus.json — run: npm run build:onchain"); + return v; +} + +export function liquidityPoolSpendValidator( + blueprint: Blueprint, +): BlueprintValidator { + const v = blueprint.validators.find( + (x) => x.title === "liquidity_pool.liquidity_pool.spend", + ); + if (!v) { + throw new Error( + "liquidity_pool.liquidity_pool.spend not found in plutus.json — run: npm run build:onchain", + ); + } + return v; +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/datum_codec.ts b/lazer/cardano/inventory-edge-protocol/lib/datum_codec.ts new file mode 100644 index 00000000..db710fd7 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/datum_codec.ts @@ -0,0 +1,106 @@ +import { Constr, type Data } from "@evolution-sdk/evolution/Data"; +import * as PolicyId from "@evolution-sdk/evolution/PolicyId"; + +export function optionNone(): Data { + return new Constr({ index: 1n, fields: [] }); +} + +export function optionSome(inner: Data): Data { + return new Constr({ index: 0n, fields: [inner] }); +} + +export function hedgeParams(strikeRaw: bigint, payoutLovelace: bigint): Data { + return new Constr({ + index: 0n, + fields: [strikeRaw, payoutLovelace], + }); +} + +/** Aiken `VaultDatum` constructor 0 — field order matches on-chain/plutus.json */ +/** Aiken `liquidity_pool.PoolDatum` constructor 0 — solo `owner` (28 bytes). */ +export function encodePoolDatum(ownerKeyHash: Uint8Array): Data { + return new Constr({ + index: 0n, + fields: [ownerKeyHash], + }); +} + +export function encodeVaultDatum(params: { + ownerKeyHash: Uint8Array; + pythPolicyHex: string; + nftPolicyHex: string; + nftNameHex: string; + debtLovelace: bigint; + collateralQty: bigint; + feedId: bigint; + hedge: Data; +}): Data { + const pyth = PolicyId.toBytes(PolicyId.fromHex(params.pythPolicyHex)); + const nft = PolicyId.toBytes(PolicyId.fromHex(params.nftPolicyHex)); + const name = Uint8Array.from(Buffer.from(params.nftNameHex, "hex")); + return new Constr({ + index: 0n, + fields: [ + params.ownerKeyHash, + pyth, + nft, + name, + params.debtLovelace, + params.collateralQty, + params.feedId, + params.hedge, + ], + }); +} + +export function redeemerAdjust(newDebt: bigint): Data { + return new Constr({ index: 0n, fields: [newDebt] }); +} + +export function redeemerApplyHedge(strike: bigint, payout: bigint): Data { + return new Constr({ index: 1n, fields: [strike, payout] }); +} + +export function redeemerClose(): Data { + return new Constr({ index: 2n, fields: [] }); +} + +export function redeemerLiquidate(): Data { + return new Constr({ index: 3n, fields: [] }); +} + +export function redeemerClaimInsurance(): Data { + return new Constr({ index: 4n, fields: [] }); +} + +/** `liquidity_pool`: el validador ignora el redeemer; usamos constructor vacío. */ +export function redeemerPoolSpend(): Data { + return new Constr({ index: 0n, fields: [] }); +} + +/** Replace hedge field on an existing inline `VaultDatum` Constr. */ +export function vaultDatumWithHedge(prev: Data, strike: bigint, payout: bigint): Data { + if (!(prev instanceof Constr) || prev.index !== 0n) { + throw new Error("Expected VaultDatum constructor 0"); + } + const f = prev.fields; + if (f.length !== 8) throw new Error("Unexpected VaultDatum field count"); + const hedge = optionSome(hedgeParams(strike, payout)); + return new Constr({ + index: 0n, + fields: [f[0], f[1], f[2], f[3], f[4], f[5], f[6], hedge], + }); +} + +/** Replace synthetic debt (Adjust redeemer). */ +export function vaultDatumWithDebt(prev: Data, newDebt: bigint): Data { + if (!(prev instanceof Constr) || prev.index !== 0n) { + throw new Error("Expected VaultDatum constructor 0"); + } + const f = prev.fields; + if (f.length !== 8) throw new Error("Unexpected VaultDatum field count"); + return new Constr({ + index: 0n, + fields: [f[0], f[1], f[2], f[3], newDebt, f[5], f[6], f[7]], + }); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts b/lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts new file mode 100644 index 00000000..5b3e82eb --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts @@ -0,0 +1,49 @@ +import { createClient } from "@evolution-sdk/evolution"; + +/** Blockfrost: evaluación de scripts vía `/utils/txs/evaluate/utxos` (más estable que Koios → ogmios). */ +const BLOCKFROST_PREPROD = "https://cardano-preprod.blockfrost.io/api/v0"; +/** Maestro Preprod (misma familia de API que usa Lucid con `Maestro({ network: "Preprod" })`). */ +const MAESTRO_PREPROD = "https://preprod.gomaestro-api.org/v1"; +const KOIOS_PREPROD = "https://preprod.koios.rest/api/v1"; + +export type PreprodChainBackend = "blockfrost" | "maestro" | "koios"; + +/** + * Misma prioridad que `newLucidPreprod`: Blockfrost → Maestro → Koios. + * Koios depende de `/ogmios` en el servidor; sin token o con carga, suele fallar en evaluateTx. + */ +export function preprodEvolutionProviderConfig(): + | { type: "blockfrost"; baseUrl: string; projectId: string } + | { type: "maestro"; baseUrl: string; apiKey: string } + | { type: "koios"; baseUrl: string; token?: string } { + const bf = process.env.BLOCKFROST_PROJECT_ID?.trim(); + if (bf) { + return { type: "blockfrost", baseUrl: BLOCKFROST_PREPROD, projectId: bf }; + } + const maestro = process.env.MAESTRO_API_KEY?.trim(); + if (maestro) { + return { type: "maestro", baseUrl: MAESTRO_PREPROD, apiKey: maestro }; + } + const koiosToken = + process.env.KOIOS_TOKEN?.trim() || process.env.KOIOS_API_TOKEN?.trim(); + return { type: "koios", baseUrl: KOIOS_PREPROD, token: koiosToken || undefined }; +} + +export function preprodChainBackendLabel(): PreprodChainBackend { + const c = preprodEvolutionProviderConfig(); + return c.type; +} + +export function createPreprodSigningClient(mnemonic: string) { + return createClient({ + network: "preprod", + provider: preprodEvolutionProviderConfig(), + }).attachWallet({ mnemonic, type: "seed" }); +} + +export function createPreprodReadClient() { + return createClient({ + network: "preprod", + provider: preprodEvolutionProviderConfig(), + }); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/feeds.ts b/lazer/cardano/inventory-edge-protocol/lib/feeds.ts new file mode 100644 index 00000000..f2bdd1c0 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/feeds.ts @@ -0,0 +1,209 @@ +/** + * Pyth Pro (Lazer) feed ids + canales mínimos por símbolo. + * @see https://docs.pyth.network/price-feeds/pro/price-feed-ids + * @see https://history.pyth-lazer.dourolabs.app/history/v1/symbols + */ +export type LazerChannel = + | "real_time" + | "fixed_rate@50ms" + | "fixed_rate@200ms"; + +/** Registro por clave interna (vault / mint). */ +export const LAZER_FEED_BY_KEY = { + XAU_USD: { + id: 346, + /** Símbolo oficial Pyth Pro */ + proSymbol: "Metal.XAU/USD", + channel: "fixed_rate@200ms" as const, + uiTitle: "Oro (XAU/USD)", + }, + /** + * Spot WTI (`Commodities.WTI/USD`, id 344) figura *inactive* en Symbols API. + * Usamos futuro estable listado como `stable` (contrato rolado; revisar IDs al cambiar de mes). + */ + WTI_USD: { + id: 2694, + proSymbol: "Commodities.WTIK6/USD", + channel: "fixed_rate@50ms" as const, + uiTitle: "Petróleo WTI (futuro)", + }, + /** `Crypto.BTC/USD` exige canal `real_time` (no `fixed_rate@200ms`). */ + BTC_USD: { + id: 1, + proSymbol: "Crypto.BTC/USD", + channel: "real_time" as const, + uiTitle: "Bitcoin (BTC/USD)", + }, +} as const; + +export const PYTH_LAZER_FEEDS = { + XAU_USD: LAZER_FEED_BY_KEY.XAU_USD.id, + WTI_USD: LAZER_FEED_BY_KEY.WTI_USD.id, + BTC_USD: LAZER_FEED_BY_KEY.BTC_USD.id, +} as const; + +/** Id antiguo del demo (inactivo en Lazer); solo para canal por defecto si aparece en vaults viejas. */ +export const LAZER_LEGACY_WTI_FEED_ID = 233; + +export type ShadowAssetKey = keyof typeof PYTH_LAZER_FEEDS; + +export type DemoSlot = "metal" | "oil" | "stock"; + +export const DEMO_SLOT_TO_KEY: Record = { + metal: "XAU_USD", + oil: "WTI_USD", + stock: "BTC_USD", +}; + +export const DEMO_SLOT_LABEL: Record< + DemoSlot, + { title: string; subtitle: string } +> = { + metal: { + title: LAZER_FEED_BY_KEY.XAU_USD.uiTitle, + subtitle: `${LAZER_FEED_BY_KEY.XAU_USD.proSymbol} · id ${LAZER_FEED_BY_KEY.XAU_USD.id}`, + }, + oil: { + title: LAZER_FEED_BY_KEY.WTI_USD.uiTitle, + subtitle: `${LAZER_FEED_BY_KEY.WTI_USD.proSymbol} · id ${LAZER_FEED_BY_KEY.WTI_USD.id}`, + }, + stock: { + title: LAZER_FEED_BY_KEY.BTC_USD.uiTitle, + subtitle: `${LAZER_FEED_BY_KEY.BTC_USD.proSymbol} · id ${LAZER_FEED_BY_KEY.BTC_USD.id}`, + }, +}; + +/** Feed ids for the three judge demo assets (same order as mint slots). */ +export const DEMO_PYTH_FEED_IDS: readonly number[] = [ + PYTH_LAZER_FEEDS.XAU_USD, + PYTH_LAZER_FEEDS.WTI_USD, + PYTH_LAZER_FEEDS.BTC_USD, +]; + +/** Un paso en la cadena HTTP `latest_price` (id + canal mínimo + etiqueta Pro). */ +export type LazerQuoteStep = { + id: number; + channel: LazerChannel; + proSymbol: string; +}; + +/** + * Cuando `Metal.XAU/USD` (346) devuelve `publisherCount: 0` (mercado NY cerrado), + * Lazer no incluye `price` en JSON — usamos XAUT (oro tokenizado), suele tener datos 24/7. + */ +export const LAZER_XAUT_FALLBACK_ID = 172; + +/** + * Varios futuros energy `stable`; se prueban en orden hasta que alguno tenga `price`. + * (Fuera de horario pueden quedar todos en 0 publicadores.) + */ +export const LAZER_OIL_QUOTE_CHAIN: readonly LazerQuoteStep[] = [ + { id: 2694, channel: "fixed_rate@50ms", proSymbol: "Commodities.WTIK6/USD" }, + { id: 2698, channel: "fixed_rate@50ms", proSymbol: "Commodities.BRENTK6/USD" }, + { id: 3032, channel: "fixed_rate@50ms", proSymbol: "Commodities.WTIM6/USD" }, + { id: 3040, channel: "fixed_rate@50ms", proSymbol: "Commodities.BRENTM6/USD" }, + { id: 3041, channel: "fixed_rate@50ms", proSymbol: "Commodities.BRENTN6/USD" }, + { id: 3007, channel: "fixed_rate@50ms", proSymbol: "Commodities.NGDM6/USD" }, +]; + +const OIL_FEED_IDS = new Set(LAZER_OIL_QUOTE_CHAIN.map((s) => s.id)); + +/** Canal Lazer a usar en `getLatestPrice` / payload Solana (por id numérico). */ +export function lazerChannelForFeedId(feedId: number): LazerChannel { + if (feedId === LAZER_XAUT_FALLBACK_ID) return "fixed_rate@200ms"; + for (const k of Object.keys(LAZER_FEED_BY_KEY) as ShadowAssetKey[]) { + const m = LAZER_FEED_BY_KEY[k]; + if (m.id === feedId) return m.channel; + } + if (OIL_FEED_IDS.has(feedId)) return "fixed_rate@50ms"; + if (feedId === LAZER_LEGACY_WTI_FEED_ID) return "fixed_rate@200ms"; + return "fixed_rate@200ms"; +} + +/** Cadena para las tarjetas del judge UI (metal = spot oro → XAUT). */ +export function quoteChainForDemoSlot(slot: DemoSlot): readonly LazerQuoteStep[] { + if (slot === "metal") { + return [ + { + id: LAZER_FEED_BY_KEY.XAU_USD.id, + channel: LAZER_FEED_BY_KEY.XAU_USD.channel, + proSymbol: LAZER_FEED_BY_KEY.XAU_USD.proSymbol, + }, + { + id: LAZER_XAUT_FALLBACK_ID, + channel: "fixed_rate@200ms", + proSymbol: "Crypto.XAUT/USD", + }, + ]; + } + if (slot === "oil") return LAZER_OIL_QUOTE_CHAIN; + return [ + { + id: LAZER_FEED_BY_KEY.BTC_USD.id, + channel: LAZER_FEED_BY_KEY.BTC_USD.channel, + proSymbol: LAZER_FEED_BY_KEY.BTC_USD.proSymbol, + }, + ]; +} + +/** Cadena para `/api/risk` según el `feed_id` guardado en el datum de la vault. */ +export function quoteChainForVaultFeedId(feedId: number): readonly LazerQuoteStep[] { + const fid = Number(feedId); + if (fid === LAZER_FEED_BY_KEY.XAU_USD.id || fid === 1521) { + return quoteChainForDemoSlot("metal"); + } + if (fid === LAZER_XAUT_FALLBACK_ID) { + return [ + { + id: LAZER_XAUT_FALLBACK_ID, + channel: "fixed_rate@200ms", + proSymbol: "Crypto.XAUT/USD", + }, + ]; + } + if (fid === LAZER_LEGACY_WTI_FEED_ID) { + return [...LAZER_OIL_QUOTE_CHAIN]; + } + const oi = LAZER_OIL_QUOTE_CHAIN.findIndex((s) => s.id === fid); + if (oi >= 0) { + const tail = LAZER_OIL_QUOTE_CHAIN.slice(oi); + const head = LAZER_OIL_QUOTE_CHAIN.slice(0, oi); + return [...tail, ...head]; + } + return [ + { + id: fid, + channel: lazerChannelForFeedId(fid), + proSymbol: `feed:${fid}`, + }, + ]; +} + +/** + * Infer Pyth feed from native asset name (utf8), e.g. ShadowXAU_USD_abc → 346. + * Covers legacy `ShadowXAU_USD` (no suffix) and suffixed mints. + */ +export function inferFeedFromShadowName(nameUtf8: string): number | null { + if (nameUtf8.startsWith("ShadowXAU_USD")) return PYTH_LAZER_FEEDS.XAU_USD; + if (nameUtf8.startsWith("ShadowWTI_USD")) return PYTH_LAZER_FEEDS.WTI_USD; + if (nameUtf8.startsWith("ShadowBTC_USD")) return PYTH_LAZER_FEEDS.BTC_USD; + return null; +} + +export const SHADOW_ASSETS: Record< + ShadowAssetKey, + { label: string; description: string } +> = { + XAU_USD: { + label: "Shadow Gold (XAU)", + description: "Metal.XAU/USD — oro spot Pyth Pro", + }, + WTI_USD: { + label: "Shadow WTI", + description: "Commodities.WTIK6/USD — futuro WTI (spot WTI/USD legacy inactivo)", + }, + BTC_USD: { + label: "Shadow BTC", + description: "Crypto.BTC/USD — Bitcoin vs USD", + }, +}; diff --git a/lazer/cardano/inventory-edge-protocol/lib/judge_store.ts b/lazer/cardano/inventory-edge-protocol/lib/judge_store.ts new file mode 100644 index 00000000..75405235 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/judge_store.ts @@ -0,0 +1,408 @@ +/** + * Pool de liquidez mock (off-chain): préstamos, reservas de seguro, beneficios. + * El datum on-chain sigue usando debt_lovelace; el pool demo financia y contabiliza. + */ +import fs from "node:fs"; +import path from "node:path"; + +const DATA_DIR = path.join(process.cwd(), "data"); +const AUDIT_PATH = path.join(DATA_DIR, "judge_audit.json"); +const POOL_PATH = path.join(DATA_DIR, "mock_insurance_pool.json"); +const MAX_AUDIT = 400; + +/** Interés demo sobre cada amortización de principal (bps). */ +export const LOAN_REPAY_INTEREST_BPS = 100n; +/** Excedente simulado de venta de colateral: % del principal que ingresa al pool. */ +export const LIQUIDATION_POOL_SURPLUS_BPS = 800n; +/** Fee mock que el pool retiene al pagar una cobertura (bps del monto reservado). */ +export const INSURANCE_POOL_FEE_BPS = 300n; + +export type AuditEvent = { + ts: string; + kind: string; + summary: string; + txHash?: string; + extra?: Record; +}; + +export type PoolReservation = { + nftPolicyHex: string; + nftNameHex: string; + payoutLovelace: string; + vaultRef: string; +}; + +export type MockPoolState = { + availableLovelace: string; + encumberedLovelace: string; + /** Principal colocado en préstamos (mock), alineado a datum.debt agregado. */ + deployedToLoansLovelace: string; + totalDepositedLovelace: string; + totalPaidOutLovelace: string; + totalRepaidPrincipalLovelace: string; + profitsFromLoansLovelace: string; + profitsFromInsuranceLovelace: string; + reservations: PoolReservation[]; + /** key policy|name → outstanding principal (string lovelace) */ + outstandingLoans: Record; +}; + +function ensureDir(): void { + if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true }); +} + +function defaultPool(): MockPoolState { + return { + availableLovelace: "0", + encumberedLovelace: "0", + deployedToLoansLovelace: "0", + totalDepositedLovelace: "0", + totalPaidOutLovelace: "0", + totalRepaidPrincipalLovelace: "0", + profitsFromLoansLovelace: "0", + profitsFromInsuranceLovelace: "0", + reservations: [], + outstandingLoans: {}, + }; +} + +let poolMem: MockPoolState | null = null; + +export function nftLoanKey(nftPolicyHex: string, nftNameHex: string): string { + return `${nftPolicyHex.toLowerCase()}|${nftNameHex.toLowerCase()}`; +} + +function migratePool(raw: unknown): MockPoolState { + const o = + raw && typeof raw === "object" ? (raw as Record) : {}; + const b = defaultPool(); + const reservations = Array.isArray(o.reservations) + ? (o.reservations as PoolReservation[]) + : []; + const outstandingLoans = + typeof o.outstandingLoans === "object" && + o.outstandingLoans !== null && + !Array.isArray(o.outstandingLoans) + ? (o.outstandingLoans as Record) + : {}; + return { + availableLovelace: String(o.availableLovelace ?? b.availableLovelace), + encumberedLovelace: String(o.encumberedLovelace ?? b.encumberedLovelace), + deployedToLoansLovelace: String( + o.deployedToLoansLovelace ?? b.deployedToLoansLovelace, + ), + totalDepositedLovelace: String( + o.totalDepositedLovelace ?? b.totalDepositedLovelace, + ), + totalPaidOutLovelace: String( + o.totalPaidOutLovelace ?? b.totalPaidOutLovelace, + ), + totalRepaidPrincipalLovelace: String( + o.totalRepaidPrincipalLovelace ?? b.totalRepaidPrincipalLovelace, + ), + profitsFromLoansLovelace: String( + o.profitsFromLoansLovelace ?? b.profitsFromLoansLovelace, + ), + profitsFromInsuranceLovelace: String( + o.profitsFromInsuranceLovelace ?? b.profitsFromInsuranceLovelace, + ), + reservations, + outstandingLoans, + }; +} + +export function loadPool(): MockPoolState { + if (poolMem) return poolMem; + ensureDir(); + if (!fs.existsSync(POOL_PATH)) { + poolMem = defaultPool(); + return poolMem; + } + try { + const raw = JSON.parse(fs.readFileSync(POOL_PATH, "utf8")) as unknown; + poolMem = migratePool(raw); + return poolMem; + } catch { + poolMem = defaultPool(); + return poolMem; + } +} + +export function savePool(p: MockPoolState): void { + poolMem = p; + ensureDir(); + fs.writeFileSync(POOL_PATH, JSON.stringify(p, null, 2), "utf8"); +} + +export function poolDeposit(lovelace: bigint): MockPoolState { + const p = loadPool(); + const av = BigInt(p.availableLovelace) + lovelace; + const td = BigInt(p.totalDepositedLovelace) + lovelace; + p.availableLovelace = av.toString(); + p.totalDepositedLovelace = td.toString(); + savePool(p); + return p; +} + +/** Tras retiro on-chain del script pool: baja liquidez disponible del mock (sin ir a negativo). */ +export function poolWithdraw(lovelace: bigint): MockPoolState { + if (lovelace <= 0n) return loadPool(); + const p = loadPool(); + const av = BigInt(p.availableLovelace); + const sub = lovelace > av ? av : lovelace; + p.availableLovelace = (av - sub).toString(); + savePool(p); + return p; +} + +/** Llamar solo tras openVault on-chain OK: saca liquidez del pool para el principal del préstamo. */ +export function poolCommitLoan(params: { + nftPolicyHex: string; + nftNameHex: string; + loanLovelace: bigint; +}): void { + if (params.loanLovelace <= 0n) return; + const p = loadPool(); + const av = BigInt(p.availableLovelace); + if (av < params.loanLovelace) { + throw new Error( + "poolCommitLoan: liquidez insuficiente (revisá pre-checks del servidor)", + ); + } + const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); + if (p.outstandingLoans[key] != null) { + throw new Error("poolCommitLoan: ya hay préstamo activo para este NFT"); + } + p.availableLovelace = (av - params.loanLovelace).toString(); + p.deployedToLoansLovelace = ( + BigInt(p.deployedToLoansLovelace) + params.loanLovelace + ).toString(); + p.outstandingLoans[key] = params.loanLovelace.toString(); + savePool(p); +} + +/** + * Tras adjustDebt: más deuda consume pool; menos devuelve principal + interés demo al pool. + */ +export function poolOnDebtAdjusted(params: { + nftPolicyHex: string; + nftNameHex: string; + oldDebtLovelace: bigint; + newDebtLovelace: bigint; +}): void { + const oldD = params.oldDebtLovelace; + const newD = params.newDebtLovelace; + if (oldD === newD) return; + + const p = loadPool(); + const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); + const deployed = BigInt(p.deployedToLoansLovelace); + + if (newD > oldD) { + const more = newD - oldD; + const av = BigInt(p.availableLovelace); + if (av < more) { + throw new Error("poolOnDebtAdjusted: liquidez insuficiente"); + } + p.availableLovelace = (av - more).toString(); + p.deployedToLoansLovelace = (deployed + more).toString(); + p.outstandingLoans[key] = newD.toString(); + savePool(p); + return; + } + + const repaid = oldD - newD; + const interest = (repaid * LOAN_REPAY_INTEREST_BPS) / 10000n; + p.availableLovelace = ( + BigInt(p.availableLovelace) + repaid + interest + ).toString(); + p.deployedToLoansLovelace = (deployed - repaid).toString(); + p.totalRepaidPrincipalLovelace = ( + BigInt(p.totalRepaidPrincipalLovelace) + repaid + ).toString(); + p.profitsFromLoansLovelace = ( + BigInt(p.profitsFromLoansLovelace) + interest + ).toString(); + if (newD === 0n) delete p.outstandingLoans[key]; + else p.outstandingLoans[key] = newD.toString(); + savePool(p); +} + +/** Tras liquidación: colateral “vendido” — recupera principal + excedente demo al pool. */ +export function poolOnLiquidateLoan(params: { + nftPolicyHex: string; + nftNameHex: string; + debtLovelace: bigint; +}): void { + const D = params.debtLovelace; + if (D <= 0n) return; + const p = loadPool(); + const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); + const surplus = (D * LIQUIDATION_POOL_SURPLUS_BPS) / 10000n; + const recovery = D + surplus; + p.availableLovelace = (BigInt(p.availableLovelace) + recovery).toString(); + p.deployedToLoansLovelace = ( + BigInt(p.deployedToLoansLovelace) - D + ).toString(); + p.profitsFromLoansLovelace = ( + BigInt(p.profitsFromLoansLovelace) + surplus + ).toString(); + delete p.outstandingLoans[key]; + savePool(p); +} + +export function poolFinalizeCloseVault(params: { + nftPolicyHex: string; + nftNameHex: string; +}): void { + const p = loadPool(); + const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); + delete p.outstandingLoans[key]; + savePool(p); +} + +/** + * Liquidez efectiva para una nueva reserva de seguro: disponible + reemplazo de reserva del mismo NFT. + */ +export function poolEffectiveAvailableForInsuranceReserve( + nftPolicyHex: string, + nftNameHex: string, +): bigint { + const p = loadPool(); + let av = BigInt(p.availableLovelace); + const pol = nftPolicyHex.toLowerCase(); + const nm = nftNameHex.toLowerCase(); + const prev = p.reservations.find( + (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, + ); + if (prev) av += BigInt(prev.payoutLovelace); + return av; +} + +/** + * Al contratar cobertura: aparta hasta `payoutRequested` del disponible (mock). + */ +export function poolReserveForHedge(params: { + nftPolicyHex: string; + nftNameHex: string; + vaultRef: string; + payoutRequested: bigint; +}): { reserved: bigint; shortfall: bigint } { + const p = loadPool(); + const pol = params.nftPolicyHex.toLowerCase(); + const nm = params.nftNameHex.toLowerCase(); + const prevIdx = p.reservations.findIndex( + (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, + ); + if (prevIdx >= 0) { + const old = p.reservations[prevIdx]!; + const oldAmt = BigInt(old.payoutLovelace); + p.reservations.splice(prevIdx, 1); + p.encumberedLovelace = (BigInt(p.encumberedLovelace) - oldAmt).toString(); + p.availableLovelace = (BigInt(p.availableLovelace) + oldAmt).toString(); + } + const av = BigInt(p.availableLovelace); + const req = params.payoutRequested; + const reserved = req <= av ? req : av; + const shortfall = req - reserved; + p.availableLovelace = (av - reserved).toString(); + p.encumberedLovelace = (BigInt(p.encumberedLovelace) + reserved).toString(); + p.reservations.push({ + nftPolicyHex: params.nftPolicyHex.toLowerCase(), + nftNameHex: params.nftNameHex.toLowerCase(), + payoutLovelace: reserved.toString(), + vaultRef: params.vaultRef, + }); + savePool(p); + return { reserved, shortfall }; +} + +/** Liquidación o cierre: la reserva vuelve al disponible (no es pago al asegurado). */ +export function poolCancelReservation(params: { + nftPolicyHex: string; + nftNameHex: string; +}): { returned: bigint; found: boolean } { + const p = loadPool(); + const pol = params.nftPolicyHex.toLowerCase(); + const nm = params.nftNameHex.toLowerCase(); + const idx = p.reservations.findIndex( + (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, + ); + if (idx < 0) return { returned: 0n, found: false }; + const r = p.reservations[idx]!; + const amt = BigInt(r.payoutLovelace); + p.reservations.splice(idx, 1); + p.encumberedLovelace = (BigInt(p.encumberedLovelace) - amt).toString(); + p.availableLovelace = (BigInt(p.availableLovelace) + amt).toString(); + savePool(p); + return { returned: amt, found: true }; +} + +export function poolReleaseOnClaim(params: { + nftPolicyHex: string; + nftNameHex: string; +}): { released: bigint; poolFee: bigint; paidOut: bigint; found: boolean } { + const p = loadPool(); + const pol = params.nftPolicyHex.toLowerCase(); + const nm = params.nftNameHex.toLowerCase(); + const idx = p.reservations.findIndex( + (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, + ); + if (idx < 0) + return { released: 0n, poolFee: 0n, paidOut: 0n, found: false }; + const r = p.reservations[idx]!; + const amt = BigInt(r.payoutLovelace); + const poolFee = (amt * INSURANCE_POOL_FEE_BPS) / 10000n; + const paidOut = amt - poolFee; + p.reservations.splice(idx, 1); + p.encumberedLovelace = (BigInt(p.encumberedLovelace) - amt).toString(); + p.availableLovelace = (BigInt(p.availableLovelace) + poolFee).toString(); + p.totalPaidOutLovelace = ( + BigInt(p.totalPaidOutLovelace) + paidOut + ).toString(); + p.profitsFromInsuranceLovelace = ( + BigInt(p.profitsFromInsuranceLovelace) + poolFee + ).toString(); + savePool(p); + return { released: amt, poolFee, paidOut, found: true }; +} + +export function readAudit(limit: number): AuditEvent[] { + ensureDir(); + if (!fs.existsSync(AUDIT_PATH)) return []; + try { + const arr = JSON.parse( + fs.readFileSync(AUDIT_PATH, "utf8"), + ) as AuditEvent[]; + return arr.slice(0, Math.min(limit, MAX_AUDIT)); + } catch { + return []; + } +} + +export function appendAudit( + e: Omit & { ts?: string }, +): void { + ensureDir(); + const prev = (() => { + if (!fs.existsSync(AUDIT_PATH)) return [] as AuditEvent[]; + try { + return JSON.parse(fs.readFileSync(AUDIT_PATH, "utf8")) as AuditEvent[]; + } catch { + return []; + } + })(); + const row: AuditEvent = { + ts: e.ts ?? new Date().toISOString(), + kind: e.kind, + summary: e.summary, + txHash: e.txHash, + extra: e.extra, + }; + prev.unshift(row); + fs.writeFileSync( + AUDIT_PATH, + JSON.stringify(prev.slice(0, MAX_AUDIT), null, 2), + "utf8", + ); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts b/lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts new file mode 100644 index 00000000..ad4c1acd --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts @@ -0,0 +1,103 @@ +/** + * Mint one native shadow NFT (Lucid) — unique asset name so judges can repeat demos. + */ +import { randomBytes } from "node:crypto"; + +import { Blockfrost, Lucid, Maestro } from "lucid-cardano"; + +import { + DEMO_SLOT_TO_KEY, + type DemoSlot, + PYTH_LAZER_FEEDS, + SHADOW_ASSETS, +} from "./feeds.js"; + +function requireEnv(name: string): string { + const v = process.env[name]; + if (!v) throw new Error(`Missing env ${name}`); + return v; +} + +/** Shared Lucid instance (Blockfrost/Maestro) — same UTxO view as mint. */ +export async function newLucidPreprod(): Promise { + const projectId = process.env.BLOCKFROST_PROJECT_ID; + const maestroKey = process.env.MAESTRO_API_KEY; + if (projectId) { + return await Lucid.new( + new Blockfrost("https://cardano-preprod.blockfrost.io/api/v0", projectId), + "Preprod", + ); + } + if (maestroKey) { + return await Lucid.new( + new Maestro({ network: "Preprod", apiKey: maestroKey }), + "Preprod", + ); + } + throw new Error("Set BLOCKFROST_PROJECT_ID or MAESTRO_API_KEY (Preprod)"); +} + +export type MintShadowResult = { + txHash: string; + policyId: string; + assetName: string; + nameHex: string; + slot: DemoSlot; + assetKey: keyof typeof PYTH_LAZER_FEEDS; + feedId: number; +}; + +export async function mintShadowNft(slot: DemoSlot): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const lucid = await newLucidPreprod(); + lucid.selectWalletFromSeed(mnemonic); + + const addr = await lucid.wallet.address(); + const details = lucid.utils.getAddressDetails(addr); + if (details.paymentCredential?.type !== "Key") { + throw new Error("Expected key payment credential"); + } + + const key = DEMO_SLOT_TO_KEY[slot]; + const suffix = randomBytes(4).toString("hex"); + const assetName = `Shadow${key}_${suffix}`; + const nameHex = Buffer.from(assetName, "utf8").toString("hex"); + + const mintingPolicy = lucid.utils.nativeScriptFromJson({ + type: "all", + scripts: [{ type: "sig", keyHash: details.paymentCredential.hash }], + }); + const policyId = lucid.utils.mintingPolicyToId(mintingPolicy); + const unit = policyId + nameHex; + + const meta = SHADOW_ASSETS[key]; + const tx = await lucid + .newTx() + .mintAssets({ [unit]: 1n }) + .attachMintingPolicy(mintingPolicy) + .attachMetadata(721, { + [policyId]: { + [assetName]: { + name: meta.label, + description: meta.description, + pyth_lazer_feed_id: String(PYTH_LAZER_FEEDS[key]), + inventory_edge_class: key, + inventory_edge_slot: slot, + }, + }, + }) + .complete(); + + const signed = await tx.sign().complete(); + const txHash = await signed.submit(); + + return { + txHash, + policyId, + assetName, + nameHex, + slot, + assetKey: key, + feedId: PYTH_LAZER_FEEDS[key], + }; +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/paths.ts b/lazer/cardano/inventory-edge-protocol/lib/paths.ts new file mode 100644 index 00000000..d91bfa50 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/paths.ts @@ -0,0 +1,11 @@ +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); + +/** Repo root: inventory-edge-protocol/ */ +export const PROJECT_ROOT = join(here, ".."); + +export const ONCHAIN_DIR = join(PROJECT_ROOT, "onchain"); + +export const PLUTUS_JSON = join(ONCHAIN_DIR, "plutus.json"); diff --git a/lazer/cardano/inventory-edge-protocol/lib/pool_datum_decode.ts b/lazer/cardano/inventory-edge-protocol/lib/pool_datum_decode.ts new file mode 100644 index 00000000..c0aa6300 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/pool_datum_decode.ts @@ -0,0 +1,20 @@ +import { Constr, type Data } from "@evolution-sdk/evolution/Data"; + +function bytesToHex(u: Uint8Array): string { + return Buffer.from(u).toString("hex"); +} + +function asBytes(d: Data, label: string): Uint8Array { + if (!(d instanceof Uint8Array)) { + throw new Error(`${label}: expected bytes`); + } + return d; +} + +/** Decodifica `liquidity_pool.PoolDatum` (solo campo `owner`, 28 bytes). */ +export function decodePoolDatumOwnerHex(data: Data): string { + if (!(data instanceof Constr) || data.index !== 0n || data.fields.length !== 1) { + throw new Error("Not a PoolDatum (Constr 0, 1 field)"); + } + return bytesToHex(asBytes(data.fields[0], "owner")); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts b/lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts new file mode 100644 index 00000000..76c107fb --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts @@ -0,0 +1,202 @@ +/** + * Depósitos reales de tADA al script `liquidity_pool` (PreProd) vía Lucid. + * Retiros: Evolution SDK + Plutus V3 (Lucid no adjunta gasto V3). + * El datum fija el payment key hash del dueño; solo esa clave puede gastar ese UTxO. + */ +import { TransactionHash } from "@evolution-sdk/evolution"; +import { toCBORHex } from "@evolution-sdk/evolution/Data"; +import * as Address from "@evolution-sdk/evolution/Address"; +import * as Assets from "@evolution-sdk/evolution/Assets"; +import { PlutusV3 } from "@evolution-sdk/evolution/PlutusV3"; +import type { UTxO } from "@evolution-sdk/evolution/UTxO"; + +import { loadBlueprint, liquidityPoolSpendValidator } from "./blueprint.js"; +import { encodePoolDatum, redeemerPoolSpend } from "./datum_codec.js"; +import { createPreprodSigningClient } from "./evolution_client.js"; +import { newLucidPreprod } from "./mint_shadow.js"; +import { decodePoolDatumOwnerHex } from "./pool_datum_decode.js"; +import { readInlineDatum } from "./transactions.js"; +import { + enterpriseVaultAddress, + paymentKeyHashBytes, +} from "./vault_address.js"; + +function requireEnv(name: string): string { + const v = process.env[name]; + if (!v) throw new Error(`Missing env ${name}`); + return v; +} + +/** Lovelace mínimo que debe quedar en la wallet para fees (configurable). */ +export function poolDepositReserveLovelace(): bigint { + const raw = process.env.POOL_DEPOSIT_RESERVE_LOVELACE?.trim(); + if (raw) return BigInt(raw); + return 4_000_000n; +} + +export function liquidityPoolAddressBech32(): string { + const bp = loadBlueprint(); + const val = liquidityPoolSpendValidator(bp); + return Address.toBech32(enterpriseVaultAddress(val.hash)); +} + +export async function walletTotalLovelaceLucid(): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const lucid = await newLucidPreprod(); + lucid.selectWalletFromSeed(mnemonic); + const utxos = await lucid.wallet.getUtxos(); + let total = 0n; + for (const u of utxos) { + total += u.assets.lovelace; + } + return total; +} + +export async function depositLiquidityPoolOnChain(params: { + lovelace: bigint; +}): Promise { + if (params.lovelace <= 0n) { + throw new Error("lovelace must be positive"); + } + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const lucid = await newLucidPreprod(); + lucid.selectWalletFromSeed(mnemonic); + const userBech32 = await lucid.wallet.address(); + const details = lucid.utils.getAddressDetails(userBech32); + if (details.paymentCredential?.type !== "Key") { + throw new Error("Expected key payment credential for pool datum owner"); + } + const hashHex = details.paymentCredential.hash; + if (hashHex.length !== 56) { + throw new Error(`Unexpected payment key hash length: ${hashHex.length}`); + } + const ownerKh = Uint8Array.from(Buffer.from(hashHex, "hex")); + const poolBech32 = liquidityPoolAddressBech32(); + const inline = toCBORHex(encodePoolDatum(ownerKh)); + + const utxos = await lucid.wallet.getUtxos(); + let total = 0n; + for (const u of utxos) { + total += u.assets.lovelace; + } + + const reserve = poolDepositReserveLovelace(); + if (total < params.lovelace + reserve) { + throw new Error( + `Saldo insuficiente: wallet ${total} lovelace; envío ${params.lovelace} + reserva fees ${reserve} = ${params.lovelace + reserve}. Bajá el monto o POOL_DEPOSIT_RESERVE_LOVELACE.`, + ); + } + + const tx = await lucid + .newTx() + .payToContract(poolBech32, { inline: inline }, { lovelace: params.lovelace }) + .complete(); + + const signed = await tx.sign().complete(); + return signed.submit(); +} + +function poolPlutusV3(): PlutusV3 { + const bp = loadBlueprint(); + const val = liquidityPoolSpendValidator(bp); + return new PlutusV3({ + bytes: Uint8Array.from(Buffer.from(val.compiledCode, "hex")), + }); +} + +export type PoolOnChainPositionRow = { + txHash: string; + outputIndex: string; + ref: string; + lovelace: string; +}; + +/** UTxOs en la dirección del pool cuyo datum `owner` coincide con la wallet de la seed. */ +export async function listPoolDepositsOnChain(): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const userAddr = await client.address(); + const ownerHex = Buffer.from(paymentKeyHashBytes(userAddr)).toString("hex"); + + const bp = loadBlueprint(); + const val = liquidityPoolSpendValidator(bp); + const poolAddr = enterpriseVaultAddress(val.hash); + const utxos = await client.getUtxos(poolAddr); + + const rows: PoolOnChainPositionRow[] = []; + for (const u of utxos) { + try { + const d = readInlineDatum(u); + const oh = decodePoolDatumOwnerHex(d); + if (oh.toLowerCase() !== ownerHex.toLowerCase()) continue; + rows.push({ + txHash: TransactionHash.toHex(u.transactionId), + outputIndex: u.index.toString(), + ref: `${TransactionHash.toHex(u.transactionId)}#${u.index}`, + lovelace: u.assets.lovelace.toString(), + }); + } catch { + /* datum distinto o hash-only */ + } + } + return rows; +} + +/** + * Gasta todos los UTxOs del pool con tu `owner` en el datum y devuelve los activos a la wallet. + */ +export async function withdrawAllLiquidityPoolOnChain(): Promise<{ + txHash: string; + withdrawnLovelace: string; + inputCount: number; +}> { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const recv = await client.address(); + const ownerHex = Buffer.from(paymentKeyHashBytes(recv)).toString("hex"); + + const bp = loadBlueprint(); + const val = liquidityPoolSpendValidator(bp); + const poolAddr = enterpriseVaultAddress(val.hash); + const script = poolPlutusV3(); + + const utxos = await client.getUtxos(poolAddr); + const mine: UTxO[] = []; + for (const u of utxos) { + try { + const d = readInlineDatum(u); + if (decodePoolDatumOwnerHex(d).toLowerCase() !== ownerHex.toLowerCase()) { + continue; + } + mine.push(u); + } catch { + /* skip */ + } + } + if (mine.length === 0) { + throw new Error( + "No hay UTxOs en el pool con datum.owner = tu payment key (depositá on-chain primero).", + ); + } + + let merged = Assets.zero; + for (const u of mine) { + merged = Assets.merge(merged, u.assets); + } + + const now = BigInt(Date.now()); + const built = await client + .newTx() + .setValidity({ from: now - 60_000n, to: now + 120_000n }) + .collectFrom({ inputs: mine, redeemer: redeemerPoolSpend() }) + .payToAddress({ address: recv, assets: merged }) + .attachScript({ script }) + .build(); + + const digest = await built.signAndSubmit(); + return { + txHash: TransactionHash.toHex(digest), + withdrawnLovelace: merged.lovelace.toString(), + inputCount: mine.length, + }; +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/pyth.ts b/lazer/cardano/inventory-edge-protocol/lib/pyth.ts new file mode 100644 index 00000000..c033c7f7 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/pyth.ts @@ -0,0 +1,32 @@ +import { PythLazerClient } from "@pythnetwork/pyth-lazer-sdk"; + +import { lazerChannelForFeedId } from "./feeds.js"; + +/** Pyth Lazer governance policy on Cardano PreProd (same as lazer-rwa). */ +export const PYTH_POLICY_ID_HEX = + "d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6"; + +export async function fetchSolanaFormatUpdate( + accessToken: string, + priceFeedIds: number[], +): Promise { + const channels = [...new Set(priceFeedIds.map(lazerChannelForFeedId))]; + if (channels.length !== 1) { + throw new Error( + "Pyth Lazer: varios canales en un solo update — pasá un solo feedId por tx o ampliá el builder", + ); + } + const channel = channels[0]!; + const lazer = await PythLazerClient.create({ token: accessToken }); + const latest = await lazer.getLatestPrice({ + channel, + formats: ["solana"], + jsonBinaryEncoding: "hex", + priceFeedIds, + properties: ["price", "bestBidPrice", "bestAskPrice", "exponent"], + }); + if (!latest.solana?.data) { + throw new Error("Pyth Lazer: missing solana-format payload"); + } + return Buffer.from(latest.solana.data, "hex"); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/pyth_quotes.ts b/lazer/cardano/inventory-edge-protocol/lib/pyth_quotes.ts new file mode 100644 index 00000000..e58ca817 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/pyth_quotes.ts @@ -0,0 +1,136 @@ +import { PythLazerClient } from "@pythnetwork/pyth-lazer-sdk"; + +import { quoteChainForVaultFeedId, type LazerQuoteStep } from "./feeds.js"; + +export type FeedQuote = { + priceFeedId: number; + priceRaw?: string; + exponent?: number; + humanApprox?: string; +}; + +export type ResolvedFeedQuote = FeedQuote & { + /** Símbolo Pro del feed que aportó `price`. */ + resolvedProSymbol?: string; + quoteNote?: string; +}; + +function requireToken(): string { + const t = process.env.ACCESS_TOKEN; + if (!t) throw new Error("Missing ACCESS_TOKEN (Pyth Lazer)"); + return t; +} + +function humanFromRaw(priceRaw: string, exp: number | undefined): string | undefined { + try { + const n = BigInt(priceRaw); + if (exp === undefined) return undefined; + if (exp >= 0) return (n * 10n ** BigInt(exp)).toString(); + const den = 10n ** BigInt(-exp); + const whole = n / den; + const frac = n % den; + if (frac === 0n) return whole.toString(); + let fs = frac.toString().padStart(-exp, "0").replace(/0+$/, ""); + if (fs === "") return whole.toString(); + return `${whole}.${fs}`; + } catch { + return undefined; + } +} + +/** + * Recorre la cadena hasta el primer feed con `price` en JSON parseado. + * (Lazer omite `price` cuando `publisherCount` es 0 aunque exista binario Solana.) + */ +export async function fetchFeedQuoteResolved( + chain: readonly LazerQuoteStep[], + options?: { emptyNote?: string }, +): Promise { + const lazer = await PythLazerClient.create({ token: requireToken() }); + for (let i = 0; i < chain.length; i++) { + const step = chain[i]!; + const u = await lazer.getLatestPrice({ + channel: step.channel, + formats: ["solana"], + jsonBinaryEncoding: "hex", + priceFeedIds: [step.id], + properties: ["price", "exponent"], + parsed: true, + }); + const f = u.parsed?.priceFeeds?.[0]; + if (f?.price) { + return { + priceFeedId: step.id, + priceRaw: f.price, + exponent: f.exponent, + humanApprox: humanFromRaw(f.price, f.exponent), + resolvedProSymbol: step.proSymbol, + quoteNote: + i === 0 + ? undefined + : `Sin publicadores en feeds anteriores; precio vía ${step.proSymbol} (id ${step.id}).`, + }; + } + } + const head = chain[0]!; + return { + priceFeedId: head.id, + quoteNote: + options?.emptyNote ?? + "Ningún feed de la cadena tiene `price` en JSON (0 publicadores / mercado cerrado).", + }; +} + +/** Una cotización por id pedido, con cadena de resolución estándar (vault / genérico). */ +export async function fetchFeedQuotes( + priceFeedIds: number[], +): Promise { + const out: ResolvedFeedQuote[] = []; + for (const id of priceFeedIds) { + out.push(await fetchFeedQuoteResolved(quoteChainForVaultFeedId(id))); + } + return out; +} + +/** + * Underwater iff price * collateralQty * 100 < debtLovelace * 110 (matches `vault.ak` Liquidate). + */ +export function isUnderwater(params: { + priceRaw: bigint; + collateralQty: bigint; + debtLovelace: bigint; +}): boolean { + if (params.debtLovelace <= 0n) return false; + const lhs = params.priceRaw * params.collateralQty * 100n; + const rhs = params.debtLovelace * 110n; + return lhs < rhs; +} + +/** + * Mínimo entero `collateral_qty` para que NO esté underwater con ese principal: + * `priceRaw * qty * 100 >= debtLovelace * 110` (misma escala que `vault.ak` Liquidate). + */ +export function minCollateralQtyForDebt( + debtLovelace: bigint, + priceRaw: bigint, +): bigint { + if (priceRaw <= 0n) { + throw new Error("priceRaw must be positive"); + } + if (debtLovelace <= 0n) return 1n; + const num = debtLovelace * 110n; + const den = priceRaw * 100n; + if (den === 0n) throw new Error("invalid price scale"); + return (num + den - 1n) / den; +} + +/** Mínimo + colchón (bps sobre el mínimo; default 25%). */ +export function suggestedCollateralQtyForDebt( + debtLovelace: bigint, + priceRaw: bigint, + bufferBps: bigint = 2500n, +): bigint { + const min = minCollateralQtyForDebt(debtLovelace, priceRaw); + if (debtLovelace <= 0n) return min; + return (min * (10000n + bufferBps) + 9999n) / 10000n; +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/transactions.ts b/lazer/cardano/inventory-edge-protocol/lib/transactions.ts new file mode 100644 index 00000000..449af77c --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/transactions.ts @@ -0,0 +1,387 @@ +import "dotenv/config"; + +import * as Address from "@evolution-sdk/evolution/Address"; +import { ScriptHash, TransactionHash } from "@evolution-sdk/evolution"; +import { toCBORHex } from "@evolution-sdk/evolution/Data"; +import { InlineDatum } from "@evolution-sdk/evolution/InlineDatum"; +import { PlutusV3 } from "@evolution-sdk/evolution/PlutusV3"; +import type { Data } from "@evolution-sdk/evolution/Data"; +import { getPythScriptHash, getPythState } from "@pythnetwork/pyth-lazer-cardano-js"; + +import { loadBlueprint, vaultSpendValidator } from "./blueprint.js"; +import { + encodeVaultDatum, + optionNone, + redeemerAdjust, + redeemerApplyHedge, + redeemerClaimInsurance, + redeemerClose, + redeemerLiquidate, + vaultDatumWithDebt, + vaultDatumWithHedge, +} from "./datum_codec.js"; +import { decodeVaultDatum } from "./vault_datum_decode.js"; +import { + createPreprodReadClient, + createPreprodSigningClient, +} from "./evolution_client.js"; +import { newLucidPreprod } from "./mint_shadow.js"; +import { fetchSolanaFormatUpdate, PYTH_POLICY_ID_HEX } from "./pyth.js"; +import { enterpriseVaultAddress, paymentKeyHashBytes } from "./vault_address.js"; +import type { UTxO } from "@evolution-sdk/evolution/UTxO"; + +function requireEnv(name: string): string { + const v = process.env[name]; + if (!v) throw new Error(`Missing env ${name}`); + return v; +} + +function plutusV3FromBlueprint(compiledHex: string): PlutusV3 { + return new PlutusV3({ + bytes: Uint8Array.from(Buffer.from(compiledHex, "hex")), + }); +} + +function normalizeAssetHex(h: string): string { + return h.replace(/\s/g, "").toLowerCase(); +} + +export function readInlineDatum(utxo: UTxO): Data { + const d = utxo.datumOption; + if (!d || d._tag !== "InlineDatum") { + throw new Error("Vault UTxO must use inline datum"); + } + return d.data; +} + +/** + * Lock shadow NFT + vault datum at the script (no Pyth in this tx). + * Uses Lucid + Blockfrost/Maestro so UTxOs match `mint_shadow` (Evolution/Koios can miss them). + */ +export async function openVault(params: { + nftPolicyHex: string; + nftNameHex: string; + feedId: number; + debtLovelace: bigint; + collateralQty: bigint; +}): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const vaultBech32 = Address.toBech32(enterpriseVaultAddress(val.hash)); + + const lucid = await newLucidPreprod(); + lucid.selectWalletFromSeed(mnemonic); + const userBech32 = await lucid.wallet.address(); + const ownerKh = paymentKeyHashBytes(Address.fromBech32(userBech32)); + + const policyHex = normalizeAssetHex(params.nftPolicyHex); + const nameHex = normalizeAssetHex(params.nftNameHex); + const unit = policyHex + nameHex; + + const utxos = await lucid.wallet.getUtxos(); + const nftIn = utxos.find((u) => u.assets[unit] === 1n); + if (!nftIn) { + const sample = utxos.flatMap((u) => + Object.entries(u.assets) + .filter(([k, q]) => k !== "lovelace" && q === 1n) + .map(([k]) => k), + ); + throw new Error( + `No UTxO with NFT (vista Lucid/Blockfrost). Unit esperado: ${unit}. ` + + `NFTs (qty=1) en wallet: ${ + sample.length ? sample.slice(0, 12).join(" | ") : "(ninguno)" + }. ¿El NFT ya está en la vault o usás otra seed?`, + ); + } + + const datum = encodeVaultDatum({ + ownerKeyHash: ownerKh, + pythPolicyHex: PYTH_POLICY_ID_HEX, + nftPolicyHex: policyHex, + nftNameHex: nameHex, + debtLovelace: params.debtLovelace, + collateralQty: params.collateralQty, + feedId: BigInt(params.feedId), + hedge: optionNone(), + }); + + const inlineDatum = toCBORHex(datum); + + const tx = await lucid + .newTx() + .collectFrom([nftIn]) + .payToContract(vaultBech32, { inline: inlineDatum }, nftIn.assets) + .complete(); + + const signed = await tx.sign().complete(); + return await signed.submit(); +} + +/** Owner adds parametric insurance fields (strike + payout) — no Pyth. */ +export async function applyHedge(params: { + vaultUtxo: UTxO; + strikeRaw: bigint; + payoutLovelace: bigint; +}): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const vaultAddr = enterpriseVaultAddress(val.hash); + const script = plutusV3FromBlueprint(val.compiledCode); + + const prev = readInlineDatum(params.vaultUtxo); + const nextDatum = vaultDatumWithHedge( + prev, + params.strikeRaw, + params.payoutLovelace, + ); + const r = redeemerApplyHedge(params.strikeRaw, params.payoutLovelace); + + const now = BigInt(Date.now()); + const built = await client + .newTx() + .setValidity({ from: now - 60_000n, to: now + 120_000n }) + .collectFrom({ inputs: [params.vaultUtxo], redeemer: r }) + .payToAddress({ + address: vaultAddr, + assets: params.vaultUtxo.assets, + datum: new InlineDatum({ data: nextDatum }), + }) + .attachScript({ script }) + .build(); + + const digest = await built.signAndSubmit(); + return TransactionHash.toHex(digest); +} + +/** + * Single tx: Pyth Lazer zero-withdrawal (verified payload) + vault `Liquidate` when underwater. + */ +export async function liquidate(params: { + vaultUtxo: UTxO; + /** Which feed id to pull from Lazer (must match vault datum feed_id on-chain). */ + priceFeedId: number; +}): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const token = requireEnv("ACCESS_TOKEN"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const script = plutusV3FromBlueprint(val.compiledCode); + + const update = await fetchSolanaFormatUpdate(token, [params.priceFeedId]); + const readClient = createPreprodReadClient(); + const pythState = await getPythState(PYTH_POLICY_ID_HEX, readClient); + const pythScriptHex = getPythScriptHash(pythState); + const recv = await client.address(); + + const now = BigInt(Date.now()); + const built = await client + .newTx() + .setValidity({ from: now - 60_000n, to: now + 120_000n }) + .readFrom({ referenceInputs: [pythState] }) + .withdraw({ + amount: 0n, + redeemer: [update], + stakeCredential: ScriptHash.fromHex(pythScriptHex), + }) + .collectFrom({ + inputs: [params.vaultUtxo], + redeemer: redeemerLiquidate(), + }) + .payToAddress({ + address: recv, + assets: params.vaultUtxo.assets, + }) + .attachScript({ script }) + .build(); + + const digest = await built.signAndSubmit(); + return TransactionHash.toHex(digest); +} + +export async function fetchFirstVaultUtxo(): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const vaultAddr = enterpriseVaultAddress(val.hash); + const utxos = await client.getUtxos(vaultAddr); + const u = utxos[0]; + if (!u) throw new Error("No UTxO at vault script — run openVault first"); + return u; +} + +export async function getVaultUtxoByRef( + txHashHex: string, + outputIndex: number, +): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const vaultAddr = enterpriseVaultAddress(val.hash); + const utxos = await client.getUtxos(vaultAddr); + const idx = BigInt(outputIndex); + const u = utxos.find( + (x) => + TransactionHash.toHex(x.transactionId) === txHashHex && x.index === idx, + ); + if (!u) { + throw new Error( + `No vault UTxO ${txHashHex}#${outputIndex} (refresh list; index may have changed)`, + ); + } + return u; +} + +export type VaultPositionRow = { + txHash: string; + outputIndex: string; + ref: string; + datum: ReturnType; + lovelace: string; +}; + +export async function listVaultPositions(): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const vaultAddr = enterpriseVaultAddress(val.hash); + const utxos = await client.getUtxos(vaultAddr); + const rows: VaultPositionRow[] = []; + for (const u of utxos) { + try { + const d = readInlineDatum(u); + const datum = decodeVaultDatum(d); + rows.push({ + txHash: TransactionHash.toHex(u.transactionId), + outputIndex: u.index.toString(), + ref: `${TransactionHash.toHex(u.transactionId)}#${u.index}`, + datum, + lovelace: u.assets.lovelace.toString(), + }); + } catch { + /* skip non-vault outputs */ + } + } + return rows; +} + +/** Owner updates synthetic debt (e.g. repay to 0 before Close). */ +export async function adjustDebt(params: { + vaultUtxo: UTxO; + newDebtLovelace: bigint; +}): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const vaultAddr = enterpriseVaultAddress(val.hash); + const script = plutusV3FromBlueprint(val.compiledCode); + + const prev = readInlineDatum(params.vaultUtxo); + const nextDatum = vaultDatumWithDebt(prev, params.newDebtLovelace); + const r = redeemerAdjust(params.newDebtLovelace); + + const now = BigInt(Date.now()); + const built = await client + .newTx() + .setValidity({ from: now - 60_000n, to: now + 120_000n }) + .collectFrom({ inputs: [params.vaultUtxo], redeemer: r }) + .payToAddress({ + address: vaultAddr, + assets: params.vaultUtxo.assets, + datum: new InlineDatum({ data: nextDatum }), + }) + .attachScript({ script }) + .build(); + + const digest = await built.signAndSubmit(); + return TransactionHash.toHex(digest); +} + +/** Owner closes vault when debt is 0; NFT returns to wallet. */ +export async function closeVault(params: { + vaultUtxo: UTxO; +}): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const script = plutusV3FromBlueprint(val.compiledCode); + const recv = await client.address(); + + const prev = readInlineDatum(params.vaultUtxo); + const decoded = decodeVaultDatum(prev); + if (decoded.debtLovelace !== 0n) { + throw new Error("debt must be 0 to Close (use Adjust first)"); + } + + const now = BigInt(Date.now()); + const built = await client + .newTx() + .setValidity({ from: now - 60_000n, to: now + 120_000n }) + .collectFrom({ + inputs: [params.vaultUtxo], + redeemer: redeemerClose(), + }) + .payToAddress({ + address: recv, + assets: params.vaultUtxo.assets, + }) + .attachScript({ script }) + .build(); + + const digest = await built.signAndSubmit(); + return TransactionHash.toHex(digest); +} + +/** + * Parametric claim: Pyth price must be < strike (on-chain). Assets return to owner wallet. + * Note: payout_lovelace in datum is informational in this MVP; settlement is the unlocked UTxO. + */ +export async function claimInsurance(params: { + vaultUtxo: UTxO; + priceFeedId: number; +}): Promise { + const mnemonic = requireEnv("CARDANO_MNEMONIC"); + const token = requireEnv("ACCESS_TOKEN"); + const client = createPreprodSigningClient(mnemonic); + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const script = plutusV3FromBlueprint(val.compiledCode); + + const update = await fetchSolanaFormatUpdate(token, [params.priceFeedId]); + const readClient = createPreprodReadClient(); + const pythState = await getPythState(PYTH_POLICY_ID_HEX, readClient); + const pythScriptHex = getPythScriptHash(pythState); + const recv = await client.address(); + + const now = BigInt(Date.now()); + const built = await client + .newTx() + .setValidity({ from: now - 60_000n, to: now + 120_000n }) + .readFrom({ referenceInputs: [pythState] }) + .withdraw({ + amount: 0n, + redeemer: [update], + stakeCredential: ScriptHash.fromHex(pythScriptHex), + }) + .collectFrom({ + inputs: [params.vaultUtxo], + redeemer: redeemerClaimInsurance(), + }) + .payToAddress({ + address: recv, + assets: params.vaultUtxo.assets, + }) + .attachScript({ script }) + .build(); + + const digest = await built.signAndSubmit(); + return TransactionHash.toHex(digest); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/vault_address.ts b/lazer/cardano/inventory-edge-protocol/lib/vault_address.ts new file mode 100644 index 00000000..8add1cde --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/vault_address.ts @@ -0,0 +1,21 @@ +import * as Address from "@evolution-sdk/evolution/Address"; +import * as Credential from "@evolution-sdk/evolution/Credential"; +import * as KeyHash from "@evolution-sdk/evolution/KeyHash"; +import * as ScriptHash from "@evolution-sdk/evolution/ScriptHash"; + +/** PreProd / testnet enterprise script address from validator blake2b-224 script hash (hex). */ +export function enterpriseVaultAddress(scriptHashHex: string): Address.Address { + const sh = ScriptHash.fromHex(scriptHashHex); + return new Address.Address({ + networkId: 0, + paymentCredential: Credential.makeScriptHash(ScriptHash.toBytes(sh)), + }); +} + +export function paymentKeyHashBytes(addr: Address.Address): Uint8Array { + const p = addr.paymentCredential; + if (p._tag !== "KeyHash") { + throw new Error("Expected a payment key address (enterprise or base with vkey payment)"); + } + return KeyHash.toBytes(p); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/vault_datum_decode.ts b/lazer/cardano/inventory-edge-protocol/lib/vault_datum_decode.ts new file mode 100644 index 00000000..85f8d36d --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/vault_datum_decode.ts @@ -0,0 +1,77 @@ +import { Constr, type Data } from "@evolution-sdk/evolution/Data"; +import * as PolicyId from "@evolution-sdk/evolution/PolicyId"; + +export type DecodedHedge = + | { tag: "none" } + | { tag: "some"; strikeRaw: bigint; payoutLovelace: bigint }; + +export type DecodedVaultDatum = { + ownerKeyHashHex: string; + pythPolicyHex: string; + nftPolicyHex: string; + nftNameHex: string; + debtLovelace: bigint; + collateralQty: bigint; + feedId: bigint; + hedge: DecodedHedge; +}; + +function bytesToHex(u: Uint8Array): string { + return Buffer.from(u).toString("hex"); +} + +function asBytes(d: Data, label: string): Uint8Array { + if (!(d instanceof Uint8Array)) { + throw new Error(`${label}: expected bytes`); + } + return d; +} + +function asBigInt(d: Data, label: string): bigint { + if (typeof d !== "bigint") { + throw new Error(`${label}: expected int`); + } + return d; +} + +function decodeOptionHedge(d: Data): DecodedHedge { + if (!(d instanceof Constr)) { + throw new Error("hedge: expected Constr"); + } + if (d.index === 1n && d.fields.length === 0) { + return { tag: "none" }; + } + if (d.index === 0n && d.fields.length === 1) { + const inner = d.fields[0]; + if (!(inner instanceof Constr) || inner.index !== 0n || inner.fields.length !== 2) { + throw new Error("hedge Some: bad HedgeParams"); + } + return { + tag: "some", + strikeRaw: asBigInt(inner.fields[0], "strike"), + payoutLovelace: asBigInt(inner.fields[1], "payout"), + }; + } + throw new Error("hedge: unknown option shape"); +} + +export function decodeVaultDatum(data: Data): DecodedVaultDatum { + if (!(data instanceof Constr) || data.index !== 0n || data.fields.length !== 8) { + throw new Error("Not a VaultDatum (Constr 0, 8 fields)"); + } + const f = data.fields; + const owner = asBytes(f[0], "owner"); + const pythB = asBytes(f[1], "pyth_policy"); + const nftB = asBytes(f[2], "nft_policy"); + const nameB = asBytes(f[3], "nft_name"); + return { + ownerKeyHashHex: bytesToHex(owner), + pythPolicyHex: PolicyId.toHex(PolicyId.fromBytes(pythB)), + nftPolicyHex: PolicyId.toHex(PolicyId.fromBytes(nftB)), + nftNameHex: bytesToHex(nameB), + debtLovelace: asBigInt(f[4], "debt"), + collateralQty: asBigInt(f[5], "collateral_qty"), + feedId: asBigInt(f[6], "feed_id"), + hedge: decodeOptionHedge(f[7]), + }; +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts b/lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts new file mode 100644 index 00000000..9a9c2b1b --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts @@ -0,0 +1,142 @@ +/** + * List shadow demo NFTs from the Lucid wallet (Blockfrost/Maestro UTxO set). + */ +import type { UTxO } from "lucid-cardano"; + +import { inferFeedFromShadowName } from "./feeds.js"; +import { newLucidPreprod } from "./mint_shadow.js"; + +function normalizeHex(h: string): string { + return h.replace(/\s/g, "").toLowerCase(); +} + +function nativeUnitsFromUtxo(u: UTxO): { unit: string; qty: bigint }[] { + const out: { unit: string; qty: bigint }[] = []; + for (const [k, v] of Object.entries(u.assets)) { + if (k === "lovelace") continue; + out.push({ unit: k, qty: v as bigint }); + } + return out; +} + +export type WalletShadowNft = { + policyId: string; + nameHex: string; + nameUtf8: string; + unit: string; + feedId: number; + utxoLovelace: string; +}; + +/** Cualquier activo nativo en la wallet Lucid (misma vista que `openVault`). */ +export type WalletNativeNft = { + policyId: string; + nameHex: string; + nameUtf8?: string; + quantity: string; + unit: string; + /** Si el nombre es shadow demo, sugerimos el feed Pyth del mint. */ + suggestedFeedId?: number; +}; + +export async function listWalletShadowNfts(mnemonic: string): Promise<{ + address: string; + nfts: WalletShadowNft[]; +}> { + const lucid = await newLucidPreprod(); + lucid.selectWalletFromSeed(mnemonic); + const address = await lucid.wallet.address(); + const utxos = await lucid.wallet.getUtxos(); + const byKey = new Map(); + + for (const u of utxos) { + for (const { unit, qty } of nativeUnitsFromUtxo(u)) { + if (qty !== 1n) continue; + const policyId = unit.slice(0, 56); + const nameHex = unit.slice(56); + let nameUtf8: string; + try { + nameUtf8 = Buffer.from(nameHex, "hex").toString("utf8"); + } catch { + continue; + } + if (!nameUtf8.startsWith("Shadow")) continue; + const feedId = inferFeedFromShadowName(nameUtf8); + if (feedId == null) continue; + const key = normalizeHex(policyId) + normalizeHex(nameHex); + if (byKey.has(key)) continue; + byKey.set(key, { + policyId: normalizeHex(policyId), + nameHex: normalizeHex(nameHex), + nameUtf8, + unit: normalizeHex(policyId) + normalizeHex(nameHex), + feedId, + utxoLovelace: (u.assets.lovelace ?? 0n).toString(), + }); + } + } + + return { + address, + nfts: [...byKey.values()].sort((a, b) => + a.nameUtf8.localeCompare(b.nameUtf8), + ), + }; +} + +/** + * Todos los activos nativos (por unit agregada), vía Lucid — coherente con `openVault`. + */ +export async function listWalletNativeNfts(mnemonic: string): Promise<{ + address: string; + nfts: WalletNativeNft[]; +}> { + const lucid = await newLucidPreprod(); + lucid.selectWalletFromSeed(mnemonic); + const address = await lucid.wallet.address(); + const utxos = await lucid.wallet.getUtxos(); + const agg = new Map(); + + for (const u of utxos) { + for (const { unit, qty } of nativeUnitsFromUtxo(u)) { + if (qty <= 0n) continue; + const key = normalizeHex(unit); + agg.set(key, (agg.get(key) ?? 0n) + qty); + } + } + + const nfts: WalletNativeNft[] = []; + for (const [unit, quantity] of agg) { + const policyId = unit.slice(0, 56); + const nameHex = unit.slice(56); + let nameUtf8: string | undefined; + try { + const s = Buffer.from(nameHex, "hex").toString("utf8"); + if (!s.includes("\uFFFD")) nameUtf8 = s; + } catch { + /* nombre binario */ + } + const shadowFeed = + nameUtf8?.startsWith("Shadow") === true + ? inferFeedFromShadowName(nameUtf8) + : null; + nfts.push({ + policyId, + nameHex, + nameUtf8, + quantity: quantity.toString(), + unit, + suggestedFeedId: shadowFeed ?? undefined, + }); + } + + nfts.sort((a, b) => { + const qa = BigInt(a.quantity); + const qb = BigInt(b.quantity); + if (qa === 1n && qb !== 1n) return -1; + if (qb === 1n && qa !== 1n) return 1; + return (a.nameUtf8 ?? a.unit).localeCompare(b.nameUtf8 ?? b.unit); + }); + + return { address, nfts }; +} diff --git a/lazer/cardano/inventory-edge-protocol/onchain/.gitignore b/lazer/cardano/inventory-edge-protocol/onchain/.gitignore new file mode 100644 index 00000000..ff7811b1 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/onchain/.gitignore @@ -0,0 +1,6 @@ +# Aiken compilation artifacts +artifacts/ +# Aiken's project working directory +build/ +# Aiken's default documentation export +docs/ diff --git a/lazer/cardano/inventory-edge-protocol/onchain/aiken.lock b/lazer/cardano/inventory-edge-protocol/onchain/aiken.lock new file mode 100644 index 00000000..8a5595d9 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/onchain/aiken.lock @@ -0,0 +1,27 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +[[requirements]] +name = "aiken-lang/stdlib" +version = "v3.0.0" +source = "github" + +[[requirements]] +name = "pyth-network/pyth-lazer-cardano" +version = "main" +source = "github" + +[[packages]] +name = "aiken-lang/stdlib" +version = "v3.0.0" +requirements = [] +source = "github" + +[[packages]] +name = "pyth-network/pyth-lazer-cardano" +version = "main" +requirements = [] +source = "github" + +[etags] +"pyth-network/pyth-lazer-cardano@main" = [{ secs_since_epoch = 1774200850, nanos_since_epoch = 629668000 }, "a46dacd97a22eb07feeaf966d48c3116c8249ddc836705656e3135cea285bcfc"] diff --git a/lazer/cardano/inventory-edge-protocol/onchain/aiken.toml b/lazer/cardano/inventory-edge-protocol/onchain/aiken.toml new file mode 100644 index 00000000..2f859b0b --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/onchain/aiken.toml @@ -0,0 +1,23 @@ +name = "team/inventory-edge-protocol" +version = "0.0.0" +compiler = "v1.1.21" +plutus = "v3" +license = "Apache-2.0" +description = "Aiken contracts for project 'team/inventory-edge-protocol'" + +[repository] +user = "team" +project = "inventory-edge-protocol" +platform = "github" + +[[dependencies]] +name = "aiken-lang/stdlib" +version = "v3.0.0" +source = "github" + +[[dependencies]] +name = "pyth-network/pyth-lazer-cardano" +version = "main" +source = "github" + +[config] diff --git a/lazer/cardano/inventory-edge-protocol/onchain/plutus.json b/lazer/cardano/inventory-edge-protocol/onchain/plutus.json new file mode 100644 index 00000000..3a8217c8 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/onchain/plutus.json @@ -0,0 +1,246 @@ +{ + "preamble": { + "title": "team/inventory-edge-protocol", + "description": "Aiken contracts for project 'team/inventory-edge-protocol'", + "version": "0.0.0", + "plutusVersion": "v3", + "compiler": { + "name": "Aiken", + "version": "v1.1.21+unknown" + }, + "license": "Apache-2.0" + }, + "validators": [ + { + "title": "liquidity_pool.liquidity_pool.spend", + "datum": { + "title": "datum", + "schema": { + "$ref": "#/definitions/liquidity_pool~1PoolDatum" + } + }, + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/Data" + } + }, + "compiledCode": "58ba01010029800aba2aba1aab9faab9eaab9dab9a48888896600264646644b30013370e900118031baa00189919912cc004cdc3a400060126ea800626464660020026eb0c038c03cc03cc03cc03cc03cc03cc03cc03cc030dd51807004912cc00400629422b30013371e6eb8c03c00400e29462660040046020002805900e1bae300c300a3754601860146ea80062c8040c028004c028c02c004c01cdd5000c59005180380098039804000980380098019baa0078a4d13656400401", + "hash": "9a265728f95db574df40dd3e5a425a2127ac53bd6968e20659bd2ce7" + }, + { + "title": "liquidity_pool.liquidity_pool.else", + "redeemer": { + "schema": {} + }, + "compiledCode": "58ba01010029800aba2aba1aab9faab9eaab9dab9a48888896600264646644b30013370e900118031baa00189919912cc004cdc3a400060126ea800626464660020026eb0c038c03cc03cc03cc03cc03cc03cc03cc03cc030dd51807004912cc00400629422b30013371e6eb8c03c00400e29462660040046020002805900e1bae300c300a3754601860146ea80062c8040c028004c028c02c004c01cdd5000c59005180380098039804000980380098019baa0078a4d13656400401", + "hash": "9a265728f95db574df40dd3e5a425a2127ac53bd6968e20659bd2ce7" + }, + { + "title": "vault.vault.spend", + "datum": { + "title": "datum", + "schema": { + "$ref": "#/definitions/vault~1VaultDatum" + } + }, + "redeemer": { + "title": "redeemer", + "schema": { + "$ref": "#/definitions/vault~1VaultRedeemer" + } + }, + "compiledCode": "591ccd01010029800aba4aba2aba1aba0aab9faab9eaab9dab9a4888888896600264653001300800198041804800cdc3a400530080024888966002600460106ea800e3300130093754007370e90034dc3a4011370e90004dc3a4009300837540089111111991192cc004c01801226464b30013018002801c590151bad30160013012375401b159800980580244c8c8cc896600260340070058b202e375a602e0026eb4c05c008c05c004c048dd5006c566002600a00915980098091baa00d800c590134566002601000915980098091baa00d800c590134566002600e00915980098091baa00d800c590134590102020404080810100acc004c014c040dd5000c66002602860226ea80064602a602c003222323322330020020012259800800c00e2646644b30013372200e00515980099b8f0070028800c01901844cc014014c0780110181bae3017001375a6030002603400280c0c8c8cc004004018896600200300389919912cc004cdc8804801456600266e3c02400a20030064065133005005301f00440646eb8c060004dd5980c800980d800a03214bd6f7b6300a400123015301630160019180a980b180b180b000c8c054c058c058c058c058c058c0580066e25200491192cc004cdc7a4500375c602e6030003130170018b20243002001911919800800801911980180098010014dd2a40013712900048c054c058c058c058c058c058c058c0580064464660020026eb0c05cc060c060c060c060c060c060c060c060c050dd5001912cc00400629422b30013371e6eb8c06000400e2946266004004603200280990164c040dd500648c054c058c058c058c0580064602a602c602c602c602c602c00323259800980398091baa0028991919191919194c004dd6980e800cc0780066eb8c07401e6eb8c07401a6eb8c0740166eb8c0740126eb4c07400e6eb4c07400922222222598009813003c4c8c9660026030003132598009814800c4c9660026034604a6ea8006264646644b3001302e00380c45902b1bad302b001375a60560046056002604c6ea80062c8120c0a00062c8130c090dd50014566002603a00315980098121baa002809459025459022204430223754002604a00f16408c301d001301c001301b001301a00130190013018001301337540051640442003371090002444444444444444445300132330010013758604e60486ea80708966002003168992cc004cdd7981498131baa0010178980a18131baa00189980180198150012048302800140992259800980c98121baa00289919192cc004c0b000a26464b3001301e0018acc004c0a8dd5001401a2c815a2b30013023001899192cc004c0c000a0111640b46eb4c0b8004c0a8dd50014566002603a00315980098151baa00280345902b459028205040a060506ea8004c0ac00e2c8148c966002605000315980098089813800c5a26042604e00281322c8148dd51815000981500098129baa0028b2046911919800800801912cc004006298103d87a80008992cc004c0100062601c6605400297ae08998018019816001204c302a00140a09114c004c0a4c098dd5001e60026eacc050c098dd5001cdd7180918131baa0159bae30113026375402a809a44b3001301c30273754005132323259800981780144cc01cc0b800c4cc01c0040122c8160c0b4004c0b4004c0a0dd5001459026488cc00c0088c96600266ebcc0b4c0a8dd5000981698151baa003899baf3018302a3754002603060546ea800e2941028181618149baa00148888c966002603e01b13259800998078121bae302f302c37540371598009808800c566002601400b13259800981098161baa001899192cc004c088c0b8dd5000c4c96600266e3cdd7181998181baa001375c606660606ea807e2b30013371e6eb8c078c0c0dd50009bae301e3030375403f15980099b8f375c603860606ea8004dd7180e18181baa01f8acc004cdc79bae301b303037540026eb8c06cc0c0dd500fc56600266e1cdd6980818181baa001375a602060606ea807e2b30013370e6eb4c068c0c0dd50009bad301a3030375403f15980099baf301430303754002602860606ea807e2b30013370e6eb4c044c0c0dd5000802c4cdc3cc004dd5980f18181baa0039bae301c3030375403f375c603660606ea807d01d004c528205c8a5040b914a08172294102e4528205c8a5040b914a08172294102e1807181918179baa0018b205a301a302e37540026060605a6ea80062c8158cc00cdd6180c18161baa0240068b20548b20548b2054375a605c60566ea809a2b3001302400d89919912cc004cc044098dd7181898171baa01d8acc004c03001e2b3001300c0028acc004c04c006264b30013023302e37540031323259800981218181baa0018992cc004cdc79bae3035303237540026eb8c0d4c0c8dd5010c56600266e3cdd7181018191baa001375c604060646ea80862b30013371e6eb8c078c0c8dd50009bae301e3032375404315980099b8f375c603a60646ea8004dd7180e98191baa0218acc004cdc39bad3013303237540026eb4c04cc0c8dd5010c56600266e1cdd6980918191baa001375a602460646ea80862b30013370e6eb4c070c0c8dd50009bad301c3032375404315980099baf3016303237540026030660686030660686ea0018cc0d0dd4002a5eb812f5c113370f30013756604060646ea800e6eb8c078c0c8dd5010cdd7180e98191baa021407c01714a081822941030452820608a5040c114a081822941030452820608a5040c06020606860626ea80062c8178c070c0c0dd5000981918179baa0018b205a3300537586034605c6ea80980222c81622c81622c81622c8160dd698178009bad302f3030001302b375404d159800980f006c5660026601c0466eb8c0b8c0acdd500d4566002603e6eb4c030c0acdd500d45660026012009159800981218151baa330023758602e60566ea808c01629462c814a2c814a2c814a2c814a2b3001302100d8acc004cdc419b82337053001375c603260566ea806a6eb4c054c0acdd500d408d0011bad300b302b375403490640099b82375a601860566ea806920dc01898049bad300c302b375403514a0814a2b30013300e023375c605c60566ea806a264b30013020302b37540031337113001375c603460586ea806e6eb4c058c0b0dd500dc0910021bad302f302c3754605e60586ea80062c8150c03cc0acdd500d459029205240a48149029111192cc004c0c8006264b30013023302e375400313259800981218179baa0018992cc004c094c0c0dd5000c4dd6981a18189baa0018b205e3033303037540031640b8603a605e6ea8c0c8c0bcdd5000c5902d198049bac301a302e37546062002466e1cdd6981918179baa0010048b205e3259800981118169baa0018992cc004c08cc0b8dd5000c4c8cc05c0048966002005132330010010042259800800c52f5c113303698009b8c480126e0120039b89480226e3120009b8c480226e0120074888888cc88cc0908c8d6600266e3cdd71820800a4410475d3c793008cc004888c8cc00cdd698228009bae3045304600130030019b89480426e3120109b804803e600e90082444446600a4646608e6ea0cde5250375c60900026608e6090609200297ae05980098029b8d0018998231ba930020013304637526600860066e340040052f5c116410846644660104660926ea0cdc7000a4000660926ea4cc00cc008dc6800800a5eb808cc0248cc128dd419b8e00148000cc128dd49980218019b8d0010014bd70111919198269818998269ba80083304d375000a6609a609c00297ae03304d304e304f0014bd7018008012cc004c0b800a2653001001a5eb820068008889660026080003123233050374e002660a06ea40092f5c065300100180252f5c080088896600200510018cc00400e60a8005330523053002001400c828a246530010059982818288008024c02400e6eb8c144c148005005191919911919801194c0040066eb0c15800a6072660aa60ac00c660aa980103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a80004bd702002222598008014400633001003982c8014c8c9660026096003132303e3305a305b0013305a303e3305a305b3058375400697ae0305c305c001305737540071598009828000c4c8c8c0fccc16cc170008cc16cc170004cc16cc0fccc16cc170c164dd500225eb80c174c174004c170004c15cdd5001c5660026094003132323230403305c305d0033305c305d0023305c305d0013305c30403305c305d305a375400a97ae0305e305e001305d001305c001305737540071598009826800c4c8c8c8c8c104cc174c178010cc174c17800ccc174c178008cc174c178004cc174c104cc174c178c16cdd500325eb80c17cc17c004c178004c174004c170004c15cdd5001c56600260980031323232323230423305e305f0053305e305f0043305e305f0033305e305f0023305e305f0013305e30423305e305f305c375400e97ae030603060001305f001305e001305d001305c0013057375400715980099b874802800626464646464646086660be60c000c660be60c000a660be60c0008660be60c0006660be60c0004660be60c0002660be6086660be60c060ba6ea80212f5c060c260c200260c000260be00260bc00260ba00260b800260ae6ea800e2b30013370e9006000c4c8c8c8c8c8c8c8c110cc180c18401ccc180c184018cc180c184014cc180c184010cc180c18400ccc180c184008cc180c184004cc180c110cc180c184c178dd5004a5eb80c188c188004c184004c180004c17c004c178004c174004c170004c15cdd5001c56600266e1d200e001899191919191919191822998309831004198309831003998309831003198309831002998309831002198309831001998309831001198309831000998309822998309831182f9baa00a4bd7018319831800983100098308009830000982f800982f000982e800982e000982b9baa0038acc004cdc3a4020003132323232323232323230463306230630093306230630083306230630073306230630063306230630053306230630043306230630033306230630023306230630013306230463306230633060375401697ae0306430640013063001306200130610013060001305f001305e001305d001305c0013057375400715980099b8748048006264646464646464646464608e660c660c8014660c660c8012660c660c8010660c660c800e660c660c800c660c660c800a660c660c8008660c660c8006660c660c8004660c660c8002660c6608e660c660c860c26ea80312f5c060ca60ca00260c800260c600260c400260c200260c000260be00260bc00260ba00260b800260ae6ea800e2b30013370e900a000c4c8c8c8c8c8c8c8c8c8c8c8c120cc190c19402ccc190c194028cc190c194024cc190c194020cc190c19401ccc190c194018cc190c194014cc190c194010cc190c19400ccc190c194008cc190c194004cc190c120cc190c194c188dd5006a5eb80c198c198004c194004c190004c18c004c188004c184004c180004c17c004c178004c174004c170004c15cdd5001c56600266e1d20160018991919191919191919191919182499832983300619832983300599832983300519832983300499832983300419832983300399832983300319832983300299832983300219832983300199832983300119832983300099832982499832983318319baa00e4bd70183398338009833000983280098320009831800983100098308009830000982f800982f000982e800982e000982b9baa00389919191919191919191919191824998329833006198329833005998329833005198329833004998329833004198329833003998329833003198329833002998329833002198329833001998329833001198329833000998329833183380099832982499832983318319baa00e4bd7025eb80c198004c194004c190004c18c004c188004c184004c180004c17c004c178004c174004c170004c15cdd5001a0aa415482a905520aa415482a905520aa415482a9055182a9baa0013058002400c82b0dd7182a982b000991acc004c0dc00e2653001001a5eb820088008889660026092003123233059374e002660b26ea40092f5c065300100180252f5c080088896600200510018cc00400e60ba0053305b305c002001400c82d2246530010059982c982d0008024c04800e6eb8c168c16c0050051802800a0a68b20a0375c60aa60ac008530012232598009800a40211598009800a4001148002266e39220108010204081020408000002414d1598009800a408113370490400219801801980a00144cdc124101010100406600600666e00009203f414c8298dc4000c888c8cc160c00cdd6982c8009982c182c982d000a5eb80c00c006660a86ea0cdc7000a4000660a86ea4cc038c034dc6800800a5eb81222323298009198029191982e1ba833794940dd7182e8009982e182e982f000a5eb816600260346e340062660b66ea4c05c004cc16cdd49980c980c1b8d0010014bd704590571191801acc004c138006298103d87a8000898201982e1ba80014bd7020b030030019bad305a003911191acc0066002609e6eb4c17800694294505944cc02000c8c014c108cc178dd4000a5eb8226016600898103d87a800041646eb8c178c17c004cc170dd419b8e00148000cc170dd49980b180a9b8d0010014bd7024446b3001304e00289801918209982e800a5eb822b3001305300289801919ba548008cc1740052f5c1159800982680144c00c8cdd2a4008660ba00297ae08acc004c14000a26600e464660bc6ea0cde5250375c60be002660bc60be60c000297ae05980098221b8d00189982e9ba930210013305d37526604e604c6e340040052f5c1164164466e9520063305d375000297ae08acc004c13c00a26600e464660bc6ea0cde5250375c60be002660bc60be60c000297ae05980098221b8d00189982e9ba930210013305d37526604e604c6e340040052f5c1164164466e9520083305d3750b3001337100029040400244006266e04004c0192020416497ae08acc004cdc3a40140051300323374a90051982e800a5eb822b30013370e900600144cc0048cdd2a4018660ba00297ae02323305e3750600c6eb4c17c004cc178c17cc1800052f5c064660bc6ea0cde5250375c60be002660bc60be60c000297ae059800980e1b8d00189982e9ba930190013305d37526603660346e340040052f5c116416515980099b874803800a266002466e95200e3305d0014bd701191982f1ba833794940dd7182f8009982f182f9830000a5eb816600260386e340062660ba6ea4c064004cc174dd49980d980d1b8d0010014bd70459059456600266e1d2010002899800919ba548040cc1740052f5c0464660bc6ea0cde5250375c60be002660bc60be60c000297ae059800980e1b8d00189982e9ba930190013305d37526603660346e340040052f5c116416515980099b874804800a246464660be66e9520123305f30600014bd701982f98301830800a5eb80c004008cc0208c8cc17cdd419bca4a06eb8c180004cc17cc180c1840052f5c0b30013045371a00313305e37526044002660bc6ea4cc0a0c09cdc6800800a5eb822c82d096600260a000314c103d87980008acc004c154006298103d87a80008acc004c13c006298103d87b80008acc004c148006298103d87c80008acc004c144006298103d87d80008b20b4416882d105a20b48acc004cdc3a40280051300323374a900a1982e800a5eb822b30013370e900b00144c00c8cdd2a402c660ba00297ae08acc004cdc3a403000513300123374a900c1982e800a5eb808c8cc178dd419bca4a06eb8c17c004cc178c17cc1800052f5c0b3001301c371a00313305d37526032002660ba6ea4cc06cc068dc6800800a5eb822c82ca2c82c105820b0416082c105820b0416082c105820b0416082c0dd7182e982f003096600266e2000520808080808080808080028800c4cdc08009801241000282a8c00c00c6eb4c14800488cc14c008cc14cdd4800a5eb80c8cc144dd419b8e00148000cc144dd49980598051b8d0010014bd701bae305130520013233050375066f29281bae305100133050305130520014bd702cc004c05cdc6800c4cc13cdd49809000998279ba9330153014371a00200297ae08b2096412916411c6e31200237009000a2c81e0dd718209821000acc004c01cdc6800c4cc0fcdd498010009981f9ba9330053004371a00200297ae08b2076375c605060786ea8cc0908c8d6600266e3cdd71820800a4504b9011a8200891919191919191991198249816998249825003998249ba900133049304a0024bd70198249825182580125eb80d660026054003125980099b89002371a00313304837526601e004002660906ea66002005337026e3400400a002b8c25eb822c82222c8218dd7182418248011bae3048004375a608e002646608c6ea0cde5250375c608e0026608c608e609000297ae05980098161b8d0018998229ba930090013304537526601e601c6e340040052f5c11641046eb8c114c118004d6600294624b30013371290201b8d0018998221ba93300b48100004cc110dd4cc005204099b80371a002901fc0057184bd7045904045903f1bae30443045001300100259800a51892cc004cdc4a4100026e340062660846ea4cc0252080010013304237533001482000666e00dc6800a40ff0015c612f5c11640f91640f51640f06eb8c104c10800566002600e6e3400626607e6ea4c008004cc0fcdd49980298021b8d0010014bd7045903b1bae303f009300348010c00d200819801001181c000a06a8991801181b8019bae303500240cc6eb0c0c8c0bcdd5000c5902d19199119801001000912cc004006298103d87a80008992cc004cdd78021818800c4c060cc0d0c0c80052f5c1133003003303600240c060680028190dd5981918199819981998199819981998199819981998179baa0033374a90021981819ba548008cc0c0c064c0b8dd5181898171baa0014bd7025eb822c8160c8cc004004dd6180e18171baa0022259800800c530103d87a80008992cc006600266ebc00530103d87a8000a50a5140b91001899801801981a001205c325980099912cc00400a294226530013259800981418199baa00189bad303430373756606e60686ea800629000206430360019baf303630370019bab003488966002003159800980100344cc01400d20008a5040d11325980099baf30360014c010140008acc004cc018010dd6981b981d1bab3037001898019ba6303b0028a5040d51598009980300224001130030078a5040d481a8c0e40050370ca60020033756604260666ea8c084c0ccdd5002488cc0dc008cc0dcdd3000a5eb810011112cc00400a26600298103d87a80004bd6f7b63044ca60026eb8c0d40066eacc0d800660740069112cc004cdc8a441000038acc004cdc7a44100003899802980f9981d9ba60024bd70000c4cc015300103d87a800000640dd19800803c006446600e0046607a66ec0dd48029ba6004001401c81b8607000481b22942294229410371ba633032337606ea4018dd31981919bb04c10b4a50797468205374617465004c010101004bd6f7b63025eb7bdb1808928c5660026052605e6ea8c0ccc0c0dd5181998181baa301e3030375400313259800981218181baa0018992cc004c098c0c4dd5000c4c8c8c8c8cc89660026078007132598009816981c1baa0018991919194c004dd69820000cdd718200024dd69820001cdd7182000124444b300130450058998141bab304400e225980080144cc0a803089660020051302b330470144bd7044c8c8cc07cc1180084c00cc12c010dd718230009824001208c899191980e98228010980198248021bae3043001304600241111641083040001303f001303e001303937540031640dc607600d1640e46eb8c0e4004dd5981c801181c800981c000981b80098191baa0018b20603034303137540031640bc603860606ea8c078c0c0dd5000c5902e4530103d87a800040b86064002818045900f18090031809180980322c8038601000260066ea802629344d95900101", + "hash": "39e330f0020708cecb4cf7dd09c3912000c9f30b31c80cb7c2ad21a1" + }, + { + "title": "vault.vault.else", + "redeemer": { + "schema": {} + }, + "compiledCode": "591ccd01010029800aba4aba2aba1aba0aab9faab9eaab9dab9a4888888896600264653001300800198041804800cdc3a400530080024888966002600460106ea800e3300130093754007370e90034dc3a4011370e90004dc3a4009300837540089111111991192cc004c01801226464b30013018002801c590151bad30160013012375401b159800980580244c8c8cc896600260340070058b202e375a602e0026eb4c05c008c05c004c048dd5006c566002600a00915980098091baa00d800c590134566002601000915980098091baa00d800c590134566002600e00915980098091baa00d800c590134590102020404080810100acc004c014c040dd5000c66002602860226ea80064602a602c003222323322330020020012259800800c00e2646644b30013372200e00515980099b8f0070028800c01901844cc014014c0780110181bae3017001375a6030002603400280c0c8c8cc004004018896600200300389919912cc004cdc8804801456600266e3c02400a20030064065133005005301f00440646eb8c060004dd5980c800980d800a03214bd6f7b6300a400123015301630160019180a980b180b180b000c8c054c058c058c058c058c058c0580066e25200491192cc004cdc7a4500375c602e6030003130170018b20243002001911919800800801911980180098010014dd2a40013712900048c054c058c058c058c058c058c058c0580064464660020026eb0c05cc060c060c060c060c060c060c060c060c050dd5001912cc00400629422b30013371e6eb8c06000400e2946266004004603200280990164c040dd500648c054c058c058c058c0580064602a602c602c602c602c602c00323259800980398091baa0028991919191919194c004dd6980e800cc0780066eb8c07401e6eb8c07401a6eb8c0740166eb8c0740126eb4c07400e6eb4c07400922222222598009813003c4c8c9660026030003132598009814800c4c9660026034604a6ea8006264646644b3001302e00380c45902b1bad302b001375a60560046056002604c6ea80062c8120c0a00062c8130c090dd50014566002603a00315980098121baa002809459025459022204430223754002604a00f16408c301d001301c001301b001301a00130190013018001301337540051640442003371090002444444444444444445300132330010013758604e60486ea80708966002003168992cc004cdd7981498131baa0010178980a18131baa00189980180198150012048302800140992259800980c98121baa00289919192cc004c0b000a26464b3001301e0018acc004c0a8dd5001401a2c815a2b30013023001899192cc004c0c000a0111640b46eb4c0b8004c0a8dd50014566002603a00315980098151baa00280345902b459028205040a060506ea8004c0ac00e2c8148c966002605000315980098089813800c5a26042604e00281322c8148dd51815000981500098129baa0028b2046911919800800801912cc004006298103d87a80008992cc004c0100062601c6605400297ae08998018019816001204c302a00140a09114c004c0a4c098dd5001e60026eacc050c098dd5001cdd7180918131baa0159bae30113026375402a809a44b3001301c30273754005132323259800981780144cc01cc0b800c4cc01c0040122c8160c0b4004c0b4004c0a0dd5001459026488cc00c0088c96600266ebcc0b4c0a8dd5000981698151baa003899baf3018302a3754002603060546ea800e2941028181618149baa00148888c966002603e01b13259800998078121bae302f302c37540371598009808800c566002601400b13259800981098161baa001899192cc004c088c0b8dd5000c4c96600266e3cdd7181998181baa001375c606660606ea807e2b30013371e6eb8c078c0c0dd50009bae301e3030375403f15980099b8f375c603860606ea8004dd7180e18181baa01f8acc004cdc79bae301b303037540026eb8c06cc0c0dd500fc56600266e1cdd6980818181baa001375a602060606ea807e2b30013370e6eb4c068c0c0dd50009bad301a3030375403f15980099baf301430303754002602860606ea807e2b30013370e6eb4c044c0c0dd5000802c4cdc3cc004dd5980f18181baa0039bae301c3030375403f375c603660606ea807d01d004c528205c8a5040b914a08172294102e4528205c8a5040b914a08172294102e1807181918179baa0018b205a301a302e37540026060605a6ea80062c8158cc00cdd6180c18161baa0240068b20548b20548b2054375a605c60566ea809a2b3001302400d89919912cc004cc044098dd7181898171baa01d8acc004c03001e2b3001300c0028acc004c04c006264b30013023302e37540031323259800981218181baa0018992cc004cdc79bae3035303237540026eb8c0d4c0c8dd5010c56600266e3cdd7181018191baa001375c604060646ea80862b30013371e6eb8c078c0c8dd50009bae301e3032375404315980099b8f375c603a60646ea8004dd7180e98191baa0218acc004cdc39bad3013303237540026eb4c04cc0c8dd5010c56600266e1cdd6980918191baa001375a602460646ea80862b30013370e6eb4c070c0c8dd50009bad301c3032375404315980099baf3016303237540026030660686030660686ea0018cc0d0dd4002a5eb812f5c113370f30013756604060646ea800e6eb8c078c0c8dd5010cdd7180e98191baa021407c01714a081822941030452820608a5040c114a081822941030452820608a5040c06020606860626ea80062c8178c070c0c0dd5000981918179baa0018b205a3300537586034605c6ea80980222c81622c81622c81622c8160dd698178009bad302f3030001302b375404d159800980f006c5660026601c0466eb8c0b8c0acdd500d4566002603e6eb4c030c0acdd500d45660026012009159800981218151baa330023758602e60566ea808c01629462c814a2c814a2c814a2c814a2b3001302100d8acc004cdc419b82337053001375c603260566ea806a6eb4c054c0acdd500d408d0011bad300b302b375403490640099b82375a601860566ea806920dc01898049bad300c302b375403514a0814a2b30013300e023375c605c60566ea806a264b30013020302b37540031337113001375c603460586ea806e6eb4c058c0b0dd500dc0910021bad302f302c3754605e60586ea80062c8150c03cc0acdd500d459029205240a48149029111192cc004c0c8006264b30013023302e375400313259800981218179baa0018992cc004c094c0c0dd5000c4dd6981a18189baa0018b205e3033303037540031640b8603a605e6ea8c0c8c0bcdd5000c5902d198049bac301a302e37546062002466e1cdd6981918179baa0010048b205e3259800981118169baa0018992cc004c08cc0b8dd5000c4c8cc05c0048966002005132330010010042259800800c52f5c113303698009b8c480126e0120039b89480226e3120009b8c480226e0120074888888cc88cc0908c8d6600266e3cdd71820800a4410475d3c793008cc004888c8cc00cdd698228009bae3045304600130030019b89480426e3120109b804803e600e90082444446600a4646608e6ea0cde5250375c60900026608e6090609200297ae05980098029b8d0018998231ba930020013304637526600860066e340040052f5c116410846644660104660926ea0cdc7000a4000660926ea4cc00cc008dc6800800a5eb808cc0248cc128dd419b8e00148000cc128dd49980218019b8d0010014bd70111919198269818998269ba80083304d375000a6609a609c00297ae03304d304e304f0014bd7018008012cc004c0b800a2653001001a5eb820068008889660026080003123233050374e002660a06ea40092f5c065300100180252f5c080088896600200510018cc00400e60a8005330523053002001400c828a246530010059982818288008024c02400e6eb8c144c148005005191919911919801194c0040066eb0c15800a6072660aa60ac00c660aa980103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a8000330554c103d87a80004bd702002222598008014400633001003982c8014c8c9660026096003132303e3305a305b0013305a303e3305a305b3058375400697ae0305c305c001305737540071598009828000c4c8c8c0fccc16cc170008cc16cc170004cc16cc0fccc16cc170c164dd500225eb80c174c174004c170004c15cdd5001c5660026094003132323230403305c305d0033305c305d0023305c305d0013305c30403305c305d305a375400a97ae0305e305e001305d001305c001305737540071598009826800c4c8c8c8c8c104cc174c178010cc174c17800ccc174c178008cc174c178004cc174c104cc174c178c16cdd500325eb80c17cc17c004c178004c174004c170004c15cdd5001c56600260980031323232323230423305e305f0053305e305f0043305e305f0033305e305f0023305e305f0013305e30423305e305f305c375400e97ae030603060001305f001305e001305d001305c0013057375400715980099b874802800626464646464646086660be60c000c660be60c000a660be60c0008660be60c0006660be60c0004660be60c0002660be6086660be60c060ba6ea80212f5c060c260c200260c000260be00260bc00260ba00260b800260ae6ea800e2b30013370e9006000c4c8c8c8c8c8c8c8c110cc180c18401ccc180c184018cc180c184014cc180c184010cc180c18400ccc180c184008cc180c184004cc180c110cc180c184c178dd5004a5eb80c188c188004c184004c180004c17c004c178004c174004c170004c15cdd5001c56600266e1d200e001899191919191919191822998309831004198309831003998309831003198309831002998309831002198309831001998309831001198309831000998309822998309831182f9baa00a4bd7018319831800983100098308009830000982f800982f000982e800982e000982b9baa0038acc004cdc3a4020003132323232323232323230463306230630093306230630083306230630073306230630063306230630053306230630043306230630033306230630023306230630013306230463306230633060375401697ae0306430640013063001306200130610013060001305f001305e001305d001305c0013057375400715980099b8748048006264646464646464646464608e660c660c8014660c660c8012660c660c8010660c660c800e660c660c800c660c660c800a660c660c8008660c660c8006660c660c8004660c660c8002660c6608e660c660c860c26ea80312f5c060ca60ca00260c800260c600260c400260c200260c000260be00260bc00260ba00260b800260ae6ea800e2b30013370e900a000c4c8c8c8c8c8c8c8c8c8c8c8c120cc190c19402ccc190c194028cc190c194024cc190c194020cc190c19401ccc190c194018cc190c194014cc190c194010cc190c19400ccc190c194008cc190c194004cc190c120cc190c194c188dd5006a5eb80c198c198004c194004c190004c18c004c188004c184004c180004c17c004c178004c174004c170004c15cdd5001c56600266e1d20160018991919191919191919191919182499832983300619832983300599832983300519832983300499832983300419832983300399832983300319832983300299832983300219832983300199832983300119832983300099832982499832983318319baa00e4bd70183398338009833000983280098320009831800983100098308009830000982f800982f000982e800982e000982b9baa00389919191919191919191919191824998329833006198329833005998329833005198329833004998329833004198329833003998329833003198329833002998329833002198329833001998329833001198329833000998329833183380099832982499832983318319baa00e4bd7025eb80c198004c194004c190004c18c004c188004c184004c180004c17c004c178004c174004c170004c15cdd5001a0aa415482a905520aa415482a905520aa415482a9055182a9baa0013058002400c82b0dd7182a982b000991acc004c0dc00e2653001001a5eb820088008889660026092003123233059374e002660b26ea40092f5c065300100180252f5c080088896600200510018cc00400e60ba0053305b305c002001400c82d2246530010059982c982d0008024c04800e6eb8c168c16c0050051802800a0a68b20a0375c60aa60ac008530012232598009800a40211598009800a4001148002266e39220108010204081020408000002414d1598009800a408113370490400219801801980a00144cdc124101010100406600600666e00009203f414c8298dc4000c888c8cc160c00cdd6982c8009982c182c982d000a5eb80c00c006660a86ea0cdc7000a4000660a86ea4cc038c034dc6800800a5eb81222323298009198029191982e1ba833794940dd7182e8009982e182e982f000a5eb816600260346e340062660b66ea4c05c004cc16cdd49980c980c1b8d0010014bd704590571191801acc004c138006298103d87a8000898201982e1ba80014bd7020b030030019bad305a003911191acc0066002609e6eb4c17800694294505944cc02000c8c014c108cc178dd4000a5eb8226016600898103d87a800041646eb8c178c17c004cc170dd419b8e00148000cc170dd49980b180a9b8d0010014bd7024446b3001304e00289801918209982e800a5eb822b3001305300289801919ba548008cc1740052f5c1159800982680144c00c8cdd2a4008660ba00297ae08acc004c14000a26600e464660bc6ea0cde5250375c60be002660bc60be60c000297ae05980098221b8d00189982e9ba930210013305d37526604e604c6e340040052f5c1164164466e9520063305d375000297ae08acc004c13c00a26600e464660bc6ea0cde5250375c60be002660bc60be60c000297ae05980098221b8d00189982e9ba930210013305d37526604e604c6e340040052f5c1164164466e9520083305d3750b3001337100029040400244006266e04004c0192020416497ae08acc004cdc3a40140051300323374a90051982e800a5eb822b30013370e900600144cc0048cdd2a4018660ba00297ae02323305e3750600c6eb4c17c004cc178c17cc1800052f5c064660bc6ea0cde5250375c60be002660bc60be60c000297ae059800980e1b8d00189982e9ba930190013305d37526603660346e340040052f5c116416515980099b874803800a266002466e95200e3305d0014bd701191982f1ba833794940dd7182f8009982f182f9830000a5eb816600260386e340062660ba6ea4c064004cc174dd49980d980d1b8d0010014bd70459059456600266e1d2010002899800919ba548040cc1740052f5c0464660bc6ea0cde5250375c60be002660bc60be60c000297ae059800980e1b8d00189982e9ba930190013305d37526603660346e340040052f5c116416515980099b874804800a246464660be66e9520123305f30600014bd701982f98301830800a5eb80c004008cc0208c8cc17cdd419bca4a06eb8c180004cc17cc180c1840052f5c0b30013045371a00313305e37526044002660bc6ea4cc0a0c09cdc6800800a5eb822c82d096600260a000314c103d87980008acc004c154006298103d87a80008acc004c13c006298103d87b80008acc004c148006298103d87c80008acc004c144006298103d87d80008b20b4416882d105a20b48acc004cdc3a40280051300323374a900a1982e800a5eb822b30013370e900b00144c00c8cdd2a402c660ba00297ae08acc004cdc3a403000513300123374a900c1982e800a5eb808c8cc178dd419bca4a06eb8c17c004cc178c17cc1800052f5c0b3001301c371a00313305d37526032002660ba6ea4cc06cc068dc6800800a5eb822c82ca2c82c105820b0416082c105820b0416082c105820b0416082c0dd7182e982f003096600266e2000520808080808080808080028800c4cdc08009801241000282a8c00c00c6eb4c14800488cc14c008cc14cdd4800a5eb80c8cc144dd419b8e00148000cc144dd49980598051b8d0010014bd701bae305130520013233050375066f29281bae305100133050305130520014bd702cc004c05cdc6800c4cc13cdd49809000998279ba9330153014371a00200297ae08b2096412916411c6e31200237009000a2c81e0dd718209821000acc004c01cdc6800c4cc0fcdd498010009981f9ba9330053004371a00200297ae08b2076375c605060786ea8cc0908c8d6600266e3cdd71820800a4504b9011a8200891919191919191991198249816998249825003998249ba900133049304a0024bd70198249825182580125eb80d660026054003125980099b89002371a00313304837526601e004002660906ea66002005337026e3400400a002b8c25eb822c82222c8218dd7182418248011bae3048004375a608e002646608c6ea0cde5250375c608e0026608c608e609000297ae05980098161b8d0018998229ba930090013304537526601e601c6e340040052f5c11641046eb8c114c118004d6600294624b30013371290201b8d0018998221ba93300b48100004cc110dd4cc005204099b80371a002901fc0057184bd7045904045903f1bae30443045001300100259800a51892cc004cdc4a4100026e340062660846ea4cc0252080010013304237533001482000666e00dc6800a40ff0015c612f5c11640f91640f51640f06eb8c104c10800566002600e6e3400626607e6ea4c008004cc0fcdd49980298021b8d0010014bd7045903b1bae303f009300348010c00d200819801001181c000a06a8991801181b8019bae303500240cc6eb0c0c8c0bcdd5000c5902d19199119801001000912cc004006298103d87a80008992cc004cdd78021818800c4c060cc0d0c0c80052f5c1133003003303600240c060680028190dd5981918199819981998199819981998199819981998179baa0033374a90021981819ba548008cc0c0c064c0b8dd5181898171baa0014bd7025eb822c8160c8cc004004dd6180e18171baa0022259800800c530103d87a80008992cc006600266ebc00530103d87a8000a50a5140b91001899801801981a001205c325980099912cc00400a294226530013259800981418199baa00189bad303430373756606e60686ea800629000206430360019baf303630370019bab003488966002003159800980100344cc01400d20008a5040d11325980099baf30360014c010140008acc004cc018010dd6981b981d1bab3037001898019ba6303b0028a5040d51598009980300224001130030078a5040d481a8c0e40050370ca60020033756604260666ea8c084c0ccdd5002488cc0dc008cc0dcdd3000a5eb810011112cc00400a26600298103d87a80004bd6f7b63044ca60026eb8c0d40066eacc0d800660740069112cc004cdc8a441000038acc004cdc7a44100003899802980f9981d9ba60024bd70000c4cc015300103d87a800000640dd19800803c006446600e0046607a66ec0dd48029ba6004001401c81b8607000481b22942294229410371ba633032337606ea4018dd31981919bb04c10b4a50797468205374617465004c010101004bd6f7b63025eb7bdb1808928c5660026052605e6ea8c0ccc0c0dd5181998181baa301e3030375400313259800981218181baa0018992cc004c098c0c4dd5000c4c8c8c8c8cc89660026078007132598009816981c1baa0018991919194c004dd69820000cdd718200024dd69820001cdd7182000124444b300130450058998141bab304400e225980080144cc0a803089660020051302b330470144bd7044c8c8cc07cc1180084c00cc12c010dd718230009824001208c899191980e98228010980198248021bae3043001304600241111641083040001303f001303e001303937540031640dc607600d1640e46eb8c0e4004dd5981c801181c800981c000981b80098191baa0018b20603034303137540031640bc603860606ea8c078c0c0dd5000c5902e4530103d87a800040b86064002818045900f18090031809180980322c8038601000260066ea802629344d95900101", + "hash": "39e330f0020708cecb4cf7dd09c3912000c9f30b31c80cb7c2ad21a1" + } + ], + "definitions": { + "ByteArray": { + "dataType": "bytes" + }, + "Data": { + "title": "Data", + "description": "Any Plutus data." + }, + "Int": { + "dataType": "integer" + }, + "Option": { + "title": "Option", + "anyOf": [ + { + "title": "Some", + "description": "An optional value.", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "$ref": "#/definitions/vault~1HedgeParams" + } + ] + }, + { + "title": "None", + "description": "Nothing.", + "dataType": "constructor", + "index": 1, + "fields": [] + } + ] + }, + "aiken/crypto/VerificationKeyHash": { + "title": "VerificationKeyHash", + "dataType": "bytes" + }, + "cardano/assets/PolicyId": { + "title": "PolicyId", + "dataType": "bytes" + }, + "liquidity_pool/PoolDatum": { + "title": "PoolDatum", + "description": "Pool de liquidez demo: solo el `owner` firmante puede mover fondos del script.\n Cualquiera puede **enviar** ADA a la dirección del script (depósitos desde wallet).", + "anyOf": [ + { + "title": "PoolDatum", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "owner", + "$ref": "#/definitions/aiken~1crypto~1VerificationKeyHash" + } + ] + } + ] + }, + "vault/HedgeParams": { + "title": "HedgeParams", + "description": "Parametric insurance params (strike in same raw units as Pyth feed price).", + "anyOf": [ + { + "title": "HedgeParams", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "strike_raw", + "$ref": "#/definitions/Int" + }, + { + "title": "payout_lovelace", + "$ref": "#/definitions/Int" + } + ] + } + ] + }, + "vault/VaultDatum": { + "title": "VaultDatum", + "description": "Vault state: one locked RWA NFT, nominal debt, Pyth feed for that asset.", + "anyOf": [ + { + "title": "VaultDatum", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "owner", + "description": "Payment key hash that controls Adjust / Close / ClaimInsurance.", + "$ref": "#/definitions/aiken~1crypto~1VerificationKeyHash" + }, + { + "title": "pyth_policy", + "description": "Pyth Lazer governance policy on Cardano (PreProd constant off-chain).", + "$ref": "#/definitions/cardano~1assets~1PolicyId" + }, + { + "title": "nft_policy", + "description": "Locked shadow NFT.", + "$ref": "#/definitions/cardano~1assets~1PolicyId" + }, + { + "title": "nft_name", + "$ref": "#/definitions/ByteArray" + }, + { + "title": "debt_lovelace", + "description": "Synthetic debt in lovelace (demo accounting).", + "$ref": "#/definitions/Int" + }, + { + "title": "collateral_qty", + "description": "Units of collateral (configure off-chain so price_raw * qty is comparable to debt).", + "$ref": "#/definitions/Int" + }, + { + "title": "feed_id", + "description": "Pyth Lazer feed id (e.g. 346 XAU/USD).", + "$ref": "#/definitions/Int" + }, + { + "title": "hedge", + "$ref": "#/definitions/Option" + } + ] + } + ] + }, + "vault/VaultRedeemer": { + "title": "VaultRedeemer", + "anyOf": [ + { + "title": "Adjust", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "new_debt", + "$ref": "#/definitions/Int" + } + ] + }, + { + "title": "ApplyHedge", + "dataType": "constructor", + "index": 1, + "fields": [ + { + "title": "strike_raw", + "$ref": "#/definitions/Int" + }, + { + "title": "payout_lovelace", + "$ref": "#/definitions/Int" + } + ] + }, + { + "title": "Close", + "dataType": "constructor", + "index": 2, + "fields": [] + }, + { + "title": "Liquidate", + "dataType": "constructor", + "index": 3, + "fields": [] + }, + { + "title": "ClaimInsurance", + "dataType": "constructor", + "index": 4, + "fields": [] + } + ] + } + } +} \ No newline at end of file diff --git a/lazer/cardano/inventory-edge-protocol/onchain/validators/liquidity_pool.ak b/lazer/cardano/inventory-edge-protocol/onchain/validators/liquidity_pool.ak new file mode 100644 index 00000000..5be91adb --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/onchain/validators/liquidity_pool.ak @@ -0,0 +1,26 @@ +use aiken/collection/list +use aiken/crypto.{VerificationKeyHash} +use cardano/transaction.{OutputReference, Transaction} +use cardano/transaction as tx + +/// Pool de liquidez demo: solo el `owner` firmante puede mover fondos del script. +/// Cualquiera puede **enviar** ADA a la dirección del script (depósitos desde wallet). +pub type PoolDatum { + owner: VerificationKeyHash, +} + +fn owner_signed(self: Transaction, who: VerificationKeyHash) -> Bool { + list.any(self.extra_signatories, fn(k) { k == who }) +} + +validator liquidity_pool { + spend( + datum: Option, + _redeemer: Data, + _own_ref: OutputReference, + self: Transaction, + ) { + expect Some(d) = datum + owner_signed(self, d.owner)? + } +} diff --git a/lazer/cardano/inventory-edge-protocol/onchain/validators/vault.ak b/lazer/cardano/inventory-edge-protocol/onchain/validators/vault.ak new file mode 100644 index 00000000..5015b9d7 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/onchain/validators/vault.ak @@ -0,0 +1,162 @@ +use aiken/collection/list +use aiken/crypto.{VerificationKeyHash} +use cardano/address.{Address} +use cardano/assets.{PolicyId, Value, quantity_of} +use cardano/transaction.{Datum, Output, OutputReference, Transaction} +use cardano/transaction as tx +use pyth +use types/u32 + +/// Parametric insurance params (strike in same raw units as Pyth feed price). +pub type HedgeParams { + strike_raw: Int, + payout_lovelace: Int, +} + +/// Vault state: one locked RWA NFT, nominal debt, Pyth feed for that asset. +pub type VaultDatum { + /// Payment key hash that controls Adjust / Close / ClaimInsurance. + owner: VerificationKeyHash, + /// Pyth Lazer governance policy on Cardano (PreProd constant off-chain). + pyth_policy: PolicyId, + /// Locked shadow NFT. + nft_policy: PolicyId, + nft_name: ByteArray, + /// Synthetic debt in lovelace (demo accounting). + debt_lovelace: Int, + /// Units of collateral (configure off-chain so price_raw * qty is comparable to debt). + collateral_qty: Int, + /// Pyth Lazer feed id (e.g. 346 XAU/USD). + feed_id: Int, + hedge: Option, +} + +pub type VaultRedeemer { + Adjust { new_debt: Int } + ApplyHedge { strike_raw: Int, payout_lovelace: Int } + Close + Liquidate + ClaimInsurance +} + +fn owner_signed(self: Transaction, who: VerificationKeyHash) -> Bool { + list.any(self.extra_signatories, fn(k) { k == who }) +} + +fn same_address(a: Address, b: Address) -> Bool { + a.payment_credential == b.payment_credential && a.stake_credential == b.stake_credential +} + +fn quantity_nft(value: Value, policy: PolicyId, name: ByteArray) -> Int { + quantity_of(value, policy, name) +} + +fn parse_vault_datum(d: Data) -> VaultDatum { + expect d: VaultDatum = d + d +} + +fn continuing_at( + outputs: List, + vault_addr: Address, +) -> Option { + list.find(outputs, fn(o) { same_address(o.address, vault_addr) }) +} + +fn pyth_price_for_feed( + pyth_policy: PolicyId, + feed_id: Int, + self: Transaction, +) -> Int { + let updates = pyth.get_updates(pyth_policy, self) + expect [update] = updates + expect Some(feed) = + list.find(update.feeds, fn(f) { u32.as_int(f.feed_id) == feed_id }) + expect Some(Some(price)) = feed.price + price +} + +validator vault { + spend( + datum: Option, + redeemer: VaultRedeemer, + own_ref: OutputReference, + self: Transaction, + ) { + expect Some(v) = datum + let own_in = tx.resolve_input(self.inputs, own_ref) + let vault_addr = own_in.address + let nft_in = quantity_nft(own_in.value, v.nft_policy, v.nft_name) + + when redeemer is { + Adjust { new_debt } -> { + expect owner_signed(self, v.owner)? + expect new_debt >= 0 + expect nft_in > 0 + expect Some(cont) = continuing_at(self.outputs, vault_addr) + expect Datum.InlineDatum(raw) = cont.datum + let next = parse_vault_datum(raw) + and { + (next.owner == v.owner)?, + (next.pyth_policy == v.pyth_policy)?, + (next.nft_policy == v.nft_policy)?, + (next.nft_name == v.nft_name)?, + (next.collateral_qty == v.collateral_qty)?, + (next.feed_id == v.feed_id)?, + (next.hedge == v.hedge)?, + (next.debt_lovelace == new_debt)?, + (quantity_nft(cont.value, v.nft_policy, v.nft_name) == nft_in)?, + } + } + + ApplyHedge { strike_raw, payout_lovelace } -> { + expect owner_signed(self, v.owner)? + expect nft_in > 0 + expect strike_raw > 0 + expect payout_lovelace >= 0 + expect Some(cont) = continuing_at(self.outputs, vault_addr) + expect Datum.InlineDatum(raw) = cont.datum + let next = parse_vault_datum(raw) + let expected_hedge = + Some(HedgeParams { strike_raw, payout_lovelace }) + and { + (next.owner == v.owner)?, + (next.pyth_policy == v.pyth_policy)?, + (next.nft_policy == v.nft_policy)?, + (next.nft_name == v.nft_name)?, + (next.debt_lovelace == v.debt_lovelace)?, + (next.collateral_qty == v.collateral_qty)?, + (next.feed_id == v.feed_id)?, + (next.hedge == expected_hedge)?, + (quantity_nft(cont.value, v.nft_policy, v.nft_name) == nft_in)?, + } + } + + Close -> { + expect owner_signed(self, v.owner)? + expect v.debt_lovelace == 0 + expect nft_in > 0 + expect None = continuing_at(self.outputs, vault_addr) + True + } + + Liquidate -> { + let price = pyth_price_for_feed(v.pyth_policy, v.feed_id, self) + // Underwater when collateral value strictly below 110% * debt (integer compare). + let lhs = price * v.collateral_qty * 100 + let rhs = v.debt_lovelace * 110 + and { + (lhs < rhs)?, + (v.debt_lovelace > 0)?, + } + } + + ClaimInsurance -> { + expect owner_signed(self, v.owner)? + expect Some(h) = v.hedge + let price = pyth_price_for_feed(v.pyth_policy, v.feed_id, self) + price < h.strike_raw + } + } + } +} diff --git a/lazer/cardano/inventory-edge-protocol/package-lock.json b/lazer/cardano/inventory-edge-protocol/package-lock.json new file mode 100644 index 00000000..ce63d186 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/package-lock.json @@ -0,0 +1,3815 @@ +{ + "name": "inventory-edge-protocol", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "inventory-edge-protocol", + "version": "0.1.0", + "dependencies": { + "@evolution-sdk/evolution": "^0.3.30", + "@pythnetwork/pyth-lazer-cardano-js": "^0.1.0", + "@pythnetwork/pyth-lazer-sdk": "^5.2.0", + "@vitejs/plugin-react": "^6.0.1", + "concurrently": "^9.2.1", + "cors": "^2.8.6", + "dotenv": "^16.4.7", + "express": "^5.2.1", + "lucid-cardano": "^0.10.11", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "vite": "^8.0.1" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/node": "^22.10.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "tsx": "^4.21.0", + "typescript": "^5.7.2" + } + }, + "node_modules/@effect/cluster": { + "version": "0.48.16", + "resolved": "https://registry.npmjs.org/@effect/cluster/-/cluster-0.48.16.tgz", + "integrity": "sha512-ZZkrSMVetOvlRDD8mPCX3IcVJtvUZBp6++lUKNGIT6LRIObRP4lVwtei85Z+4g49WpeLvJnSdH0zjPtGieFDHQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@effect/platform": "^0.90.10", + "@effect/rpc": "^0.69.4", + "@effect/sql": "^0.44.2", + "@effect/workflow": "^0.9.6", + "effect": "^3.17.14" + } + }, + "node_modules/@effect/experimental": { + "version": "0.54.6", + "resolved": "https://registry.npmjs.org/@effect/experimental/-/experimental-0.54.6.tgz", + "integrity": "sha512-UqHMvCQmrZT6kUVoUC0lqyno4Yad+j9hBGCdUjW84zkLwAq08tPqySiZUKRwY+Ae5B2Ab8rISYJH7nQvct9DMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "uuid": "^11.0.3" + }, + "peerDependencies": { + "@effect/platform": "^0.90.2", + "effect": "^3.17.7", + "ioredis": "^5", + "lmdb": "^3" + }, + "peerDependenciesMeta": { + "ioredis": { + "optional": true + }, + "lmdb": { + "optional": true + } + } + }, + "node_modules/@effect/platform": { + "version": "0.90.10", + "resolved": "https://registry.npmjs.org/@effect/platform/-/platform-0.90.10.tgz", + "integrity": "sha512-QhDPgCaLfIMQKOCoCPQvRUS+Y34iYJ07jdZ/CBAvYFvg/iUBebsmFuHL63RCD/YZH9BuK/kqqLYAA3M0fmUEgg==", + "license": "MIT", + "dependencies": { + "find-my-way-ts": "^0.1.6", + "msgpackr": "^1.11.4", + "multipasta": "^0.2.7" + }, + "peerDependencies": { + "effect": "^3.17.13" + } + }, + "node_modules/@effect/platform-node": { + "version": "0.96.1", + "resolved": "https://registry.npmjs.org/@effect/platform-node/-/platform-node-0.96.1.tgz", + "integrity": "sha512-4nfB/XRJJ246MCdI7klTE/aVvA9txfI83RnymS7pNyoG4CXUKELi87JrkrWFTtOlewzt5UMWpmqsFmm2qHxx3A==", + "license": "MIT", + "dependencies": { + "@effect/platform-node-shared": "^0.49.0", + "mime": "^3.0.0", + "undici": "^7.10.0", + "ws": "^8.18.2" + }, + "peerDependencies": { + "@effect/cluster": "^0.48.2", + "@effect/platform": "^0.90.6", + "@effect/rpc": "^0.69.1", + "@effect/sql": "^0.44.2", + "effect": "^3.17.10" + } + }, + "node_modules/@effect/platform-node-shared": { + "version": "0.49.2", + "resolved": "https://registry.npmjs.org/@effect/platform-node-shared/-/platform-node-shared-0.49.2.tgz", + "integrity": "sha512-uYlQi2swDV9hdHatr2Onov3G+VlEF+3+Qm9dvdOZiZNE1bVqvs/zs6LVT8Yrz/3Vq/4JPzGcN+acx0iiJo5ZVw==", + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.5.1", + "multipasta": "^0.2.7", + "ws": "^8.18.2" + }, + "peerDependencies": { + "@effect/cluster": "^0.48.10", + "@effect/platform": "^0.90.10", + "@effect/rpc": "^0.69.3", + "@effect/sql": "^0.44.2", + "effect": "^3.17.13" + } + }, + "node_modules/@effect/rpc": { + "version": "0.69.5", + "resolved": "https://registry.npmjs.org/@effect/rpc/-/rpc-0.69.5.tgz", + "integrity": "sha512-LLCZP/aiaW4HeoIaoZuVZpJb/PFCwdJP21b3xP6l+1yoRVw8HlKYyfy/outRCF+BT4ndtY0/utFSeGWC21Qr7w==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@effect/platform": "^0.90.10", + "effect": "^3.17.14" + } + }, + "node_modules/@effect/sql": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@effect/sql/-/sql-0.44.2.tgz", + "integrity": "sha512-DEcvriHvj88zu7keruH9NcHQzam7yQzLNLJO6ucDXMCAwWzYZSJOsmkxBznRFv8ylFtccSclKH2fuj+wRKPjCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "uuid": "^11.0.3" + }, + "peerDependencies": { + "@effect/experimental": "^0.54.6", + "@effect/platform": "^0.90.4", + "effect": "^3.17.7" + } + }, + "node_modules/@effect/workflow": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@effect/workflow/-/workflow-0.9.6.tgz", + "integrity": "sha512-uPBpSJ8NYwYA6VLZovfejwNik+2kAaoDtlPi+VTlxFMscWNYx+xlGiRg8CO/oa2pHCwkJYjOI27SGOlUawiz1w==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@effect/platform": "^0.90.10", + "@effect/rpc": "^0.69.4", + "effect": "^3.17.14" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@evolution-sdk/evolution": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@evolution-sdk/evolution/-/evolution-0.3.30.tgz", + "integrity": "sha512-8DMgxNdjWCEhCu8sMKz/ikY4qfGmoW8vBPT53Dji7i5PmHYMXLuVk0Nt5mNpIovujwSbC271GIJQo2pINtQsxg==", + "license": "MIT", + "dependencies": { + "@effect/platform": "^0.90.10", + "@effect/platform-node": "^0.96.1", + "@noble/curves": "^2.0.1", + "@noble/hashes": "^1.8.0", + "@scure/base": "^1.2.6", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "@types/bip39": "^3.0.4", + "bip39": "^3.1.0", + "effect": "^3.19.3" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.8.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@pythnetwork/pyth-lazer-cardano-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-lazer-cardano-js/-/pyth-lazer-cardano-js-0.1.0.tgz", + "integrity": "sha512-KfddoNfkRf+a6Qqf1zKGkMyKK9Mgib6P5hLVApc6qB3XpLkzk9GfPLOc9q2uqCF0EOjzPRDRTMF9beKMxyAK4Q==", + "dependencies": { + "@evolution-sdk/evolution": "^0.3.29" + }, + "engines": { + "node": "^24.0.0" + } + }, + "node_modules/@pythnetwork/pyth-lazer-sdk": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-lazer-sdk/-/pyth-lazer-sdk-5.2.1.tgz", + "integrity": "sha512-/0rrPi6sZdVH+cWtW+X/hmQzw+nP2fIF6BlrJRTrqwK2IXWZWW+40sHoj839HrbyUXOJyG9zKyco37eFuTE/nA==", + "license": "Apache-2.0", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "buffer": "^6.0.3", + "isomorphic-ws": "^5.0.0", + "ts-log": "^2.2.7", + "ws": "^8.18.0" + }, + "engines": { + "node": "^24.0.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/bip39": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/bip39/-/bip39-3.0.4.tgz", + "integrity": "sha512-kgmgxd14vTUMqcKu/gRi7adMchm7teKnOzdkeP0oQ5QovXpbUJISU0KUtBt84DdxCws/YuNlSCIoZqgXexe6KQ==", + "deprecated": "This is a stub types definition. bip39 provides its own type definitions, so you do not need this installed.", + "license": "MIT", + "dependencies": { + "bip39": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "license": "ISC", + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/effect": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-my-way-ts": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", + "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", + "license": "MIT" + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucid-cardano": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/lucid-cardano/-/lucid-cardano-0.10.11.tgz", + "integrity": "sha512-bpfrLQjpathPAH/N+BMXfLzp+O5P7LtZjg6aaVsC3EUfRo7I9Y85ZIhxpfEIati+CcpRviWRVMKcAlDnPuPFkA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "@peculiar/webcrypto": "^1.4.0", + "node-fetch": "^3.2.3", + "ws": "^8.10.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.9.tgz", + "integrity": "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multipasta": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz", + "integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", + "license": "MIT" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-log": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.7.tgz", + "integrity": "sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", + "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", + "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.10", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/lazer/cardano/inventory-edge-protocol/package.json b/lazer/cardano/inventory-edge-protocol/package.json new file mode 100644 index 00000000..f9ea289c --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/package.json @@ -0,0 +1,40 @@ +{ + "name": "inventory-edge-protocol", + "version": "0.1.0", + "type": "module", + "scripts": { + "build:onchain": "cd onchain && aiken build", + "mock-assets": "npx tsx scripts/mock_assets.ts", + "tx:open-vault": "npx tsx scripts/run_open_vault.ts", + "tx:hedge": "npx tsx scripts/run_apply_hedge.ts", + "tx:liquidate": "npx tsx scripts/run_liquidate.ts", + "dev:api": "tsx server/index.ts", + "dev:web": "vite --config web/vite.config.ts", + "demo": "concurrently -n api,web -c blue,green \"npm run dev:api\" \"npm run dev:web\"", + "build:web": "vite build --config web/vite.config.ts", + "preview:web": "vite preview --config web/vite.config.ts --port 5173" + }, + "dependencies": { + "@evolution-sdk/evolution": "^0.3.30", + "@pythnetwork/pyth-lazer-cardano-js": "^0.1.0", + "@pythnetwork/pyth-lazer-sdk": "^5.2.0", + "@vitejs/plugin-react": "^6.0.1", + "concurrently": "^9.2.1", + "cors": "^2.8.6", + "dotenv": "^16.4.7", + "express": "^5.2.1", + "lucid-cardano": "^0.10.11", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "vite": "^8.0.1" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/node": "^22.10.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "tsx": "^4.21.0", + "typescript": "^5.7.2" + } +} diff --git a/lazer/cardano/inventory-edge-protocol/scripts/mock_assets.ts b/lazer/cardano/inventory-edge-protocol/scripts/mock_assets.ts new file mode 100644 index 00000000..1ce071bd --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/scripts/mock_assets.ts @@ -0,0 +1,98 @@ +/** + * Mint 3 native "shadow" NFTs (Lucid + Blockfrost or Maestro PreProd). + * Metadata follows CIP-25 label 721 with Lazer-style feed id hints for judges. + */ +import "dotenv/config"; + +import { Blockfrost, Lucid, Maestro } from "lucid-cardano"; + +import { + PYTH_LAZER_FEEDS, + SHADOW_ASSETS, + type ShadowAssetKey, +} from "../lib/feeds.js"; + +const ORDER: ShadowAssetKey[] = ["XAU_USD", "WTI_USD", "BTC_USD"]; + +async function main() { + const projectId = process.env.BLOCKFROST_PROJECT_ID; + const maestroKey = process.env.MAESTRO_API_KEY; + const mnemonic = process.env.CARDANO_MNEMONIC; + if (!mnemonic) throw new Error("Set CARDANO_MNEMONIC"); + + const lucid = + projectId + ? await Lucid.new( + new Blockfrost( + "https://cardano-preprod.blockfrost.io/api/v0", + projectId, + ), + "Preprod", + ) + : maestroKey + ? await Lucid.new( + new Maestro({ network: "Preprod", apiKey: maestroKey }), + "Preprod", + ) + : (() => { + throw new Error( + "Set BLOCKFROST_PROJECT_ID or MAESTRO_API_KEY (Preprod)", + ); + })(); + lucid.selectWalletFromSeed(mnemonic); + + const addr = await lucid.wallet.address(); + const details = lucid.utils.getAddressDetails(addr); + if (details.paymentCredential?.type !== "Key") { + throw new Error("Expected key payment credential"); + } + + const mintingPolicy = lucid.utils.nativeScriptFromJson({ + type: "all", + scripts: [{ type: "sig", keyHash: details.paymentCredential.hash }], + }); + const policyId = lucid.utils.mintingPolicyToId(mintingPolicy); + + const mintMap: Record = {}; + const metaInner: Record = {}; + + for (const key of ORDER) { + const assetName = `Shadow${key}`; + const nameHex = Buffer.from(assetName, "utf8").toString("hex"); + const unit = policyId + nameHex; + mintMap[unit] = 1n; + const m = SHADOW_ASSETS[key]; + metaInner[assetName] = { + name: m.label, + description: m.description, + pyth_lazer_feed_id: String(PYTH_LAZER_FEEDS[key]), + inventory_edge_class: key, + }; + } + + const tx = await lucid + .newTx() + .mintAssets(mintMap) + .attachMintingPolicy(mintingPolicy) + .attachMetadata(721, { [policyId]: metaInner }) + .complete(); + + const signed = await tx.sign().complete(); + const txHash = await signed.submit(); + + console.log("Submitted mint tx:", txHash); + console.log("PolicyId:", policyId); + console.log("\nPer-asset (use for openVault env):"); + for (const key of ORDER) { + const assetName = `Shadow${key}`; + const nameHex = Buffer.from(assetName, "utf8").toString("hex"); + console.log( + ` ${key}: SHADOW_POLICY_ID=${policyId} SHADOW_NAME_HEX=${nameHex} (feed ${PYTH_LAZER_FEEDS[key]})`, + ); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/lazer/cardano/inventory-edge-protocol/scripts/run_apply_hedge.ts b/lazer/cardano/inventory-edge-protocol/scripts/run_apply_hedge.ts new file mode 100644 index 00000000..d9d8bd5e --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/scripts/run_apply_hedge.ts @@ -0,0 +1,21 @@ +import "dotenv/config"; + +import { applyHedge, fetchFirstVaultUtxo } from "../lib/transactions.js"; + +async function main() { + const strike = BigInt(process.env.HEDGE_STRIKE_RAW ?? "2500000"); + const payout = BigInt(process.env.HEDGE_PAYOUT_LOVELACE ?? "5000000"); + + const vaultUtxo = await fetchFirstVaultUtxo(); + const txHash = await applyHedge({ + vaultUtxo, + strikeRaw: strike, + payoutLovelace: payout, + }); + console.log("applyHedge tx:", txHash); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/lazer/cardano/inventory-edge-protocol/scripts/run_liquidate.ts b/lazer/cardano/inventory-edge-protocol/scripts/run_liquidate.ts new file mode 100644 index 00000000..c175f96f --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/scripts/run_liquidate.ts @@ -0,0 +1,23 @@ +import "dotenv/config"; + +import { PYTH_LAZER_FEEDS, type ShadowAssetKey } from "../lib/feeds.js"; +import { fetchFirstVaultUtxo, liquidate } from "../lib/transactions.js"; + +async function main() { + const asset = (process.env.SHADOW_ASSET ?? "XAU_USD") as ShadowAssetKey; + if (!(asset in PYTH_LAZER_FEEDS)) { + throw new Error(`Invalid SHADOW_ASSET ${asset}`); + } + + const vaultUtxo = await fetchFirstVaultUtxo(); + const txHash = await liquidate({ + vaultUtxo, + priceFeedId: PYTH_LAZER_FEEDS[asset], + }); + console.log("liquidate tx:", txHash); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/lazer/cardano/inventory-edge-protocol/scripts/run_open_vault.ts b/lazer/cardano/inventory-edge-protocol/scripts/run_open_vault.ts new file mode 100644 index 00000000..f931b26d --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/scripts/run_open_vault.ts @@ -0,0 +1,35 @@ +import "dotenv/config"; + +import { PYTH_LAZER_FEEDS, type ShadowAssetKey } from "../lib/feeds.js"; +import { openVault } from "../lib/transactions.js"; + +async function main() { + const policy = process.env.SHADOW_POLICY_ID; + const nameHex = process.env.SHADOW_NAME_HEX; + const asset = (process.env.SHADOW_ASSET ?? "XAU_USD") as ShadowAssetKey; + if (!policy || !nameHex) { + throw new Error( + "Set SHADOW_POLICY_ID and SHADOW_NAME_HEX (printed by npm run mock-assets)", + ); + } + if (!(asset in PYTH_LAZER_FEEDS)) { + throw new Error(`Invalid SHADOW_ASSET ${asset}`); + } + + const debt = BigInt(process.env.VAULT_DEBT_LOVELACE ?? "1000000000"); + const qty = BigInt(process.env.VAULT_COLLATERAL_QTY ?? "1000000"); + + const txHash = await openVault({ + nftPolicyHex: policy, + nftNameHex: nameHex, + feedId: PYTH_LAZER_FEEDS[asset], + debtLovelace: debt, + collateralQty: qty, + }); + console.log("openVault tx:", txHash); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/lazer/cardano/inventory-edge-protocol/server/index.ts b/lazer/cardano/inventory-edge-protocol/server/index.ts new file mode 100644 index 00000000..aca92f6b --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/server/index.ts @@ -0,0 +1,973 @@ +/** + * Local judge demo API: signs with CARDANO_MNEMONIC from `.env` (PreProd only). + * Run with `npm run dev:api` from repo root; UI proxies `/api` in dev. + */ +import "dotenv/config"; + +import * as Address from "@evolution-sdk/evolution/Address"; +import * as AssetName from "@evolution-sdk/evolution/AssetName"; +import * as PolicyId from "@evolution-sdk/evolution/PolicyId"; +import cors from "cors"; +import express from "express"; + +import { + loadBlueprint, + liquidityPoolSpendValidator, + vaultSpendValidator, +} from "../lib/blueprint.js"; +import { + DEMO_SLOT_LABEL, + DEMO_SLOT_TO_KEY, + LAZER_FEED_BY_KEY, + quoteChainForDemoSlot, + quoteChainForVaultFeedId, + type DemoSlot, + type ShadowAssetKey, + PYTH_LAZER_FEEDS, +} from "../lib/feeds.js"; +import { + listWalletNativeNfts, + listWalletShadowNfts, +} from "../lib/wallet_shadow_nfts.js"; +import { mintShadowNft } from "../lib/mint_shadow.js"; +import { PYTH_POLICY_ID_HEX } from "../lib/pyth.js"; +import { + fetchFeedQuoteResolved, + fetchFeedQuotes, + isUnderwater, + minCollateralQtyForDebt, + suggestedCollateralQtyForDebt, +} from "../lib/pyth_quotes.js"; +import { + adjustDebt, + applyHedge, + claimInsurance, + closeVault, + getVaultUtxoByRef, + liquidate, + listVaultPositions, + openVault, + readInlineDatum, +} from "../lib/transactions.js"; +import { + decodeVaultDatum, + type DecodedVaultDatum, +} from "../lib/vault_datum_decode.js"; +import { enterpriseVaultAddress } from "../lib/vault_address.js"; + +import { + createPreprodSigningClient, + preprodChainBackendLabel, +} from "../lib/evolution_client.js"; +import { + depositLiquidityPoolOnChain, + listPoolDepositsOnChain, + liquidityPoolAddressBech32, + poolDepositReserveLovelace, + walletTotalLovelaceLucid, + withdrawAllLiquidityPoolOnChain, +} from "../lib/pool_onchain.js"; +import { + appendAudit, + loadPool, + LOAN_REPAY_INTEREST_BPS, + poolCancelReservation, + poolCommitLoan, + poolDeposit, + poolEffectiveAvailableForInsuranceReserve, + poolFinalizeCloseVault, + poolOnDebtAdjusted, + poolOnLiquidateLoan, + poolReleaseOnClaim, + poolReserveForHedge, + poolWithdraw, + readAudit, +} from "../lib/judge_store.js"; + +const PORT = Number(process.env.JUDGE_API_PORT ?? 8787); + +function sleep(ms: number): Promise { + return new Promise((r) => setTimeout(r, ms)); +} + +async function findVaultRefByNft( + policyHex: string, + nameHex: string, +): Promise { + const rows = await listVaultPositions(); + const p = policyHex.toLowerCase(); + const n = nameHex.toLowerCase(); + const hit = rows.find( + (r) => + r.datum.nftPolicyHex.toLowerCase() === p && + r.datum.nftNameHex.toLowerCase() === n, + ); + return hit?.ref ?? null; +} + +async function findVaultRefByNftWithRetry( + policyHex: string, + nameHex: string, +): Promise { + await sleep(2000); + for (let attempt = 0; attempt < 5; attempt++) { + const ref = await findVaultRefByNft(policyHex, nameHex); + if (ref) return ref; + await sleep(1500); + } + return null; +} + +function requireMnemonic(): string { + const m = process.env.CARDANO_MNEMONIC; + if (!m) throw new Error("CARDANO_MNEMONIC not set"); + return m; +} + +function jsonDatum(d: DecodedVaultDatum) { + return { + ownerKeyHashHex: d.ownerKeyHashHex, + pythPolicyHex: d.pythPolicyHex, + nftPolicyHex: d.nftPolicyHex, + nftNameHex: d.nftNameHex, + debtLovelace: d.debtLovelace.toString(), + collateralQty: d.collateralQty.toString(), + feedId: d.feedId.toString(), + hedge: + d.hedge.tag === "none" + ? null + : { + strikeRaw: d.hedge.strikeRaw.toString(), + payoutLovelace: d.hedge.payoutLovelace.toString(), + }, + }; +} + +function parseSlot(s: string): DemoSlot { + if (s === "metal" || s === "oil" || s === "stock") return s; + throw new Error('slot must be "metal", "oil", or "stock"'); +} + +const app = express(); +app.use(cors({ origin: true })); +app.use(express.json({ limit: "512kb" })); + +app.get("/api/health", (_req, res) => { + res.json({ + ok: true, + preprod: true, + hasMnemonic: Boolean(process.env.CARDANO_MNEMONIC), + hasAccessToken: Boolean(process.env.ACCESS_TOKEN), + hasBlockfrostOrMaestro: Boolean( + process.env.BLOCKFROST_PROJECT_ID ?? process.env.MAESTRO_API_KEY, + ), + /** Evolution (vault txs) usa este backend para evaluar scripts; Koios/ogmios público suele fallar. */ + evolutionChainBackend: preprodChainBackendLabel(), + }); +}); + +app.get("/api/config", (_req, res) => { + try { + const bp = loadBlueprint(); + const val = vaultSpendValidator(bp); + const vaultAddr = enterpriseVaultAddress(val.hash); + const poolVal = liquidityPoolSpendValidator(bp); + const poolAddr = enterpriseVaultAddress(poolVal.hash); + res.json({ + network: "preprod", + vaultScriptHash: val.hash, + vaultAddressBech32: Address.toBech32(vaultAddr), + liquidityPoolScriptHash: poolVal.hash, + liquidityPoolAddressBech32: Address.toBech32(poolAddr), + poolDepositReserveLovelace: poolDepositReserveLovelace().toString(), + pythPolicyIdHex: PYTH_POLICY_ID_HEX, + feeds: PYTH_LAZER_FEEDS, + demoSlots: DEMO_SLOT_LABEL, + slotToFeedKey: DEMO_SLOT_TO_KEY, + formulas: { + liquidateWhen: + "price * collateralQty * 100 < debtLovelace * 110 (same units as on-chain Pyth price)", + claimInsuranceWhen: "hedge active and price < strike_raw", + }, + }); + } catch (e) { + res.status(500).json({ + error: e instanceof Error ? e.message : String(e), + }); + } +}); + +app.get("/api/wallet", async (_req, res) => { + try { + const mnemonic = requireMnemonic(); + const client = createPreprodSigningClient(mnemonic); + const addr = await client.address(); + const utxos = await client.getWalletUtxos(); + let lovelace = 0n; + const nfts: { unit: string; quantity: string }[] = []; + for (const u of utxos) { + lovelace += u.assets.lovelace; + const m = u.assets.multiAsset; + if (!m) continue; + for (const [pid, inner] of m.map) { + const ph = PolicyId.toHex(pid); + for (const [aname, qty] of inner) { + if (qty > 0n) { + nfts.push({ + unit: ph + AssetName.toHex(aname), + quantity: qty.toString(), + }); + } + } + } + } + const shadows = await listWalletShadowNfts(mnemonic); + const native = await listWalletNativeNfts(mnemonic); + res.json({ + address: Address.toBech32(addr), + lucidAddress: shadows.address, + lovelace: lovelace.toString(), + adaApprox: (Number(lovelace) / 1e6).toFixed(6), + nftCount: nfts.length, + nfts, + nativeNfts: native.nfts, + shadowNfts: shadows.nfts, + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.get("/api/pyth/demo-feeds", async (_req, res) => { + try { + const slots: DemoSlot[] = ["metal", "oil", "stock"]; + const rows = []; + for (const slot of slots) { + const key = DEMO_SLOT_TO_KEY[slot] as ShadowAssetKey; + const m = LAZER_FEED_BY_KEY[key]; + const chain = quoteChainForDemoSlot(slot); + const quote = await fetchFeedQuoteResolved(chain); + const marketOpen = Boolean(quote.priceRaw); + rows.push({ + slot, + key, + feedId: m.id, + proSymbol: m.proSymbol, + uiTitle: m.uiTitle, + lazerChannel: m.channel, + label: DEMO_SLOT_LABEL[slot].title, + quote, + proSymbolUsed: + quote.resolvedProSymbol ?? + (quote.priceRaw ? m.proSymbol : undefined), + quoteNote: quote.quoteNote, + marketOpen, + marketLabel: marketOpen ? "Mercado abierto" : "Mercado cerrado", + }); + } + res.json({ + asOf: Date.now(), + docsUrl: + "https://docs.pyth.network/price-feeds/pro/price-feed-ids", + rows, + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.get("/api/audit", (req, res) => { + const lim = Number(req.query.limit ?? 100); + const limit = Number.isFinite(lim) + ? Math.min(400, Math.max(1, Math.floor(lim))) + : 100; + res.json({ events: readAudit(limit) }); +}); + +app.get("/api/mock/pool", (_req, res) => { + try { + res.json(loadPool()); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/mock/pool/deposit", (req, res) => { + try { + const raw = req.body?.lovelace; + if (raw == null) { + res.status(400).json({ error: "lovelace required" }); + return; + } + const lovelace = BigInt(String(raw)); + if (lovelace <= 0n) { + res.status(400).json({ error: "lovelace must be positive" }); + return; + } + const p = poolDeposit(lovelace); + appendAudit({ + kind: "mock_pool_deposit", + summary: `Depósito demo al pool de seguros: ${lovelace} lovelace`, + extra: { lovelace: lovelace.toString() }, + }); + res.json(p); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +/** + * Suma al pool mock un % del lovelace total que ve la wallet (Evolution). + * No construye ni envía tx: no mueve ADA on-chain. + */ +app.post("/api/mock/pool/deposit-wallet-percent", async (req, res) => { + try { + const pct = Number(req.body?.percent ?? 80); + if (!Number.isFinite(pct) || pct < 1 || pct > 100 || Math.floor(pct) !== pct) { + res.status(400).json({ error: "percent debe ser entero entre 1 y 100" }); + return; + } + const mnemonic = requireMnemonic(); + const client = createPreprodSigningClient(mnemonic); + const utxos = await client.getWalletUtxos(); + let total = 0n; + for (const u of utxos) { + total += u.assets.lovelace; + } + if (total <= 0n) { + res.status(400).json({ + error: "La wallet no tiene lovelace según el proveedor (Evolution).", + }); + return; + } + const amount = (total * BigInt(pct)) / 100n; + if (amount <= 0n) { + res.status(400).json({ + error: `Con saldo ${total} lovelace, el ${pct}% redondea a 0. Aumentá el saldo o usá depósito manual.`, + walletLovelace: total.toString(), + }); + return; + } + const p = poolDeposit(amount); + appendAudit({ + kind: "mock_pool_deposit_wallet_pct", + summary: `Pool mock +${amount} lovelace (${pct}% de wallet ${total})`, + extra: { + percent: pct, + walletLovelace: total.toString(), + depositedLovelace: amount.toString(), + }, + }); + res.json({ + pool: p, + walletLovelace: total.toString(), + depositedLovelace: amount.toString(), + percent: pct, + note: "Solo contabilidad en data/mock_insurance_pool.json — no se firmó ninguna transacción Cardano.", + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +/** Depósito real al script `liquidity_pool` + sincroniza el pool mock. */ +app.post("/api/pool/onchain/deposit-percent", async (req, res) => { + try { + const pct = Number(req.body?.percent ?? 80); + if (!Number.isFinite(pct) || pct < 1 || pct > 100 || Math.floor(pct) !== pct) { + res.status(400).json({ error: "percent debe ser entero entre 1 y 100" }); + return; + } + const total = await walletTotalLovelaceLucid(); + if (total <= 0n) { + res.status(400).json({ error: "Wallet sin lovelace (Lucid)" }); + return; + } + const reserve = poolDepositReserveLovelace(); + const amount = (total * BigInt(pct)) / 100n; + if (amount <= 0n) { + res.status(400).json({ + error: `El ${pct}% redondea a 0.`, + walletLovelace: total.toString(), + }); + return; + } + if (amount + reserve > total) { + res.status(400).json({ + error: + `Saldo ${total} lovelace: el ${pct}% son ${amount}; necesitás ~${reserve} extra para fees. Bajá el % o POOL_DEPOSIT_RESERVE_LOVELACE.`, + walletLovelace: total.toString(), + requestedLovelace: amount.toString(), + reserveLovelace: reserve.toString(), + }); + return; + } + const txHash = await depositLiquidityPoolOnChain({ lovelace: amount }); + const p = poolDeposit(amount); + appendAudit({ + kind: "pool_onchain_deposit_pct", + summary: `Pool on-chain +${amount} lovelace (${pct}% wallet)`, + txHash, + extra: { + percent: pct, + walletLovelace: total.toString(), + depositedLovelace: amount.toString(), + }, + }); + res.json({ + txHash, + pool: p, + walletLovelace: total.toString(), + depositedLovelace: amount.toString(), + percent: pct, + liquidityPoolAddressBech32: liquidityPoolAddressBech32(), + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/pool/onchain/deposit", async (req, res) => { + try { + const raw = req.body?.lovelace; + if (raw == null) { + res.status(400).json({ error: "lovelace required" }); + return; + } + const amount = BigInt(String(raw)); + if (amount <= 0n) { + res.status(400).json({ error: "lovelace must be positive" }); + return; + } + const txHash = await depositLiquidityPoolOnChain({ lovelace: amount }); + const p = poolDeposit(amount); + appendAudit({ + kind: "pool_onchain_deposit", + summary: `Pool on-chain +${amount} lovelace`, + txHash, + extra: { depositedLovelace: amount.toString() }, + }); + res.json({ + txHash, + pool: p, + depositedLovelace: amount.toString(), + liquidityPoolAddressBech32: liquidityPoolAddressBech32(), + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.get("/api/pool/onchain/positions", async (_req, res) => { + try { + const positions = await listPoolDepositsOnChain(); + const total = positions.reduce((a, r) => a + BigInt(r.lovelace), 0n); + res.json({ + positions, + totalLovelace: total.toString(), + liquidityPoolAddressBech32: liquidityPoolAddressBech32(), + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +/** Gasta todos los UTxOs del pool con tu owner en datum; sincroniza pool mock. */ +app.post("/api/pool/onchain/withdraw-all", async (_req, res) => { + try { + const { txHash, withdrawnLovelace, inputCount } = + await withdrawAllLiquidityPoolOnChain(); + const p = poolWithdraw(BigInt(withdrawnLovelace)); + appendAudit({ + kind: "pool_onchain_withdraw_all", + summary: `Pool on-chain retiro ${withdrawnLovelace} lovelace (${inputCount} inputs)`, + txHash, + extra: { withdrawnLovelace, inputCount }, + }); + res.json({ + txHash, + withdrawnLovelace, + inputCount, + pool: p, + liquidityPoolAddressBech32: liquidityPoolAddressBech32(), + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/mint", async (req, res) => { + try { + const slot = parseSlot(String(req.body?.slot ?? "")); + const out = await mintShadowNft(slot); + appendAudit({ + kind: "mint_shadow", + summary: `Mint NFT sombra (${out.slot}) · ${out.assetName}`, + txHash: out.txHash, + extra: { feedId: out.feedId, policyId: out.policyId }, + }); + res.json(out); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.get("/api/vaults", async (_req, res) => { + try { + const rows = await listVaultPositions(); + res.json({ + vaults: rows.map((r) => ({ + ref: r.ref, + txHash: r.txHash, + outputIndex: r.outputIndex, + lovelace: r.lovelace, + datum: jsonDatum(r.datum), + })), + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/vault/open", async (req, res) => { + try { + const { + nftPolicyHex, + nftNameHex, + feedId, + debtLovelace, + collateralQty, + } = req.body ?? {}; + if (!nftPolicyHex || !nftNameHex || feedId == null) { + res.status(400).json({ error: "nftPolicyHex, nftNameHex, feedId required" }); + return; + } + const pol = String(nftPolicyHex); + const nm = String(nftNameHex); + const loan = BigInt(debtLovelace ?? "0"); + if (loan > 0n) { + const av = BigInt(loadPool().availableLovelace); + if (av < loan) { + res.status(400).json({ + error: + `El pool no tiene tADA suficientes para financiar este préstamo. Disponible: ${av} lovelace; solicitado: ${loan}. Depositá liquidez mock en la pestaña Seguros.`, + code: "POOL_INSUFFICIENT_FUNDS", + availableLovelace: av.toString(), + requestedLovelace: loan.toString(), + }); + return; + } + } + const txHash = await openVault({ + nftPolicyHex: pol, + nftNameHex: nm, + feedId: Number(feedId), + debtLovelace: loan, + collateralQty: BigInt(collateralQty ?? "1"), + }); + if (loan > 0n) { + poolCommitLoan({ + nftPolicyHex: pol, + nftNameHex: nm, + loanLovelace: loan, + }); + } + appendAudit({ + kind: "vault_open", + summary: + loan > 0n + ? `openVault: préstamo desde pool ${loan} lovelace · feed_id=${feedId}` + : `openVault colateral-only (principal 0) · feed_id=${feedId}`, + txHash, + extra: { + nftPolicyHex: pol, + nftNameHex: nm, + feedId: Number(feedId), + loanLovelace: loan.toString(), + }, + }); + res.json({ txHash }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/vault/hedge", async (req, res) => { + try { + const { txHash, outputIndex, strikeRaw, payoutLovelace } = req.body ?? {}; + if (txHash == null || outputIndex == null) { + res.status(400).json({ error: "txHash and outputIndex required" }); + return; + } + const u = await getVaultUtxoByRef(String(txHash), Number(outputIndex)); + const decoded = decodeVaultDatum(readInlineDatum(u)); + const payoutReq = BigInt(payoutLovelace ?? "0"); + if (payoutReq > 0n) { + const eff = poolEffectiveAvailableForInsuranceReserve( + decoded.nftPolicyHex, + decoded.nftNameHex, + ); + if (eff < payoutReq) { + res.status(400).json({ + error: + `El pool no puede respaldar este payout (${payoutReq} lovelace). Liquidez efectiva para esta cobertura: ${eff}. Aportá más tADA mock o bajá el payout.`, + code: "POOL_INSUFFICIENT_FUNDS", + requestedLovelace: payoutReq.toString(), + effectiveAvailableLovelace: eff.toString(), + }); + return; + } + } + const h = await applyHedge({ + vaultUtxo: u, + strikeRaw: BigInt(strikeRaw ?? "0"), + payoutLovelace: payoutReq, + }); + appendAudit({ + kind: "vault_hedge", + summary: `applyHedge strike=${strikeRaw} payout=${payoutLovelace}`, + txHash: h, + extra: { + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + }, + }); + const nextRef = await findVaultRefByNftWithRetry( + decoded.nftPolicyHex, + decoded.nftNameHex, + ); + if (nextRef) { + const { reserved, shortfall } = poolReserveForHedge({ + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + vaultRef: nextRef, + payoutRequested: payoutReq, + }); + appendAudit({ + kind: "mock_pool_reserve", + summary: + shortfall > 0n + ? `Pool: apartado ${reserved} lovelace; shortfall ${shortfall} (inconsistencia — revisar)` + : `Pool: apartado ${reserved} lovelace para cobertura`, + txHash: h, + extra: { + vaultRef: nextRef, + reserved: reserved.toString(), + shortfall: shortfall.toString(), + }, + }); + } else { + appendAudit({ + kind: "mock_pool_reserve_skipped", + summary: + "Pool mock: no se encontró la vault tras hedge (indexador); reintentá o reservá tras refrescar.", + txHash: h, + }); + } + res.json({ txHash: h, poolVaultRef: nextRef }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/vault/adjust", async (req, res) => { + try { + const { txHash, outputIndex, newDebtLovelace } = req.body ?? {}; + if (txHash == null || outputIndex == null || newDebtLovelace == null) { + res + .status(400) + .json({ error: "txHash, outputIndex, newDebtLovelace required" }); + return; + } + const u = await getVaultUtxoByRef(String(txHash), Number(outputIndex)); + const prev = decodeVaultDatum(readInlineDatum(u)); + const oldD = prev.debtLovelace; + const newD = BigInt(newDebtLovelace); + if (newD > oldD) { + const need = newD - oldD; + const av = BigInt(loadPool().availableLovelace); + if (av < need) { + res.status(400).json({ + error: + `El pool no tiene tADA para aumentar el préstamo en ${need} lovelace. Disponible: ${av}.`, + code: "POOL_INSUFFICIENT_FUNDS", + availableLovelace: av.toString(), + requestedLovelace: need.toString(), + }); + return; + } + } + const h = await adjustDebt({ + vaultUtxo: u, + newDebtLovelace: newD, + }); + poolOnDebtAdjusted({ + nftPolicyHex: prev.nftPolicyHex, + nftNameHex: prev.nftNameHex, + oldDebtLovelace: oldD, + newDebtLovelace: newD, + }); + appendAudit({ + kind: "vault_adjust", + summary: `adjustDebt ${oldD.toString()} → ${newD.toString()} (interés demo al amortizar: ${LOAN_REPAY_INTEREST_BPS} bps del principal devuelto)`, + txHash: h, + }); + res.json({ txHash: h }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/vault/close", async (req, res) => { + try { + const { txHash, outputIndex } = req.body ?? {}; + if (txHash == null || outputIndex == null) { + res.status(400).json({ error: "txHash and outputIndex required" }); + return; + } + const u = await getVaultUtxoByRef(String(txHash), Number(outputIndex)); + const decoded = decodeVaultDatum(readInlineDatum(u)); + const h = await closeVault({ vaultUtxo: u }); + if (decoded.hedge.tag === "some") { + poolCancelReservation({ + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + }); + } + poolFinalizeCloseVault({ + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + }); + appendAudit({ + kind: "vault_close", + summary: "closeVault (debt=0)", + txHash: h, + extra: { + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + }, + }); + res.json({ txHash: h }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/vault/liquidate", async (req, res) => { + try { + const { txHash, outputIndex, priceFeedId } = req.body ?? {}; + if (txHash == null || outputIndex == null || priceFeedId == null) { + res + .status(400) + .json({ error: "txHash, outputIndex, priceFeedId required" }); + return; + } + const u = await getVaultUtxoByRef(String(txHash), Number(outputIndex)); + const decoded = decodeVaultDatum(readInlineDatum(u)); + const h = await liquidate({ + vaultUtxo: u, + priceFeedId: Number(priceFeedId), + }); + poolCancelReservation({ + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + }); + poolOnLiquidateLoan({ + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + debtLovelace: decoded.debtLovelace, + }); + appendAudit({ + kind: "vault_liquidate", + summary: `liquidate (venta simulada de colateral → pool) · deuda ${decoded.debtLovelace.toString()} · feed ${priceFeedId}`, + txHash: h, + extra: { + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + }, + }); + res.json({ txHash: h }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.post("/api/vault/claim", async (req, res) => { + try { + const { txHash, outputIndex, priceFeedId } = req.body ?? {}; + if (txHash == null || outputIndex == null || priceFeedId == null) { + res + .status(400) + .json({ error: "txHash, outputIndex, priceFeedId required" }); + return; + } + const u = await getVaultUtxoByRef(String(txHash), Number(outputIndex)); + const decoded = decodeVaultDatum(readInlineDatum(u)); + const h = await claimInsurance({ + vaultUtxo: u, + priceFeedId: Number(priceFeedId), + }); + const rel = poolReleaseOnClaim({ + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + }); + appendAudit({ + kind: "vault_claim", + summary: rel.found + ? `claimInsurance: pago simulado ${rel.paidOut} lovelace; fee pool ${rel.poolFee}` + : "claimInsurance (sin reserva previa en pool mock)", + txHash: h, + extra: { + nftPolicyHex: decoded.nftPolicyHex, + nftNameHex: decoded.nftNameHex, + poolReleased: rel.released.toString(), + poolFee: rel.poolFee.toString(), + poolFound: rel.found, + }, + }); + res.json({ txHash: h }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.get("/api/pyth/quotes", async (req, res) => { + try { + const raw = String(req.query.ids ?? ""); + const ids = raw + .split(",") + .map((x) => x.trim()) + .filter(Boolean) + .map((x) => Number(x)); + const quotes = await fetchFeedQuotes(ids); + res.json({ quotes }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +/** + * `collateral_qty` mínimo y sugerido (con 25% colchón) alineados a `vault.ak` Liquidate: + * `priceRaw * qty * 100 >= debtLovelace * 110`. + */ +app.get("/api/pyth/collateral-hint", async (req, res) => { + try { + const feedId = Number(req.query.feedId ?? ""); + const debtLovelace = BigInt(String(req.query.debtLovelace ?? "0")); + if (!Number.isFinite(feedId) || feedId <= 0) { + res.status(400).json({ error: "feedId query required (positive number)" }); + return; + } + const chain = quoteChainForVaultFeedId(feedId); + const q = await fetchFeedQuoteResolved(chain); + if (!q.priceRaw) { + res.json({ + quote: q, + minCollateralQty: null, + suggestedCollateralQty: null, + note: + q.quoteNote ?? + "Sin precio Pyth en esta cadena — no se puede derivar colateral.", + }); + return; + } + const priceRaw = BigInt(q.priceRaw); + const minCollateralQty = minCollateralQtyForDebt( + debtLovelace, + priceRaw, + ).toString(); + const suggestedCollateralQty = suggestedCollateralQtyForDebt( + debtLovelace, + priceRaw, + ).toString(); + res.json({ + quote: q, + minCollateralQty, + suggestedCollateralQty, + debtLovelace: debtLovelace.toString(), + formula: + "Liquidación si price×qty×100 < debt×110. min qty = ceil(debt×110/(price×100)); sugerido = min + 25% buffer.", + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.get("/api/risk", async (req, res) => { + try { + const txHash = String(req.query.txHash ?? ""); + const outputIndex = Number(req.query.outputIndex ?? ""); + if (!txHash || Number.isNaN(outputIndex)) { + res.status(400).json({ error: "txHash and outputIndex query required" }); + return; + } + const u = await getVaultUtxoByRef(txHash, outputIndex); + const datum = decodeVaultDatum(readInlineDatum(u)); + const feedId = Number(datum.feedId); + const chain = quoteChainForVaultFeedId(feedId); + const q = await fetchFeedQuoteResolved(chain); + let underwater = false; + let claimEligible = false; + let note = q.quoteNote ?? ""; + if (q.priceRaw) { + const priceRaw = BigInt(q.priceRaw); + underwater = isUnderwater({ + priceRaw, + collateralQty: datum.collateralQty, + debtLovelace: datum.debtLovelace, + }); + if (datum.hedge.tag === "some") { + claimEligible = priceRaw < datum.hedge.strikeRaw; + } + if (q.priceFeedId !== feedId) { + note = + (note ? `${note} ` : "") + + `Precio mostrado es del feed ${q.priceFeedId} (${q.resolvedProSymbol ?? "?"}); el datum usa feed_id ${feedId} — on-chain el testigo Pyth debe coincidir con ese id para liquidar.`; + } + } else if (!note) { + note = + "Mercado cerrado o sin publicadores en esta cadena de feeds — no es un error de la aplicación."; + } + const marketOpen = Boolean(q.priceRaw); + const loanPoolNote = + `On-chain: el validador compara precio×colateral vs principal en datum. ` + + `Intereses: demo off-chain (${LOAN_REPAY_INTEREST_BPS} bps sobre lo amortizado) van al pool al bajar la deuda.`; + res.json({ + ref: `${txHash}#${outputIndex}`, + feedId, + quote: q, + underwater, + claimEligible, + debtLovelace: datum.debtLovelace.toString(), + collateralQty: datum.collateralQty.toString(), + hedge: jsonDatum(datum).hedge, + note, + loanPoolNote, + marketOpen, + marketLabel: marketOpen ? "Mercado abierto" : "Mercado cerrado", + marketHint: marketOpen + ? undefined + : "Sin precio en este momento. Suele deberse a mercado cerrado o a la ausencia temporal de publicadores.", + }); + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); + } +}); + +app.use( + ( + err: unknown, + _req: express.Request, + res: express.Response, + _next: express.NextFunction, + ) => { + console.error(err); + res.status(500).json({ + error: err instanceof Error ? err.message : String(err), + }); + }, +); + +app.listen(PORT, () => { + console.log(`Judge API http://127.0.0.1:${PORT}`); +}); diff --git a/lazer/cardano/inventory-edge-protocol/tsconfig.json b/lazer/cardano/inventory-edge-protocol/tsconfig.json new file mode 100644 index 00000000..5b2d3477 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": ["lib/**/*.ts", "scripts/**/*.ts", "server/**/*.ts"] +} diff --git a/lazer/cardano/inventory-edge-protocol/web/index.html b/lazer/cardano/inventory-edge-protocol/web/index.html new file mode 100644 index 00000000..ea856388 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/web/index.html @@ -0,0 +1,18 @@ + + + + + + Inventory-Edge — Judge Demo + + + + + +
+ + + diff --git a/lazer/cardano/inventory-edge-protocol/web/src/App.tsx b/lazer/cardano/inventory-edge-protocol/web/src/App.tsx new file mode 100644 index 00000000..a345d02f --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/web/src/App.tsx @@ -0,0 +1,1654 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { + formatTAdaFromLovelace, + VaultContentsCard, + VaultMiniCard, +} from "./vault_display"; + +type Health = { + ok: boolean; + hasMnemonic: boolean; + hasAccessToken: boolean; + hasBlockfrostOrMaestro: boolean; + evolutionChainBackend?: "blockfrost" | "maestro" | "koios"; +}; + +type Config = { + vaultAddressBech32: string; + vaultScriptHash: string; + liquidityPoolAddressBech32?: string; + liquidityPoolScriptHash?: string; + poolDepositReserveLovelace?: string; + pythPolicyIdHex: string; + feeds: Record; + demoSlots: Record; + formulas: { liquidateWhen: string; claimInsuranceWhen: string }; +}; + +type MintOut = { + txHash: string; + policyId: string; + assetName: string; + nameHex: string; + slot: string; + feedId: number; +}; + +type VaultRow = { + ref: string; + txHash: string; + outputIndex: string; + lovelace: string; + datum: { + debtLovelace: string; + collateralQty: string; + feedId: string; + nftPolicyHex: string; + nftNameHex: string; + hedge: null | { strikeRaw: string; payoutLovelace: string }; + }; +}; + +type Risk = { + underwater: boolean; + claimEligible: boolean; + quote?: { priceRaw?: string; exponent?: number; humanApprox?: string }; + note?: string; + debtLovelace: string; + collateralQty: string; + hedge: null | { strikeRaw: string; payoutLovelace: string }; + marketOpen?: boolean; + marketLabel?: string; + marketHint?: string; + loanPoolNote?: string; +}; + +type ShadowNft = { + policyId: string; + nameHex: string; + nameUtf8: string; + unit: string; + feedId: number; + utxoLovelace: string; +}; + +/** Todos los nativos en Lucid (`/api/wallet` → nativeNfts). */ +type NativeNft = { + policyId: string; + nameHex: string; + nameUtf8?: string; + quantity: string; + unit: string; + suggestedFeedId?: number; +}; + +type DemoFeedRow = { + slot: string; + key: string; + label: string; + uiTitle: string; + proSymbol: string; + lazerChannel: string; + feedId: number; + proSymbolUsed?: string; + quoteNote?: string; + marketOpen?: boolean; + marketLabel?: string; + quote: { + priceFeedId?: number; + priceRaw?: string; + exponent?: number; + humanApprox?: string; + resolvedProSymbol?: string; + quoteNote?: string; + }; +}; + +type AuditEvent = { + ts: string; + kind: string; + summary: string; + txHash?: string; + extra?: Record; +}; + +type MockPoolState = { + availableLovelace: string; + encumberedLovelace: string; + deployedToLoansLovelace: string; + totalDepositedLovelace: string; + totalPaidOutLovelace: string; + totalRepaidPrincipalLovelace: string; + profitsFromLoansLovelace: string; + profitsFromInsuranceLovelace: string; + reservations: { + nftPolicyHex: string; + nftNameHex: string; + payoutLovelace: string; + vaultRef: string; + }[]; + outstandingLoans: Record; +}; + +type TabId = "general" | "creditos" | "seguros"; + +async function api(path: string, init?: RequestInit): Promise { + const r = await fetch(path, { + ...init, + headers: { + "Content-Type": "application/json", + ...(init?.headers ?? {}), + }, + }); + const text = await r.text(); + const trimmed = text.trim(); + if (!trimmed) { + const proxyHint = + r.status === 502 || r.status === 503 || r.status === 504 + ? " ¿Corre la API en :8787 (`npm run dev:api` o `npm run demo`)?" + : ""; + throw new Error( + `Respuesta vacía del servidor (HTTP ${r.status}).${proxyHint}`, + ); + } + let parsed: unknown; + try { + parsed = JSON.parse(trimmed); + } catch { + throw new Error( + trimmed.length > 280 ? `${trimmed.slice(0, 280)}…` : trimmed, + ); + } + if (!r.ok) { + const errBody = parsed as { + error?: string; + code?: string; + availableLovelace?: string; + requestedLovelace?: string; + effectiveAvailableLovelace?: string; + }; + const bits = [ + errBody.error ?? r.statusText, + errBody.code ? `[${errBody.code}]` : "", + errBody.availableLovelace != null + ? `disponible ${errBody.availableLovelace}` + : "", + errBody.requestedLovelace != null + ? `pedido ${errBody.requestedLovelace}` + : "", + errBody.effectiveAvailableLovelace != null + ? `efectivo cobertura ${errBody.effectiveAvailableLovelace}` + : "", + ].filter(Boolean); + throw new Error(bits.join(" · ")); + } + return parsed as T; +} + +function shortTx(h?: string, n = 14): string { + if (!h) return "—"; + return h.length <= n ? h : `${h.slice(0, n)}…`; +} + +export default function App() { + const [tab, setTab] = useState("general"); + const [health, setHealth] = useState(null); + const [config, setConfig] = useState(null); + const [wallet, setWallet] = useState<{ + address: string; + lucidAddress?: string; + adaApprox: string; + nftCount: number; + nativeNfts: NativeNft[]; + shadowNfts: ShadowNft[]; + } | null>(null); + const [demoFeeds, setDemoFeeds] = useState(null); + const [vaults, setVaults] = useState([]); + const [risk, setRisk] = useState(null); + const [lastMint, setLastMint] = useState(null); + const [audit, setAudit] = useState([]); + const [pool, setPool] = useState(null); + + const [nftPolicy, setNftPolicy] = useState(""); + const [nftNameHex, setNftNameHex] = useState(""); + const [feedId, setFeedId] = useState("346"); + const [debtLovelace, setDebtLovelace] = useState("0"); + const [collateralQty, setCollateralQty] = useState("1000000"); + const [openVaultCollateralHint, setOpenVaultCollateralHint] = useState<{ + minCollateralQty: string | null; + suggestedCollateralQty: string | null; + humanApprox?: string; + priceRaw?: string; + quoteNote?: string; + formula?: string; + } | null>(null); + + const [selRef, setSelRef] = useState(null); + const [strikeRaw, setStrikeRaw] = useState("2500000"); + const [payoutLovelace, setPayoutLovelace] = useState("5000000"); + const [newDebt, setNewDebt] = useState("0"); + const [depositLovelace, setDepositLovelace] = useState("10000000"); + + const [busy, setBusy] = useState(null); + const [msg, setMsg] = useState<{ type: "ok" | "err"; text: string } | null>( + null, + ); + + const selectedVault = useMemo( + () => vaults.find((v) => v.ref === selRef) ?? null, + [vaults, selRef], + ); + + const demoFeedsLite = useMemo( + () => + demoFeeds?.map((f) => ({ + feedId: f.feedId, + uiTitle: f.uiTitle, + slot: f.slot, + })) ?? null, + [demoFeeds], + ); + + const nativeSelectValue = useMemo(() => { + if (!wallet?.nativeNfts.length) return ""; + const p = nftPolicy.replace(/\s/g, "").toLowerCase(); + const n = nftNameHex.replace(/\s/g, "").toLowerCase(); + const hit = wallet.nativeNfts.find( + (x) => x.policyId === p && x.nameHex === n, + ); + return hit?.unit ?? ""; + }, [wallet?.nativeNfts, nftPolicy, nftNameHex]); + + const refreshStatic = useCallback(async () => { + const [h, c] = await Promise.all([ + api("/api/health"), + api("/api/config"), + ]); + setHealth(h); + setConfig(c); + }, []); + + const refreshWallet = useCallback(async () => { + const w = await api<{ + address: string; + lucidAddress?: string; + adaApprox: string; + nftCount: number; + nativeNfts?: NativeNft[]; + shadowNfts: ShadowNft[]; + }>("/api/wallet"); + setWallet({ + address: w.address, + lucidAddress: w.lucidAddress, + adaApprox: w.adaApprox, + nftCount: w.nftCount, + nativeNfts: w.nativeNfts ?? [], + shadowNfts: w.shadowNfts ?? [], + }); + }, []); + + const refreshDemoFeeds = useCallback(async () => { + const d = await api<{ rows: DemoFeedRow[] }>("/api/pyth/demo-feeds"); + setDemoFeeds(d.rows); + }, []); + + const refreshVaults = useCallback(async () => { + const v = await api<{ vaults: VaultRow[] }>("/api/vaults"); + setVaults(v.vaults); + setSelRef((cur) => { + if (cur && !v.vaults.some((x) => x.ref === cur)) { + return v.vaults[0]?.ref ?? null; + } + return cur; + }); + }, []); + + const refreshRisk = useCallback(async () => { + if (!selRef) { + setRisk(null); + return; + } + const [txHash, outputIndex] = selRef.split("#"); + const r = await api( + `/api/risk?txHash=${encodeURIComponent(txHash)}&outputIndex=${encodeURIComponent(outputIndex)}`, + ); + setRisk(r); + }, [selRef]); + + const refreshAudit = useCallback(async () => { + const j = await api<{ events: AuditEvent[] }>("/api/audit?limit=120"); + setAudit(j.events); + }, []); + + const refreshPool = useCallback(async () => { + const p = await api("/api/mock/pool"); + setPool(p); + }, []); + + useEffect(() => { + refreshStatic().catch((e) => + setMsg({ type: "err", text: String(e.message) }), + ); + }, [refreshStatic]); + + useEffect(() => { + if (!health?.hasMnemonic) return; + refreshWallet().catch(() => {}); + refreshVaults().catch(() => {}); + }, [health?.hasMnemonic, refreshWallet, refreshVaults]); + + useEffect(() => { + if (!health?.hasAccessToken) return; + refreshDemoFeeds().catch(() => setDemoFeeds(null)); + }, [health?.hasAccessToken, refreshDemoFeeds]); + + useEffect(() => { + refreshAudit().catch(() => setAudit([])); + refreshPool().catch(() => setPool(null)); + }, [health?.hasMnemonic, refreshAudit, refreshPool]); + + useEffect(() => { + if (!selRef) { + setRisk(null); + return; + } + refreshRisk().catch(() => setRisk(null)); + }, [selRef, refreshRisk]); + + async function afterChainAction(): Promise { + await Promise.all([ + refreshVaults(), + refreshWallet(), + refreshAudit(), + refreshPool(), + ]); + } + + async function run( + label: string, + fn: () => Promise, + ): Promise { + setBusy(label); + setMsg(null); + try { + await fn(); + } catch (e) { + setMsg({ type: "err", text: e instanceof Error ? e.message : String(e) }); + } finally { + setBusy(null); + } + } + + const vaultSelect = ( +
+ +

+ Cada opción es una posición en el contrato: adentro está{" "} + un NFT (tu colateral) y los números del préstamo / seguro. +

+ +
+ ); + + return ( +
+ + +
+
+

Inventory-Edge Protocol

+

+ Demo para entender el flujo sin ser experto en crypto:{" "} + pool (liquidez de prueba), cajas fuertes{" "} + (contrato que guarda tu NFT y los números del préstamo), y{" "} + precios Pyth para riesgo y liquidación. Los datos + técnicos siguen disponibles en cada sección. +

+
+ + {tab === "general" && ( + <> +
+
+

Cotizaciones Pyth Pro

+ +
+ {!health?.hasAccessToken ? ( +

Falta ACCESS_TOKEN para ver precios.

+ ) : !demoFeeds ? ( +

Cargando feeds…

+ ) : ( +
+ {demoFeeds.map((row) => { + const open = + row.marketOpen === true || Boolean(row.quote?.priceRaw); + return ( +
+
+
+ {row.uiTitle} +
+ + {open + ? "Mercado abierto" + : row.marketLabel ?? "Mercado cerrado"} + +
+
+ {row.proSymbolUsed ?? row.proSymbol} + {row.proSymbolUsed && + row.proSymbolUsed !== row.proSymbol && ( + + (ref. alternativa) + + )} +
+
+ vault/mint id {row.feedId} · canal típ. {row.lazerChannel} + {row.quote?.priceFeedId != null && ( + <> · cotización id {row.quote.priceFeedId} + )} +
+ {(row.quoteNote ?? row.quote?.quoteNote) && ( +

+ {row.quoteNote ?? row.quote?.quoteNote} +

+ )} + {row.quote?.priceRaw ? ( +
+ {row.quote.priceRaw} + {row.quote.humanApprox && ( + + {" "} + ≈ {row.quote.humanApprox} + + )} +
+ ) : ( +

+ Sin precio en JSON en este momento — estado informativo, + no un fallo de la app. +

+ )} +
+ ); + })} +
+ )} +
+ +
+
+

Estado del entorno

+ {!health ? ( +

Cargando…

+ ) : ( +
    +
  • + Mnemonic (.env):{" "} + {health.hasMnemonic ? ( + OK + ) : ( + Falta + )} +
  • +
  • + Pyth ACCESS_TOKEN:{" "} + {health.hasAccessToken ? ( + OK + ) : ( + Falta + )} +
  • +
  • + Blockfrost / Maestro (mint):{" "} + {health.hasBlockfrostOrMaestro ? ( + OK + ) : ( + Falta + )} +
  • +
  • + Vault txs — evaluación on-chain vía:{" "} + {health.evolutionChainBackend === "blockfrost" ? ( + Blockfrost + ) : health.evolutionChainBackend === "maestro" ? ( + Maestro + ) : ( + + Koios (probar Blockfrost) + + )} +
  • +
+ )} +

+ API local firma con CARDANO_MNEMONIC — solo demo. +

+
+ +
+

Wallet demo

+ {!wallet ? ( +

+ ) : ( + <> +

{wallet.address}

+ {wallet.lucidAddress && + wallet.lucidAddress !== wallet.address && ( +

+ Lucid (mint/openVault): {wallet.lucidAddress} +

+ )} +

+ {wallet.adaApprox} tADA · {wallet.nftCount}{" "} + líneas nativas (Evolution) ·{" "} + {wallet.nativeNfts.length} activos Lucid + (incl. shadow) +

+ + + )} +
+
+ + {config && ( +
+

Contratos on-chain (PreProd)

+

+ Vault (NFT + préstamo) +

+

{config.vaultAddressBech32}

+

+ Script hash:{" "} + {config.vaultScriptHash} +

+ {config.liquidityPoolAddressBech32 && ( + <> +

+ Pool de liquidez (solo tADA, datum = tu clave) +

+

{config.liquidityPoolAddressBech32}

+

+ Script hash:{" "} + + {config.liquidityPoolScriptHash} + +

+

+ Reserva mínima en wallet al depositar:{" "} + + {config.poolDepositReserveLovelace ?? "4000000"} + {" "} + lovelace (fees). Config:{" "} + POOL_DEPOSIT_RESERVE_LOVELACE +

+ + )} +
+ )} + +
+

Qué custodia el contrato (vaults)

+

+ Cada tarjeta es una posición donde el protocolo guarda{" "} + un NFT (tu colateral) y los números del préstamo / + seguro. Elegí una para ver el detalle y para usarla en Préstamo o + Cobertura. +

+ {vaults.length === 0 ? ( +

+ Aún no hay posiciones. En Préstamo, minteá un NFT + sombra y abrí una caja. +

+ ) : ( + <> +
+ {vaults.map((v) => ( + setSelRef(v.ref)} + /> + ))} +
+ {selectedVault && ( + + )} + + )} +
+ +
+

Pool de liquidez (mock)

+

+ Financia préstamos (principal en el datum), reserva coberturas y + acumula ganancias demo (intereses de amortización, fee de seguros, + excedente simulado al liquidar colateral). Depósitos en Cobertura. +

+ {!pool ? ( +

+ ) : ( +
    +
  • + Disponible: {pool.availableLovelace}{" "} + lovelace +
  • +
  • + En préstamos (deployed):{" "} + + {pool.deployedToLoansLovelace ?? "0"} + +
  • +
  • + Apartado (coberturas):{" "} + {pool.encumberedLovelace} +
  • +
  • + Total depositado (hist.):{" "} + {pool.totalDepositedLovelace} +
  • +
  • + Principal amortizado (hist.):{" "} + + {pool.totalRepaidPrincipalLovelace ?? "0"} + +
  • +
  • + Ganancias préstamos (interés demo):{" "} + + {pool.profitsFromLoansLovelace ?? "0"} + +
  • +
  • + Ganancias seguros (fee demo):{" "} + + {pool.profitsFromInsuranceLovelace ?? "0"} + +
  • +
  • + Total “pagado” simulado (siniestros):{" "} + {pool.totalPaidOutLovelace} +
  • +
  • Reservas cobertura: {pool.reservations.length}
  • +
  • + Préstamos tracked:{" "} + {pool.outstandingLoans + ? Object.keys(pool.outstandingLoans).length + : 0} +
  • +
+ )} + +
+ +
+
+

Movimientos (auditoría)

+ +
+

+ Registro local en data/judge_audit.json{" "} + para trazabilidad del jurado. +

+ {audit.length === 0 ? ( +

Aún no hay eventos.

+ ) : ( +
+ + + + + + + + + + + {audit.map((e, i) => ( + + + + + + + ))} + +
FechaTipoResumenTx
{e.ts.slice(0, 19)} + {e.kind} + {e.summary}{shortTx(e.txHash, 12)}
+
+ )} +
+ + )} + + {tab === "creditos" && ( + <> +
+

Crédito y colateral

+

+ El vault custodia el NFT colateral. El{" "} + principal del préstamo sale del{" "} + pool mock (no se “imprime” deuda). Si el colateral + no cubre principal + margen on-chain (~110%), podés{" "} + liquidar (venta simulada → ingreso al pool). El + interés demo se acredita al pool al bajar el + principal con Adjust. Cobertura paramétrica: pestaña{" "} + Seguros. +

+
+ +
+
+

Acuñar NFT sombra

+

+ NFT único por feed (oro / WTI / BTC proxy). Sirve de colateral + en la vault. +

+
+ {(["metal", "oil", "stock"] as const).map((slot) => ( + + ))} +
+ {lastMint && ( +

+ {lastMint.assetName} · feed {lastMint.feedId} +

+ )} +
+ +
+

Abrir vault

+

+ Bloquea el NFT en el script (misma vista de UTxOs que{" "} + Lucid). Si el principal > 0, el pool debe + tener tADA disponible (depositá antes en Seguros). +

+ {wallet && wallet.nativeNfts.length > 0 && ( +
+ + +

+ Incluye shadow y cualquier otro nativo. Los shadow traen feed + Pyth sugerido por nombre. +

+
+ )} + {wallet && wallet.nativeNfts.length === 0 && ( +

+ No hay activos nativos en la wallet Lucid. Acuñá un shadow o + pulsá "Refrescar wallet" en Resumen. +

+ )} +
+ + setNftPolicy(e.target.value)} + /> +
+
+ + setNftNameHex(e.target.value)} + /> +
+
+ + { + setFeedId(e.target.value); + setOpenVaultCollateralHint(null); + }} + /> +
+
+ + { + setDebtLovelace(e.target.value); + setOpenVaultCollateralHint(null); + }} + /> +
+
+ + { + setCollateralQty(e.target.value); + setOpenVaultCollateralHint(null); + }} + /> +
+ +
+ {openVaultCollateralHint && ( +

+ Precio raw:{" "} + {openVaultCollateralHint.priceRaw ?? "—"} + {openVaultCollateralHint.humanApprox != null && ( + <> + {" "} + · humano ≈ {openVaultCollateralHint.humanApprox} + + )} +
+ Mínimo (sin buffer):{" "} + + {openVaultCollateralHint.minCollateralQty ?? "—"} + + {openVaultCollateralHint.quoteNote != null && + openVaultCollateralHint.quoteNote !== "" && ( + <> +
+ {openVaultCollateralHint.quoteNote} + + )} + {openVaultCollateralHint.formula != null && ( + <> +
+ + {openVaultCollateralHint.formula} + + + )} +

+ )} +
+ +
+
+ +
+

Tu caja fuerte activa

+ {vaults.length === 0 ? ( +

Primero abrí una caja desde el bloque de arriba.

+ ) : ( + <> + {vaultSelect} + {selectedVault ? ( + + Las acciones de abajo aplican a esta posición. +

+ } + /> + ) : ( +

Seleccioná una caja en el menú.

+ )} + + )} +
+ +
+

Acciones de crédito

+

+ Si el colateral ya no cubre el principal on-chain + margen, liquidá: + regla Pyth{" "} + + price × qty × 100 < principal × 110 + + . El pool demo suma un excedente simulado por la “venta”. +

+
+ +
+
+
+ + setNewDebt(e.target.value)} + /> +
+
+ + +
+
+ + )} + + {tab === "seguros" && ( + <> +
+

Seguro paramétrico + pool

+

+ strike y payout en el datum. El pool aparta + tADA; si no alcanza, la API rechaza la cobertura. Al cobrar, un{" "} + fee demo queda en el pool; el resto es pago + simulado al asegurado. +

+
+ +
+
+

Aportar al pool (demo)

+

+ Simula capital del asegurador. Persistido en{" "} + data/mock_insurance_pool.json. + No transfiere ADA en cadena — solo anota números + para el demo del pool. +

+

+ Saldo wallet (proveedor):{" "} + + {wallet?.lovelace ?? "—"} lovelace (~{wallet?.adaApprox ?? "?"}{" "} + tADA) + +

+
+ + setDepositLovelace(e.target.value)} + /> +
+
+ + + + +
+ {pool && ( +

+ Disponible ahora:{" "} + {pool.availableLovelace} · + Apartado:{" "} + {pool.encumberedLovelace} +

+ )} +
+ +
+

Reservas del pool

+ {!pool || pool.reservations.length === 0 ? ( +

Ninguna reserva activa.

+ ) : ( +
    + {pool.reservations.map((r, i) => ( +
  • + {r.payoutLovelace} ₳ raw cap · vault {shortTx(r.vaultRef, 20)} +
  • + ))} +
+ )} + +
+
+ +
+

Caja para cobertura y cobro

+ {vaults.length === 0 ? ( +

+ Necesitás una caja con NFT adentro. Creala en{" "} + Préstamo. +

+ ) : ( + <> + {vaultSelect} + {selectedVault ? ( + + ) : ( +

Elegí una caja para ver el NFT y el estado.

+ )} + + )} +
+ +
+
+

Activar / actualizar cobertura

+

+ Tras la tx, el servidor intenta apartar fondos del pool mock + según el payout indicado (puede quedar shortfall si el pool está + vacío). +

+
+ + setStrikeRaw(e.target.value)} + /> +
+
+ + setPayoutLovelace(e.target.value)} + /> +
+ +
+ +
+

Condición de mercado y cobro

+ {!risk ? ( +

Seleccioná una vault.

+ ) : ( + <> +
+ + {risk.marketLabel ?? + (risk.quote?.priceRaw + ? "Mercado abierto" + : "Mercado cerrado")} + +
+ {risk.marketHint && ( +

{risk.marketHint}

+ )} + {risk.loanPoolNote && ( +

+ {risk.loanPoolNote} +

+ )} + {risk.quote?.priceRaw ? ( +

+ Precio raw:{" "} + {risk.quote.priceRaw} + {risk.quote.humanApprox && ( + <> · ≈ {risk.quote.humanApprox} + )} +

+ ) : ( +

+ {risk.note ?? "Sin precio parseado."} +

+ )} +

+ Cobro (claim): precio Pyth < strike y testigo en la tx. +

+
+ {risk.hedge ? ( + risk.claimEligible ? ( + Indemnización habilitada (precio < strike) + ) : ( + Condición de cobro no cumplida + ) + ) : ( + Sin cobertura en datum + )} +
+ +
+ + + )} +
+
+ + )} + + {busy && ( +

+ Ejecutando: {busy}… +

+ )} + {msg?.type === "err" &&

{msg.text}

} + {msg?.type === "ok" &&

{msg.text}

} +
+
+ ); +} diff --git a/lazer/cardano/inventory-edge-protocol/web/src/index.css b/lazer/cardano/inventory-edge-protocol/web/src/index.css new file mode 100644 index 00000000..5ee02934 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/web/src/index.css @@ -0,0 +1,578 @@ +:root { + color-scheme: dark; + --bg: #0c0f14; + --surface: #141a22; + --border: #243044; + --text: #e8edf4; + --muted: #8b9cb3; + --accent: #3d9cf9; + --accent-dim: #2563a8; + --warn: #f5a623; + --danger: #ff5c5c; + --ok: #3ecf8e; + font-family: "DM Sans", system-ui, sans-serif; + line-height: 1.45; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: radial-gradient(1200px 600px at 10% -10%, #1a2840 0%, var(--bg) 55%); + color: var(--text); +} + +#root { + max-width: 1240px; + margin: 0 auto; + padding: 1.25rem 1rem 3rem; +} + +@media (min-width: 900px) { + #root { + padding: 1.5rem 1.5rem 3rem; + } +} + +.app-shell { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +@media (min-width: 960px) { + .app-shell { + flex-direction: row; + align-items: flex-start; + gap: 1.75rem; + } + + .nav-rail { + position: sticky; + top: 1rem; + flex: 0 0 220px; + max-width: 260px; + } + + .main-content { + flex: 1; + min-width: 0; + } +} + +.nav-rail { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.nav-rail__title { + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--muted); + margin: 0 0 0.35rem; + padding-left: 0.15rem; +} + +.nav-rail button.tab { + width: 100%; + text-align: left; + border-radius: 10px; + padding: 0.65rem 0.85rem; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.2rem; +} + +.nav-rail .tab__label { + font-weight: 700; + font-size: 0.95rem; + color: var(--text); +} + +.nav-rail .tab__hint { + font-weight: 400; + font-size: 0.72rem; + color: var(--muted); + line-height: 1.35; + text-transform: none; + letter-spacing: 0; +} + +.main-content { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.page-intro { + background: linear-gradient(135deg, rgba(61, 156, 249, 0.08), transparent); + border: 1px solid var(--border); + border-radius: 14px; + padding: 1rem 1.15rem; +} + +.page-intro h1 { + margin-bottom: 0.5rem; +} + +.page-intro p { + margin: 0; + max-width: 40rem; + font-size: 0.9rem; +} + +.kicker { + font-size: 0.68rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--accent); + margin: 0 0 0.5rem; +} + +.vault-gallery { + display: grid; + gap: 0.6rem; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + margin-top: 0.75rem; +} + +.vault-mini { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + padding: 0.75rem 0.85rem; + border-radius: 12px; + border: 1px solid var(--border); + background: #0e1218; + cursor: pointer; + text-align: left; + transition: border-color 0.15s, box-shadow 0.15s; +} + +.vault-mini:hover { + border-color: var(--accent-dim); +} + +.vault-mini--selected { + border-color: var(--accent); + box-shadow: 0 0 0 1px rgba(61, 156, 249, 0.25); +} + +.vault-mini__title { + font-weight: 700; + font-size: 0.9rem; + color: var(--text); +} + +.vault-mini__meta { + font-size: 0.72rem; + color: var(--muted); + line-height: 1.35; +} + +.vault-contents { + margin-top: 0.25rem; +} + +.vault-contents__hero { + display: flex; + gap: 1rem; + align-items: flex-start; + margin-bottom: 1rem; + padding: 1rem; + background: #0a0d12; + border-radius: 12px; + border: 1px solid var(--border); +} + +.vault-contents__icon { + flex-shrink: 0; + width: 56px; + height: 56px; + border-radius: 14px; + background: linear-gradient(145deg, #1e3a5f, #0f1724); + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); +} + +.vault-contents__icon-inner { + font-size: 0.65rem; + font-weight: 800; + letter-spacing: 0.06em; + color: var(--accent); +} + +.vault-contents__title { + margin: 0 0 0.35rem; + font-size: 1.15rem; + font-weight: 700; + color: var(--text); + letter-spacing: -0.02em; +} + +.vault-contents__tagline { + margin: 0 0 0.6rem; + font-size: 0.84rem; + color: var(--muted); + line-height: 1.45; +} + +.vault-contents__chips { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.chip { + font-size: 0.68rem; + font-weight: 600; + padding: 0.2rem 0.5rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.06); + color: var(--muted); + border: 1px solid var(--border); +} + +.chip--ok { + color: var(--ok); + border-color: rgba(62, 207, 142, 0.35); + background: rgba(62, 207, 142, 0.08); +} + +.chip--neutral { + color: #7eb6f0; + border-color: rgba(61, 156, 249, 0.3); +} + +.chip--warn { + color: var(--warn); + border-color: rgba(245, 166, 35, 0.35); + background: rgba(245, 166, 35, 0.08); +} + +.stat-grid { + display: grid; + gap: 0.65rem; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + margin-bottom: 1rem; +} + +.stat-tile { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + padding: 0.65rem 0.75rem; +} + +.stat-tile__label { + display: block; + font-size: 0.68rem; + font-weight: 600; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 0.35rem; +} + +.stat-tile__value { + display: block; + font-size: 1.05rem; + font-weight: 700; + color: var(--text); +} + +.stat-tile__hint { + display: block; + font-size: 0.68rem; + color: var(--muted); + margin-top: 0.25rem; + font-family: "JetBrains Mono", ui-monospace, monospace; +} + +.technical-block { + border: 1px solid var(--border); + border-radius: 10px; + background: #0a0d12; + overflow: hidden; +} + +.technical-block summary { + cursor: pointer; + padding: 0.65rem 0.85rem; + font-size: 0.8rem; + font-weight: 600; + color: var(--muted); + list-style: none; +} + +.technical-block summary::-webkit-details-marker { + display: none; +} + +.technical-block[open] summary { + border-bottom: 1px solid var(--border); +} + +.technical-dl { + margin: 0; + padding: 0.65rem 0.85rem 0.85rem; + display: grid; + grid-template-columns: minmax(100px, 140px) 1fr; + gap: 0.4rem 0.75rem; + font-size: 0.72rem; +} + +.technical-dl dt { + margin: 0; + color: var(--muted); + font-weight: 600; +} + +.technical-dl dd { + margin: 0; + word-break: break-all; +} + +.field-hint { + font-size: 0.75rem; + color: var(--muted); + margin: -0.35rem 0 0.65rem; + line-height: 1.4; +} + +.card h2 { + font-size: 1rem; + color: var(--text); + letter-spacing: -0.01em; +} + +.card > p:first-of-type { + margin-top: 0; +} + +h1 { + font-size: 1.65rem; + font-weight: 700; + letter-spacing: -0.02em; + margin: 0 0 0.35rem; +} + +h2 { + font-size: 1.05rem; + font-weight: 600; + margin: 0 0 0.75rem; + color: var(--muted); +} + +p { + margin: 0.4rem 0; + color: var(--muted); + font-size: 0.92rem; +} + +.mono { + font-family: "JetBrains Mono", ui-monospace, monospace; + font-size: 0.78rem; + word-break: break-all; +} + +.grid { + display: grid; + gap: 1rem; +} + +@media (min-width: 880px) { + .grid-2 { + grid-template-columns: 1fr 1fr; + } +} + +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + padding: 1.1rem 1.2rem; +} + +.badge { + display: inline-block; + padding: 0.2rem 0.55rem; + border-radius: 6px; + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.badge.ok { + background: rgba(62, 207, 142, 0.15); + color: var(--ok); +} +.badge.bad { + background: rgba(255, 92, 92, 0.12); + color: var(--danger); +} +.badge.warn { + background: rgba(245, 166, 35, 0.12); + color: var(--warn); +} + +.badge.neutral { + background: rgba(61, 156, 249, 0.14); + color: #7eb6f0; +} + +.tab-bar { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-bottom: 1.25rem; +} + +button.tab { + border-radius: 999px; + padding: 0.4rem 1rem; + font-size: 0.82rem; + background: #0a0d12; +} + +button.tab.active { + border-color: var(--accent); + background: rgba(61, 156, 249, 0.12); + color: var(--text); +} + +.audit-table-wrap { + overflow-x: auto; + margin-top: 0.75rem; + border: 1px solid var(--border); + border-radius: 10px; +} + +table.audit-table { + width: 100%; + border-collapse: collapse; + font-size: 0.78rem; +} + +table.audit-table th, +table.audit-table td { + text-align: left; + padding: 0.45rem 0.55rem; + border-bottom: 1px solid var(--border); + vertical-align: top; +} + +table.audit-table th { + color: var(--muted); + font-weight: 600; + background: #0a0d12; +} + +table.audit-table tr:last-child td { + border-bottom: none; +} + +button { + font-family: inherit; + cursor: pointer; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--surface); + color: var(--text); + padding: 0.55rem 0.9rem; + font-size: 0.88rem; + font-weight: 600; + transition: border-color 0.15s, background 0.15s; +} + +button:hover:not(:disabled) { + border-color: var(--accent-dim); + background: #1a2330; +} + +button:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +button.primary { + background: linear-gradient(165deg, #3d9cf9, #2563eb); + border-color: transparent; + color: #fff; +} + +button.primary:hover:not(:disabled) { + filter: brightness(1.06); +} + +button.danger { + border-color: #8b3030; + color: #ffb4b4; +} + +.row { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; +} + +label { + display: block; + font-size: 0.78rem; + color: var(--muted); + margin-bottom: 0.25rem; +} + +input, +select { + width: 100%; + padding: 0.45rem 0.55rem; + border-radius: 8px; + border: 1px solid var(--border); + background: #0a0d12; + color: var(--text); + font-family: "JetBrains Mono", monospace; + font-size: 0.82rem; +} + +.field { + margin-bottom: 0.75rem; +} + +.err { + color: var(--danger); + font-size: 0.85rem; + margin-top: 0.5rem; +} + +.ok-msg { + color: var(--ok); + font-size: 0.85rem; + margin-top: 0.5rem; +} + +hr.sep { + border: none; + border-top: 1px solid var(--border); + margin: 1rem 0; +} + +ul.compact { + margin: 0; + padding-left: 1.1rem; + color: var(--muted); + font-size: 0.85rem; +} diff --git a/lazer/cardano/inventory-edge-protocol/web/src/main.tsx b/lazer/cardano/inventory-edge-protocol/web/src/main.tsx new file mode 100644 index 00000000..0c5505fb --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/web/src/main.tsx @@ -0,0 +1,11 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import App from "./App"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/lazer/cardano/inventory-edge-protocol/web/src/vault_display.tsx b/lazer/cardano/inventory-edge-protocol/web/src/vault_display.tsx new file mode 100644 index 00000000..83749b7f --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/web/src/vault_display.tsx @@ -0,0 +1,266 @@ +import type { ReactNode } from "react"; + +export type ShadowNftLite = { + policyId: string; + nameHex: string; + nameUtf8: string; + feedId: number; +}; + +export type NativeNftLite = { + policyId: string; + nameHex: string; + nameUtf8?: string; +}; + +export type DemoFeedLite = { + feedId: number; + uiTitle: string; + slot: string; +}; + +export type VaultForDisplay = { + ref: string; + txHash: string; + outputIndex: string; + lovelace: string; + datum: { + debtLovelace: string; + collateralQty: string; + feedId: string; + nftPolicyHex: string; + nftNameHex: string; + hedge: null | { strikeRaw: string; payoutLovelace: string }; + }; +}; + +export function formatTAdaFromLovelace(lovelace: string): string { + try { + const n = BigInt(lovelace || "0"); + const whole = n / 1_000_000n; + const frac = n % 1_000_000n; + const fracStr = frac.toString().padStart(6, "0").replace(/0+$/, ""); + return fracStr ? `${whole}.${fracStr}` : `${whole}`; + } catch { + return lovelace || "0"; + } +} + +function tryDecodeNameFromHex(hex: string): string | null { + const clean = hex.replace(/\s/g, "").toLowerCase(); + if (!clean || clean.length % 2 !== 0 || !/^[0-9a-f]+$/.test(clean)) { + return null; + } + try { + const bytes = new Uint8Array(clean.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); + } + const dec = new TextDecoder("utf-8", { fatal: false }).decode(bytes); + const t = dec.trim(); + if (t.length < 2 || t.length > 80) return null; + if (!/^[\p{L}\p{N}\s._\-/+]+$/u.test(t)) return null; + return t; + } catch { + return null; + } +} + +export function resolveVaultNftSummary( + v: VaultForDisplay, + shadowNfts: ShadowNftLite[], + demoFeeds: DemoFeedLite[] | null, + nativeNfts?: NativeNftLite[] | null, +): { + title: string; + tagline: string; + feedLabel: string; + knownFromWallet: boolean; + slotHint: string | null; +} { + const pol = v.datum.nftPolicyHex.replace(/\s/g, "").toLowerCase(); + const nm = v.datum.nftNameHex.replace(/\s/g, "").toLowerCase(); + const match = (s: { policyId: string; nameHex: string }) => + s.policyId.toLowerCase() === pol && s.nameHex.toLowerCase() === nm; + const shadowHit = shadowNfts.find(match); + const nativeHit = nativeNfts?.find(match); + const fromHex = tryDecodeNameFromHex(v.datum.nftNameHex); + const fid = Number(v.datum.feedId); + const feedRow = demoFeeds?.find((f) => f.feedId === fid); + const feedLabel = feedRow?.uiTitle ?? `Precio Pyth · id ${v.datum.feedId}`; + const slotHint = feedRow?.slot ?? null; + + if (shadowHit) { + return { + title: shadowHit.nameUtf8 || fromHex || "NFT sombra", + tagline: + "Mismo activo que figura en tu lista de NFTs demo (sombra). Está bloqueado en el contrato.", + feedLabel, + knownFromWallet: true, + slotHint, + }; + } + if (nativeHit) { + return { + title: nativeHit.nameUtf8 ?? fromHex ?? "Activo nativo", + tagline: + "Coincide con un token en tu wallet Lucid (lista completa de nativos).", + feedLabel, + knownFromWallet: true, + slotHint, + }; + } + return { + title: fromHex || "Activo tokenizado (NFT)", + tagline: + "Identificadores del token debajo (policy + nombre). Si lo minteaste con esta demo, suele coincidir con un “shadow NFT”.", + feedLabel, + knownFromWallet: false, + slotHint, + }; +} + +type VaultContentsCardProps = { + vault: VaultForDisplay; + shadowNfts: ShadowNftLite[]; + /** Lista Lucid completa (opcional) para reconocer nombres fuera de shadow. */ + nativeNfts?: NativeNftLite[] | null; + demoFeeds: DemoFeedLite[] | null; + /** Título de sección encima de la tarjeta */ + heading?: string; + intro?: ReactNode; + defaultOpenTechnical?: boolean; +}; + +export function VaultContentsCard({ + vault, + shadowNfts, + nativeNfts = null, + demoFeeds, + heading = "Qué hay dentro del vault", + intro, + defaultOpenTechnical = false, +}: VaultContentsCardProps) { + const s = resolveVaultNftSummary(vault, shadowNfts, demoFeeds, nativeNfts); + const hasHedge = Boolean(vault.datum.hedge); + const ada = formatTAdaFromLovelace(vault.lovelace); + + return ( +
+ {heading &&

{heading}

} + {intro} +
+
+ NFT +
+
+

{s.title}

+

{s.tagline}

+
+ {s.feedLabel} + {s.knownFromWallet ? ( + Reconocido en wallet demo + ) : ( + Solo datos on-chain + )} + {hasHedge ? ( + Cobertura activa + ) : ( + Sin cobertura + )} +
+
+
+ +
+
+ tADA en esta posición + {ada} + Lovelace en el UTxO: {vault.lovelace} +
+
+ Principal préstamo (datum) + + {formatTAdaFromLovelace(vault.datum.debtLovelace)} tADA + + {vault.datum.debtLovelace} lovelace +
+
+ Cantidad colateral (escala) + {vault.datum.collateralQty} + Usada en la fórmula de liquidación +
+
+ Cobertura (seguro) + + {hasHedge ? "Sí" : "No"} + + + {hasHedge + ? `Tope ${vault.datum.hedge!.payoutLovelace} lovelace` + : "Activar en Seguros"} + +
+
+ +
+ Datos técnicos (on-chain) +
+
Referencia UTxO
+
{vault.ref}
+
Transacción · índice
+
+ {vault.txHash} · #{vault.outputIndex} +
+
Policy id (hex)
+
{vault.datum.nftPolicyHex}
+
Nombre del activo (hex)
+
{vault.datum.nftNameHex}
+
Feed Pyth (id)
+
{vault.datum.feedId}
+ {hasHedge && ( + <> +
Strike raw
+
{vault.datum.hedge!.strikeRaw}
+
Payout lovelace
+
{vault.datum.hedge!.payoutLovelace}
+ + )} +
+
+
+ ); +} + +export function VaultMiniCard({ + vault, + shadowNfts, + nativeNfts = null, + demoFeeds, + selected, + onSelect, +}: { + vault: VaultForDisplay; + shadowNfts: ShadowNftLite[]; + nativeNfts?: NativeNftLite[] | null; + demoFeeds: DemoFeedLite[] | null; + selected: boolean; + onSelect: () => void; +}) { + const s = resolveVaultNftSummary(vault, shadowNfts, demoFeeds, nativeNfts); + return ( + + ); +} diff --git a/lazer/cardano/inventory-edge-protocol/web/tsconfig.json b/lazer/cardano/inventory-edge-protocol/web/tsconfig.json new file mode 100644 index 00000000..ac4f23c0 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/web/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "isolatedModules": true, + "resolveJsonModule": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/lazer/cardano/inventory-edge-protocol/web/vite.config.ts b/lazer/cardano/inventory-edge-protocol/web/vite.config.ts new file mode 100644 index 00000000..913a8732 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/web/vite.config.ts @@ -0,0 +1,40 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +const dir = path.dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + plugins: [react()], + root: dir, + server: { + port: 5173, + proxy: { + "/api": { + target: "http://127.0.0.1:8787", + changeOrigin: true, + configure(proxy) { + proxy.on("error", (_err, _req, res) => { + const r = res as { + writeHead?: (c: number, h: Record) => void; + end?: (b: string) => void; + headersSent?: boolean; + }; + if (typeof r.writeHead === "function" && !r.headersSent) { + r.writeHead(502, { "Content-Type": "application/json" }); + r.end( + JSON.stringify({ + error: + "No hay API en :8787. En otra terminal: npm run dev:api o npm run demo.", + code: "API_UNAVAILABLE", + }), + ); + } + }); + }, + }, + }, + }, +}); From 918601012bff9309cee412c3090f110453e90334 Mon Sep 17 00:00:00 2001 From: JuanCasimiro Date: Sun, 22 Mar 2026 19:53:44 -0300 Subject: [PATCH 2/5] =?UTF-8?q?feat(lazer/cardano):=20inventory-edge-proto?= =?UTF-8?q?col=20=E2=80=94=20judge=20docs,=20env=20loading,=20pool/mint=20?= =?UTF-8?q?UX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README/PITCH: static review path for judges, CLI vs UI, operator pitfalls - server/load_env.ts: load .env from package root; harden stdout under concurrently - mint shadow subprocess, pool chain state, API/UI and onchain lock updates Made-with: Cursor --- .../cardano/inventory-edge-protocol/PITCH.md | 6 +- .../cardano/inventory-edge-protocol/README.md | 65 +- .../lib/evolution_client.ts | 2 +- .../lib/judge_store.ts | 350 +---------- .../lib/mint_shadow.ts | 135 ++-- .../lib/mint_shadow_subprocess.ts | 66 ++ .../lib/pool_chain_state.ts | 118 ++++ .../lib/pool_onchain.ts | 84 +-- .../lib/transactions.ts | 83 +-- .../lib/wallet_shadow_nfts.ts | 74 ++- .../onchain/aiken.lock | 2 +- .../inventory-edge-protocol/package.json | 3 +- .../scripts/mint_shadow_child.ts | 33 + .../inventory-edge-protocol/server/index.ts | 295 ++++----- .../server/load_env.ts | 24 + .../inventory-edge-protocol/web/src/App.tsx | 595 ++++++++++++++---- .../web/vite.config.ts | 2 +- 17 files changed, 1106 insertions(+), 831 deletions(-) create mode 100644 lazer/cardano/inventory-edge-protocol/lib/mint_shadow_subprocess.ts create mode 100644 lazer/cardano/inventory-edge-protocol/lib/pool_chain_state.ts create mode 100644 lazer/cardano/inventory-edge-protocol/scripts/mint_shadow_child.ts create mode 100644 lazer/cardano/inventory-edge-protocol/server/load_env.ts diff --git a/lazer/cardano/inventory-edge-protocol/PITCH.md b/lazer/cardano/inventory-edge-protocol/PITCH.md index b9f01e2c..75c98d2f 100644 --- a/lazer/cardano/inventory-edge-protocol/PITCH.md +++ b/lazer/cardano/inventory-edge-protocol/PITCH.md @@ -37,11 +37,15 @@ The three **shadow** assets (gold / WTI / BTC) map to **Pyth Lazer feed ids** in ## How to demo (operators) +**Judges:** the scoring surface is the **Aiken vault + blueprint** — see the “For judges” section in [`README.md`](./README.md). The steps below are for live PreProd demos only. + 1. `npm run build:onchain` -2. Set `BLOCKFROST_PROJECT_ID`, `CARDANO_MNEMONIC`, `ACCESS_TOKEN` in `.env` (see `.env.example`). +2. Set `BLOCKFROST_PROJECT_ID` (or `MAESTRO_API_KEY`), `CARDANO_MNEMONIC`, and `ACCESS_TOKEN` in `.env` (see `.env.example`). Skipping Blockfrost/Maestro usually breaks Evolution’s chain simulation (Koios-only is brittle). 3. `npm run mock-assets` → copy `SHADOW_POLICY_ID` / `SHADOW_NAME_HEX` for one NFT. 4. `npm run tx:open-vault` → locks NFT + vault datum at the script. 5. Optional: `npm run tx:hedge` → sets insurance fields. 6. `npm run tx:liquidate` → Pyth payload + `Liquidate` when the vault is underwater. +**Beyond the CLI:** `package.json` does not ship scripts for **Close**, **Adjust**, **ClaimInsurance**, or **pool** actions — those are available from the **judge API + Vite UI** via `npm run demo` (same `.env`), which calls into `lib/transactions.ts` / `lib/pool_onchain.ts`. + This is **PreProd-only** MVP code: do not reuse keys or mnemonics from demos on mainnet. diff --git a/lazer/cardano/inventory-edge-protocol/README.md b/lazer/cardano/inventory-edge-protocol/README.md index 9164a4f9..b58057eb 100644 --- a/lazer/cardano/inventory-edge-protocol/README.md +++ b/lazer/cardano/inventory-edge-protocol/README.md @@ -4,6 +4,23 @@ RealFi / RWA hackathon MVP: a **Cardano vault** that locks a demo NFT, tracks sy **Narrative deck (why it matters):** see [PITCH.md](./PITCH.md). +**Location in `pyth-examples`:** `lazer/cardano/inventory-edge-protocol/` (from repo root, `cd` into this folder before `npm install`). + +### For judges (static review first) + +Contract logic is reviewable **without** running the browser demo or holding keys: + +| Priority | Artifact | Why it matters | +|----------|----------|----------------| +| **1** | [`onchain/validators/vault.ak`](onchain/validators/vault.ak) | Datum/redeemers, `pyth.get_updates`, liquidation and insurance checks. | +| **2** | [`onchain/plutus.json`](onchain/plutus.json) | Committed blueprint; rebuild with `npm run build:onchain` if you want to verify it matches sources. | +| **3** | [`onchain/validators/liquidity_pool.ak`](onchain/validators/liquidity_pool.ak) | tADA pool validator used with the pool APIs / UI. | +| **4** | [`PITCH.md`](./PITCH.md) | Product story, honest limits, alignment with Pyth Lazer examples. | + +**Optional, no secrets:** `npm install` → `npm run build:onchain` (needs [Aiken](https://aiken-lang.org) **1.1+**; first build may need network to fetch the git dependency in `onchain/aiken.toml`) → `npx tsc` (typechecks `lib/`, `scripts/`, `server/` only; the Vite app under `web/` is separate). + +The **judge UI** (`npm run demo`) is for operator demos only; it is **not** required to score the on-chain design. + --- ## 1. What on-chain contracts did we ship? @@ -28,7 +45,7 @@ We do **not** deploy a separate Pyth contract. We integrate the **existing Pyth |----------|--------| | **Pyth Lazer governance `PolicyId` (PreProd, hex)** | `d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6` | -That matches the reference integration in [`pyth-examples/lazer/cardano/lazer-rwa`](https://github.com/pyth-network/pyth-examples/tree/main/lazer/cardano/lazer-rwa). +That is the same **PreProd governance policy** used by the official Pyth Lazer Cardano integration in [`pyth-network/pyth-lazer-cardano`](https://github.com/pyth-network/pyth-lazer-cardano) (this example depends on that library via `onchain/aiken.toml`). For product docs, see [Pyth Lazer](https://docs.pyth.network/lazer). **Shadow NFTs** are **native assets** minted off-chain (Lucid + signing policy), **not** minted by this Aiken project. Metadata is **CIP-25** label `721` with fields such as `pyth_lazer_feed_id` and `inventory_edge_class` (see `scripts/mock_assets.ts`). @@ -67,9 +84,9 @@ That matches the reference integration in [`pyth-examples/lazer/cardano/lazer-rw | Layer | Tech | Used for | |-------|------|----------| | Mint + metadata | **Lucid** + **Blockfrost** (or **Maestro**) | `npm run mock-assets` or judge UI mint — PreProd NFT demo. | -| Vault txs + Pyth witness | **Evolution SDK** + **Blockfrost o Maestro** (misma prioridad que Lucid; Koios solo si no hay ninguno) | `openVault`, `applyHedge`, `adjustDebt`, `closeVault`, `liquidate`, `claimInsurance` en [`lib/transactions.ts`](lib/transactions.ts). | -| Pool de liquidez (tADA) | **Aiken** [`onchain/validators/liquidity_pool.ak`](onchain/validators/liquidity_pool.ak) + **Lucid** (depósito) + **Evolution** (retiro Plutus V3) [`lib/pool_onchain.ts`](lib/pool_onchain.ts) | Datum = tu payment key hash; solo vos podés gastar. API: `POST /api/pool/onchain/deposit-percent`, `GET /api/pool/onchain/positions`, `POST /api/pool/onchain/withdraw-all`. | -| Judge UI | **Vite + React** + **Express** (local API) | `npm run demo` — mint, vault, pool on-chain/mock, hedge, Pyth risk, liquidate / claim. | +| Vault txs + Pyth witness | **Evolution SDK** + **Blockfrost** or **Maestro** (same priority as Lucid; **Koios** only if neither is available) | `openVault`, `applyHedge`, `adjustDebt`, `closeVault`, `liquidate`, `claimInsurance` in [`lib/transactions.ts`](lib/transactions.ts). | +| Liquidity pool (tADA) | **Aiken** [`onchain/validators/liquidity_pool.ak`](onchain/validators/liquidity_pool.ak) + **Lucid** (deposit) + **Evolution** (Plutus v3 withdraw) [`lib/pool_onchain.ts`](lib/pool_onchain.ts) | Datum locks to your payment key hash (only you can spend). API: `POST /api/pool/onchain/deposit-percent`, `GET /api/pool/onchain/positions`, `POST /api/pool/onchain/withdraw-all`. | +| Judge UI | **Vite + React** + **Express** (local API) | `npm run demo` — mint, vault, pool balances read from chain (script + vault datums), hedge, Pyth risk, liquidate / claim. Optional local audit: `data/judge_audit.json`. | | Oracle payload | **@pythnetwork/pyth-lazer-sdk** | Fetch latest update in Solana binary encoding. | | Pyth state resolution | **@pythnetwork/pyth-lazer-cardano-js** | `getPythState` / `getPythScriptHash`. | @@ -104,7 +121,8 @@ inventory-edge-protocol/ ├── aiken.toml ├── plutus.json ← Blueprint (commit after build) └── validators/ - └── vault.ak ← Core contract + ├── vault.ak ← Core vault contract + └── liquidity_pool.ak ← tADA pool validator ``` --- @@ -114,12 +132,24 @@ inventory-edge-protocol/ **Prerequisites:** Node 20+, [Aiken](https://aiken-lang.org) 1.1+, PreProd **tADA**, **Pyth Lazer** `ACCESS_TOKEN`, and **Blockfrost Preprod** `BLOCKFROST_PROJECT_ID` or **Maestro** `MAESTRO_API_KEY`. ```bash -cd inventory-edge-protocol +cd lazer/cardano/inventory-edge-protocol # if you cloned pyth-examples from its root npm install npm run build:onchain # produces onchain/plutus.json npx tsc # optional typecheck ``` +**`package.json` scripts:** + +| Script | Purpose | +|--------|---------| +| `build:onchain` | `aiken build` in `onchain/` | +| `mock-assets` | Mint demo shadow NFTs (`scripts/mock_assets.ts`) | +| `tx:open-vault` / `tx:hedge` / `tx:liquidate` | CLI flows in `scripts/` | +| `dev:api` | Express judge API only (`server/index.ts`) | +| `dev:web` | Vite dev server for `web/` | +| `demo` | API + web together (`concurrently`) | +| `build:web` / `preview:web` | Production build / preview for the UI | + **Environment:** copy `.env.example` → `.env` and fill secrets (never commit `.env`). | Variable | Purpose | @@ -130,6 +160,8 @@ npx tsc # optional typecheck | `SHADOW_POLICY_ID` / `SHADOW_NAME_HEX` | After mint, to open vault | | `SHADOW_ASSET` | `XAU_USD` \| `WTI_USD` \| `BTC_USD` (feeds in `lib/feeds.ts`) | +**Loader:** `server/load_env.ts` reads `.env` from the **package root** (parent of `server/`), not from whatever the current working directory is — useful when debugging “API sees no env”. + **Suggested demo sequence:** ```bash @@ -148,22 +180,33 @@ npm run demo Open [http://127.0.0.1:5173](http://127.0.0.1:5173). The UI proxies `/api` to `http://127.0.0.1:8787` (override with `JUDGE_API_PORT`). Signing stays server-side via `CARDANO_MNEMONIC` — suitable for hackathon booths, not for production custody. +**CLI vs UI:** `package.json` only wires **open-vault**, **hedge**, and **liquidate** as `tx:*` scripts. **Close**, **adjust debt**, **claim insurance**, and **liquidity pool** flows live in [`lib/transactions.ts`](lib/transactions.ts) / [`lib/pool_onchain.ts`](lib/pool_onchain.ts) and are driven from the **judge API** when you run `npm run demo` (or via your own `tsx` one-liner). + --- ## 6. External references (for comparison) -- **Pyth Lazer Cardano + Aiken:** [`pyth-lazer-cardano`](https://github.com/pyth-network/pyth-lazer-cardano) (dependency in `onchain/aiken.toml`). -- **Reference app in this repo:** `lazer/cardano/lazer-rwa` — minimal Pyth verify tx + `rwa_threshold.ak` threshold pattern. +- **Pyth Lazer Cardano + Aiken:** [`pyth-lazer-cardano`](https://github.com/pyth-network/pyth-lazer-cardano) (dependency in `onchain/aiken.toml`; `pyth.get_updates` and PreProd policy id come from there). +- **Pyth Lazer docs:** [docs.pyth.network — Lazer](https://docs.pyth.network/lazer) (access token, feeds, integration concepts). --- -## 7. Honest MVP limits (read before scoring) +## 7. Honest MVP limits & pitfalls (read before scoring) -- **BTC / WTI feed ids** in `lib/feeds.ts` may be **placeholders**; **XAU (346)** matches the reference app. Confirm ids via Pyth Lazer symbols API before claiming production accuracy. +**Design / product** + +- **BTC / WTI feed ids** in `lib/feeds.ts` may be **placeholders**; **XAU (346)** is aligned with typical Lazer metal feeds — confirm every id via the Lazer symbols API before production use. - **Insurance payout:** on-chain we **authorize** `ClaimInsurance` when price < strike; routing exact **payout ADA** to outputs is left as a product-layer refinement (datum already stores `payout_lovelace` for demos). -- **No dedicated CLI** for `ClaimInsurance` in `package.json` — redeemer exists on-chain; add a script if you need a live demo of that path. +- **`pyth-lazer-cardano` in `aiken.toml` tracks `main` on GitHub** — reproducible builds depend on fetch time; the **committed** `onchain/plutus.json` (+ lockfile) is what reviewers should treat as the shipped artifact unless they intentionally rebuild. - **PreProd only** — do not reuse demo mnemonics or API keys on mainnet. +**If someone runs the live demo (operators, not required for judges)** + +- **No `BLOCKFROST_PROJECT_ID` or `MAESTRO_API_KEY`:** Lucid mint and Evolution simulation paths often break; Koios-only setups frequently hit `evaluateTx` failures (see comments in [`.env.example`](./.env.example)). +- **No `ACCESS_TOKEN` (Pyth Lazer):** `Liquidate` and `ClaimInsurance` cannot be fully exercised — both require the pull-model witness. +- **`SHADOW_POLICY_ID` / `SHADOW_NAME_HEX` unset:** `tx:open-vault` and related flows fail until set after `mock-assets`. +- **`ClaimInsurance`:** implemented on-chain and in [`lib/transactions.ts`](lib/transactions.ts); there is **no** `tx:claim-insurance` script — use the judge **API/UI** under `npm run demo` or call the library from a small script. + --- ## 8. License diff --git a/lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts b/lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts index 5b3e82eb..cc1053cf 100644 --- a/lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts +++ b/lazer/cardano/inventory-edge-protocol/lib/evolution_client.ts @@ -9,7 +9,7 @@ const KOIOS_PREPROD = "https://preprod.koios.rest/api/v1"; export type PreprodChainBackend = "blockfrost" | "maestro" | "koios"; /** - * Misma prioridad que `newLucidPreprod`: Blockfrost → Maestro → Koios. + * Misma prioridad que Lucid (`withLucidPreprod`): Blockfrost → Maestro → Koios. * Koios depende de `/ogmios` en el servidor; sin token o con carga, suele fallar en evaluateTx. */ export function preprodEvolutionProviderConfig(): diff --git a/lazer/cardano/inventory-edge-protocol/lib/judge_store.ts b/lazer/cardano/inventory-edge-protocol/lib/judge_store.ts index 75405235..766963f9 100644 --- a/lazer/cardano/inventory-edge-protocol/lib/judge_store.ts +++ b/lazer/cardano/inventory-edge-protocol/lib/judge_store.ts @@ -1,22 +1,14 @@ /** - * Pool de liquidez mock (off-chain): préstamos, reservas de seguro, beneficios. - * El datum on-chain sigue usando debt_lovelace; el pool demo financia y contabiliza. + * Auditoría local opcional (`data/judge_audit.json`). Los balances del pool salen de la cadena + * (`pool_chain_state.ts`), no de acá. */ import fs from "node:fs"; import path from "node:path"; const DATA_DIR = path.join(process.cwd(), "data"); const AUDIT_PATH = path.join(DATA_DIR, "judge_audit.json"); -const POOL_PATH = path.join(DATA_DIR, "mock_insurance_pool.json"); const MAX_AUDIT = 400; -/** Interés demo sobre cada amortización de principal (bps). */ -export const LOAN_REPAY_INTEREST_BPS = 100n; -/** Excedente simulado de venta de colateral: % del principal que ingresa al pool. */ -export const LIQUIDATION_POOL_SURPLUS_BPS = 800n; -/** Fee mock que el pool retiene al pagar una cobertura (bps del monto reservado). */ -export const INSURANCE_POOL_FEE_BPS = 300n; - export type AuditEvent = { ts: string; kind: string; @@ -25,348 +17,10 @@ export type AuditEvent = { extra?: Record; }; -export type PoolReservation = { - nftPolicyHex: string; - nftNameHex: string; - payoutLovelace: string; - vaultRef: string; -}; - -export type MockPoolState = { - availableLovelace: string; - encumberedLovelace: string; - /** Principal colocado en préstamos (mock), alineado a datum.debt agregado. */ - deployedToLoansLovelace: string; - totalDepositedLovelace: string; - totalPaidOutLovelace: string; - totalRepaidPrincipalLovelace: string; - profitsFromLoansLovelace: string; - profitsFromInsuranceLovelace: string; - reservations: PoolReservation[]; - /** key policy|name → outstanding principal (string lovelace) */ - outstandingLoans: Record; -}; - function ensureDir(): void { if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true }); } -function defaultPool(): MockPoolState { - return { - availableLovelace: "0", - encumberedLovelace: "0", - deployedToLoansLovelace: "0", - totalDepositedLovelace: "0", - totalPaidOutLovelace: "0", - totalRepaidPrincipalLovelace: "0", - profitsFromLoansLovelace: "0", - profitsFromInsuranceLovelace: "0", - reservations: [], - outstandingLoans: {}, - }; -} - -let poolMem: MockPoolState | null = null; - -export function nftLoanKey(nftPolicyHex: string, nftNameHex: string): string { - return `${nftPolicyHex.toLowerCase()}|${nftNameHex.toLowerCase()}`; -} - -function migratePool(raw: unknown): MockPoolState { - const o = - raw && typeof raw === "object" ? (raw as Record) : {}; - const b = defaultPool(); - const reservations = Array.isArray(o.reservations) - ? (o.reservations as PoolReservation[]) - : []; - const outstandingLoans = - typeof o.outstandingLoans === "object" && - o.outstandingLoans !== null && - !Array.isArray(o.outstandingLoans) - ? (o.outstandingLoans as Record) - : {}; - return { - availableLovelace: String(o.availableLovelace ?? b.availableLovelace), - encumberedLovelace: String(o.encumberedLovelace ?? b.encumberedLovelace), - deployedToLoansLovelace: String( - o.deployedToLoansLovelace ?? b.deployedToLoansLovelace, - ), - totalDepositedLovelace: String( - o.totalDepositedLovelace ?? b.totalDepositedLovelace, - ), - totalPaidOutLovelace: String( - o.totalPaidOutLovelace ?? b.totalPaidOutLovelace, - ), - totalRepaidPrincipalLovelace: String( - o.totalRepaidPrincipalLovelace ?? b.totalRepaidPrincipalLovelace, - ), - profitsFromLoansLovelace: String( - o.profitsFromLoansLovelace ?? b.profitsFromLoansLovelace, - ), - profitsFromInsuranceLovelace: String( - o.profitsFromInsuranceLovelace ?? b.profitsFromInsuranceLovelace, - ), - reservations, - outstandingLoans, - }; -} - -export function loadPool(): MockPoolState { - if (poolMem) return poolMem; - ensureDir(); - if (!fs.existsSync(POOL_PATH)) { - poolMem = defaultPool(); - return poolMem; - } - try { - const raw = JSON.parse(fs.readFileSync(POOL_PATH, "utf8")) as unknown; - poolMem = migratePool(raw); - return poolMem; - } catch { - poolMem = defaultPool(); - return poolMem; - } -} - -export function savePool(p: MockPoolState): void { - poolMem = p; - ensureDir(); - fs.writeFileSync(POOL_PATH, JSON.stringify(p, null, 2), "utf8"); -} - -export function poolDeposit(lovelace: bigint): MockPoolState { - const p = loadPool(); - const av = BigInt(p.availableLovelace) + lovelace; - const td = BigInt(p.totalDepositedLovelace) + lovelace; - p.availableLovelace = av.toString(); - p.totalDepositedLovelace = td.toString(); - savePool(p); - return p; -} - -/** Tras retiro on-chain del script pool: baja liquidez disponible del mock (sin ir a negativo). */ -export function poolWithdraw(lovelace: bigint): MockPoolState { - if (lovelace <= 0n) return loadPool(); - const p = loadPool(); - const av = BigInt(p.availableLovelace); - const sub = lovelace > av ? av : lovelace; - p.availableLovelace = (av - sub).toString(); - savePool(p); - return p; -} - -/** Llamar solo tras openVault on-chain OK: saca liquidez del pool para el principal del préstamo. */ -export function poolCommitLoan(params: { - nftPolicyHex: string; - nftNameHex: string; - loanLovelace: bigint; -}): void { - if (params.loanLovelace <= 0n) return; - const p = loadPool(); - const av = BigInt(p.availableLovelace); - if (av < params.loanLovelace) { - throw new Error( - "poolCommitLoan: liquidez insuficiente (revisá pre-checks del servidor)", - ); - } - const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); - if (p.outstandingLoans[key] != null) { - throw new Error("poolCommitLoan: ya hay préstamo activo para este NFT"); - } - p.availableLovelace = (av - params.loanLovelace).toString(); - p.deployedToLoansLovelace = ( - BigInt(p.deployedToLoansLovelace) + params.loanLovelace - ).toString(); - p.outstandingLoans[key] = params.loanLovelace.toString(); - savePool(p); -} - -/** - * Tras adjustDebt: más deuda consume pool; menos devuelve principal + interés demo al pool. - */ -export function poolOnDebtAdjusted(params: { - nftPolicyHex: string; - nftNameHex: string; - oldDebtLovelace: bigint; - newDebtLovelace: bigint; -}): void { - const oldD = params.oldDebtLovelace; - const newD = params.newDebtLovelace; - if (oldD === newD) return; - - const p = loadPool(); - const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); - const deployed = BigInt(p.deployedToLoansLovelace); - - if (newD > oldD) { - const more = newD - oldD; - const av = BigInt(p.availableLovelace); - if (av < more) { - throw new Error("poolOnDebtAdjusted: liquidez insuficiente"); - } - p.availableLovelace = (av - more).toString(); - p.deployedToLoansLovelace = (deployed + more).toString(); - p.outstandingLoans[key] = newD.toString(); - savePool(p); - return; - } - - const repaid = oldD - newD; - const interest = (repaid * LOAN_REPAY_INTEREST_BPS) / 10000n; - p.availableLovelace = ( - BigInt(p.availableLovelace) + repaid + interest - ).toString(); - p.deployedToLoansLovelace = (deployed - repaid).toString(); - p.totalRepaidPrincipalLovelace = ( - BigInt(p.totalRepaidPrincipalLovelace) + repaid - ).toString(); - p.profitsFromLoansLovelace = ( - BigInt(p.profitsFromLoansLovelace) + interest - ).toString(); - if (newD === 0n) delete p.outstandingLoans[key]; - else p.outstandingLoans[key] = newD.toString(); - savePool(p); -} - -/** Tras liquidación: colateral “vendido” — recupera principal + excedente demo al pool. */ -export function poolOnLiquidateLoan(params: { - nftPolicyHex: string; - nftNameHex: string; - debtLovelace: bigint; -}): void { - const D = params.debtLovelace; - if (D <= 0n) return; - const p = loadPool(); - const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); - const surplus = (D * LIQUIDATION_POOL_SURPLUS_BPS) / 10000n; - const recovery = D + surplus; - p.availableLovelace = (BigInt(p.availableLovelace) + recovery).toString(); - p.deployedToLoansLovelace = ( - BigInt(p.deployedToLoansLovelace) - D - ).toString(); - p.profitsFromLoansLovelace = ( - BigInt(p.profitsFromLoansLovelace) + surplus - ).toString(); - delete p.outstandingLoans[key]; - savePool(p); -} - -export function poolFinalizeCloseVault(params: { - nftPolicyHex: string; - nftNameHex: string; -}): void { - const p = loadPool(); - const key = nftLoanKey(params.nftPolicyHex, params.nftNameHex); - delete p.outstandingLoans[key]; - savePool(p); -} - -/** - * Liquidez efectiva para una nueva reserva de seguro: disponible + reemplazo de reserva del mismo NFT. - */ -export function poolEffectiveAvailableForInsuranceReserve( - nftPolicyHex: string, - nftNameHex: string, -): bigint { - const p = loadPool(); - let av = BigInt(p.availableLovelace); - const pol = nftPolicyHex.toLowerCase(); - const nm = nftNameHex.toLowerCase(); - const prev = p.reservations.find( - (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, - ); - if (prev) av += BigInt(prev.payoutLovelace); - return av; -} - -/** - * Al contratar cobertura: aparta hasta `payoutRequested` del disponible (mock). - */ -export function poolReserveForHedge(params: { - nftPolicyHex: string; - nftNameHex: string; - vaultRef: string; - payoutRequested: bigint; -}): { reserved: bigint; shortfall: bigint } { - const p = loadPool(); - const pol = params.nftPolicyHex.toLowerCase(); - const nm = params.nftNameHex.toLowerCase(); - const prevIdx = p.reservations.findIndex( - (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, - ); - if (prevIdx >= 0) { - const old = p.reservations[prevIdx]!; - const oldAmt = BigInt(old.payoutLovelace); - p.reservations.splice(prevIdx, 1); - p.encumberedLovelace = (BigInt(p.encumberedLovelace) - oldAmt).toString(); - p.availableLovelace = (BigInt(p.availableLovelace) + oldAmt).toString(); - } - const av = BigInt(p.availableLovelace); - const req = params.payoutRequested; - const reserved = req <= av ? req : av; - const shortfall = req - reserved; - p.availableLovelace = (av - reserved).toString(); - p.encumberedLovelace = (BigInt(p.encumberedLovelace) + reserved).toString(); - p.reservations.push({ - nftPolicyHex: params.nftPolicyHex.toLowerCase(), - nftNameHex: params.nftNameHex.toLowerCase(), - payoutLovelace: reserved.toString(), - vaultRef: params.vaultRef, - }); - savePool(p); - return { reserved, shortfall }; -} - -/** Liquidación o cierre: la reserva vuelve al disponible (no es pago al asegurado). */ -export function poolCancelReservation(params: { - nftPolicyHex: string; - nftNameHex: string; -}): { returned: bigint; found: boolean } { - const p = loadPool(); - const pol = params.nftPolicyHex.toLowerCase(); - const nm = params.nftNameHex.toLowerCase(); - const idx = p.reservations.findIndex( - (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, - ); - if (idx < 0) return { returned: 0n, found: false }; - const r = p.reservations[idx]!; - const amt = BigInt(r.payoutLovelace); - p.reservations.splice(idx, 1); - p.encumberedLovelace = (BigInt(p.encumberedLovelace) - amt).toString(); - p.availableLovelace = (BigInt(p.availableLovelace) + amt).toString(); - savePool(p); - return { returned: amt, found: true }; -} - -export function poolReleaseOnClaim(params: { - nftPolicyHex: string; - nftNameHex: string; -}): { released: bigint; poolFee: bigint; paidOut: bigint; found: boolean } { - const p = loadPool(); - const pol = params.nftPolicyHex.toLowerCase(); - const nm = params.nftNameHex.toLowerCase(); - const idx = p.reservations.findIndex( - (r) => r.nftPolicyHex === pol && r.nftNameHex === nm, - ); - if (idx < 0) - return { released: 0n, poolFee: 0n, paidOut: 0n, found: false }; - const r = p.reservations[idx]!; - const amt = BigInt(r.payoutLovelace); - const poolFee = (amt * INSURANCE_POOL_FEE_BPS) / 10000n; - const paidOut = amt - poolFee; - p.reservations.splice(idx, 1); - p.encumberedLovelace = (BigInt(p.encumberedLovelace) - amt).toString(); - p.availableLovelace = (BigInt(p.availableLovelace) + poolFee).toString(); - p.totalPaidOutLovelace = ( - BigInt(p.totalPaidOutLovelace) + paidOut - ).toString(); - p.profitsFromInsuranceLovelace = ( - BigInt(p.profitsFromInsuranceLovelace) + poolFee - ).toString(); - savePool(p); - return { released: amt, poolFee, paidOut, found: true }; -} - export function readAudit(limit: number): AuditEvent[] { ensureDir(); if (!fs.existsSync(AUDIT_PATH)) return []; diff --git a/lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts b/lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts index ad4c1acd..d9835e66 100644 --- a/lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts +++ b/lazer/cardano/inventory-edge-protocol/lib/mint_shadow.ts @@ -18,8 +18,14 @@ function requireEnv(name: string): string { return v; } -/** Shared Lucid instance (Blockfrost/Maestro) — same UTxO view as mint. */ -export async function newLucidPreprod(): Promise { +/** + * CML WASM is not safe with overlapping use: another request must not call + * `Lucid.new` / wallet / tx while this one is still using an instance. + * Queue the whole create + callback (not only `Lucid.new`). + */ +let lucidOpQueue: Promise = Promise.resolve(); + +async function createLucidPreprod(): Promise { const projectId = process.env.BLOCKFROST_PROJECT_ID; const maestroKey = process.env.MAESTRO_API_KEY; if (projectId) { @@ -37,6 +43,42 @@ export async function newLucidPreprod(): Promise { throw new Error("Set BLOCKFROST_PROJECT_ID or MAESTRO_API_KEY (Preprod)"); } +function rethrowIfLucidWasmPanic(e: unknown): never { + const msg = e instanceof Error ? e.message : String(e); + const low = msg.toLowerCase(); + if ( + low.includes("unreachable") || + low.includes("recursive use") || + low.includes("unsafe aliasing") + ) { + throw new Error( + "Lucid/CML (WASM) falló en runtime. Suele deberse a Node 24+ inestable con lucid-cardano, " + + "o a carga muy paralela. Probá Node 20 o 22 LTS (`nvm use 22`), reiniciá `npm run demo` y una sola pestaña. " + + `Original: ${msg}`, + ); + } + throw e; +} + +/** Run Lucid work (mint, UTxO scan, openVault, …) one request at a time. */ +export async function withLucidPreprod( + fn: (lucid: Lucid) => Promise, +): Promise { + const job = lucidOpQueue.then(async () => { + const lucid = await createLucidPreprod(); + try { + return await fn(lucid); + } catch (e) { + rethrowIfLucidWasmPanic(e); + } + }); + lucidOpQueue = job.then( + () => undefined, + () => undefined, + ); + return job; +} + export type MintShadowResult = { txHash: string; policyId: string; @@ -49,55 +91,56 @@ export type MintShadowResult = { export async function mintShadowNft(slot: DemoSlot): Promise { const mnemonic = requireEnv("CARDANO_MNEMONIC"); - const lucid = await newLucidPreprod(); - lucid.selectWalletFromSeed(mnemonic); + return withLucidPreprod(async (lucid) => { + lucid.selectWalletFromSeed(mnemonic); - const addr = await lucid.wallet.address(); - const details = lucid.utils.getAddressDetails(addr); - if (details.paymentCredential?.type !== "Key") { - throw new Error("Expected key payment credential"); - } + const addr = await lucid.wallet.address(); + const details = lucid.utils.getAddressDetails(addr); + if (details.paymentCredential?.type !== "Key") { + throw new Error("Expected key payment credential"); + } - const key = DEMO_SLOT_TO_KEY[slot]; - const suffix = randomBytes(4).toString("hex"); - const assetName = `Shadow${key}_${suffix}`; - const nameHex = Buffer.from(assetName, "utf8").toString("hex"); + const key = DEMO_SLOT_TO_KEY[slot]; + const suffix = randomBytes(4).toString("hex"); + const assetName = `Shadow${key}_${suffix}`; + const nameHex = Buffer.from(assetName, "utf8").toString("hex"); - const mintingPolicy = lucid.utils.nativeScriptFromJson({ - type: "all", - scripts: [{ type: "sig", keyHash: details.paymentCredential.hash }], - }); - const policyId = lucid.utils.mintingPolicyToId(mintingPolicy); - const unit = policyId + nameHex; + const mintingPolicy = lucid.utils.nativeScriptFromJson({ + type: "all", + scripts: [{ type: "sig", keyHash: details.paymentCredential.hash }], + }); + const policyId = lucid.utils.mintingPolicyToId(mintingPolicy); + const unit = policyId + nameHex; - const meta = SHADOW_ASSETS[key]; - const tx = await lucid - .newTx() - .mintAssets({ [unit]: 1n }) - .attachMintingPolicy(mintingPolicy) - .attachMetadata(721, { - [policyId]: { - [assetName]: { - name: meta.label, - description: meta.description, - pyth_lazer_feed_id: String(PYTH_LAZER_FEEDS[key]), - inventory_edge_class: key, - inventory_edge_slot: slot, + const meta = SHADOW_ASSETS[key]; + const tx = await lucid + .newTx() + .mintAssets({ [unit]: 1n }) + .attachMintingPolicy(mintingPolicy) + .attachMetadata(721, { + [policyId]: { + [assetName]: { + name: meta.label, + description: meta.description, + pyth_lazer_feed_id: String(PYTH_LAZER_FEEDS[key]), + inventory_edge_class: key, + inventory_edge_slot: slot, + }, }, - }, - }) - .complete(); + }) + .complete(); - const signed = await tx.sign().complete(); - const txHash = await signed.submit(); + const signed = await tx.sign().complete(); + const txHash = await signed.submit(); - return { - txHash, - policyId, - assetName, - nameHex, - slot, - assetKey: key, - feedId: PYTH_LAZER_FEEDS[key], - }; + return { + txHash, + policyId, + assetName, + nameHex, + slot, + assetKey: key, + feedId: PYTH_LAZER_FEEDS[key], + }; + }); } diff --git a/lazer/cardano/inventory-edge-protocol/lib/mint_shadow_subprocess.ts b/lazer/cardano/inventory-edge-protocol/lib/mint_shadow_subprocess.ts new file mode 100644 index 00000000..ce7fc8ce --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/mint_shadow_subprocess.ts @@ -0,0 +1,66 @@ +/** + * Mint shadow NFT en un subproceso: instancia WASM de Lucid/CML limpia, sin compartir + * estado con Express ni con otras rutas que usen Lucid en el mismo proceso. + */ +import { spawn } from "node:child_process"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import type { DemoSlot } from "./feeds.js"; +import type { MintShadowResult } from "./mint_shadow.js"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const childScript = path.join(repoRoot, "scripts", "mint_shadow_child.ts"); + +export function mintShadowNftSubprocess(slot: DemoSlot): Promise { + return new Promise((resolve, reject) => { + const child = spawn( + process.execPath, + ["--import", "tsx", childScript, slot], + { + cwd: repoRoot, + env: process.env, + stdio: ["ignore", "pipe", "pipe"], + }, + ); + + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (c: string) => { + stdout += c; + }); + child.stderr.on("data", (c: string) => { + stderr += c; + }); + child.on("error", reject); + child.on("close", (code, signal) => { + if (code !== 0) { + reject( + new Error( + stderr.trim() || + stdout.trim() || + `mint subprocess failed (code ${code}, signal ${signal ?? "none"})`, + ), + ); + return; + } + const lines = stdout.trim().split("\n").filter(Boolean); + const last = lines[lines.length - 1]; + if (!last) { + reject(new Error("mint subprocess: empty stdout")); + return; + } + try { + resolve(JSON.parse(last) as MintShadowResult); + } catch { + reject( + new Error( + `mint subprocess: invalid JSON: ${last.slice(0, 400)}`, + ), + ); + } + }); + }); +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/pool_chain_state.ts b/lazer/cardano/inventory-edge-protocol/lib/pool_chain_state.ts new file mode 100644 index 00000000..b5c55e0c --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/lib/pool_chain_state.ts @@ -0,0 +1,118 @@ +/** + * Estado del pool derivado solo de la cadena: UTxOs del script `liquidity_pool` + * + datums de vaults del usuario (deuda y coberturas). + */ +import type { UTxO } from "@evolution-sdk/evolution/UTxO"; + +import { listPoolDepositsOnChain } from "./pool_onchain.js"; +import { listVaultPositions, readInlineDatum } from "./transactions.js"; +import { decodeVaultDatum } from "./vault_datum_decode.js"; +import { createPreprodSigningClient } from "./evolution_client.js"; +import { paymentKeyHashBytes } from "./vault_address.js"; + +export type PoolReservationRow = { + nftPolicyHex: string; + nftNameHex: string; + payoutLovelace: string; + vaultRef: string; +}; + +/** Compatible con la UI / API previa; métricas “históricas” quedan en 0 (no on-chain). */ +export type PoolStateFromChain = { + /** Suma lovelace en script pool (tu owner). */ + poolScriptTotalLovelace: string; + availableLovelace: string; + encumberedLovelace: string; + deployedToLoansLovelace: string; + totalDepositedLovelace: string; + totalPaidOutLovelace: string; + totalRepaidPrincipalLovelace: string; + profitsFromLoansLovelace: string; + profitsFromInsuranceLovelace: string; + reservations: PoolReservationRow[]; + outstandingLoans: Record; +}; + +function requireMnemonic(): string { + const m = process.env.CARDANO_MNEMONIC?.trim(); + if (!m) throw new Error("Missing CARDANO_MNEMONIC"); + return m; +} + +export function nftLoanKey(nftPolicyHex: string, nftNameHex: string): string { + return `${nftPolicyHex.toLowerCase()}|${nftNameHex.toLowerCase()}`; +} + +export async function getPoolStateFromChain(): Promise { + const mnemonic = requireMnemonic(); + const client = createPreprodSigningClient(mnemonic); + const addr = await client.address(); + const ownerHex = Buffer.from(paymentKeyHashBytes(addr)) + .toString("hex") + .toLowerCase(); + + const poolRows = await listPoolDepositsOnChain(); + let poolTotal = 0n; + for (const r of poolRows) { + poolTotal += BigInt(r.lovelace); + } + + const vaults = await listVaultPositions(); + let deployed = 0n; + let encumbered = 0n; + const reservations: PoolReservationRow[] = []; + const outstandingLoans: Record = {}; + + for (const row of vaults) { + const d = row.datum; + if (d.ownerKeyHashHex.toLowerCase() !== ownerHex) continue; + + deployed += d.debtLovelace; + if (d.debtLovelace > 0n) { + const k = nftLoanKey(d.nftPolicyHex, d.nftNameHex); + const prev = BigInt(outstandingLoans[k] ?? "0"); + outstandingLoans[k] = (prev + d.debtLovelace).toString(); + } + if (d.hedge.tag === "some") { + encumbered += d.hedge.payoutLovelace; + reservations.push({ + nftPolicyHex: d.nftPolicyHex.toLowerCase(), + nftNameHex: d.nftNameHex.toLowerCase(), + payoutLovelace: d.hedge.payoutLovelace.toString(), + vaultRef: row.ref, + }); + } + } + + let available = poolTotal - deployed - encumbered; + if (available < 0n) available = 0n; + + return { + poolScriptTotalLovelace: poolTotal.toString(), + availableLovelace: available.toString(), + encumberedLovelace: encumbered.toString(), + deployedToLoansLovelace: deployed.toString(), + totalDepositedLovelace: "0", + totalPaidOutLovelace: "0", + totalRepaidPrincipalLovelace: "0", + profitsFromLoansLovelace: "0", + profitsFromInsuranceLovelace: "0", + reservations, + outstandingLoans, + }; +} + +/** + * Liquidez efectiva para un nuevo payout de hedge: disponible on-chain + payout ya + * reservado en el datum de esta vault (se reemplaza en la misma tx). + */ +export async function effectiveAvailableForHedgePayout( + vaultUtxo: UTxO, +): Promise { + const st = await getPoolStateFromChain(); + const base = BigInt(st.availableLovelace); + const decoded = decodeVaultDatum(readInlineDatum(vaultUtxo)); + const prev = + decoded.hedge.tag === "some" ? decoded.hedge.payoutLovelace : 0n; + return base + prev; +} diff --git a/lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts b/lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts index 76c107fb..c8e1a1b6 100644 --- a/lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts +++ b/lazer/cardano/inventory-edge-protocol/lib/pool_onchain.ts @@ -13,7 +13,7 @@ import type { UTxO } from "@evolution-sdk/evolution/UTxO"; import { loadBlueprint, liquidityPoolSpendValidator } from "./blueprint.js"; import { encodePoolDatum, redeemerPoolSpend } from "./datum_codec.js"; import { createPreprodSigningClient } from "./evolution_client.js"; -import { newLucidPreprod } from "./mint_shadow.js"; +import { withLucidPreprod } from "./mint_shadow.js"; import { decodePoolDatumOwnerHex } from "./pool_datum_decode.js"; import { readInlineDatum } from "./transactions.js"; import { @@ -42,14 +42,15 @@ export function liquidityPoolAddressBech32(): string { export async function walletTotalLovelaceLucid(): Promise { const mnemonic = requireEnv("CARDANO_MNEMONIC"); - const lucid = await newLucidPreprod(); - lucid.selectWalletFromSeed(mnemonic); - const utxos = await lucid.wallet.getUtxos(); - let total = 0n; - for (const u of utxos) { - total += u.assets.lovelace; - } - return total; + return withLucidPreprod(async (lucid) => { + lucid.selectWalletFromSeed(mnemonic); + const utxos = await lucid.wallet.getUtxos(); + let total = 0n; + for (const u of utxos) { + total += u.assets.lovelace; + } + return total; + }); } export async function depositLiquidityPoolOnChain(params: { @@ -59,41 +60,42 @@ export async function depositLiquidityPoolOnChain(params: { throw new Error("lovelace must be positive"); } const mnemonic = requireEnv("CARDANO_MNEMONIC"); - const lucid = await newLucidPreprod(); - lucid.selectWalletFromSeed(mnemonic); - const userBech32 = await lucid.wallet.address(); - const details = lucid.utils.getAddressDetails(userBech32); - if (details.paymentCredential?.type !== "Key") { - throw new Error("Expected key payment credential for pool datum owner"); - } - const hashHex = details.paymentCredential.hash; - if (hashHex.length !== 56) { - throw new Error(`Unexpected payment key hash length: ${hashHex.length}`); - } - const ownerKh = Uint8Array.from(Buffer.from(hashHex, "hex")); - const poolBech32 = liquidityPoolAddressBech32(); - const inline = toCBORHex(encodePoolDatum(ownerKh)); - - const utxos = await lucid.wallet.getUtxos(); - let total = 0n; - for (const u of utxos) { - total += u.assets.lovelace; - } + return withLucidPreprod(async (lucid) => { + lucid.selectWalletFromSeed(mnemonic); + const userBech32 = await lucid.wallet.address(); + const details = lucid.utils.getAddressDetails(userBech32); + if (details.paymentCredential?.type !== "Key") { + throw new Error("Expected key payment credential for pool datum owner"); + } + const hashHex = details.paymentCredential.hash; + if (hashHex.length !== 56) { + throw new Error(`Unexpected payment key hash length: ${hashHex.length}`); + } + const ownerKh = Uint8Array.from(Buffer.from(hashHex, "hex")); + const poolBech32 = liquidityPoolAddressBech32(); + const inline = toCBORHex(encodePoolDatum(ownerKh)); + + const utxos = await lucid.wallet.getUtxos(); + let total = 0n; + for (const u of utxos) { + total += u.assets.lovelace; + } - const reserve = poolDepositReserveLovelace(); - if (total < params.lovelace + reserve) { - throw new Error( - `Saldo insuficiente: wallet ${total} lovelace; envío ${params.lovelace} + reserva fees ${reserve} = ${params.lovelace + reserve}. Bajá el monto o POOL_DEPOSIT_RESERVE_LOVELACE.`, - ); - } + const reserve = poolDepositReserveLovelace(); + if (total < params.lovelace + reserve) { + throw new Error( + `Saldo insuficiente: wallet ${total} lovelace; envío ${params.lovelace} + reserva fees ${reserve} = ${params.lovelace + reserve}. Bajá el monto o POOL_DEPOSIT_RESERVE_LOVELACE.`, + ); + } - const tx = await lucid - .newTx() - .payToContract(poolBech32, { inline: inline }, { lovelace: params.lovelace }) - .complete(); + const tx = await lucid + .newTx() + .payToContract(poolBech32, { inline: inline }, { lovelace: params.lovelace }) + .complete(); - const signed = await tx.sign().complete(); - return signed.submit(); + const signed = await tx.sign().complete(); + return signed.submit(); + }); } function poolPlutusV3(): PlutusV3 { diff --git a/lazer/cardano/inventory-edge-protocol/lib/transactions.ts b/lazer/cardano/inventory-edge-protocol/lib/transactions.ts index 449af77c..8b71488f 100644 --- a/lazer/cardano/inventory-edge-protocol/lib/transactions.ts +++ b/lazer/cardano/inventory-edge-protocol/lib/transactions.ts @@ -25,7 +25,7 @@ import { createPreprodReadClient, createPreprodSigningClient, } from "./evolution_client.js"; -import { newLucidPreprod } from "./mint_shadow.js"; +import { withLucidPreprod } from "./mint_shadow.js"; import { fetchSolanaFormatUpdate, PYTH_POLICY_ID_HEX } from "./pyth.js"; import { enterpriseVaultAddress, paymentKeyHashBytes } from "./vault_address.js"; import type { UTxO } from "@evolution-sdk/evolution/UTxO"; @@ -70,52 +70,53 @@ export async function openVault(params: { const val = vaultSpendValidator(bp); const vaultBech32 = Address.toBech32(enterpriseVaultAddress(val.hash)); - const lucid = await newLucidPreprod(); - lucid.selectWalletFromSeed(mnemonic); - const userBech32 = await lucid.wallet.address(); - const ownerKh = paymentKeyHashBytes(Address.fromBech32(userBech32)); - const policyHex = normalizeAssetHex(params.nftPolicyHex); const nameHex = normalizeAssetHex(params.nftNameHex); const unit = policyHex + nameHex; - const utxos = await lucid.wallet.getUtxos(); - const nftIn = utxos.find((u) => u.assets[unit] === 1n); - if (!nftIn) { - const sample = utxos.flatMap((u) => - Object.entries(u.assets) - .filter(([k, q]) => k !== "lovelace" && q === 1n) - .map(([k]) => k), - ); - throw new Error( - `No UTxO with NFT (vista Lucid/Blockfrost). Unit esperado: ${unit}. ` + - `NFTs (qty=1) en wallet: ${ - sample.length ? sample.slice(0, 12).join(" | ") : "(ninguno)" - }. ¿El NFT ya está en la vault o usás otra seed?`, - ); - } + return withLucidPreprod(async (lucid) => { + lucid.selectWalletFromSeed(mnemonic); + const userBech32 = await lucid.wallet.address(); + const ownerKh = paymentKeyHashBytes(Address.fromBech32(userBech32)); + + const utxos = await lucid.wallet.getUtxos(); + const nftIn = utxos.find((u) => u.assets[unit] === 1n); + if (!nftIn) { + const sample = utxos.flatMap((u) => + Object.entries(u.assets) + .filter(([k, q]) => k !== "lovelace" && q === 1n) + .map(([k]) => k), + ); + throw new Error( + `No UTxO with NFT (vista Lucid/Blockfrost). Unit esperado: ${unit}. ` + + `NFTs (qty=1) en wallet: ${ + sample.length ? sample.slice(0, 12).join(" | ") : "(ninguno)" + }. ¿El NFT ya está en la vault o usás otra seed?`, + ); + } - const datum = encodeVaultDatum({ - ownerKeyHash: ownerKh, - pythPolicyHex: PYTH_POLICY_ID_HEX, - nftPolicyHex: policyHex, - nftNameHex: nameHex, - debtLovelace: params.debtLovelace, - collateralQty: params.collateralQty, - feedId: BigInt(params.feedId), - hedge: optionNone(), + const datum = encodeVaultDatum({ + ownerKeyHash: ownerKh, + pythPolicyHex: PYTH_POLICY_ID_HEX, + nftPolicyHex: policyHex, + nftNameHex: nameHex, + debtLovelace: params.debtLovelace, + collateralQty: params.collateralQty, + feedId: BigInt(params.feedId), + hedge: optionNone(), + }); + + const inlineDatum = toCBORHex(datum); + + const tx = await lucid + .newTx() + .collectFrom([nftIn]) + .payToContract(vaultBech32, { inline: inlineDatum }, nftIn.assets) + .complete(); + + const signed = await tx.sign().complete(); + return await signed.submit(); }); - - const inlineDatum = toCBORHex(datum); - - const tx = await lucid - .newTx() - .collectFrom([nftIn]) - .payToContract(vaultBech32, { inline: inlineDatum }, nftIn.assets) - .complete(); - - const signed = await tx.sign().complete(); - return await signed.submit(); } /** Owner adds parametric insurance fields (strike + payout) — no Pyth. */ diff --git a/lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts b/lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts index 9a9c2b1b..f23cc4b4 100644 --- a/lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts +++ b/lazer/cardano/inventory-edge-protocol/lib/wallet_shadow_nfts.ts @@ -3,8 +3,11 @@ */ import type { UTxO } from "lucid-cardano"; +import * as Address from "@evolution-sdk/evolution/Address"; + import { inferFeedFromShadowName } from "./feeds.js"; -import { newLucidPreprod } from "./mint_shadow.js"; +import { withLucidPreprod } from "./mint_shadow.js"; +import { paymentKeyHashBytes } from "./vault_address.js"; function normalizeHex(h: string): string { return h.replace(/\s/g, "").toLowerCase(); @@ -35,18 +38,13 @@ export type WalletNativeNft = { nameUtf8?: string; quantity: string; unit: string; + /** Al menos un UTxO tiene qty 1 de esta unit (requerido para `openVault`). */ + hasSingletonUtxo: boolean; /** Si el nombre es shadow demo, sugerimos el feed Pyth del mint. */ suggestedFeedId?: number; }; -export async function listWalletShadowNfts(mnemonic: string): Promise<{ - address: string; - nfts: WalletShadowNft[]; -}> { - const lucid = await newLucidPreprod(); - lucid.selectWalletFromSeed(mnemonic); - const address = await lucid.wallet.address(); - const utxos = await lucid.wallet.getUtxos(); +function shadowNftsFromUtxos(utxos: UTxO[]): WalletShadowNft[] { const byKey = new Map(); for (const u of utxos) { @@ -76,32 +74,21 @@ export async function listWalletShadowNfts(mnemonic: string): Promise<{ } } - return { - address, - nfts: [...byKey.values()].sort((a, b) => - a.nameUtf8.localeCompare(b.nameUtf8), - ), - }; + return [...byKey.values()].sort((a, b) => + a.nameUtf8.localeCompare(b.nameUtf8), + ); } -/** - * Todos los activos nativos (por unit agregada), vía Lucid — coherente con `openVault`. - */ -export async function listWalletNativeNfts(mnemonic: string): Promise<{ - address: string; - nfts: WalletNativeNft[]; -}> { - const lucid = await newLucidPreprod(); - lucid.selectWalletFromSeed(mnemonic); - const address = await lucid.wallet.address(); - const utxos = await lucid.wallet.getUtxos(); +function nativeNftsFromUtxos(utxos: UTxO[]): WalletNativeNft[] { const agg = new Map(); + const singletonUnit = new Set(); for (const u of utxos) { for (const { unit, qty } of nativeUnitsFromUtxo(u)) { if (qty <= 0n) continue; const key = normalizeHex(unit); agg.set(key, (agg.get(key) ?? 0n) + qty); + if (qty === 1n) singletonUnit.add(key); } } @@ -126,11 +113,15 @@ export async function listWalletNativeNfts(mnemonic: string): Promise<{ nameUtf8, quantity: quantity.toString(), unit, + hasSingletonUtxo: singletonUnit.has(unit), suggestedFeedId: shadowFeed ?? undefined, }); } nfts.sort((a, b) => { + if (a.hasSingletonUtxo !== b.hasSingletonUtxo) { + return a.hasSingletonUtxo ? -1 : 1; + } const qa = BigInt(a.quantity); const qb = BigInt(b.quantity); if (qa === 1n && qb !== 1n) return -1; @@ -138,5 +129,34 @@ export async function listWalletNativeNfts(mnemonic: string): Promise<{ return (a.nameUtf8 ?? a.unit).localeCompare(b.nameUtf8 ?? b.unit); }); - return { address, nfts }; + return nfts; +} + +/** + * Una sesión Lucid + un `getUtxos` para sombras y nativos (evita dos `Lucid.new` por request). + */ +export async function listWalletShadowAndNative(mnemonic: string): Promise<{ + shadows: { address: string; nfts: WalletShadowNft[] }; + native: { + address: string; + paymentKeyHashHex: string; + nfts: WalletNativeNft[]; + }; +}> { + return withLucidPreprod(async (lucid) => { + lucid.selectWalletFromSeed(mnemonic); + const address = await lucid.wallet.address(); + const paymentKeyHashHex = Buffer.from( + paymentKeyHashBytes(Address.fromBech32(address)), + ).toString("hex"); + const utxos = await lucid.wallet.getUtxos(); + return { + shadows: { address, nfts: shadowNftsFromUtxos(utxos) }, + native: { + address, + paymentKeyHashHex, + nfts: nativeNftsFromUtxos(utxos), + }, + }; + }); } diff --git a/lazer/cardano/inventory-edge-protocol/onchain/aiken.lock b/lazer/cardano/inventory-edge-protocol/onchain/aiken.lock index 8a5595d9..7b4b66c4 100644 --- a/lazer/cardano/inventory-edge-protocol/onchain/aiken.lock +++ b/lazer/cardano/inventory-edge-protocol/onchain/aiken.lock @@ -24,4 +24,4 @@ requirements = [] source = "github" [etags] -"pyth-network/pyth-lazer-cardano@main" = [{ secs_since_epoch = 1774200850, nanos_since_epoch = 629668000 }, "a46dacd97a22eb07feeaf966d48c3116c8249ddc836705656e3135cea285bcfc"] +"pyth-network/pyth-lazer-cardano@main" = [{ secs_since_epoch = 1774214841, nanos_since_epoch = 214039000 }, "a46dacd97a22eb07feeaf966d48c3116c8249ddc836705656e3135cea285bcfc"] diff --git a/lazer/cardano/inventory-edge-protocol/package.json b/lazer/cardano/inventory-edge-protocol/package.json index f9ea289c..bebe28f5 100644 --- a/lazer/cardano/inventory-edge-protocol/package.json +++ b/lazer/cardano/inventory-edge-protocol/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "type": "module", "scripts": { + "dev": "npm run demo", "build:onchain": "cd onchain && aiken build", "mock-assets": "npx tsx scripts/mock_assets.ts", "tx:open-vault": "npx tsx scripts/run_open_vault.ts", @@ -10,7 +11,7 @@ "tx:liquidate": "npx tsx scripts/run_liquidate.ts", "dev:api": "tsx server/index.ts", "dev:web": "vite --config web/vite.config.ts", - "demo": "concurrently -n api,web -c blue,green \"npm run dev:api\" \"npm run dev:web\"", + "demo": "concurrently -n api,web -c blue,green \"tsx server/index.ts\" \"vite --config web/vite.config.ts\"", "build:web": "vite build --config web/vite.config.ts", "preview:web": "vite preview --config web/vite.config.ts --port 5173" }, diff --git a/lazer/cardano/inventory-edge-protocol/scripts/mint_shadow_child.ts b/lazer/cardano/inventory-edge-protocol/scripts/mint_shadow_child.ts new file mode 100644 index 00000000..6baead24 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/scripts/mint_shadow_child.ts @@ -0,0 +1,33 @@ +/** + * Se ejecuta en un proceso aparte (`node --import tsx`) para aislar el WASM de Lucid + * del servidor Express (evita "unreachable" / corrupción CML en el mismo proceso). + */ +import "../server/load_env.js"; + +import type { DemoSlot } from "../lib/feeds.js"; +import { mintShadowNft } from "../lib/mint_shadow.js"; + +const slot = process.argv[2]; +if (slot !== "metal" && slot !== "oil" && slot !== "stock") { + console.error('slot must be "metal", "oil", or "stock"'); + process.exit(1); +} + +mintShadowNft(slot as DemoSlot) + .then((r) => { + process.stdout.write( + `${JSON.stringify({ + txHash: r.txHash, + policyId: r.policyId, + assetName: r.assetName, + nameHex: r.nameHex, + slot: r.slot, + assetKey: r.assetKey, + feedId: r.feedId, + })}\n`, + ); + }) + .catch((e) => { + console.error(e instanceof Error ? e.message : String(e)); + process.exit(1); + }); diff --git a/lazer/cardano/inventory-edge-protocol/server/index.ts b/lazer/cardano/inventory-edge-protocol/server/index.ts index aca92f6b..85481c0a 100644 --- a/lazer/cardano/inventory-edge-protocol/server/index.ts +++ b/lazer/cardano/inventory-edge-protocol/server/index.ts @@ -2,7 +2,7 @@ * Local judge demo API: signs with CARDANO_MNEMONIC from `.env` (PreProd only). * Run with `npm run dev:api` from repo root; UI proxies `/api` in dev. */ -import "dotenv/config"; +import "./load_env.js"; import * as Address from "@evolution-sdk/evolution/Address"; import * as AssetName from "@evolution-sdk/evolution/AssetName"; @@ -26,10 +26,11 @@ import { PYTH_LAZER_FEEDS, } from "../lib/feeds.js"; import { - listWalletNativeNfts, - listWalletShadowNfts, + listWalletShadowAndNative, + type WalletNativeNft, + type WalletShadowNft, } from "../lib/wallet_shadow_nfts.js"; -import { mintShadowNft } from "../lib/mint_shadow.js"; +import { mintShadowNftSubprocess } from "../lib/mint_shadow_subprocess.js"; import { PYTH_POLICY_ID_HEX } from "../lib/pyth.js"; import { fetchFeedQuoteResolved, @@ -68,24 +69,34 @@ import { withdrawAllLiquidityPoolOnChain, } from "../lib/pool_onchain.js"; import { - appendAudit, - loadPool, - LOAN_REPAY_INTEREST_BPS, - poolCancelReservation, - poolCommitLoan, - poolDeposit, - poolEffectiveAvailableForInsuranceReserve, - poolFinalizeCloseVault, - poolOnDebtAdjusted, - poolOnLiquidateLoan, - poolReleaseOnClaim, - poolReserveForHedge, - poolWithdraw, - readAudit, -} from "../lib/judge_store.js"; + effectiveAvailableForHedgePayout, + getPoolStateFromChain, + nftLoanKey, + type PoolStateFromChain, +} from "../lib/pool_chain_state.js"; +import { appendAudit, readAudit } from "../lib/judge_store.js"; const PORT = Number(process.env.JUDGE_API_PORT ?? 8787); +/** Referencia en textos de auditoría (la amortización no mueve tADA en cadena en este MVP). */ +const LOAN_REPAY_INTEREST_BPS = 100n; + +function emptyPoolState(): PoolStateFromChain { + return { + poolScriptTotalLovelace: "0", + availableLovelace: "0", + encumberedLovelace: "0", + deployedToLoansLovelace: "0", + totalDepositedLovelace: "0", + totalPaidOutLovelace: "0", + totalRepaidPrincipalLovelace: "0", + profitsFromLoansLovelace: "0", + profitsFromInsuranceLovelace: "0", + reservations: [], + outstandingLoans: {}, + }; +} + function sleep(ms: number): Promise { return new Promise((r) => setTimeout(r, ms)); } @@ -156,10 +167,11 @@ app.get("/api/health", (_req, res) => { res.json({ ok: true, preprod: true, - hasMnemonic: Boolean(process.env.CARDANO_MNEMONIC), - hasAccessToken: Boolean(process.env.ACCESS_TOKEN), + hasMnemonic: Boolean(process.env.CARDANO_MNEMONIC?.trim()), + hasAccessToken: Boolean(process.env.ACCESS_TOKEN?.trim()), hasBlockfrostOrMaestro: Boolean( - process.env.BLOCKFROST_PROJECT_ID ?? process.env.MAESTRO_API_KEY, + process.env.BLOCKFROST_PROJECT_ID?.trim() ?? + process.env.MAESTRO_API_KEY?.trim(), ), /** Evolution (vault txs) usa este backend para evaluar scripts; Koios/ogmios público suele fallar. */ evolutionChainBackend: preprodChainBackendLabel(), @@ -221,17 +233,33 @@ app.get("/api/wallet", async (_req, res) => { } } } - const shadows = await listWalletShadowNfts(mnemonic); - const native = await listWalletNativeNfts(mnemonic); + let lucidAddress = ""; + let paymentKeyHashHex = ""; + let nativeNfts: WalletNativeNft[] = []; + let shadowNfts: WalletShadowNft[] = []; + let lucidError: string | undefined; + try { + const { shadows, native } = await listWalletShadowAndNative(mnemonic); + lucidAddress = shadows.address; + paymentKeyHashHex = native.paymentKeyHashHex; + nativeNfts = native.nfts; + shadowNfts = shadows.nfts; + } catch (e) { + lucidError = + e instanceof Error ? e.message : String(e); + } res.json({ address: Address.toBech32(addr), - lucidAddress: shadows.address, + lucidAddress: lucidAddress || undefined, + /** Mismo criterio que datum `owner` del vault (Lucid payment key). */ + paymentKeyHashHex: paymentKeyHashHex || undefined, lovelace: lovelace.toString(), adaApprox: (Number(lovelace) / 1e6).toFixed(6), nftCount: nfts.length, nfts, - nativeNfts: native.nfts, - shadowNfts: shadows.nfts, + nativeNfts, + shadowNfts, + ...(lucidError ? { lucidError } : {}), }); } catch (e) { res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); @@ -284,93 +312,20 @@ app.get("/api/audit", (req, res) => { res.json({ events: readAudit(limit) }); }); -app.get("/api/mock/pool", (_req, res) => { - try { - res.json(loadPool()); - } catch (e) { - res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); - } -}); - -app.post("/api/mock/pool/deposit", (req, res) => { +/** Pool: totales desde script `liquidity_pool` + vaults (deuda y hedge en datum). */ +app.get("/api/pool/state", async (_req, res) => { try { - const raw = req.body?.lovelace; - if (raw == null) { - res.status(400).json({ error: "lovelace required" }); - return; - } - const lovelace = BigInt(String(raw)); - if (lovelace <= 0n) { - res.status(400).json({ error: "lovelace must be positive" }); + if (!process.env.CARDANO_MNEMONIC?.trim()) { + res.json(emptyPoolState()); return; } - const p = poolDeposit(lovelace); - appendAudit({ - kind: "mock_pool_deposit", - summary: `Depósito demo al pool de seguros: ${lovelace} lovelace`, - extra: { lovelace: lovelace.toString() }, - }); - res.json(p); + res.json(await getPoolStateFromChain()); } catch (e) { res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); } }); -/** - * Suma al pool mock un % del lovelace total que ve la wallet (Evolution). - * No construye ni envía tx: no mueve ADA on-chain. - */ -app.post("/api/mock/pool/deposit-wallet-percent", async (req, res) => { - try { - const pct = Number(req.body?.percent ?? 80); - if (!Number.isFinite(pct) || pct < 1 || pct > 100 || Math.floor(pct) !== pct) { - res.status(400).json({ error: "percent debe ser entero entre 1 y 100" }); - return; - } - const mnemonic = requireMnemonic(); - const client = createPreprodSigningClient(mnemonic); - const utxos = await client.getWalletUtxos(); - let total = 0n; - for (const u of utxos) { - total += u.assets.lovelace; - } - if (total <= 0n) { - res.status(400).json({ - error: "La wallet no tiene lovelace según el proveedor (Evolution).", - }); - return; - } - const amount = (total * BigInt(pct)) / 100n; - if (amount <= 0n) { - res.status(400).json({ - error: `Con saldo ${total} lovelace, el ${pct}% redondea a 0. Aumentá el saldo o usá depósito manual.`, - walletLovelace: total.toString(), - }); - return; - } - const p = poolDeposit(amount); - appendAudit({ - kind: "mock_pool_deposit_wallet_pct", - summary: `Pool mock +${amount} lovelace (${pct}% de wallet ${total})`, - extra: { - percent: pct, - walletLovelace: total.toString(), - depositedLovelace: amount.toString(), - }, - }); - res.json({ - pool: p, - walletLovelace: total.toString(), - depositedLovelace: amount.toString(), - percent: pct, - note: "Solo contabilidad en data/mock_insurance_pool.json — no se firmó ninguna transacción Cardano.", - }); - } catch (e) { - res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); - } -}); - -/** Depósito real al script `liquidity_pool` + sincroniza el pool mock. */ +/** Depósito real al script `liquidity_pool` (estado del pool = lectura de cadena). */ app.post("/api/pool/onchain/deposit-percent", async (req, res) => { try { const pct = Number(req.body?.percent ?? 80); @@ -384,26 +339,30 @@ app.post("/api/pool/onchain/deposit-percent", async (req, res) => { return; } const reserve = poolDepositReserveLovelace(); - const amount = (total * BigInt(pct)) / 100n; - if (amount <= 0n) { + if (total <= reserve) { res.status(400).json({ - error: `El ${pct}% redondea a 0.`, + error: `Saldo ${total} lovelace ≤ reserva fees ${reserve}. Necesitás más tADA o bajá POOL_DEPOSIT_RESERVE_LOVELACE en .env.`, walletLovelace: total.toString(), + reserveLovelace: reserve.toString(), }); return; } - if (amount + reserve > total) { + /** Plain pct of wallet (integer division). */ + const byPercent = (total * BigInt(pct)) / 100n; + /** Max send while leaving `reserve` for tx fees (same rule as `depositLiquidityPoolOnChain`). */ + const maxAfterReserve = total - reserve; + const amount = + byPercent <= maxAfterReserve ? byPercent : maxAfterReserve; + if (amount <= 0n) { res.status(400).json({ - error: - `Saldo ${total} lovelace: el ${pct}% son ${amount}; necesitás ~${reserve} extra para fees. Bajá el % o POOL_DEPOSIT_RESERVE_LOVELACE.`, + error: `El ${pct}% redondea a 0.`, walletLovelace: total.toString(), - requestedLovelace: amount.toString(), - reserveLovelace: reserve.toString(), }); return; } + const cappedToReserve = byPercent > maxAfterReserve; const txHash = await depositLiquidityPoolOnChain({ lovelace: amount }); - const p = poolDeposit(amount); + const p = await getPoolStateFromChain(); appendAudit({ kind: "pool_onchain_deposit_pct", summary: `Pool on-chain +${amount} lovelace (${pct}% wallet)`, @@ -421,6 +380,13 @@ app.post("/api/pool/onchain/deposit-percent", async (req, res) => { depositedLovelace: amount.toString(), percent: pct, liquidityPoolAddressBech32: liquidityPoolAddressBech32(), + ...(cappedToReserve + ? { + note: `Pediste ${pct}% (${byPercent} lovelace) pero solo cabe ${maxAfterReserve} sin tocar la reserva de fees (${reserve}). Se depositó ese máximo.`, + requestedByPercentLovelace: byPercent.toString(), + reserveLovelace: reserve.toString(), + } + : {}), }); } catch (e) { res.status(500).json({ error: e instanceof Error ? e.message : String(e) }); @@ -440,7 +406,7 @@ app.post("/api/pool/onchain/deposit", async (req, res) => { return; } const txHash = await depositLiquidityPoolOnChain({ lovelace: amount }); - const p = poolDeposit(amount); + const p = await getPoolStateFromChain(); appendAudit({ kind: "pool_onchain_deposit", summary: `Pool on-chain +${amount} lovelace`, @@ -472,12 +438,12 @@ app.get("/api/pool/onchain/positions", async (_req, res) => { } }); -/** Gasta todos los UTxOs del pool con tu owner en datum; sincroniza pool mock. */ +/** Gasta todos los UTxOs del pool con tu owner en datum. */ app.post("/api/pool/onchain/withdraw-all", async (_req, res) => { try { const { txHash, withdrawnLovelace, inputCount } = await withdrawAllLiquidityPoolOnChain(); - const p = poolWithdraw(BigInt(withdrawnLovelace)); + const p = await getPoolStateFromChain(); appendAudit({ kind: "pool_onchain_withdraw_all", summary: `Pool on-chain retiro ${withdrawnLovelace} lovelace (${inputCount} inputs)`, @@ -499,7 +465,7 @@ app.post("/api/pool/onchain/withdraw-all", async (_req, res) => { app.post("/api/mint", async (req, res) => { try { const slot = parseSlot(String(req.body?.slot ?? "")); - const out = await mintShadowNft(slot); + const out = await mintShadowNftSubprocess(slot); appendAudit({ kind: "mint_shadow", summary: `Mint NFT sombra (${out.slot}) · ${out.assetName}`, @@ -546,11 +512,20 @@ app.post("/api/vault/open", async (req, res) => { const nm = String(nftNameHex); const loan = BigInt(debtLovelace ?? "0"); if (loan > 0n) { - const av = BigInt(loadPool().availableLovelace); + const st = await getPoolStateFromChain(); + if (st.outstandingLoans[nftLoanKey(pol, nm)]) { + res.status(400).json({ + error: + "Ya existe una vault con deuda on-chain para este NFT (cerrala o usá otro activo).", + code: "VAULT_ALREADY_OWES", + }); + return; + } + const av = BigInt(st.availableLovelace); if (av < loan) { res.status(400).json({ error: - `El pool no tiene tADA suficientes para financiar este préstamo. Disponible: ${av} lovelace; solicitado: ${loan}. Depositá liquidez mock en la pestaña Seguros.`, + `El pool no tiene tADA suficientes para financiar este préstamo. Disponible (cadena): ${av} lovelace; solicitado: ${loan}. Depositá tADA al contrato pool on-chain (pestaña Seguros).`, code: "POOL_INSUFFICIENT_FUNDS", availableLovelace: av.toString(), requestedLovelace: loan.toString(), @@ -565,13 +540,6 @@ app.post("/api/vault/open", async (req, res) => { debtLovelace: loan, collateralQty: BigInt(collateralQty ?? "1"), }); - if (loan > 0n) { - poolCommitLoan({ - nftPolicyHex: pol, - nftNameHex: nm, - loanLovelace: loan, - }); - } appendAudit({ kind: "vault_open", summary: @@ -603,14 +571,11 @@ app.post("/api/vault/hedge", async (req, res) => { const decoded = decodeVaultDatum(readInlineDatum(u)); const payoutReq = BigInt(payoutLovelace ?? "0"); if (payoutReq > 0n) { - const eff = poolEffectiveAvailableForInsuranceReserve( - decoded.nftPolicyHex, - decoded.nftNameHex, - ); + const eff = await effectiveAvailableForHedgePayout(u); if (eff < payoutReq) { res.status(400).json({ error: - `El pool no puede respaldar este payout (${payoutReq} lovelace). Liquidez efectiva para esta cobertura: ${eff}. Aportá más tADA mock o bajá el payout.`, + `El pool no puede respaldar este payout (${payoutReq} lovelace). Liquidez efectiva para esta cobertura: ${eff}. Aportá más tADA on-chain al pool o bajá el payout.`, code: "POOL_INSUFFICIENT_FUNDS", requestedLovelace: payoutReq.toString(), effectiveAvailableLovelace: eff.toString(), @@ -637,30 +602,20 @@ app.post("/api/vault/hedge", async (req, res) => { decoded.nftNameHex, ); if (nextRef) { - const { reserved, shortfall } = poolReserveForHedge({ - nftPolicyHex: decoded.nftPolicyHex, - nftNameHex: decoded.nftNameHex, - vaultRef: nextRef, - payoutRequested: payoutReq, - }); appendAudit({ - kind: "mock_pool_reserve", - summary: - shortfall > 0n - ? `Pool: apartado ${reserved} lovelace; shortfall ${shortfall} (inconsistencia — revisar)` - : `Pool: apartado ${reserved} lovelace para cobertura`, + kind: "vault_hedge_onchain", + summary: `Cobertura en datum vault (cap ${payoutReq} lovelace) · ref ${nextRef}`, txHash: h, extra: { vaultRef: nextRef, - reserved: reserved.toString(), - shortfall: shortfall.toString(), + payoutLovelace: payoutReq.toString(), }, }); } else { appendAudit({ - kind: "mock_pool_reserve_skipped", + kind: "pool_reserve_hedge_skipped", summary: - "Pool mock: no se encontró la vault tras hedge (indexador); reintentá o reservá tras refrescar.", + "No se encontró la vault tras hedge (indexador); reintentá o reservá tras refrescar.", txHash: h, }); } @@ -685,7 +640,7 @@ app.post("/api/vault/adjust", async (req, res) => { const newD = BigInt(newDebtLovelace); if (newD > oldD) { const need = newD - oldD; - const av = BigInt(loadPool().availableLovelace); + const av = BigInt((await getPoolStateFromChain()).availableLovelace); if (av < need) { res.status(400).json({ error: @@ -701,15 +656,9 @@ app.post("/api/vault/adjust", async (req, res) => { vaultUtxo: u, newDebtLovelace: newD, }); - poolOnDebtAdjusted({ - nftPolicyHex: prev.nftPolicyHex, - nftNameHex: prev.nftNameHex, - oldDebtLovelace: oldD, - newDebtLovelace: newD, - }); appendAudit({ kind: "vault_adjust", - summary: `adjustDebt ${oldD.toString()} → ${newD.toString()} (interés demo al amortizar: ${LOAN_REPAY_INTEREST_BPS} bps del principal devuelto)`, + summary: `adjustDebt on-chain ${oldD.toString()} → ${newD.toString()} (referencia demo: ${LOAN_REPAY_INTEREST_BPS} bps no mueve tADA en este MVP)`, txHash: h, }); res.json({ txHash: h }); @@ -728,16 +677,6 @@ app.post("/api/vault/close", async (req, res) => { const u = await getVaultUtxoByRef(String(txHash), Number(outputIndex)); const decoded = decodeVaultDatum(readInlineDatum(u)); const h = await closeVault({ vaultUtxo: u }); - if (decoded.hedge.tag === "some") { - poolCancelReservation({ - nftPolicyHex: decoded.nftPolicyHex, - nftNameHex: decoded.nftNameHex, - }); - } - poolFinalizeCloseVault({ - nftPolicyHex: decoded.nftPolicyHex, - nftNameHex: decoded.nftNameHex, - }); appendAudit({ kind: "vault_close", summary: "closeVault (debt=0)", @@ -768,18 +707,9 @@ app.post("/api/vault/liquidate", async (req, res) => { vaultUtxo: u, priceFeedId: Number(priceFeedId), }); - poolCancelReservation({ - nftPolicyHex: decoded.nftPolicyHex, - nftNameHex: decoded.nftNameHex, - }); - poolOnLiquidateLoan({ - nftPolicyHex: decoded.nftPolicyHex, - nftNameHex: decoded.nftNameHex, - debtLovelace: decoded.debtLovelace, - }); appendAudit({ kind: "vault_liquidate", - summary: `liquidate (venta simulada de colateral → pool) · deuda ${decoded.debtLovelace.toString()} · feed ${priceFeedId}`, + summary: `liquidate on-chain · deuda ${decoded.debtLovelace.toString()} · feed ${priceFeedId}`, txHash: h, extra: { nftPolicyHex: decoded.nftPolicyHex, @@ -807,22 +737,13 @@ app.post("/api/vault/claim", async (req, res) => { vaultUtxo: u, priceFeedId: Number(priceFeedId), }); - const rel = poolReleaseOnClaim({ - nftPolicyHex: decoded.nftPolicyHex, - nftNameHex: decoded.nftNameHex, - }); appendAudit({ kind: "vault_claim", - summary: rel.found - ? `claimInsurance: pago simulado ${rel.paidOut} lovelace; fee pool ${rel.poolFee}` - : "claimInsurance (sin reserva previa en pool mock)", + summary: `claimInsurance on-chain (payout según outputs de la tx)`, txHash: h, extra: { nftPolicyHex: decoded.nftPolicyHex, nftNameHex: decoded.nftNameHex, - poolReleased: rel.released.toString(), - poolFee: rel.poolFee.toString(), - poolFound: rel.found, }, }); res.json({ txHash: h }); @@ -931,7 +852,7 @@ app.get("/api/risk", async (req, res) => { const marketOpen = Boolean(q.priceRaw); const loanPoolNote = `On-chain: el validador compara precio×colateral vs principal en datum. ` + - `Intereses: demo off-chain (${LOAN_REPAY_INTEREST_BPS} bps sobre lo amortizado) van al pool al bajar la deuda.`; + `La deuda solo cambia con txs (Adjust/Close/Liquidate/Claim). Referencia demo: ${LOAN_REPAY_INTEREST_BPS} bps no mueve tADA en este MVP.`; res.json({ ref: `${txHash}#${outputIndex}`, feedId, diff --git a/lazer/cardano/inventory-edge-protocol/server/load_env.ts b/lazer/cardano/inventory-edge-protocol/server/load_env.ts new file mode 100644 index 00000000..ea52d076 --- /dev/null +++ b/lazer/cardano/inventory-edge-protocol/server/load_env.ts @@ -0,0 +1,24 @@ +/** + * Carga `.env` desde la raíz del repo (padre de `server/`), no desde `process.cwd()`. + * Así `tsx server/index.ts` o `npm run dev:api` ven las variables aunque el cwd varíe. + */ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import dotenv from "dotenv"; + +const serverDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(serverDir, ".."); +dotenv.config({ path: path.join(repoRoot, ".env") }); + +/** Sin esto, con stdout en pipe (`concurrently`, logs), EPIPE puede tumbar el proceso tras `listen`. */ +for (const stream of [process.stdout, process.stderr]) { + stream.on("error", (e: NodeJS.ErrnoException) => { + if (e.code === "EPIPE" || e.code === "ERR_STREAM_DESTROYED") return; + }); +} +try { + process.stdin.resume(); +} catch { + /* sin stdin */ +} diff --git a/lazer/cardano/inventory-edge-protocol/web/src/App.tsx b/lazer/cardano/inventory-edge-protocol/web/src/App.tsx index a345d02f..56390585 100644 --- a/lazer/cardano/inventory-edge-protocol/web/src/App.tsx +++ b/lazer/cardano/inventory-edge-protocol/web/src/App.tsx @@ -41,6 +41,7 @@ type VaultRow = { outputIndex: string; lovelace: string; datum: { + ownerKeyHashHex?: string; debtLovelace: string; collateralQty: string; feedId: string; @@ -80,6 +81,8 @@ type NativeNft = { nameUtf8?: string; quantity: string; unit: string; + /** Si falta (API vieja), se usa quantity agregada === 1. */ + hasSingletonUtxo?: boolean; suggestedFeedId?: number; }; @@ -114,6 +117,8 @@ type AuditEvent = { }; type MockPoolState = { + /** Suma lovelace en UTxOs del script pool (tu owner). */ + poolScriptTotalLovelace?: string; availableLovelace: string; encumberedLovelace: string; deployedToLoansLovelace: string; @@ -181,6 +186,11 @@ async function api(path: string, init?: RequestInit): Promise { ? `efectivo cobertura ${errBody.effectiveAvailableLovelace}` : "", ].filter(Boolean); + if (errBody.code === "API_UNAVAILABLE") { + bits.push( + "Tip: en `inventory-edge-protocol` ejecutá `npm run dev` (sube API :8787 y Vite juntos).", + ); + } throw new Error(bits.join(" · ")); } return parsed as T; @@ -191,18 +201,150 @@ function shortTx(h?: string, n = 14): string { return h.length <= n ? h : `${h.slice(0, n)}…`; } +function sleep(ms: number): Promise { + return new Promise((r) => setTimeout(r, ms)); +} + +function normalizeUnitKey(unit: string): string { + return unit.replace(/\s/g, "").toLowerCase(); +} + +/** Unit agregada (policy + name hex), misma clave que `nativeNfts[].unit`. */ +function normalizedNftUnit(policyId: string, nameHex: string): string { + return normalizeUnitKey(policyId + nameHex); +} + +type WalletApiPayload = { + address: string; + lucidAddress?: string; + paymentKeyHashHex?: string; + lovelace: string; + adaApprox: string; + nftCount: number; + nativeNfts?: NativeNft[]; + shadowNfts: ShadowNft[]; + /** Si Lucid falló pero Evolution respondió (mint/listado pueden fallar aparte). */ + lucidError?: string; +}; + +type WalletState = { + address: string; + lucidAddress?: string; + paymentKeyHashHex?: string; + lovelace: string; + adaApprox: string; + nftCount: number; + nativeNfts: NativeNft[]; + shadowNfts: ShadowNft[]; + lucidError?: string; +}; + +function mapApiToWalletState(w: WalletApiPayload): WalletState { + return { + address: w.address, + lucidAddress: w.lucidAddress, + paymentKeyHashHex: w.paymentKeyHashHex, + lovelace: w.lovelace ?? "0", + adaApprox: w.adaApprox, + nftCount: w.nftCount, + nativeNfts: w.nativeNfts ?? [], + shadowNfts: w.shadowNfts ?? [], + lucidError: w.lucidError, + }; +} + +/** API actualiza filas; conserva filas que el indexador aún no devuelve (p. ej. recién minteadas). */ +function mergeWalletFromApi(prev: WalletState, w: WalletApiPayload): WalletState { + const fromApiNative = w.nativeNfts ?? []; + const fromApiShadow = w.shadowNfts ?? []; + const nativeMap = new Map(); + for (const n of prev.nativeNfts) { + nativeMap.set(normalizeUnitKey(n.unit), n); + } + for (const n of fromApiNative) { + nativeMap.set(normalizeUnitKey(n.unit), n); + } + const shadowMap = new Map(); + for (const s of prev.shadowNfts) { + shadowMap.set(normalizeUnitKey(s.unit), s); + } + for (const s of fromApiShadow) { + shadowMap.set(normalizeUnitKey(s.unit), s); + } + const nativeNfts = sortNativeNftList([...nativeMap.values()]); + const shadowNfts = [...shadowMap.values()].sort((a, b) => + a.nameUtf8.localeCompare(b.nameUtf8), + ); + return { + address: w.address, + lucidAddress: w.lucidAddress, + paymentKeyHashHex: w.paymentKeyHashHex ?? prev.paymentKeyHashHex, + lovelace: w.lovelace ?? "0", + adaApprox: w.adaApprox, + nftCount: w.nftCount, + nativeNfts, + shadowNfts, + lucidError: w.lucidError, + }; +} + +function sortNativeNftList(nfts: NativeNft[]): NativeNft[] { + return [...nfts].sort((a, b) => { + if (a.hasSingletonUtxo !== b.hasSingletonUtxo) { + return a.hasSingletonUtxo ? -1 : 1; + } + try { + const qa = BigInt(a.quantity); + const qb = BigInt(b.quantity); + if (qa === 1n && qb !== 1n) return -1; + if (qb === 1n && qa !== 1n) return 1; + } catch { + /* ignore */ + } + return (a.nameUtf8 ?? a.unit).localeCompare(b.nameUtf8 ?? b.unit); + }); +} + +function applyMintOptimisticToWallet(prev: WalletState, out: MintOut): WalletState { + const policyId = out.policyId.replace(/\s/g, "").toLowerCase(); + const nameHex = out.nameHex.replace(/\s/g, "").toLowerCase(); + const unit = policyId + nameHex; + const native: NativeNft = { + policyId, + nameHex, + nameUtf8: out.assetName, + quantity: "1", + unit, + hasSingletonUtxo: true, + suggestedFeedId: out.feedId, + }; + const shadow: ShadowNft = { + policyId, + nameHex, + nameUtf8: out.assetName, + unit, + feedId: out.feedId, + utxoLovelace: "0", + }; + return { + ...prev, + lucidError: undefined, + nativeNfts: [ + native, + ...prev.nativeNfts.filter((n) => normalizeUnitKey(n.unit) !== unit), + ], + shadowNfts: [ + shadow, + ...prev.shadowNfts.filter((s) => normalizeUnitKey(s.unit) !== unit), + ], + }; +} + export default function App() { const [tab, setTab] = useState("general"); const [health, setHealth] = useState(null); const [config, setConfig] = useState(null); - const [wallet, setWallet] = useState<{ - address: string; - lucidAddress?: string; - adaApprox: string; - nftCount: number; - nativeNfts: NativeNft[]; - shadowNfts: ShadowNft[]; - } | null>(null); + const [wallet, setWallet] = useState(null); const [demoFeeds, setDemoFeeds] = useState(null); const [vaults, setVaults] = useState([]); const [risk, setRisk] = useState(null); @@ -228,7 +370,7 @@ export default function App() { const [strikeRaw, setStrikeRaw] = useState("2500000"); const [payoutLovelace, setPayoutLovelace] = useState("5000000"); const [newDebt, setNewDebt] = useState("0"); - const [depositLovelace, setDepositLovelace] = useState("10000000"); + const [onchainPoolLovelace, setOnchainPoolLovelace] = useState("10000000"); const [busy, setBusy] = useState(null); const [msg, setMsg] = useState<{ type: "ok" | "err"; text: string } | null>( @@ -254,12 +396,59 @@ export default function App() { if (!wallet?.nativeNfts.length) return ""; const p = nftPolicy.replace(/\s/g, "").toLowerCase(); const n = nftNameHex.replace(/\s/g, "").toLowerCase(); + const want = normalizedNftUnit(p, n); const hit = wallet.nativeNfts.find( - (x) => x.policyId === p && x.nameHex === n, + (x) => normalizeUnitKey(x.unit) === want, ); return hit?.unit ?? ""; }, [wallet?.nativeNfts, nftPolicy, nftNameHex]); + /** + * NFTs que `openVault` puede gastar (al menos un UTxO con qty 1) y que no están + * ya en una vault tuya (mismo owner en datum). + */ + const vaultableNativeNfts = useMemo(() => { + if (!wallet?.nativeNfts.length) return []; + const me = wallet.paymentKeyHashHex?.replace(/\s/g, "").toLowerCase(); + const inVault = new Set(); + if (me) { + for (const v of vaults) { + const ow = v.datum.ownerKeyHashHex?.replace(/\s/g, "").toLowerCase(); + if (!ow || ow !== me) continue; + inVault.add( + `${v.datum.nftPolicyHex.replace(/\s/g, "").toLowerCase()}|${v.datum.nftNameHex.replace(/\s/g, "").toLowerCase()}`, + ); + } + } + return wallet.nativeNfts.filter((n) => { + let canOpen: boolean; + if (n.hasSingletonUtxo === true) canOpen = true; + else if (n.hasSingletonUtxo === false) canOpen = false; + else { + try { + canOpen = BigInt(n.quantity) === 1n; + } catch { + canOpen = false; + } + } + if (!canOpen) return false; + const k = `${n.policyId.toLowerCase()}|${n.nameHex.toLowerCase()}`; + return !inVault.has(k); + }); + }, [wallet?.nativeNfts, wallet?.paymentKeyHashHex, vaults]); + + /** Un solo NFT libre: rellenar campos al cargar / refrescar wallet o vaults. */ + useEffect(() => { + if (vaultableNativeNfts.length !== 1) return; + const only = vaultableNativeNfts[0]!; + setNftPolicy(only.policyId); + setNftNameHex(only.nameHex); + if (only.suggestedFeedId != null) { + setFeedId(String(only.suggestedFeedId)); + } + setOpenVaultCollateralHint(null); + }, [vaultableNativeNfts]); + const refreshStatic = useCallback(async () => { const [h, c] = await Promise.all([ api("/api/health"), @@ -269,23 +458,35 @@ export default function App() { setConfig(c); }, []); - const refreshWallet = useCallback(async () => { - const w = await api<{ - address: string; - lucidAddress?: string; - adaApprox: string; - nftCount: number; - nativeNfts?: NativeNft[]; - shadowNfts: ShadowNft[]; - }>("/api/wallet"); - setWallet({ - address: w.address, - lucidAddress: w.lucidAddress, - adaApprox: w.adaApprox, - nftCount: w.nftCount, - nativeNfts: w.nativeNfts ?? [], - shadowNfts: w.shadowNfts ?? [], - }); + const refreshWallet = useCallback(async (): Promise => { + const w = await api("/api/wallet"); + setWallet(mapApiToWalletState(w)); + return w; + }, []); + + /** + * Sincroniza con Blockfrost/Maestro sin borrar filas optimistas: merge con estado actual. + * Corre en background (no bloquea el cartel de mint ni el dropdown). + */ + const reconcileWalletAfterMint = useCallback((out: MintOut) => { + const want = normalizedNftUnit(out.policyId, out.nameHex); + void (async () => { + for (let attempt = 0; attempt < 12; attempt++) { + if (attempt > 0) await sleep(2000); + try { + const w = await api("/api/wallet"); + setWallet((prev) => + prev ? mergeWalletFromApi(prev, w) : mapApiToWalletState(w), + ); + const native = w.nativeNfts ?? []; + if (native.some((n) => normalizeUnitKey(n.unit) === want)) { + return; + } + } catch { + /* seguir */ + } + } + })(); }, []); const refreshDemoFeeds = useCallback(async () => { @@ -322,7 +523,7 @@ export default function App() { }, []); const refreshPool = useCallback(async () => { - const p = await api("/api/mock/pool"); + const p = await api("/api/pool/state"); setPool(p); }, []); @@ -334,7 +535,12 @@ export default function App() { useEffect(() => { if (!health?.hasMnemonic) return; - refreshWallet().catch(() => {}); + refreshWallet().catch((e) => + setMsg({ + type: "err", + text: `No se pudo cargar la wallet (NFTs): ${e instanceof Error ? e.message : String(e)}`, + }), + ); refreshVaults().catch(() => {}); }, [health?.hasMnemonic, refreshWallet, refreshVaults]); @@ -438,7 +644,7 @@ export default function App() { [ "seguros", "Cobertura", - "Aportar al pool, activar seguro y cobrar si aplica.", + "Depositar tADA al pool on-chain, activar seguro y cobrar si aplica.", ], ] as const ).map(([id, label, hint]) => ( @@ -460,13 +666,48 @@ export default function App() {

Inventory-Edge Protocol

Demo para entender el flujo sin ser experto en crypto:{" "} - pool (liquidez de prueba), cajas fuertes{" "} + pool on-chain (script liquidity_pool + ), cajas fuertes{" "} (contrato que guarda tu NFT y los números del préstamo), y{" "} precios Pyth para riesgo y liquidación. Los datos técnicos siguen disponibles en cada sección.

+ {(busy || msg) && ( +
+ {busy && ( +

+ Ejecutando: {busy}… +

+ )} + {msg?.type === "err" && ( +

+ {msg.text} +

+ )} + {msg?.type === "ok" && ( +

+ {msg.text} +

+ )} +
+ )} + {tab === "general" && ( <>
@@ -665,6 +906,11 @@ export default function App() { {wallet.nativeNfts.length} activos Lucid (incl. shadow)

+ {wallet.lucidError && ( +

+ Lucid (mint / listado NFT): {wallet.lucidError} +

+ )}
-

Pool de liquidez (mock)

+

Pool (solo blockchain)

- Financia préstamos (principal en el datum), reserva coberturas y - acumula ganancias demo (intereses de amortización, fee de seguros, - excedente simulado al liquidar colateral). Depósitos en Cobertura. + Totales leídos en vivo: script{" "} + liquidity_pool + datums de tus vaults + (deuda y hedge). Las filas de + “histórico / ganancias” no se persisten (siempre 0) — no valen txs + extra solo para métricas.

{!pool ? (

) : (
    + {pool.poolScriptTotalLovelace != null && ( +
  • + Total en script pool:{" "} + {pool.poolScriptTotalLovelace}{" "} + lovelace +
  • + )}
  • Disponible: {pool.availableLovelace}{" "} lovelace @@ -886,7 +1141,7 @@ export default function App() {

    El vault custodia el NFT colateral. El{" "} principal del préstamo sale del{" "} - pool mock (no se “imprime” deuda). Si el colateral + pool on-chain (no se “imprime” deuda). Si el colateral no cubre principal + margen on-chain (~110%), podés{" "} liquidar (venta simulada → ingreso al pool). El interés demo se acredita al pool al bajar el @@ -900,7 +1155,9 @@ export default function App() {

    Acuñar NFT sombra

    NFT único por feed (oro / WTI / BTC proxy). Sirve de colateral - en la vault. + en la vault. Requiere Blockfrost o Maestro en .env. + Tras acuñar, el listado se actualiza al instante; el indexador + puede tardar unos segundos en coincidir con la cadena.

    {(["metal", "oil", "stock"] as const).map((slot) => ( @@ -915,15 +1172,51 @@ export default function App() { method: "POST", body: JSON.stringify({ slot }), }); + let wApi: WalletApiPayload; + try { + wApi = await api("/api/wallet"); + } catch (e) { + setWallet((prev) => + prev + ? applyMintOptimisticToWallet(prev, out) + : prev, + ); + setLastMint(out); + setNftPolicy(out.policyId); + setNftNameHex(out.nameHex); + setFeedId(String(out.feedId)); + setMsg({ + type: "ok", + text: `Mint OK en cadena · ${out.assetName} · ${shortTx(out.txHash, 18)}. No se pudo leer /api/wallet: ${e instanceof Error ? e.message : String(e)}. Probá «Refrescar todo» en Resumen.`, + }); + await Promise.all([ + refreshVaults(), + refreshAudit(), + refreshPool(), + ]); + reconcileWalletAfterMint(out); + return; + } + setWallet((prev) => { + const base = prev + ? mergeWalletFromApi(prev, wApi) + : mapApiToWalletState(wApi); + return applyMintOptimisticToWallet(base, out); + }); setLastMint(out); setNftPolicy(out.policyId); setNftNameHex(out.nameHex); setFeedId(String(out.feedId)); setMsg({ type: "ok", - text: `Mint OK · ${out.assetName} · tx ${out.txHash}`, + text: `Mint OK · ${out.assetName} · tx ${shortTx(out.txHash, 20)}. Elegí el colateral en «NFT disponible para bloquear» (cada shadow es una línea distinta; podés tener varios del mismo feed).`, }); - await afterChainAction(); + await Promise.all([ + refreshVaults(), + refreshAudit(), + refreshPool(), + ]); + reconcileWalletAfterMint(out); }) } > @@ -936,29 +1229,58 @@ export default function App() { ))}
    {lastMint && ( -

    - {lastMint.assetName} · feed {lastMint.feedId} -

    +
    + Último mint:{" "} + {lastMint.assetName} · feed{" "} + {lastMint.feedId} ·{" "} + {shortTx(lastMint.txHash, 22)} +
    )}

Abrir vault

- Bloquea el NFT en el script (misma vista de UTxOs que{" "} - Lucid). Si el principal > 0, el pool debe - tener tADA disponible (depositá antes en Seguros). + Elegí un NFT de la wallet (vista Lucid + ): cada sombra que minteaste es un activo distinto{" "} + (nombre único), así que dos WTI aparecen como{" "} + dos opciones en el menú. Hace falta al menos un + UTxO con exactamente 1 unidad de ese token y que no esté ya en + una vault tuya. Si el principal > 0, el pool on-chain necesita + tADA (Seguros).

- {wallet && wallet.nativeNfts.length > 0 && ( + {health?.hasMnemonic && wallet === null && ( +

Cargando NFTs (Lucid)…

+ )} + {wallet && vaultableNativeNfts.length > 0 && (
- +

- Incluye shadow y cualquier otro nativo. Los shadow traen feed - Pyth sugerido por nombre. + Hace falta un UTxO con exactamente 1 de ese + activo (si el total agregado es >1 en varios UTxOs, igual + aparece). Solo se excluyen NFTs ya bloqueados en{" "} + tus vaults (mismo owner en datum).

)} + {wallet && wallet.nativeNfts.length > 0 && ( +
+ + Ver los {wallet.nativeNfts.length} nativos Lucid (referencia) + +
    + {wallet.nativeNfts.map((n) => ( +
  • + {n.nameUtf8 ?? `${n.policyId.slice(0, 10)}…`} · qty{" "} + {n.quantity} · singleton UTxO:{" "} + {n.hasSingletonUtxo === true + ? "sí" + : n.hasSingletonUtxo === false + ? "no" + : "?"} +
  • + ))} +
+
+ )} + {wallet && + wallet.nativeNfts.length > 0 && + vaultableNativeNfts.length === 0 && ( +

+ No hay NFTs listos para abrir vault: ya están en una vault + tuya, o ningún UTxo tiene exactamente 1 unidad de ese + token. Consolidá en un solo UTxO, cerrá una vault, minteá + otro shadow o usá edición manual. +

+ )} {wallet && wallet.nativeNfts.length === 0 && (

No hay activos nativos en la wallet Lucid. Acuñá un shadow o pulsá "Refrescar wallet" en Resumen.

)} -
- - setNftPolicy(e.target.value)} - /> -
-
- - setNftNameHex(e.target.value)} - /> -
+
+ + Edición manual (policy / name hex) + +
+ + setNftPolicy(e.target.value)} + /> +
+
+ + setNftNameHex(e.target.value)} + /> +
+
run("open", async () => { const { txHash } = await api<{ txHash: string }>( @@ -1296,80 +1670,58 @@ export default function App() {
-

Aportar al pool (demo)

+

Aportar al pool (on-chain)

- Simula capital del asegurador. Persistido en{" "} - data/mock_insurance_pool.json. - No transfiere ADA en cadena — solo anota números - para el demo del pool. + Depósitos al script{" "} + liquidity_pool en PreProd (Lucid). + El panel del pool se calcula desde la cadena (sin archivo de + balances).

- Saldo wallet (proveedor):{" "} + Saldo wallet (proveedor / Evolution):{" "} {wallet?.lovelace ?? "—"} lovelace (~{wallet?.adaApprox ?? "?"}{" "} tADA)

- + setDepositLovelace(e.target.value)} + id="dep-onchain" + value={onchainPoolLovelace} + onChange={(e) => setOnchainPoolLovelace(e.target.value)} />
-
- + )} - {busy && ( -

- Ejecutando: {busy}… -

- )} - {msg?.type === "err" &&

{msg.text}

} - {msg?.type === "ok" &&

{msg.text}

}
); diff --git a/lazer/cardano/inventory-edge-protocol/web/vite.config.ts b/lazer/cardano/inventory-edge-protocol/web/vite.config.ts index 913a8732..5bac3911 100644 --- a/lazer/cardano/inventory-edge-protocol/web/vite.config.ts +++ b/lazer/cardano/inventory-edge-protocol/web/vite.config.ts @@ -27,7 +27,7 @@ export default defineConfig({ r.end( JSON.stringify({ error: - "No hay API en :8787. En otra terminal: npm run dev:api o npm run demo.", + "No hay API en :8787. Desde la carpeta del proyecto: npm run dev (recomendado), npm run demo, o en otra terminal npm run dev:api.", code: "API_UNAVAILABLE", }), ); From 1f6b266cad45a86692b984282ba8c457dadf8359 Mon Sep 17 00:00:00 2001 From: JuanCasimiro Date: Sun, 22 Mar 2026 20:17:17 -0300 Subject: [PATCH 3/5] docs(inventory-edge): add Google Doc with UI screenshots for judges Made-with: Cursor --- lazer/cardano/inventory-edge-protocol/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lazer/cardano/inventory-edge-protocol/README.md b/lazer/cardano/inventory-edge-protocol/README.md index b58057eb..2c492468 100644 --- a/lazer/cardano/inventory-edge-protocol/README.md +++ b/lazer/cardano/inventory-edge-protocol/README.md @@ -1,5 +1,7 @@ # Inventory-Edge Protocol — Judge Briefing +**Screenshots & UI pages (for judges):** [Google Doc — captures](https://docs.google.com/document/d/10R7zigvUrFPtYojwtqCrn2axnEarXEnd423Ktbk2eNo/edit?usp=sharing) + RealFi / RWA hackathon MVP: a **Cardano vault** that locks a demo NFT, tracks synthetic debt, supports **parametric insurance** (strike in datum), and uses **Pyth Lazer** (pull model) for **liquidation** and **insurance** checks. **Narrative deck (why it matters):** see [PITCH.md](./PITCH.md). From d99c4a44dca60caa5df6e20f3e4f19a07a4f5c11 Mon Sep 17 00:00:00 2001 From: JuanCasimiro Date: Sun, 22 Mar 2026 20:21:20 -0300 Subject: [PATCH 4/5] docs: surface judge screenshot link on root README (GitHub home view) Made-with: Cursor --- README.md | 6 +++++- lazer/cardano/inventory-edge-protocol/README.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bd9c4a52..56d85885 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # Pyth Examples -This repository contains examples of applications integrating Pyth products and services. \ No newline at end of file +This repository contains examples of applications integrating Pyth products and services. + +### Inventory-Edge Protocol (Cardano / Pyth Lazer) + +**Judges — UI screenshots:** [Google Doc](https://docs.google.com/document/d/10R7zigvUrFPtYojwtqCrn2axnEarXEnd423Ktbk2eNo/edit?usp=sharing) · **Full README:** [lazer/cardano/inventory-edge-protocol/README.md](lazer/cardano/inventory-edge-protocol/README.md) \ No newline at end of file diff --git a/lazer/cardano/inventory-edge-protocol/README.md b/lazer/cardano/inventory-edge-protocol/README.md index 2c492468..2bff11ac 100644 --- a/lazer/cardano/inventory-edge-protocol/README.md +++ b/lazer/cardano/inventory-edge-protocol/README.md @@ -1,6 +1,6 @@ # Inventory-Edge Protocol — Judge Briefing -**Screenshots & UI pages (for judges):** [Google Doc — captures](https://docs.google.com/document/d/10R7zigvUrFPtYojwtqCrn2axnEarXEnd423Ktbk2eNo/edit?usp=sharing) +**Screenshots & UI pages (for judges):** [Google Doc — captures](https://docs.google.com/document/d/10R7zigvUrFPtYojwtqCrn2axnEarXEnd423Ktbk2eNo/edit?usp=sharing) (the same link appears in the [repository root README](../../../README.md) so it shows on the GitHub repo / PR home view.) RealFi / RWA hackathon MVP: a **Cardano vault** that locks a demo NFT, tracks synthetic debt, supports **parametric insurance** (strike in datum), and uses **Pyth Lazer** (pull model) for **liquidation** and **insurance** checks. From ac7107a27eb726ce9eeb00622b7e70d349726788 Mon Sep 17 00:00:00 2001 From: Juan Casimiro <52933122+JuanCasimiro@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:25:32 -0300 Subject: [PATCH 5/5] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 56d85885..de5e8176 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ # Pyth Examples This repository contains examples of applications integrating Pyth products and services. - -### Inventory-Edge Protocol (Cardano / Pyth Lazer) - -**Judges — UI screenshots:** [Google Doc](https://docs.google.com/document/d/10R7zigvUrFPtYojwtqCrn2axnEarXEnd423Ktbk2eNo/edit?usp=sharing) · **Full README:** [lazer/cardano/inventory-edge-protocol/README.md](lazer/cardano/inventory-edge-protocol/README.md) \ No newline at end of file