Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lazer/cardano/tokenized commodities/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Cardano / Blockfrost
CARDANO_NETWORK=PreProd
BLOCKFROST_PREPROD_URL=https://cardano-preprod.blockfrost.io/api/v0
BLOCKFROST_PROJECT_ID=replace_me

# Wallet seed only for local demo / test wallets
CARDANO_MNEMONIC=replace_me

# Pyth
PYTH_API_TOKEN=replace_me
PYTH_POLICY_ID_PREPROD=d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6

# Database
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/cardano_hackathon

# Product flags
SOLARCHAIN_PRICE_FEED_ID=16
COMMODITIES_PRICE_FEED_ID=16
9 changes: 9 additions & 0 deletions lazer/cardano/tokenized commodities/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
dist
.next
coverage
.env
.env.local
build
*.tsbuildinfo
.DS_Store
213 changes: 213 additions & 0 deletions lazer/cardano/tokenized commodities/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Norma operativa unificada — SolarChain + Tokenized Commodities

Este monorepo define una **plataforma Cardano común** para ambos equipos y dos capas de dominio separadas.
Objetivo: **baja fricción**, **misma disciplina técnica**, **mínima duplicación**, **máximo foco de hackathon**.

---

## 1. Stack estándar obligatorio

- **On-chain:** Aiken
- **Off-chain blockchain:** TypeScript + Node.js 20 + Lucid
- **Proveedor:** Blockfrost
- **Oracle de precio:** Pyth
- **Backend producto:** Fastify + PostgreSQL
- **Front:** Next.js + TypeScript
- **Red:** Cardano PreProd
- **Tokenización:** Native Assets
- **Metadata dinámica:** CIP-68 / transaction metadata según el caso

---

## 2. Regla madre de arquitectura

### Lo compartido
- conexión Cardano
- provider
- tipos
- fetch de Pyth
- helpers de metadata / tx
- disciplina de CI
- convenciones de carpetas
- estrategia de logs, errores y validaciones

### Lo NO compartido
- reglas de negocio
- datums / redeemers específicos
- endpoints de dominio
- dashboards
- contratos por caso de uso

---

## 3. Squads y responsabilidades

### Squad Platform / Shared Core
Responsable de:
- `packages/config`
- `packages/shared-types`
- `packages/cardano-core`
- `packages/pyth-adapter`
- pipelines, calidad y normalización del repo

### Squad SolarChain
Responsable de:
- `packages/solarchain-domain`
- `apps/solarchain-api`
- `apps/solarchain-web`
- validator `solarchain_settlement.ak`

### Squad Tokenized Commodities
Responsable de:
- `packages/commodities-domain`
- `apps/tokenized-commodities-api`
- `apps/tokenized-commodities-web`
- validator `commodity_escrow.ak`

### Regla de ownership
Nadie modifica dominio ajeno sin PR y review del owner.
Lo compartido requiere review del Squad Platform.

---

## 4. Estructura del monorepo

```text
apps/
solarchain-api/
solarchain-web/
tokenized-commodities-api/
tokenized-commodities-web/

packages/
config/
shared-types/
cardano-core/
pyth-adapter/
solarchain-domain/
commodities-domain/
contracts-aiken/
```

---

## 5. Flujo de trabajo obligatorio

1. Pull del branch principal.
2. Crear branch por feature:
- `feat/solarchain-*`
- `feat/commodities-*`
- `feat/platform-*`
3. Implementar.
4. Ejecutar:
- `npm run typecheck`
- `npm run build`
5. Abrir PR.
6. Review técnico y de seguridad.
7. Merge.

---

## 6. Convenciones mínimas

- Node **20+** obligatorio.
- ESM obligatorio.
- Ningún secreto hardcodeado.
- Ningún endpoint escribe en chain sin validación previa.
- Ningún contrato on-chain hace trabajo que puede vivir off-chain.
- Telemetría cruda de SolarChain **no se sube on-chain**.
- Contratos de commodities **no centralizan todo en un solo UTxO**.
- Pyth solo se usa para pricing / settlement references, no para inventar datos físicos.

---

## 7. Checklist de implementación — 72 horas

## Hora 0 a 6
- [ ] Clonar repo
- [ ] Copiar `.env.example` a `.env`
- [ ] Levantar Postgres
- [ ] Verificar Blockfrost
- [ ] Verificar token Pyth
- [ ] Ejecutar API de health en ambos productos

## Hora 6 a 18
- [ ] Compilar Aiken
- [ ] Exportar `plutus.json`
- [ ] Conectar Lucid a PreProd
- [ ] Probar lectura de wallet / UTxOs
- [ ] Probar fetch de update firmado de Pyth

## Hora 18 a 36
- [ ] SolarChain: endpoint de batch + settlement
- [ ] Commodities: endpoint de acuerdo + settlement
- [ ] Persistencia de snapshots en Postgres
- [ ] Metadata para txs y eventos

## Hora 36 a 54
- [ ] Demo UI SolarChain
- [ ] Demo UI Commodities
- [ ] Integración API -> shared core -> chain builder
- [ ] Logs estructurados
- [ ] Manejo de errores y validaciones

