Skip to content

Cardano#113

Open
Sixela33 wants to merge 4 commits intopyth-network:mainfrom
Sixela33:main
Open

Cardano#113
Sixela33 wants to merge 4 commits intopyth-network:mainfrom
Sixela33:main

Conversation

@Sixela33
Copy link

@Sixela33 Sixela33 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)
  • [ x] Hackathon Submission (Submitting a project from a hackathon)

Project Information

5minBets:

Pyth Product Used:

  • [x ] Pyth Price Feeds
  • Pyth Entropy
  • Multiple Products
  • Other: ___________

Blockchain/Platform:

  • Ethereum/EVM
  • Solana
  • Aptos
  • Sui
  • Fuel
  • Starknet
  • TON
  • [x ] Other: cardano

Description

What does this contribution do?

This contribution adds 5minBets, a Cardano-native price prediction market concept written in Aiken. The project models a simple 5-minute betting flow where users bet UP or DOWN on the XAU/USD price, with the current repository containing an initial validator prototype and a README that describes the intended protocol design and architecture.

How does it integrate with Pyth?

The project is designed to use Pyth price data as the oracle source for round creation and settlement. A relayer records the opening XAU/USD price from Pyth, users place bets during the round, and the closing Pyth price is later used to determine the winning side and calculate payouts.

What problem does it solve or demonstrate?

This example demonstrates how Pyth-powered market data can be used in a Cardano application to drive outcome-based logic for a prediction market. It also serves as a hackathon prototype and starting point for building a more complete oracle-driven betting protocol in Aiken.

Directory Structure (for new examples)

[product]/[example-name]/
├── contract/          # Smart contracts (if applicable)
├── app/              # Frontend application (if applicable)  
├── README.md         # Project documentation
└── ...

Testing & Verification

How to Test This Contribution

This example is currently an Aiken project prototype. Reviewers can validate formatting, buildability, and test execution using the standard Aiken workflow.

Prerequisites

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

Setup & Run Instructions

cd pyth-examples/lazer/cardano/5minBets
aiken fmt --check
aiken check -D
aiken build

Deployment Information (if applicable)

Network: cardano

Contract Address(es): N/A

Demo URL: N/A

Checklist

Code Quality

  • [ x] 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

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 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +147 to +152
let datum_ok =
new_d.feed_id == xau_usd_feed_id && new_d.relayer == d.relayer && new_d.open_price == d.open_price && new_d.close_price == Some(
close_price,
) && new_d.status == new_status && new_d.deadline_ms == d.deadline_ms && new_d.bets == d.bets && new_d.claimed == []

after_deadline && relayer_signed && datum_ok
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Settle action has no value-preservation check, allowing the relayer to drain the entire pool

The Settle handler (lines 117–153) checks after_deadline && relayer_signed && datum_ok but, unlike Claim (round_validator.ak:189) and Refund (round_validator.ak:228), it never verifies that the continued script output retains the same lovelace as the input. A malicious relayer can build a settlement transaction whose continued script output holds just the Cardano minimum UTxO (~2 ADA), directing the remaining pool lovelace to their own wallet. The datum passes validation because it only checks logical fields, not value. This completely drains bettor funds before any claims or refunds can occur.

Suggested change
let datum_ok =
new_d.feed_id == xau_usd_feed_id && new_d.relayer == d.relayer && new_d.open_price == d.open_price && new_d.close_price == Some(
close_price,
) && new_d.status == new_status && new_d.deadline_ms == d.deadline_ms && new_d.bets == d.bets && new_d.claimed == []
after_deadline && relayer_signed && datum_ok
let datum_ok =
new_d.feed_id == xau_usd_feed_id && new_d.relayer == d.relayer && new_d.open_price == d.open_price && new_d.close_price == Some(
close_price,
) && new_d.status == new_status && new_d.deadline_ms == d.deadline_ms && new_d.bets == d.bets && new_d.claimed == []
let script_value_ok = lovelace_of(cont_out.value) >= own_lovelace
after_deadline && relayer_signed && datum_ok && script_value_ok
Open in Devin Review

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

Comment on lines +170 to +171
expect Some(bet) =
list.find(d.bets, fn(b) { b.bettor == bettor && b.side == winner })
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Bettors who place multiple bets permanently lose funds from all but the first bet

Nothing in PlaceBet (round_validator.ak:79-103) prevents the same bettor from placing multiple bets. However, Claim uses list.find (round_validator.ak:171) which only returns the first matching bet, and then adds the bettor to the claimed list (round_validator.ak:183-186). The not_yet_claimed guard (round_validator.ak:168) blocks any subsequent claim attempt. The same issue exists for Refund (round_validator.ak:214). This means a bettor's second, third, etc. bets are permanently locked in the script with no way to recover them.

Example scenario

Bettor A places two 5-ADA bets on Up. Up wins. Claim finds the first 5-ADA bet, pays out based on it, and marks bettor A as claimed. The second 5-ADA bet's stake is irrecoverable.

Prompt for agents
There are two possible approaches to fix this:

Option A (simpler): Prevent duplicate bets. In PlaceBet (round_validator.ak:79-103), add a check that the bettor does not already have a bet in d.bets. For example, add: let no_existing_bet = !list.any(d.bets, fn(b) { b.bettor == bettor }) and include it in the final conjunction on line 102.

Option B (more flexible): Aggregate all matching bets during Claim/Refund. In Claim (round_validator.ak:170-175), instead of list.find to get a single bet, use list.foldl over d.bets to sum the amounts of all bets where b.bettor == bettor && b.side == winner. Similarly in Refund (round_validator.ak:214), aggregate all bets where b.bettor == bettor. Then calculate the payout based on the total aggregated stake.
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.

1 participant