Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
df8f8f3
initial commit
igrinspan Mar 22, 2026
0b884d5
prueba
Mar 22, 2026
b97ef92
Borrando mi prueba
Mar 22, 2026
0c5377e
MeshJs init
Mar 22, 2026
1ce0990
Agregue template de aiken
matiWaisman Mar 22, 2026
8a21fd0
Renombre
matiWaisman Mar 22, 2026
9a8718f
Agruegue landing
matiWaisman Mar 22, 2026
8024aec
add wallet connection
igrinspan Mar 22, 2026
90a9cdf
Validator First Version + Flow Diagram
Mar 22, 2026
db75b3f
vals
Mar 22, 2026
e125e53
Tidy up some files in validator folder
Mar 22, 2026
e23ff25
Validadores bet, y NFT para unicidad sin errores de compilacion
Mar 22, 2026
6abe18b
Added pestaña de crear e unirse a juego
matiWaisman Mar 22, 2026
f09f83a
Merge branch 'main' of https://github.com/matiWaisman/pyth-examples
matiWaisman Mar 22, 2026
32dbb21
Grafico de flujo de Txs
Mar 22, 2026
3f3af55
subo primera transaccion
igrinspan Mar 22, 2026
81425e3
Front redesign
Mar 22, 2026
c6fdfac
Anda demo
Mar 22, 2026
e5c39a6
Front demo vuela
Mar 22, 2026
abbb141
Readme etc
Mar 22, 2026
fc26e45
Agregue transaccion depositA pero no anda
matiWaisman Mar 22, 2026
cbb355f
Funciona el primer jugador
Mar 22, 2026
1391a49
add depositB and fix integers in validator Bet
igrinspan Mar 22, 2026
00adfd5
validador de winning tokens
Mar 22, 2026
58f2d05
Agregue deposit b
matiWaisman Mar 22, 2026
db386a2
Merge branch 'main' of https://github.com/matiWaisman/pyth-examples
matiWaisman Mar 22, 2026
7b31ca6
Archivos basura afuera
Mar 22, 2026
1dffa56
fix validator 0.01 instead of 1 percent for draws
igrinspan Mar 22, 2026
1054a5f
add resolve transactions
igrinspan Mar 22, 2026
8aea3bf
push deposit A with saving data in json
igrinspan Mar 22, 2026
fa33ebc
Cambie el readme y el icono que se sigue viendo mal
matiWaisman Mar 22, 2026
792e8d7
Agregando cosas para finalizar integracion con el front
Mar 22, 2026
67c69c0
Confetti
Mar 22, 2026
313ebb4
update resolve so it uses Horseshoe winner token validator
igrinspan Mar 22, 2026
a9d4fcd
Arreglos menores Horseshoe
Mar 22, 2026
d310950
Trofeo etc
Mar 22, 2026
537c50b
Trofeo etc
Mar 22, 2026
05a920f
add buddha
igrinspan Mar 22, 2026
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
418 changes: 418 additions & 0 deletions lazer/cardano/lafhis/README.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions lazer/cardano/lafhis/pyth-coin-stable-front/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Required for the web app on-chain flow
BLOCKFROST_ID=
PYTH_POLICY_ID=
BACKEND_PKH=
MNEMONIC="word1 word2 word3 ..."

# Required only for standalone scripts in src/*.mjs
PYTH_TOKEN=
3 changes: 3 additions & 0 deletions lazer/cardano/lafhis/pyth-coin-stable-front/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
43 changes: 43 additions & 0 deletions lazer/cardano/lafhis/pyth-coin-stable-front/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# env files (can opt-in for committing if needed)
.env*
!.env.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

package-lock.json
124 changes: 124 additions & 0 deletions lazer/cardano/lafhis/pyth-coin-stable-front/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# pyth-coin-stable-front

Next.js frontend for creating and joining Cardano duels and for driving the on-chain deposit flow backed by Pyth price data.

## What is included

- Landing page and duel UI
- Create game / join game flow
- Next.js API routes for temporary game state
- Client and server transaction helpers for `depositA` and `depositB`
- Standalone Node scripts for end-to-end on-chain testing

## Important assumptions

