Skip to content

Commit e78f6b7

Browse files
committed
WIP of tx functions
1 parent 4aa1450 commit e78f6b7

3 files changed

Lines changed: 176 additions & 5 deletions

File tree

frontend/src/tx/burn.ts

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,162 @@
1-
// TODO: implement buildBurnTx
1+
import {
2+
BlockfrostProvider,
3+
MeshTxBuilder,
4+
applyParamsToScript,
5+
serializeAddressObj,
6+
deserializeAddress,
7+
mConStr0,
8+
mConStr1,
9+
mBytes,
10+
mList,
11+
type UTxO,
12+
type BrowserWallet,
13+
} from "@meshsdk/core";
14+
15+
import {
16+
UNPARAMETERISED_SCRIPT_CBOR,
17+
PARAMS,
18+
PYTH,
19+
computeBurnReturn,
20+
} from "./contract";
21+
22+
// ── Derive parameterised script once ─────────────────────────────────────────
23+
24+
function getScript() {
25+
const scriptCbor = applyParamsToScript(UNPARAMETERISED_SCRIPT_CBOR, [
26+
PARAMS.PYTH_POLICY_ID,
27+
PARAMS.ADA_USD_FEED_ID,
28+
PARAMS.COLLATERAL_RATIO,
29+
PARAMS.LIQUIDATION_THRESHOLD,
30+
]);
31+
32+
const { scriptHash } = deserializeAddress(
33+
serializeAddressObj({ scriptHash: scriptCbor }, 0)
34+
);
35+
36+
const poolAddress = serializeAddressObj({ scriptHash }, 0);
37+
38+
return { scriptCbor, scriptHash, poolAddress };
39+
}
40+
41+
// ── buildBurnTx ───────────────────────────────────────────────────────────────
42+
43+
/**
44+
* Build, sign, and submit a Burn transaction.
45+
*
46+
* @param wallet Connected CIP-30 browser wallet (MeshSDK BrowserWallet)
47+
* @param synthToBurn Synth token amount to burn (in micro-USD, 6 decimals)
48+
* @param pythHex Signed Pyth price message (solanaPayload from backend)
49+
* @param adaUsdPrice Current ADA/USD price as a float (e.g. 0.70)
50+
* @param blockfrostKey Blockfrost preprod project ID
51+
* @returns Submitted transaction hash
52+
*/
53+
export async function buildBurnTx(
54+
wallet: BrowserWallet,
55+
synthToBurn: bigint,
56+
pythHex: string,
57+
adaUsdPrice: number,
58+
blockfrostKey: string
59+
): Promise<string> {
60+
const provider = new BlockfrostProvider(blockfrostKey);
61+
const { scriptCbor, scriptHash, poolAddress } = getScript();
62+
63+
// ── 1. Fetch UTxOs ────────────────────────────────────────────────────────
64+
65+
// Pool UTxO — the single UTxO locked at the script address.
66+
const poolUtxos: UTxO[] = await provider.fetchAddressUtxos(poolAddress);
67+
if (poolUtxos.length === 0) throw new Error("Pool UTxO not found");
68+
const poolUtxo = poolUtxos[0];
69+
70+
// Pyth State NFT UTxO — reference input carrying the oracle state.
71+
const pythStateUnit = PARAMS.PYTH_POLICY_ID + PYTH.STATE_ASSET_NAME;
72+
const pythUtxos: UTxO[] = await provider.fetchAddressUtxos(PYTH.STATE_ADDRESS, pythStateUnit);
73+
if (pythUtxos.length === 0) throw new Error("Pyth State NFT UTxO not found");
74+
const stateUtxo = pythUtxos[0];
75+
76+
// User UTxOs — for synth token input and collateral.
77+
const walletUtxos: UTxO[] = await wallet.getUtxos();
78+
const collateral: UTxO[] = await wallet.getCollateral();
79+
if (collateral.length === 0)
80+
throw new Error("No collateral set in wallet. Enable collateral in your wallet settings.");
81+
82+
const walletAddress = await wallet.getChangeAddress();
83+
84+
// ── 2. Compute amounts ────────────────────────────────────────────────────
85+
86+
const adaToReturn = computeBurnReturn(synthToBurn, adaUsdPrice);
87+
if (adaToReturn <= 0n) throw new Error("ADA return amount too small");
88+
89+
const currentPoolLovelace = BigInt(
90+
poolUtxo.output.amount.find((a) => a.unit === "lovelace")?.quantity ?? "0"
91+
);
92+
if (adaToReturn > currentPoolLovelace)
93+
throw new Error("Insufficient ADA in pool for this burn amount");
94+
95+
const newPoolLovelace = currentPoolLovelace - adaToReturn;
96+
97+
// ── 3. Build datums and redeemers ─────────────────────────────────────────
98+
99+
// Keep the existing pool datum (owner stays the same).
100+
const ownerPkh = deserializeAddress(walletAddress).pubKeyHash;
101+
const poolDatum = mConStr0([mBytes(ownerPkh)]);
102+
103+
// Action.Burn — Constr(1, [])
104+
const burnRedeemer = mConStr1([]);
105+
106+
// Pyth withdraw redeemer — List<ByteArray> with the signed price message.
107+
const pythRedeemer = mList([mBytes(pythHex)]);
108+
109+
const col = collateral[0];
110+
111+
// ── 4. Build transaction ──────────────────────────────────────────────────
112+
113+
const txBuilder = new MeshTxBuilder({ fetcher: provider, submitter: provider });
114+
115+
await txBuilder
116+
// Spend the pool UTxO (spend validator delegates to mint validator).
117+
.spendingPlutusScriptV3()
118+
.txIn(
119+
poolUtxo.input.txHash,
120+
poolUtxo.input.outputIndex,
121+
poolUtxo.output.amount,
122+
poolUtxo.output.address
123+
)
124+
.txInInlineDatumPresent()
125+
.txInRedeemerValue(burnRedeemer, "Mesh")
126+
.txInScript(scriptCbor)
127+
128+
// Return pool UTxO with decreased ADA + same datum.
129+
.txOut(poolAddress, [{ unit: "lovelace", quantity: newPoolLovelace.toString() }])
130+
.txOutInlineDatumValue(poolDatum, "Mesh")
131+
132+
// Burn synth tokens (negative mint amount).
133+
.mintPlutusScriptV3()
134+
.mint((-synthToBurn).toString(), scriptHash, "")
135+
.mintingScript(scriptCbor)
136+
.mintRedeemerValue(burnRedeemer, "Mesh")
137+
138+
// Pyth State NFT as reference input (never spent).
139+
.readOnlyTxInReference(stateUtxo.input.txHash, stateUtxo.input.outputIndex)
140+
141+
// Zero-ADA withdrawal from Pyth verify script — carries the signed price message.
142+
.withdrawal(PYTH.WITHDRAW_ADDRESS, "0")
143+
.withdrawalPlutusScriptV3()
144+
.withdrawalScript(PYTH.WITHDRAW_SCRIPT_CBOR)
145+
.withdrawalRedeemerValue(pythRedeemer, "Mesh")
146+
147+
// Collateral + change.
148+
.txInCollateral(
149+
col.input.txHash,
150+
col.input.outputIndex,
151+
col.output.amount,
152+
col.output.address
153+
)
154+
.changeAddress(walletAddress)
155+
.selectUtxosFrom(walletUtxos)
156+
.requiredSignerHash(ownerPkh) // Burn requires owner signature (on-chain check)
157+
.complete();
158+
159+
const unsignedTx = txBuilder.txHex;
160+
const signedTx = await wallet.signTx(unsignedTx);
161+
return wallet.submitTx(signedTx);
162+
}

