Skip to content

Commit ac663de

Browse files
committed
cambios en los contratos mint burn
1 parent 08be850 commit ac663de

6 files changed

Lines changed: 135 additions & 58 deletions

File tree

frontend/src/pages/Dashboard.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ export default function Dashboard() {
5151

5252
setBurnResult({ ok: true, msg: `Tx enviada: ${txHash}` });
5353
} catch (e: any) {
54-
setBurnResult({ ok: false, msg: e.message ?? "Error desconocido" });
54+
console.error("[handleBurn] error:", e);
55+
if (e?.info) console.error("[handleBurn] node error info:", JSON.stringify(e.info));
56+
if (e?.data) console.error("[handleBurn] error data:", JSON.stringify(e.data));
57+
setBurnResult({ ok: false, msg: e?.info ?? e.message ?? "Error desconocido" });
5558
} finally {
5659
setBurnLoading(false);
5760
}
@@ -79,7 +82,11 @@ export default function Dashboard() {
7982

8083
setMintResult({ ok: true, msg: `Tx enviada: ${txHash}` });
8184
} catch (e: any) {
82-
setMintResult({ ok: false, msg: e.message ?? "Error desconocido" });
85+
console.error("[handleMint] error:", e);
86+
// CIP-30 TxSendError has .info with the actual node rejection reason
87+
if (e?.info) console.error("[handleMint] node error info:", JSON.stringify(e.info));
88+
if (e?.data) console.error("[handleMint] error data:", JSON.stringify(e.data));
89+
setMintResult({ ok: false, msg: e?.info ?? e.message ?? "Error desconocido" });
8390
} finally {
8491
setMintLoading(false);
8592
}

