Skip to content

Cardano - Hydra Boys#108

Open
nicolasLuduena wants to merge 5 commits intopyth-network:mainfrom
nicolasLuduena:main
Open

Cardano - Hydra Boys#108
nicolasLuduena wants to merge 5 commits intopyth-network:mainfrom
nicolasLuduena:main

Conversation

@nicolasLuduena
Copy link

@nicolasLuduena nicolasLuduena commented Mar 22, 2026

Pyth Examples Contribution

Type of Contribution

  • New Example Project (Adding a new example to demonstrate Pyth integration)
  • Bug Fix (Fixing an issue in existing examples)
  • Documentation Update (Improving README, comments, or guides)
  • Enhancement (Improving existing functionality or adding features)
  • Hackathon Submission (Submitting a project from a hackathon)

Project Information

Hermes:

Pyth Product Used:

  • Pyth Price Feeds
  • Pyth Entropy
  • Multiple Products
  • Other: ___________

Blockchain/Platform:

  • Cardano
  • Ethereum/EVM
  • Solana
  • Aptos
  • Sui
  • Fuel
  • Starknet
  • TON
  • Other: ___________

Description

What does this contribution do?

5 minute market for Cardano L2 Hydra

How does it integrate with Pyth?

Uses price feeds on Cardano for validating the bets of users of whether or not an asset is above or below a threshold every 5 minutes in real time.

What problem does it solve or demonstrate?

It demonstrates an actual high speed integration of a 5 minute market with Cardano through an L2. Nothing that's shown on how to use on examples.

Directory Structure (for new examples)

hermes/
├── doc/            # Diagrams of the designed transactions 
├── infra/          # Necessary code to spin up the hydra node in offline mode
├── onchain/        # Complete onchain validations
├── server/         # Backend server that handles pyth feed pulling and bot simulations
├── ui/             # Frontend and app
└── README.md       # Project description and documentation

Testing & Verification

How to Test This Contribution

Prerequisites

  • Node.js version: ___
  • Other dependencies: ___

Setup & Run Instructions

# Add your setup and run commands here
cd [path-to-your-example]
npm install
# ... other commands

Deployment Information (if applicable)

Network:

Contract Address(es):

Demo URL:

Checklist

Code Quality

  • Code follows existing patterns in the repository
  • Proper error handling implemented
  • No hardcoded values (use environment variables where appropriate)

Testing

  • Tested locally and works as expected
  • All existing functionality still works (no breaking changes)

Additional Context

Related Issues

Fixes #

Screenshots/Demo (if applicable)

Notes for Reviewers


Thank you for contributing to Pyth Examples!


Open with Devin

@nicolasLuduena nicolasLuduena changed the title feat: initial submission hydra boys Cardano - Hydra Boys Mar 22, 2026
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +64 to +67
const result: UTxO[] = [];
const utxos = await this.getUtxos(addressOrCredential);
utxos.filter((utxo) => utxo.assets[unit] > 0n);
return result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 getUtxosWithUnit always returns an empty array — filter result is discarded

getUtxosWithUnit at lazer/cardano/hermes/server/src/offchain/hydra/provider.ts:63-67 creates an empty result array, calls utxos.filter(...) (which returns a new filtered array without mutating utxos), and then returns the original empty result. The filtered array is silently discarded, so this method will always return [] regardless of what UTxOs exist.

Code showing discarded filter result
async getUtxosWithUnit(...): Promise<UTxO[]> {
    const result: UTxO[] = [];
    const utxos = await this.getUtxos(addressOrCredential);
    utxos.filter((utxo) => utxo.assets[unit] > 0n); // result discarded!
    return result; // always empty
}
Suggested change
const result: UTxO[] = [];
const utxos = await this.getUtxos(addressOrCredential);
utxos.filter((utxo) => utxo.assets[unit] > 0n);
return result;
const result: UTxO[] = [];
const utxos = await this.getUtxos(addressOrCredential);
return utxos.filter((utxo) => utxo.assets[unit] > 0n);
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +84 to +91
const marketInlineDatum = Data.to<MarketDatum>({
startPrice: {
numerator: BigInt(price),
denominator: BigInt((10 ** exponent).toFixed(0)),
},
endPrice: null,
remainingShares: 0n,
}, MarketDatum)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Market datum denominator becomes 0 for negative Pyth exponents (e.g., -8 for BTC/USD)