frontend/src/tx/contract.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ export const PARAMS = {
2929
// ── Pyth on-chain infrastructure (preprod) ────────────────────────────────────
3030

3131
export const PYTH = {
32-
// UTxO carrying the Pyth State NFT (reference input, never spent)
33-
STATE_UTXO_HASH: "TODO" as string,
34-
STATE_UTXO_INDEX: 0,
32+
// Address where the Pyth State NFT lives (constant — the NFT always returns here).
33+
// Query this at runtime to find the current UTxO holding the NFT.
34+
STATE_ADDRESS: "TODO" as string,
35+
36+
// Asset unit = policy_id + hex("Pyth State") used to identify the NFT in the UTxO.
37+
STATE_ASSET_NAME: "50797468205374617465", // "Pyth State" in hex
3538

3639
// The Pyth withdraw script verifies the Ed25519 price signature.
3740
// Its reward address is used for the zero-ADA withdrawal.

frontend/src/tx/mint.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ export async function buildMintTx(
7171
if (poolUtxos.length === 0) throw new Error("Pool UTxO not found");
7272
const poolUtxo = poolUtxos[0];
7373

74+
// Pyth State NFT UTxO — reference input carrying the oracle state.
75+
// The UTxO changes on every oracle update, so we query by address + asset name.
76+
const pythStateUnit = PARAMS.PYTH_POLICY_ID + PYTH.STATE_ASSET_NAME;
77+
const pythUtxos: UTxO[] = await provider.fetchAddressUtxos(PYTH.STATE_ADDRESS, pythStateUnit);
78+
if (pythUtxos.length === 0) throw new Error("Pyth State NFT UTxO not found");
79+
const stateUtxo = pythUtxos[0];
80+
7481
// User UTxOs — for ADA input and collateral.
7582
const walletUtxos: UTxO[] = await wallet.getUtxos();
7683
const collateral: UTxO[] = await wallet.getCollateral();
@@ -133,7 +140,7 @@ export async function buildMintTx(
133140
.mintRedeemerValue(mintRedeemer, "Mesh")
134141

135142
// Pyth State NFT as reference input (never spent).
136-
.readOnlyTxInReference(PYTH.STATE_UTXO_HASH, PYTH.STATE_UTXO_INDEX)
143+
.readOnlyTxInReference(stateUtxo.input.txHash, stateUtxo.input.outputIndex)
137144

138145
// Zero-ADA withdrawal from Pyth verify script — carries the signed price message.
139146
.withdrawal(PYTH.WITHDRAW_ADDRESS, "0")

0 commit comments

Comments
 (0)