frontend/src/tx/burn.ts

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import {
1111
type UTxO,
1212
type BrowserWallet,
1313
} from "@meshsdk/core";
14-
import { getPythScriptHash } from "@pythnetwork/pyth-lazer-cardano-js";
15-
14+
import { addVKeyWitnessSetToTransaction } from "@meshsdk/core-cst";
1615
import {
1716
UNPARAMETERISED_SCRIPT_CBOR,
1817
PARAMS,
@@ -59,6 +58,14 @@ export async function buildBurnTx(
5958
const provider = new BlockfrostProvider(blockfrostKey);
6059
const { scriptCbor, scriptHash, poolAddress } = getScript();
6160

61+
// Bounded validity range required by the Pyth verify script.
62+
const latestBlockResp = await fetch(
63+
"https://cardano-preprod.blockfrost.io/api/v0/blocks/latest",
64+
{ headers: { project_id: blockfrostKey } }
65+
);
66+
const latestBlock = await latestBlockResp.json();
67+
const currentSlot: number = latestBlock.slot;
68+
6269
// ── 1. Fetch UTxOs ────────────────────────────────────────────────────────
6370

6471
// Pool UTxO — the single UTxO locked at the script address.
@@ -72,8 +79,10 @@ export async function buildBurnTx(
7279
if (pythUtxos.length === 0) throw new Error("Pyth State NFT UTxO not found");
7380
const stateUtxo = pythUtxos[0];
7481

75-
// Read the withdraw script hash dynamically from the Pyth State inline datum.
76-
const withdrawScriptHash = getPythScriptHash(stateUtxo as any);
82+
// Use the hardcoded withdraw script hash (same approach as mint.ts).
83+
// getPythScriptHash() fails because Blockfrost returns the datum in a format
84+
// the function doesn't recognise.
85+
const withdrawScriptHash = PYTH.WITHDRAW_SCRIPT_HASH;
7786
const withdrawAddress = serializeRewardAddress(withdrawScriptHash, true, 0);
7887

7988
// Find the UTxO at the Pyth State address that has the withdraw script published as a reference script.
@@ -83,32 +92,61 @@ export async function buildBurnTx(
8392
);
8493
if (!withdrawRefUtxo) throw new Error("Pyth withdraw reference script UTxO not found");
8594

86-
// User UTxOs — for synth token input and collateral.
87-
const walletUtxos: UTxO[] = await wallet.getUtxos();
88-
const collateral: UTxO[] = await wallet.getCollateral();
89-
if (collateral.length === 0)
90-
throw new Error("No collateral set in wallet. Enable collateral in your wallet settings.");
95+
const walletAddress = await (wallet as any).getChangeAddressBech32();
9196

92-
const walletAddress = await wallet.getChangeAddress();
97+
// wallet.getUtxos() also returns raw CBOR — use Blockfrost to get decoded UTxOs instead.
98+
const walletUtxos: UTxO[] = await provider.fetchAddressUTxOs(walletAddress);
99+
100+
// wallet.getCollateral() returns raw CIP-30 CBOR hex strings, not decoded UTxOs.
101+
// CBOR: 82 82 5820 <32B txHash> <uint outputIndex> 82 5839 <57B address> <uint lovelace>
102+
const collateralRaw: string[] = (await wallet.getCollateral()) as unknown as string[];
103+
if (collateralRaw.length === 0)
104+
throw new Error("No collateral set in wallet. Enable collateral in your wallet settings.");
105+
const colTxHash = collateralRaw[0].slice(8, 72);
106+
const colOiByte = parseInt(collateralRaw[0].slice(72, 74), 16);
107+
const colIndex = colOiByte <= 23 ? colOiByte : parseInt(collateralRaw[0].slice(74, 76), 16);
108+
// Construct collateral UTxO directly — address is always the user's wallet address.
109+
const col: UTxO = {
110+
input: { txHash: colTxHash, outputIndex: colIndex },
111+
output: { address: walletAddress, amount: [{ unit: "lovelace", quantity: "5000000" }] },
112+
};
93113

94114
// ── 2. Compute amounts ────────────────────────────────────────────────────
95115

96116
const adaToReturn = computeBurnReturn(synthToBurn, adaUsdPrice);
117+
console.log("[buildBurnTx] synthToBurn:", synthToBurn.toString(), "adaUsdPrice:", adaUsdPrice, "adaToReturn:", adaToReturn.toString());
97118
if (adaToReturn <= 0n) throw new Error("ADA return amount too small");
98119

99120
const currentPoolLovelace = BigInt(
100121
poolUtxo.output.amount.find((a) => a.unit === "lovelace")?.quantity ?? "0"
101122
);
102-
if (adaToReturn > currentPoolLovelace)
103-
throw new Error("Insufficient ADA in pool for this burn amount");
123+
console.log("[buildBurnTx] currentPoolLovelace:", currentPoolLovelace.toString());
124+
if (adaToReturn > currentPoolLovelace) {
125+
const rawPrice = BigInt(Math.round(adaUsdPrice * 1e8));
126+
const maxBurnable = (currentPoolLovelace * rawPrice) / 100_000_000n;
127+
throw new Error(
128+
`Insufficient ADA in pool. Max burnable synth at current price: ${maxBurnable} micro-synth (${Number(maxBurnable) / 1e6} synth). ` +
129+
`You may have old tokens from a previous contract deployment — burn only what you minted in this session.`
130+
);
131+
}
104132

105133
const newPoolLovelace = currentPoolLovelace - adaToReturn;
106134

107135
// ── 3. Build datums and redeemers ─────────────────────────────────────────
108136

109137
// Read owner PKH from the pool datum — this is what the validator checks.
110138
// PoolDatum is Constr(0, [owner_pkh_hex]).
111-
const datumOwnerPkh: string = (poolUtxo.output.plutusData as any)?.fields?.[0] ?? "";
139+
// Blockfrost decodes inline datums as { constructor: N, fields: [{ bytes: "..." }] }
140+
// so we need .fields[0].bytes, not .fields[0] directly.
141+
// plutusData from Blockfrost is raw CBOR hex, e.g.:
142+
// d8799f581c<28-byte-pkh>ff
143+
// d879 = tag 121 (Constr 0), 9f = indef array, 581c = 28-byte bytestring
144+
// Extract the 28-byte (56 hex char) PKH directly from the CBOR string.
145+
const plutusCbor = poolUtxo.output.plutusData as string;
146+
const CONSTR0_PREFIX = "d8799f581c"; // Constr(0,[ByteArray(28)])
147+
const datumOwnerPkh: string = plutusCbor?.startsWith(CONSTR0_PREFIX)
148+
? plutusCbor.slice(CONSTR0_PREFIX.length, CONSTR0_PREFIX.length + 56)
149+
: "";
112150
if (!datumOwnerPkh) throw new Error("Could not read owner from pool datum");
113151

114152
// Fail fast if the connected wallet is not the position owner.
@@ -125,10 +163,13 @@ export async function buildBurnTx(
125163
// Pyth withdraw redeemer — List<ByteArray> with the signed price message.
126164
const pythRedeemer = [pythHex];
127165

128-
const col = collateral[0];
129166

130167
// ── 4. Build transaction ──────────────────────────────────────────────────
131168

169+
const exSpend = { mem: 2_000_000, steps: 1_000_000_000 };
170+
const exMint = { mem: 4_000_000, steps: 2_000_000_000 };
171+
const exWithdraw = { mem: 8_000_000, steps: 5_000_000_000 };
172+
132173
const txBuilder = new MeshTxBuilder({ fetcher: provider, submitter: provider });
133174

134175
await txBuilder
@@ -141,7 +182,7 @@ export async function buildBurnTx(
141182
poolUtxo.output.address
142183
)
143184
.txInInlineDatumPresent()
144-
.txInRedeemerValue(burnRedeemer, "Mesh")
185+
.txInRedeemerValue(burnRedeemer, "Mesh", exSpend)
145186
.txInScript(scriptCbor)
146187

147188
// Return pool UTxO with decreased ADA + same datum.
@@ -152,23 +193,23 @@ export async function buildBurnTx(
152193
.mintPlutusScriptV3()
153194
.mint((-synthToBurn).toString(), scriptHash, "")
154195
.mintingScript(scriptCbor)
155-
.mintRedeemerValue(burnRedeemer, "Mesh")
196+
.mintRedeemerValue(burnRedeemer, "Mesh", exMint)
156197

157198
// Pyth State NFT as reference input (never spent).
158199
.readOnlyTxInReference(stateUtxo.input.txHash, stateUtxo.input.outputIndex)
159200

160201
// Zero-ADA withdrawal from Pyth verify script — carries the signed price message.
161202
// Address and script hash derived dynamically from the Pyth State datum.
162203
// Script is referenced by UTxO (no CBOR needed).
163-
.withdrawal(withdrawAddress, "0")
164204
.withdrawalPlutusScriptV3()
205+
.withdrawal(withdrawAddress, "0")
165206
.withdrawalTxInReference(
166207
withdrawRefUtxo.input.txHash,
167208
withdrawRefUtxo.input.outputIndex,
168209
String(withdrawRefUtxo.output.scriptRef?.length ? withdrawRefUtxo.output.scriptRef.length / 2 : 0),
169210
withdrawScriptHash
170211
)
171-
.withdrawalRedeemerValue(pythRedeemer, "Mesh")
212+
.withdrawalRedeemerValue(pythRedeemer, "Mesh", exWithdraw)
172213

173214
// Collateral + change.
174215
.txInCollateral(
@@ -180,9 +221,12 @@ export async function buildBurnTx(
180221
.changeAddress(walletAddress)
181222
.selectUtxosFrom(walletUtxos)
182223
.requiredSignerHash(datumOwnerPkh) // Burn requires owner signature (on-chain check)
224+
.invalidBefore(currentSlot - 60)
225+
.invalidHereafter(currentSlot + 600)
183226
.complete();
184227

185228
const unsignedTx = txBuilder.txHex;
186-
const signedTx = await wallet.signTx(unsignedTx);
187-
return wallet.submitTx(signedTx);
229+
const witnessSet = await wallet.signTx(unsignedTx);
230+
const signedTx = addVKeyWitnessSetToTransaction(unsignedTx, witnessSet);
231+
return provider.submitTx(signedTx);
188232
}

0 commit comments

Comments
 (0)