## Hora 54 a 72
- [ ] Dry run end-to-end en PreProd
- [ ] QA funcional
- [ ] QA de seguridad
- [ ] Definir narrativa de pitch
- [ ] Congelar scope
- [ ] Preparar video/demo final

---

## 8. Runbook local

```bash
cp .env.example .env
docker compose up -d
npm install

npm run dev:solarchain:api
npm run dev:commodities:api
npm run dev:solarchain:web
npm run dev:commodities:web
```

---

## 9. Riesgos operativos que NO se permiten

- meter Haskell/Plutus como estándar base de hackathon
- reimplementar providers u oráculos por producto
- meter microservicios innecesarios
- subir documentos o telemetría completa on-chain
- centralizar el estado de commodities en un único UTxO

---

## 10. Entregable mínimo de cada producto

### SolarChain
- alta de batch energético
- snapshot off-chain
- settlement candidate
- tx builder de liquidación
- dashboard con generación / ahorro / equivalencia

### Tokenized Commodities
- alta de acuerdo
- pricing reference desde Pyth
- cálculo de settlement
- builder de tx de liquidación
- dashboard con posición, cap, floor y vencimiento

---

## Tokenized Commodities package notes

- Product docs: `docs/tokenized-commodities/`
- Example quote request: `examples/commodity-quote-request.json`
- Example dispute request: `examples/commodity-dispute-request.json`
- API port: `4020`
- Web port: `3001`
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@apps/solarchain-api",
"version": "0.1.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc -p tsconfig.json",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"fastify": "^5.2.1",
"@packages/cardano-core": "file:../../../../packages/cardano-core",
"@packages/solarchain-domain": "file:../../../../packages/solarchain-domain",
"@packages/shared-types": "file:../../../../packages/shared-types"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Fastify from "fastify";
import { buildSolarSettlementQuote } from "@packages/solarchain-domain";
import { buildHackathonMetadata, HACKATHON_METADATA_LABEL } from "@packages/cardano-core";
import type { SolarBatch } from "@packages/shared-types";

const app = Fastify({ logger: true });

app.get("/health", async () => ({
ok: true,
product: "solarchain",
now: new Date().toISOString()
}));

app.post<{ Body: SolarBatch }>("/batches/quote", async (request, reply) => {
const quote = buildSolarSettlementQuote(request.body);
return reply.send(quote);
});

app.post<{ Body: SolarBatch }>("/batches/prepare-settlement", async (request, reply) => {
const quote = buildSolarSettlementQuote(request.body);

const metadata = buildHackathonMetadata("solarchain", {
batchId: quote.batchId,
exportedWh: quote.exportedWh,
avoidedCo2Kg: quote.avoidedCo2Kg,
savingsUsd: quote.savingsUsd,
batchHash: quote.batchHash
});

/**
* Acá se debería:
* 1. cargar el validator exportado por Aiken
* 2. construir la tx con Lucid
* 3. agregar metadata CIP / label 674
* 4. firmar y enviar
*
* En un hackathon real se puede devolver una "tx request" para que el front
* o una wallet service termine el firmado.
*/
return reply.send({
product: "solarchain",
quote,
metadataLabel: HACKATHON_METADATA_LABEL,
metadata,
nextAction: "build_and_sign_cardano_tx"
});
});

app.listen({ port: 4010, host: "0.0.0.0" }).catch((error) => {
app.log.error(error);
process.exit(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ReactNode } from "react";

export const metadata = {
title: "SolarChain Demo",
description: "Solar settlement demo on Cardano PreProd"
};

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="es">
<body style={{ margin: 0, background: "#f8fafc" }}>{children}</body>
</html>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const cards = [
{
"title": "Batch intake",
"body": "Carga de lecturas energéticas y construcción de quote de settlement."
},
{
"title": "Savings",
"body": "Cálculo de ahorro económico, exportación y reducción de emisiones."
},
{
"title": "Tokenization",
"body": "Preparación de metadata y liquidación hacia Native Assets / CIP-68."
}
];

export default function Page() {
return (
<main style={{
fontFamily: "Inter, system-ui, sans-serif",
margin: "0 auto",
maxWidth: 1100,
padding: "32px"
}}>
<h1 style={{ fontSize: 36, marginBottom: 12 }}>SolarChain</h1>
<p style={{ color: "#444", maxWidth: 900 }}>
Demo web del hackathon. Mantiene el mismo stack base Cardano que el otro producto,
pero cambia la lógica de dominio. El backend esperado corre en <code>http://localhost:4010</code>.
</p>

<section style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
gap: 16,
marginTop: 24
}}>
{cards.map((card) => (
<article
key={card.title}
style={{
border: "1px solid #ddd",
borderRadius: 16,
padding: 16,
boxShadow: "0 8px 24px rgba(0,0,0,0.04)"
}}
>
<h2 style={{ fontSize: 20 }}>{card.title}</h2>
<p style={{ color: "#555" }}>{card.body}</p>
</article>
))}
</section>
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
Loading