Skip to content

Commit fe35212

Browse files
committed
bbb
1 parent 01bda65 commit fe35212

8,376 files changed

Lines changed: 1182339 additions & 527 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frontend/package-lock.json

Lines changed: 31 additions & 515 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/pages/Dashboard.tsx

Lines changed: 245 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,87 @@ import NFTCard from "../components/NFTCard";
44
import { subscribe, getPrice } from "../services/pythService";
55
import { useState, useEffect } from "react";
66
import { useWallet } from "@meshsdk/react";
7+
import { buildMintTx } from "../tx/mint";
8+
import { buildBurnTx } from "../tx/burn";
79

810
const ff =
911
"-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', sans-serif";
1012

13+
const API_URL = import.meta.env.VITE_API_URL as string;
14+
const BLOCKFROST_KEY = import.meta.env.VITE_BLOCKFROST_KEY as string;
15+
console.log(BLOCKFROST_KEY);
16+
1117
export default function Dashboard() {
12-
const { connected } = useWallet();
18+
const { connected, wallet } = useWallet();
1319
const [price, setPrice] = useState(getPrice());
1420
const [priceDir, setPriceDir] = useState<"up" | "down" | null>(null);
1521

22+
// Mint modal state
23+
const [mintOpen, setMintOpen] = useState(false);
24+
const [adaInput, setAdaInput] = useState("");
25+
const [mintLoading, setMintLoading] = useState(false);
26+
const [mintResult, setMintResult] = useState<{ ok: boolean; msg: string } | null>(null);
27+
28+
// Burn modal state
29+
const [burnOpen, setBurnOpen] = useState(false);
30+
const [synthInput, setSynthInput] = useState("");
31+
const [burnLoading, setBurnLoading] = useState(false);
32+
const [burnResult, setBurnResult] = useState<{ ok: boolean; msg: string } | null>(null);
33+
34+
const handleBurn = async () => {
35+
const synthFloat = parseFloat(synthInput);
36+
if (!synthFloat || synthFloat <= 0) return;
37+
38+
setBurnLoading(true);
39+
setBurnResult(null);
40+
41+
try {
42+
const res = await fetch(`${API_URL}/api/get-adaprice`);
43+
const data = await res.json();
44+
const pythHex: string = data.solanaPayload;
45+
const adaUsdPrice: number = data.price;
46+
47+
// synthToBurn en micro-USD (6 decimales)
48+
const synthMicro = BigInt(Math.round(synthFloat * 1_000_000));
49+
50+
const txHash = await buildBurnTx(wallet as any, synthMicro, pythHex, adaUsdPrice, BLOCKFROST_KEY);
51+
52+
setBurnResult({ ok: true, msg: `Tx enviada: ${txHash}` });
53+
} catch (e: any) {
54+
setBurnResult({ ok: false, msg: e.message ?? "Error desconocido" });
55+
} finally {
56+
setBurnLoading(false);
57+
}
58+
};
59+
60+
const handleMint = async () => {
61+
const adaFloat = parseFloat(adaInput);
62+
if (!adaFloat || adaFloat <= 0) return;
63+
64+
setMintLoading(true);
65+
setMintResult(null);
66+
67+
try {
68+
// 1. Fetch current price + signed Pyth payload from backend
69+
const res = await fetch(`${API_URL}/api/get-adaprice`);
70+
const data = await res.json();
71+
const pythHex: string = data.solanaPayload;
72+
const adaUsdPrice: number = data.price;
73+
74+
// 2. Convert ADA to lovelaces (bigint)
75+
const lovelaces = BigInt(Math.round(adaFloat * 1_000_000));
76+
77+
// 3. Build, sign and submit the mint tx
78+
const txHash = await buildMintTx(wallet as any, lovelaces, pythHex, adaUsdPrice, BLOCKFROST_KEY);
79+
80+
setMintResult({ ok: true, msg: `Tx enviada: ${txHash}` });
81+
} catch (e: any) {
82+
setMintResult({ ok: false, msg: e.message ?? "Error desconocido" });
83+
} finally {
84+
setMintLoading(false);
85+
}
86+
};
87+
1688
useEffect(() => {
1789
const unsub = subscribe((newPrice) => {
1890
setPrice((prev) => {
@@ -201,7 +273,7 @@ export default function Dashboard() {
201273
{connected && (
202274
<div style={{ display: "flex", justifyContent: "center", gap: 16, marginBottom: 40 }}>
203275
<button
204-
onClick={() => {}}
276+
onClick={() => { setMintOpen(true); setMintResult(null); setAdaInput(""); }}
205277
style={{
206278
background: "linear-gradient(135deg, #0071e3, #30d158)",
207279
border: "none",
@@ -228,7 +300,7 @@ export default function Dashboard() {
228300
Mint
229301
</button>
230302
<button
231-
onClick={() => {}}
303+
onClick={() => { setBurnOpen(true); setBurnResult(null); setSynthInput(""); }}
232304
style={{
233305
background: "rgba(255,69,58,0.15)",
234306
border: "1px solid rgba(255,69,58,0.35)",
@@ -328,6 +400,176 @@ export default function Dashboard() {
328400
</section>
329401
</main>
330402

403+
{/* Mint Modal */}
404+
{mintOpen && (
405+
<div
406+
onClick={() => !mintLoading && setMintOpen(false)}
407+
style={{
408+
position: "fixed", inset: 0, zIndex: 9999,
409+
display: "flex", alignItems: "center", justifyContent: "center",
410+
background: "rgba(0,0,0,0.6)", backdropFilter: "blur(16px)",
411+
}}
412+
>
413+
<div
414+
onClick={(e) => e.stopPropagation()}
415+
style={{
416+
background: "rgba(28,28,30,0.97)",
417+
border: "1px solid rgba(255,255,255,0.1)",
418+
borderRadius: 20, padding: 28, width: 360,
419+
boxShadow: "0 40px 100px rgba(0,0,0,0.8)", fontFamily: ff,
420+
}}
421+
>
422+
<h2 style={{ color: "#f5f5f7", fontSize: 18, fontWeight: 600, marginBottom: 6 }}>
423+
Mint Synth-USD
424+
</h2>
425+
<p style={{ color: "#98989d", fontSize: 13, marginBottom: 20 }}>
426+
Ingresa cuánto ADA deseas depositar
427+
</p>
428+
429+
<input
430+
type="number"
431+
min="1"
432+
placeholder="Ej: 10"
433+
value={adaInput}
434+
onChange={(e) => setAdaInput(e.target.value)}
435+
style={{
436+
width: "100%", boxSizing: "border-box",
437+
background: "rgba(255,255,255,0.06)",
438+
border: "1px solid rgba(255,255,255,0.12)",
439+
borderRadius: 12, padding: "12px 16px",
440+
color: "#f5f5f7", fontSize: 16, fontFamily: ff,
441+
outline: "none", marginBottom: 8,
442+
}}
443+
/>
444+
<p style={{ color: "#98989d", fontSize: 12, marginBottom: 20 }}>
445+
Precio actual: ${price.toFixed(4)} · Recibirás ≈ {adaInput ? (parseFloat(adaInput) * price * 100 / 150).toFixed(4) : "0"} synth-USD
446+
</p>
447+
448+
{mintResult && (
449+
<p style={{
450+
color: mintResult.ok ? "#30d158" : "#ff453a",
451+
fontSize: 13, marginBottom: 16,
452+
wordBreak: "break-all",
453+
}}>
454+
{mintResult.msg}
455+
</p>
456+
)}
457+
458+
<div style={{ display: "flex", gap: 10 }}>
459+
<button
460+
onClick={() => setMintOpen(false)}
461+
disabled={mintLoading}
462+
style={{
463+
flex: 1, padding: "12px 0", borderRadius: 12,
464+
background: "rgba(255,255,255,0.07)",
465+
border: "1px solid rgba(255,255,255,0.1)",
466+
color: "#98989d", fontSize: 15, cursor: "pointer", fontFamily: ff,
467+
}}
468+
>
469+
Cancelar
470+
</button>
471+
<button
472+
onClick={handleMint}
473+
disabled={mintLoading || !adaInput}
474+
style={{
475+
flex: 1, padding: "12px 0", borderRadius: 12,
476+
background: mintLoading ? "rgba(0,113,227,0.5)" : "linear-gradient(135deg, #0071e3, #30d158)",
477+
border: "none", color: "#fff", fontSize: 15,
478+
fontWeight: 600, cursor: mintLoading ? "not-allowed" : "pointer", fontFamily: ff,
479+
}}
480+
>
481+
{mintLoading ? "Procesando..." : "Confirmar"}
482+
</button>
483+
</div>
484+
</div>
485+
</div>
486+
)}
487+
488+
{/* Burn Modal */}
489+
{burnOpen && (
490+
<div
491+
onClick={() => !burnLoading && setBurnOpen(false)}
492+
style={{
493+
position: "fixed", inset: 0, zIndex: 9999,
494+
display: "flex", alignItems: "center", justifyContent: "center",
495+
background: "rgba(0,0,0,0.6)", backdropFilter: "blur(16px)",
496+
}}
497+
>
498+
<div
499+
onClick={(e) => e.stopPropagation()}
500+
style={{
501+
background: "rgba(28,28,30,0.97)",
502+
border: "1px solid rgba(255,255,255,0.1)",
503+
borderRadius: 20, padding: 28, width: 360,
504+
boxShadow: "0 40px 100px rgba(0,0,0,0.8)", fontFamily: ff,
505+
}}
506+
>
507+
<h2 style={{ color: "#f5f5f7", fontSize: 18, fontWeight: 600, marginBottom: 6 }}>
508+
Burn Synth-USD
509+
</h2>
510+
<p style={{ color: "#98989d", fontSize: 13, marginBottom: 20 }}>
511+
Ingresa cuánto synth-USD deseas quemar
512+
</p>
513+
514+
<input
515+
type="number"
516+
min="0"
517+
placeholder="Ej: 5.00"
518+
value={synthInput}
519+
onChange={(e) => setSynthInput(e.target.value)}
520+
style={{
521+
width: "100%", boxSizing: "border-box",
522+
background: "rgba(255,255,255,0.06)",
523+
border: "1px solid rgba(255,255,255,0.12)",
524+
borderRadius: 12, padding: "12px 16px",
525+
color: "#f5f5f7", fontSize: 16, fontFamily: ff,
526+
outline: "none", marginBottom: 8,
527+
}}
528+
/>
529+
<p style={{ color: "#98989d", fontSize: 12, marginBottom: 20 }}>
530+
Precio actual: ${price.toFixed(4)} · Recibirás ≈ {synthInput ? (parseFloat(synthInput) / price).toFixed(4) : "0"} ADA
531+
</p>
532+
533+
{burnResult && (
534+
<p style={{
535+
color: burnResult.ok ? "#30d158" : "#ff453a",
536+
fontSize: 13, marginBottom: 16,
537+
wordBreak: "break-all",
538+
}}>
539+
{burnResult.msg}
540+
</p>
541+
)}
542+
543+
<div style={{ display: "flex", gap: 10 }}>
544+
<button
545+
onClick={() => setBurnOpen(false)}
546+
disabled={burnLoading}
547+
style={{
548+
flex: 1, padding: "12px 0", borderRadius: 12,
549+
background: "rgba(255,255,255,0.07)",
550+
border: "1px solid rgba(255,255,255,0.1)",
551+
color: "#98989d", fontSize: 15, cursor: "pointer", fontFamily: ff,
552+
}}
553+
>
554+
Cancelar
555+
</button>
556+
<button
557+
onClick={handleBurn}
558+
disabled={burnLoading || !synthInput}
559+
style={{
560+
flex: 1, padding: "12px 0", borderRadius: 12,
561+
background: burnLoading ? "rgba(255,69,58,0.3)" : "rgba(255,69,58,0.85)",
562+
border: "none", color: "#fff", fontSize: 15,
563+
fontWeight: 600, cursor: burnLoading ? "not-allowed" : "pointer", fontFamily: ff,
564+
}}
565+
>
566+
{burnLoading ? "Procesando..." : "Confirmar"}
567+
</button>
568+
</div>
569+
</div>
570+
</div>
571+
)}
572+
331573
<style>{`
332574
@keyframes pulse {
333575
0%, 100% { opacity: 1; }

frontend/src/tx/mint.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ import {
44
applyParamsToScript,
55
serializePlutusScript,
66
resolvePlutusScriptHash,
7-
serializeRewardAddress,
87
deserializeAddress,
98
mConStr0,
109
type UTxO,
1110
type BrowserWallet,
1211
} from "@meshsdk/core";
13-
import { getPythScriptHash } from "@pythnetwork/pyth-lazer-cardano-js";
14-
1512
import {
1613
UNPARAMETERISED_SCRIPT_CBOR,
1714
PARAMS,
@@ -67,6 +64,7 @@ export async function buildMintTx(
6764
): Promise<string> {
6865
const provider = new BlockfrostProvider(blockfrostKey);
6966
const { scriptCbor, scriptHash, poolAddress } = getScript();
67+
console.log("api key de blockfrost mint.ts",blockfrostKey);
7068

7169
// ── 1. Fetch UTxOs ────────────────────────────────────────────────────────
7270

@@ -79,14 +77,15 @@ export async function buildMintTx(
7977
// The UTxO changes on every oracle update, so we query by address + asset unit.
8078
const pythStateUnit = PARAMS.PYTH_POLICY_ID + PYTH.STATE_ASSET_NAME;
8179
const pythUtxos: UTxO[] = await provider.fetchAddressUTxOs(PYTH.STATE_ADDRESS, pythStateUnit);
80+
console.log("address de pyth",PYTH.STATE_ADDRESS);
8281
if (pythUtxos.length === 0) throw new Error("Pyth State NFT UTxO not found");
82+
console.log("utxos de pyth",pythUtxos);
8383
const stateUtxo = pythUtxos[0];
8484

85-
// Read the withdraw script hash dynamically from the Pyth State inline datum.
86-
// Mirrors getPythScriptHash() from @pythnetwork/pyth-lazer-cardano-js.
87-
// The datum is Constr(0, [governance, trusted_signers, deprecated_scripts, withdraw_script])
88-
const withdrawScriptHash = getPythScriptHash(stateUtxo as any);
89-
const withdrawAddress = serializeRewardAddress(withdrawScriptHash, true, 0);
85+
// Use hardcoded values from contract.ts — the preprod Pyth State UTxO stores
86+
// its datum by hash (not inline), so we can't derive these dynamically.
87+
const withdrawScriptHash = PYTH.WITHDRAW_SCRIPT_HASH;
88+
const withdrawAddress = PYTH.WITHDRAW_ADDRESS;
9089
console.log("[buildMintTx] withdrawScriptHash:", withdrawScriptHash);
9190
console.log("[buildMintTx] withdrawAddress:", withdrawAddress);
9291

node_modules/.bin/download-msgpackr-prebuilds

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node_modules/.bin/mime

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node_modules/.bin/node-gyp-build-optional-packages

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node_modules/.bin/node-gyp-build-optional-packages-optional

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node_modules/.bin/node-gyp-build-optional-packages-test

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node_modules/.bin/uuid

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)