At lazer/cardano/hermes/server/src/market.ts:87, the denominator is computed as BigInt((10 ** exponent).toFixed(0)). Pyth BTC/USD feeds use a negative exponent (typically -8). 10 ** -8 evaluates to 1e-8 in JavaScript, and (1e-8).toFixed(0) produces "0", making denominator = 0n. This creates an invalid Price with a zero denominator, which would cause division-by-zero on-chain and also fail the validate_init_market check since the off-chain computed price won't match the on-chain rational arithmetic result from common_checks.ak:33-34.

Suggested change
const marketInlineDatum = Data.to<MarketDatum>({
startPrice: {
numerator: BigInt(price),
denominator: BigInt((10 ** exponent).toFixed(0)),
},
endPrice: null,
remainingShares: 0n,
}, MarketDatum)
const marketInlineDatum = Data.to<MarketDatum>({
startPrice: {
numerator: BigInt(price),
denominator: BigInt(10 ** Math.abs(exponent)),
},
endPrice: null,
remainingShares: 0n,
}, MarketDatum)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +259 to +261
// Value is increased by the matched lovelace (1-to-1 with token scale in matching context)
let matched_lovelace = matched_amount
let expected_value = merge(own_value, from_lovelace(matched_lovelace))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 1,000,000x lovelace scale mismatch between match deposits and claim withdrawals

validate_process_match at market_checks.ak:260 deposits matched_amount lovelace per position token matched (1 lovelace = 1 token), but validate_claim_winnings at market_checks.ak:126 expects to withdraw claimed_tokens * 1_000_000 lovelace per token claimed (1 ADA = 1 token). This means the market UTxO accumulates far too little lovelace during matching to satisfy the claim validation. For example, matching 100 tokens deposits only 100 lovelace, but claiming those 100 tokens tries to withdraw 100,000,000 lovelace (100 ADA). The remaining_value ends up with deeply negative lovelace, making the value check at line 159 trivially pass for any output — effectively bypassing the intended value guard.

Prompt for agents
In lazer/cardano/hermes/onchain/lib/subvalidators/market_checks.ak, there is a lovelace scale mismatch between validate_process_match (line 260) and validate_claim_winnings (line 126). The match function uses matched_lovelace = matched_amount (1 lovelace per token) while the claim function uses claimed_tokens * 1_000_000 (1 ADA per token). These must use the same scale. Either change line 260 to matched_lovelace = matched_amount * 1_000_000 to match the claim logic, or change line 126 to paid_value = from_lovelace(claimed_tokens) to match the match logic. The choice depends on the intended economic design (whether 1 position token represents 1 lovelace or 1 ADA).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


for (const up of upBuys) {
for (const down of downBuys) {
if (up.price + down.price !== 1) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Floating-point equality check up.price + down.price !== 1 will rarely match

At lazer/cardano/hermes/server/src/matcher.ts:23, the matcher uses strict inequality up.price + down.price !== 1 to check if two order prices sum to 1. Due to IEEE 754 floating-point representation, sums like 0.3 + 0.7 or 0.48 + 0.52 frequently produce results like 0.9999999999999999 or 1.0000000000000002 rather than exactly 1. This means most valid complementary orders will be skipped. An epsilon-based comparison should be used instead.

Suggested change
if (up.price + down.price !== 1) continue;
if (Math.abs(up.price + down.price - 1) > 1e-9) continue;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants