Skip to content

Commit c9e7f35

Browse files
committed
Improve tx functions
1 parent fc533f7 commit c9e7f35

3 files changed

Lines changed: 78 additions & 26 deletions

File tree

frontend/src/tx/burn.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import {
44
applyParamsToScript,
55
serializePlutusScript,
66
resolvePlutusScriptHash,
7+
serializeRewardAddress,
78
deserializeAddress,
89
mConStr0,
910
mConStr1,
1011
type UTxO,
1112
type BrowserWallet,
1213
} from "@meshsdk/core";
14+
import { getPythScriptHash } from "@pythnetwork/pyth-lazer-cardano-js";
1315

1416
import {
1517
UNPARAMETERISED_SCRIPT_CBOR,
@@ -70,6 +72,17 @@ export async function buildBurnTx(
7072
if (pythUtxos.length === 0) throw new Error("Pyth State NFT UTxO not found");
7173
const stateUtxo = pythUtxos[0];
7274

75+
// Read the withdraw script hash dynamically from the Pyth State inline datum.
76+
const withdrawScriptHash = getPythScriptHash(stateUtxo as any);
77+
const withdrawAddress = serializeRewardAddress(withdrawScriptHash, true, 0);
78+
79+
// Find the UTxO at the Pyth State address that has the withdraw script published as a reference script.
80+
const allPythUtxos: UTxO[] = await provider.fetchAddressUTxOs(PYTH.STATE_ADDRESS);
81+
const withdrawRefUtxo = allPythUtxos.find(
82+
(u) => u.output.scriptHash === withdrawScriptHash
83+
);
84+
if (!withdrawRefUtxo) throw new Error("Pyth withdraw reference script UTxO not found");
85+
7386
// User UTxOs — for synth token input and collateral.
7487
const walletUtxos: UTxO[] = await wallet.getUtxos();
7588
const collateral: UTxO[] = await wallet.getCollateral();
@@ -93,10 +106,18 @@ export async function buildBurnTx(
93106

94107
// ── 3. Build datums and redeemers ─────────────────────────────────────────
95108

96-
// Keep the existing pool datum (owner stays the same).
97-
// In Mesh "Data" format: ByteArray is a plain hex string, Constr is mConStr0.
98-
const ownerPkh = deserializeAddress(walletAddress).pubKeyHash;
99-
const poolDatum = mConStr0([ownerPkh]);
109+
// Read owner PKH from the pool datum — this is what the validator checks.
110+
// PoolDatum is Constr(0, [owner_pkh_hex]).
111+
const datumOwnerPkh: string = (poolUtxo.output.plutusData as any)?.fields?.[0] ?? "";
112+
if (!datumOwnerPkh) throw new Error("Could not read owner from pool datum");
113+
114+
// Fail fast if the connected wallet is not the position owner.
115+
const walletPkh = deserializeAddress(walletAddress).pubKeyHash;
116+
if (walletPkh !== datumOwnerPkh)
117+
throw new Error("Connected wallet is not the position owner");
118+
119+
// Preserve the existing datum owner when writing back (owner never changes).
120+
const poolDatum = mConStr0([datumOwnerPkh]);
100121

101122
// Action.Burn — Constr(1, [])
102123
const burnRedeemer = mConStr1([]);
@@ -137,9 +158,16 @@ export async function buildBurnTx(
137158
.readOnlyTxInReference(stateUtxo.input.txHash, stateUtxo.input.outputIndex)
138159

139160
// Zero-ADA withdrawal from Pyth verify script — carries the signed price message.
140-
.withdrawal(PYTH.WITHDRAW_ADDRESS, "0")
161+
// Address and script hash derived dynamically from the Pyth State datum.
162+
// Script is referenced by UTxO (no CBOR needed).
163+
.withdrawal(withdrawAddress, "0")
141164
.withdrawalPlutusScriptV3()
142-
.withdrawalScript(PYTH.WITHDRAW_SCRIPT_CBOR)
165+
.withdrawalTxInReference(
166+
withdrawRefUtxo.input.txHash,
167+
withdrawRefUtxo.input.outputIndex,
168+
String(withdrawRefUtxo.output.scriptRef?.length ? withdrawRefUtxo.output.scriptRef.length / 2 : 0),
169+
withdrawScriptHash
170+
)
143171
.withdrawalRedeemerValue(pythRedeemer, "Mesh")
144172

145173
// Collateral + change.
@@ -151,7 +179,7 @@ export async function buildBurnTx(
151179
)
152180
.changeAddress(walletAddress)
153181
.selectUtxosFrom(walletUtxos)
154-
.requiredSignerHash(ownerPkh) // Burn requires owner signature (on-chain check)
182+
.requiredSignerHash(datumOwnerPkh) // Burn requires owner signature (on-chain check)
155183
.complete();
156184

157185
const unsignedTx = txBuilder.txHex;

frontend/src/tx/liquidate.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import {
44
applyParamsToScript,
55
serializePlutusScript,
66
resolvePlutusScriptHash,
7+
serializeRewardAddress,
78
mConStr0,
89
mConStr2,
910
type UTxO,
1011
type BrowserWallet,
1112
} from "@meshsdk/core";
13+
import { getPythScriptHash } from "@pythnetwork/pyth-lazer-cardano-js";
1214

1315
import {
1416
UNPARAMETERISED_SCRIPT_CBOR,
@@ -72,6 +74,17 @@ export async function buildLiquidateTx(
7274
if (pythUtxos.length === 0) throw new Error("Pyth State NFT UTxO not found");
7375
const stateUtxo = pythUtxos[0];
7476

77+
// Read the withdraw script hash dynamically from the Pyth State inline datum.
78+
const withdrawScriptHash = getPythScriptHash(stateUtxo as any);
79+
const withdrawAddress = serializeRewardAddress(withdrawScriptHash, true, 0);
80+
81+
// Find the UTxO at the Pyth State address that has the withdraw script published as a reference script.
82+
const allPythUtxos: UTxO[] = await provider.fetchAddressUTxOs(PYTH.STATE_ADDRESS);
83+
const withdrawRefUtxo = allPythUtxos.find(
84+
(u) => u.output.scriptHash === withdrawScriptHash
85+
);
86+
if (!withdrawRefUtxo) throw new Error("Pyth withdraw reference script UTxO not found");
87+
7588
// Liquidator UTxOs — for collateral; ADA reward goes to liquidator's change address.
7689
const walletUtxos: UTxO[] = await wallet.getUtxos();
7790
const collateral: UTxO[] = await wallet.getCollateral();
@@ -140,9 +153,16 @@ export async function buildLiquidateTx(
140153
.readOnlyTxInReference(stateUtxo.input.txHash, stateUtxo.input.outputIndex)
141154

142155
// Zero-ADA withdrawal from Pyth verify script — carries the signed price message.
143-
.withdrawal(PYTH.WITHDRAW_ADDRESS, "0")
156+
// Address and script hash derived dynamically from the Pyth State datum.
157+
// Script is referenced by UTxO (no CBOR needed).
158+
.withdrawal(withdrawAddress, "0")
144159
.withdrawalPlutusScriptV3()
145-
.withdrawalScript(PYTH.WITHDRAW_SCRIPT_CBOR)
160+
.withdrawalTxInReference(
161+
withdrawRefUtxo.input.txHash,
162+
withdrawRefUtxo.input.outputIndex,
163+
String(withdrawRefUtxo.output.scriptRef?.length ? withdrawRefUtxo.output.scriptRef.length / 2 : 0),
164+
withdrawScriptHash
165+
)
146166
.withdrawalRedeemerValue(pythRedeemer, "Mesh")
147167

148168
// Collateral + change (liquidator receives the ADA profit via change).

frontend/src/tx/mint.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ export async function buildMintTx(
7171
// ── 1. Fetch UTxOs ────────────────────────────────────────────────────────
7272

7373
// Pool UTxO — the single UTxO locked at the script address.
74+
// May not exist on the very first mint; the validator handles that case on-chain.
7475
const poolUtxos: UTxO[] = await provider.fetchAddressUTxOs(poolAddress);
75-
if (poolUtxos.length === 0) throw new Error("Pool UTxO not found");
76-
const poolUtxo = poolUtxos[0];
76+
const poolUtxo: UTxO | undefined = poolUtxos[0];
7777

7878
// Pyth State NFT UTxO — reference input carrying the oracle state.
7979
// The UTxO changes on every oracle update, so we query by address + asset unit.
@@ -112,9 +112,9 @@ export async function buildMintTx(
112112
const mintAmount = computeMintAmount(adaToDeposit, adaUsdPrice);
113113
if (mintAmount <= 0n) throw new Error("Mint amount too small");
114114

115-
const currentPoolLovelace = BigInt(
116-
poolUtxo.output.amount.find((a) => a.unit === "lovelace")?.quantity ?? "0"
117-
);
115+
const currentPoolLovelace = poolUtxo
116+
? BigInt(poolUtxo.output.amount.find((a) => a.unit === "lovelace")?.quantity ?? "0")
117+
: 0n;
118118
const newPoolLovelace = currentPoolLovelace + adaToDeposit;
119119

120120
// ── 3. Build datums and redeemers ─────────────────────────────────────────
@@ -138,19 +138,23 @@ export async function buildMintTx(
138138

139139
const txBuilder = new MeshTxBuilder({ fetcher: provider, submitter: provider });
140140

141-
await txBuilder
142-
// Spend the pool UTxO (spend validator delegates to mint validator).
143-
.spendingPlutusScriptV3()
144-
.txIn(
145-
poolUtxo.input.txHash,
146-
poolUtxo.input.outputIndex,
147-
poolUtxo.output.amount,
148-
poolUtxo.output.address
149-
)
150-
.txInInlineDatumPresent()
151-
.txInRedeemerValue(mintRedeemer, "Mesh")
152-
.txInScript(scriptCbor)
141+
// On the first-ever mint there is no existing pool UTxO to spend.
142+
// On subsequent mints the old pool UTxO must be spent and recreated.
143+
if (poolUtxo) {
144+
txBuilder
145+
.spendingPlutusScriptV3()
146+
.txIn(
147+
poolUtxo.input.txHash,
148+
poolUtxo.input.outputIndex,
149+
poolUtxo.output.amount,
150+
poolUtxo.output.address
151+
)
152+
.txInInlineDatumPresent()
153+
.txInRedeemerValue(mintRedeemer, "Mesh")
154+
.txInScript(scriptCbor);
155+
}
153156

157+
await txBuilder
154158
// Return pool UTxO with increased ADA + updated datum.
155159
.txOut(poolAddress, [{ unit: "lovelace", quantity: newPoolLovelace.toString() }])
156160
.txOutInlineDatumValue(poolDatum, "Mesh")

0 commit comments

Comments
 (0)