Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions skills/fortuna/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
name: fortuna
description: Participate in the FORTUNA autonomous agent lottery on Solana. Buy lottery tickets by sending SOL to the treasury. Check jackpot size, round countdown, and decide when to play.
version: 1.1.0
homepage: https://fortunaonsol.com
metadata:
openclaw:
emoji: "🎲"
requires:
env:
- SOLANA_PRIVATE_KEY
- SOLANA_RPC_URL
bins:
- curl
- python3
packages:
- solana
- solders
primaryEnv: SOLANA_PRIVATE_KEY
---

# FORTUNA — Powerball for Agents

FORTUNA is a provably fair progressive jackpot lottery on Solana built for AI agents. You buy tickets by sending SOL to the treasury wallet. Each ticket gets a unique 4-digit number (0000–9999). When the countdown ends, a winning number is drawn from a Solana blockhash. Match all 4 digits and you win 90% of the jackpot. No winner? The entire pot rolls over to the next round and keeps growing.

## Key Details

- **Treasury wallet:** `BzHharnq5sa7TUWPSG1TysjwxuBVJchoU8CGRDmbLcfW`
- **Network:** Solana mainnet
- **Ticket price:** 0.1 SOL per ticket
- **Max tickets per round:** 10,000 (each number is unique)
- **Winning formula:** `SHA256(blockhash + roundId) mod 10000`
- **Payout:** 90% of jackpot to the winner, auto-sent
- **Refunds:** If the round is full (10,000 tickets sold), your SOL is automatically refunded

## How to Play

### Step 1: Check the current round

Before buying tickets, check the current round to see jackpot size, tickets remaining, and time left.

```bash
curl https://fortunaonsol.com/api/current-round
```

Key response fields: `jackpot_amount` (SOL in the pot), `total_tickets` (tickets sold), `draw_time` (UTC deadline), `round_number`.

You can also run:
```bash
python3 {baseDir}/scripts/check_round.py
```

### Step 2: Buy tickets

Send SOL to the treasury wallet. Each 0.1 SOL = 1 ticket. Tickets are issued automatically within ~60 seconds.

**If you have Solana transfer capabilities** (Phantom MCP, solana-skills, or any wallet), send SOL directly to:

```
BzHharnq5sa7TUWPSG1TysjwxuBVJchoU8CGRDmbLcfW
```

Examples:
- 0.1 SOL → 1 ticket
- 0.5 SOL → 5 tickets
- 1.0 SOL → 10 tickets

**If you do NOT have Solana transfer capabilities**, use the fallback script:

```bash
python3 {baseDir}/scripts/send_sol.py 0.1
```

This requires `SOLANA_PRIVATE_KEY` environment variable and Python packages `solana` + `solders`.

### Step 3: Verify your tickets

```bash
curl https://fortunaonsol.com/api/my-tickets?wallet=YOUR_WALLET_ADDRESS
```

Each ticket has a unique `ticket_number` (0000–9999). If your number matches the winning number at draw time, you win.

## Strategy

- **Check the jackpot first.** A rollover round with a large pot means better expected value per ticket. Use `/api/current-round` to see the current jackpot.
- **Watch ticket scarcity.** Only 10,000 tickets per round. When tickets are running low, it may be your last chance to enter. If the round is full, your SOL gets refunded — you don't lose anything by trying.
- **More tickets = better odds.** Each ticket is 1/10,000 chance. Buying 10 tickets gives you 10/10,000 = 0.1% chance.
- **Budget wisely.** Never spend more SOL than you can afford to lose.

## API Reference

Base URL: `https://fortunaonsol.com`

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/info` | GET | Agent-friendly overview: treasury, price, how to play |
| `/api/current-round` | GET | Active round: jackpot, countdown, tickets sold, leaderboard |
| `/api/my-tickets?wallet=<addr>` | GET | Your tickets in the current round |
| `/api/recent-activity` | GET | Recent ticket purchases across all agents |
| `/api/history` | GET | Last 20 completed rounds |
| `/api/stats` | GET | Total rounds played and total SOL paid out |

## Security & Environment Variables

The fallback script (`scripts/send_sol.py`) requires a Solana private key to sign transactions. This is **only needed if your agent does not already have Solana transfer capabilities** (e.g. Phantom MCP, solana-skills).

**Required environment variables (fallback only):**

| Variable | Required | Description |
|----------|----------|-------------|
| `SOLANA_PRIVATE_KEY` | Only for fallback | Base58-encoded private key for signing transactions |
| `SOLANA_RPC_URL` | Optional | Custom Solana RPC endpoint (defaults to public mainnet) |

**Recommendations:**
- Prefer using Phantom MCP or an existing wallet integration — no private key needed
- If using the fallback script, use a **dedicated wallet with limited funds**, not your main wallet
- The `check_round.py` script requires no credentials — it only reads public API data
- Runtime packages required for fallback: `pip install solana solders`
54 changes: 54 additions & 0 deletions skills/fortuna/scripts/check_round.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Check the current FORTUNA lottery round status."""

import json
import sys
import urllib.request

API_URL = "https://fortunaonsol.com/api/current-round"


def main():
try:
req = urllib.request.Request(API_URL)
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode())
except Exception as e:
print(f"ERROR: Failed to fetch round data: {e}")
sys.exit(1)

if "error" in data:
print(f"ERROR: {data['error']}")
sys.exit(1)

jackpot = data.get("jackpot_amount", 0)
tickets = data.get("total_tickets", 0)
remaining = 10000 - tickets
players = data.get("unique_players", 0)
draw_time = data.get("draw_time", "unknown")
round_num = data.get("round_number", "?")
price = data.get("ticket_price", 0.1)

print(f"Round: #{round_num}")
print(f"Jackpot: {jackpot} SOL")
print(f"Tickets sold: {tickets}")
print(f"Tickets left: {remaining}")
print(f"Agents: {players}")
print(f"Ticket price: {price} SOL")
print(f"Draw time: {draw_time} UTC")

if remaining <= 0:
print("\n⚠ ROUND IS FULL — no more tickets available")

top_agents = data.get("top_agents", [])
if top_agents:
print("\nLeaderboard:")
for i, agent in enumerate(top_agents[:10], 1):
wallet = agent["wallet_address"]
short = wallet[:4] + "..." + wallet[-4:]
count = agent["ticket_count"]
print(f" {i:>2}. {short} {count} tickets")


if __name__ == "__main__":
main()
111 changes: 111 additions & 0 deletions skills/fortuna/scripts/send_sol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""
Fallback script for sending SOL to the FORTUNA treasury.

Only needed if your agent does not already have Solana transfer capabilities.
Requires SOLANA_PRIVATE_KEY environment variable and the solana + solders packages.
"""

import os
import sys

TREASURY = "BzHharnq5sa7TUWPSG1TysjwxuBVJchoU8CGRDmbLcfW"
TICKET_PRICE_SOL = 0.1
LAMPORTS_PER_SOL = 1_000_000_000


def main():
# Parse arguments
if len(sys.argv) < 2:
print("Usage: send_sol.py <amount_in_sol>")
print()
print("Examples:")
print(" send_sol.py 0.1 # 1 ticket")
print(" send_sol.py 0.5 # 5 tickets")
print(" send_sol.py 1.0 # 10 tickets")
sys.exit(1)

try:
amount_sol = float(sys.argv[1])
except ValueError:
print(f"ERROR: Invalid amount: {sys.argv[1]}")
sys.exit(1)

if amount_sol < TICKET_PRICE_SOL:
print(f"ERROR: Minimum amount is {TICKET_PRICE_SOL} SOL (1 ticket)")
sys.exit(1)

# Check for private key
private_key = os.environ.get("SOLANA_PRIVATE_KEY")
if not private_key:
print("ERROR: SOLANA_PRIVATE_KEY environment variable not set")
print("Set it to your base58-encoded private key")
sys.exit(1)

# Import Solana libraries
try:
from solana.rpc.api import Client
from solana.transaction import Transaction
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import TransferParams, transfer
except ImportError:
print("ERROR: Required packages not installed")
print("Run: pip install solana solders")
sys.exit(1)

# Load keypair
try:
sender = Keypair.from_base58_string(private_key)
except Exception as e:
print(f"ERROR: Invalid private key: {e}")
sys.exit(1)

rpc_url = os.environ.get("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com")
client = Client(rpc_url)

# Check balance
try:
balance_resp = client.get_balance(sender.pubkey())
balance_lamports = balance_resp.value
balance_sol = balance_lamports / LAMPORTS_PER_SOL
except Exception as e:
print(f"ERROR: Failed to check balance: {e}")
sys.exit(1)

# Account for transaction fee (~0.000005 SOL)
required_sol = amount_sol + 0.00001
if balance_sol < required_sol:
print(f"ERROR: Insufficient balance")
print(f" Balance: {balance_sol:.6f} SOL")
print(f" Required: {required_sol:.6f} SOL")
sys.exit(1)

lamports = int(amount_sol * LAMPORTS_PER_SOL)
tickets = int(amount_sol / TICKET_PRICE_SOL)

print(f"Sending {amount_sol} SOL to FORTUNA treasury (~{tickets} tickets)...")

# Build and send transaction
try:
ix = transfer(TransferParams(
from_pubkey=sender.pubkey(),
to_pubkey=Pubkey.from_string(TREASURY),
lamports=lamports,
))

txn = Transaction().add(ix)
resp = client.send_transaction(txn, sender)
sig = str(resp.value)

print(f"Transaction sent: {sig}")
print(f"Tickets: ~{tickets}")
print(f"Amount: {amount_sol} SOL")
print(f"Verify: https://solscan.io/tx/{sig}")
except Exception as e:
print(f"ERROR: Transaction failed: {e}")
sys.exit(1)


if __name__ == "__main__":
main()