- The app is currently wired for **Cardano preprod**.
- Local game state is stored in memory in [`src/server/gameStore.ts`](/home/mati/Facu/pyth-examples/lazer/cardano/lafhis/pyth-coin-stable-front/src/server/gameStore.ts), so restarting the Next server clears all lobbies.
- The backend reads `../pyth-coin-stable-validators/plutus.json`. If that file is missing, the on-chain routes will fail.

Expected folder layout:

```text
parent/
├── pyth-coin-stable-front/
└── pyth-coin-stable-validators/
└── plutus.json
```

## Requirements

- Node.js 20+ recommended
- npm
- A CIP-30 Cardano wallet extension in the browser
- A funded preprod wallet if you want to test real transactions
- A Blockfrost project ID for Cardano preprod
- The backend wallet mnemonic and its payment key hash (`BACKEND_PKH`)

## Installation

```bash
npm install
```

## Environment variables

Use the example file as a starting point:

```bash
cp .env.example .env.local
```

If you want to run the standalone scripts in `src/*.mjs`, also copy the same values to `.env` because those scripts load environment variables with `dotenv`:

```bash
cp .env.example .env
```

### Required for the web app on-chain flow

These variables are needed to create and join games from the UI:

```env
BLOCKFROST_ID=...
PYTH_POLICY_ID=...
BACKEND_PKH=...
MNEMONIC="word1 word2 word3 ..."
```

Notes:

- `MNEMONIC` is required by the Next.js API routes that co-sign and submit transactions.
- `BACKEND_PKH` must be the payment key hash of the backend wallet used to sign those transactions.
- `../pyth-coin-stable-validators/plutus.json` must exist and contain the compiled validators used by the API routes.

### Required only for standalone scripts

These scripts also need a Pyth token, they are examples of transactions that work using our validator:

- [`src/testFlow.mjs`](/home/mati/Facu/pyth-examples/lazer/cardano/lafhis/pyth-coin-stable-front/src/testFlow.mjs)
- [`src/depositA.mjs`](/home/mati/Facu/pyth-examples/lazer/cardano/lafhis/pyth-coin-stable-front/src/depositA.mjs)
- [`src/depositB.mjs`](/home/mati/Facu/pyth-examples/lazer/cardano/lafhis/pyth-coin-stable-front/src/depositB.mjs)
- [`src/resolve.mjs`](/home/mati/Facu/pyth-examples/lazer/cardano/lafhis/pyth-coin-stable-front/src/resolve.mjs)

```env
PYTH_TOKEN=...
```

## Run locally

```bash
npm run dev
```

Open `http://localhost:3000`.

Useful routes:

- `/`
- `/create-game`
- `/join-game`

## Production build

```bash
npm run build
npm run start
```

## Standalone scripts

The repository also includes direct Node scripts for manual testing:

```bash
node src/testFlow.mjs
node src/depositA.mjs
node src/depositB.mjs
node src/resolve.mjs
```

Those scripts assume:

- `.env` exists
- `PYTH_TOKEN` is set
- `../pyth-coin-stable-validators/plutus.json` exists
- the backend wallet has funds on preprod
7 changes: 7 additions & 0 deletions lazer/cardano/lafhis/pyth-coin-stable-front/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
reactStrictMode: true,
};

export default nextConfig;
34 changes: 34 additions & 0 deletions lazer/cardano/lafhis/pyth-coin-stable-front/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "pyth-coin-stable",
"description": "A Mesh starter template for NextJS",
"author": "MeshJS",
"license": "Apache-2.0",
"version": "0.1.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@meshsdk/core": "^1.9.0-beta.0",
"@meshsdk/react": "^1.9.0-beta.0",
"@pythnetwork/hermes-client": "^2.1.0",
"@pythnetwork/pyth-lazer-sdk": "^2.0.0",
"bech32": "^2.0.0",
"dotenv": "^17.3.1",
"next": "^16.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "15.0.3",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};

export default config;
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useMemo, useState } from "react";

const RATE_OPTIONS = ["ADA/USD", "BTC/USD", "ETH/USD", "BNB/USD"] as const;
const DURATION_OPTIONS = [
{ label: "1 minute", value: "1m" },
{ label: "5 minutes", value: "5m" },
{ label: "1 hour", value: "1h" },
] as const;

export type CreateGameConfigInput = {
rate: (typeof RATE_OPTIONS)[number];
betAda: number;
duration: (typeof DURATION_OPTIONS)[number]["value"];
};

type CreateGameConfigBarProps = {
creating?: boolean;
error?: string | null;
onCreate?: (config: CreateGameConfigInput) => void | Promise<void>;
};

export default function CreateGameConfigBar({
creating = false,
error = null,
onCreate,
}: CreateGameConfigBarProps) {
const [selectedRate, setSelectedRate] = useState<(typeof RATE_OPTIONS)[number]>("ADA/USD");
const [selectedBet, setSelectedBet] = useState("10");
const [selectedDuration, setSelectedDuration] = useState<(typeof DURATION_OPTIONS)[number]["value"]>(
"1m",
);

const selectedDurationLabel = useMemo(
() => DURATION_OPTIONS.find((option) => option.value === selectedDuration)?.label ?? selectedDuration,
[selectedDuration],
);
const betAda = Number(selectedBet);
const canCreate = Number.isFinite(betAda) && betAda >= 5 && !creating;

return (
<section className="rounded-2xl border border-violet-500/25 bg-slate-900/70 p-4 md:p-6">
<p className="mb-4 text-xs font-bold uppercase tracking-[0.18em] text-violet-300">
Create Game Setup
</p>

<div className="grid gap-4 md:grid-cols-3">
<div>
<p className="mb-2 text-[11px] font-bold uppercase tracking-[0.12em] text-violet-100/85">
1. Rate
</p>
<select
value={selectedRate}
onChange={(e) => setSelectedRate(e.target.value as (typeof RATE_OPTIONS)[number])}
className="w-full rounded-lg border border-violet-500/35 bg-slate-950/70 px-3 py-2 text-xs font-bold text-slate-100 outline-none transition focus:border-violet-400/75 focus:shadow-[0_0_16px_rgba(124,58,237,0.28)]"
>
{RATE_OPTIONS.map((rate) => (
<option key={rate} value={rate}>
{rate}
</option>
))}
</select>
</div>

<div>
<p className="mb-2 text-[11px] font-bold uppercase tracking-[0.12em] text-violet-100/85">
2. Bet
</p>
<div className="flex items-center gap-2 rounded-lg border border-violet-500/35 bg-slate-950/70 px-3 py-2">
<input
type="number"
min="5"
step="0.5"
value={selectedBet}
onChange={(e) => setSelectedBet(e.target.value)}
placeholder="Min 5"
className="bet-input-no-spinner w-full bg-transparent text-xs font-bold text-slate-100 outline-none"
/>
<span className="text-xs font-bold text-violet-100/80">ADA</span>
</div>
</div>

<div>
<p className="mb-2 text-[11px] font-bold uppercase tracking-[0.12em] text-violet-100/85">
3. Duration
</p>
<select
value={selectedDuration}
onChange={(e) =>
setSelectedDuration(e.target.value as (typeof DURATION_OPTIONS)[number]["value"])
}
className="w-full rounded-lg border border-violet-500/35 bg-slate-950/70 px-3 py-2 text-xs font-bold text-slate-100 outline-none transition focus:border-violet-400/75 focus:shadow-[0_0_16px_rgba(124,58,237,0.28)]"
>
{DURATION_OPTIONS.map((duration) => (
<option key={duration.value} value={duration.value}>
{duration.label}
</option>
))}
</select>
</div>
</div>

<div className="mt-4 rounded-xl border border-violet-500/20 bg-slate-950/65 px-4 py-3 text-xs text-violet-100/80">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<p>
Current selection: <strong>{selectedRate}</strong> | <strong>{selectedBet || "0"} ADA</strong> |{" "}
<strong>{selectedDurationLabel}</strong>
</p>
<button
type="button"
disabled={!canCreate}
onClick={() => {
if (!canCreate || !onCreate) return;
void onCreate({
rate: selectedRate,
betAda,
duration: selectedDuration,
});
}}
className="rounded-lg border border-violet-400/60 bg-violet-400/15 px-4 py-2 text-[11px] font-bold uppercase tracking-[0.1em] text-slate-100 transition hover:border-violet-300/80 hover:bg-violet-400/22 disabled:cursor-not-allowed disabled:opacity-50"
>
{creating ? "Creating..." : "Create Game"}
</button>
</div>
{error && <p className="mt-3 text-[11px] text-red-400">{error}</p>}
</div>
</section>
);
}
Loading