Complete API documentation for Bounty Challenge.
All requests should go through the platform bridge:
https://chain.platform.network/api/v1/bridge/bounty-challenge/
For local development or direct server access:
http://localhost:8080/
Requests that modify state require sr25519 signatures:
message = "{action}:{data}:{timestamp}"
signature = sr25519_sign(message, secret_key)
- Timestamps must be within 5 minutes of server time
- Uses Unix timestamps (seconds since epoch)
- Prevents replay attacks
Check if the server is healthy.
GET /health
Response:
{
"healthy": true,
"load": 0.0,
"pending": 0,
"uptime_secs": 3600,
"version": "0.1.0",
"challenge_id": "bounty-challenge"
}Register a GitHub username with a hotkey.
POST /register
Request Body:
{
"hotkey": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"github_username": "johndoe",
"signature": "0x...",
"timestamp": 1705590000
}Signature Message Format:
register_github:{github_username_lowercase}:{timestamp}
Success Response (200):
{
"success": true,
"message": "Successfully registered @johndoe with your hotkey."
}Error Response (400):
{
"success": false,
"error": "Invalid signature. Make sure you're using the correct key."
}Possible Errors:
| Error | Cause |
|---|---|
Timestamp expired |
Request older than 5 minutes |
Invalid signature |
Signature doesn't match hotkey |
Registration failed |
Database error |
Get status for a specific hotkey.
GET /status/{hotkey}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
hotkey |
string | SS58-encoded hotkey |
Response:
{
"registered": true,
"github_username": "johndoe",
"valid_issues_count": 5,
"invalid_issues_count": 2,
"balance": 3,
"is_penalized": false,
"weight": 0.05
}Not Registered Response:
{
"registered": false,
"github_username": null,
"valid_issues_count": null,
"invalid_issues_count": null,
"balance": null,
"is_penalized": false,
"weight": null
}Get current standings.
GET /leaderboard
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer | 20 | Number of entries to return |
Response:
{
"leaderboard": [
{
"github_username": "alice",
"hotkey": "5GrwvaEF...",
"issues_resolved_24h": 12,
"weight": 0.12
},
{
"github_username": "bob",
"hotkey": "5FHneW46...",
"issues_resolved_24h": 8,
"weight": 0.08
}
]
}Get challenge statistics.
GET /stats
Response:
{
"total_bounties": 150,
"total_miners": 25,
"total_invalid": 10,
"penalized_miners": 3,
"challenge_id": "bounty-challenge",
"version": "0.1.0"
}Record an invalid issue (maintainers only).
POST /invalid
Request Body:
{
"issue_id": 123,
"repo_owner": "PlatformNetwork",
"repo_name": "bounty-challenge",
"github_username": "johndoe",
"issue_url": "https://github.com/PlatformNetwork/bounty-challenge/issues/123",
"issue_title": "Optional title",
"reason": "Optional reason for marking invalid"
}Success Response (200):
{
"success": true,
"message": "Recorded invalid issue #123 by @johndoe"
}Error Response:
{
"success": false,
"error": "Failed to record invalid issue: <error message>"
}Get a list of issues from the cache.
GET /issues
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
state |
string | - | Filter by issue state (open, closed) |
label |
string | - | Filter by label |
limit |
integer | 100 | Maximum number of issues to return (max: 1000) |
offset |
integer | 0 | Number of issues to skip |
Response:
{
"issues": [
{
"id": 123,
"title": "Issue title",
"state": "closed",
"labels": ["valid"],
"user": "johndoe",
"created_at": "2025-01-15T10:00:00Z",
"closed_at": "2025-01-16T15:30:00Z"
}
],
"count": 10,
"limit": 100,
"offset": 0
}Get a list of pending (unprocessed) issues.
GET /issues/pending
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer | 100 | Maximum number of issues to return (max: 1000) |
offset |
integer | 0 | Number of issues to skip |
Response:
{
"issues": [
{
"id": 456,
"title": "Pending issue title",
"state": "open",
"labels": [],
"user": "alice",
"created_at": "2025-01-17T08:00:00Z"
}
],
"count": 5,
"limit": 100,
"offset": 0
}Get statistics about issues.
GET /issues/stats
Response:
{
"total_issues": 500,
"open_issues": 50,
"closed_issues": 450,
"valid_issues": 200,
"invalid_issues": 30,
"pending_issues": 20
}Get detailed information for a specific hotkey.
GET /hotkey/{hotkey}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
hotkey |
string | SS58-encoded hotkey |
Response:
{
"hotkey": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"github_username": "johndoe",
"registered_at": "2025-01-10T12:00:00Z",
"valid_issues_count": 15,
"invalid_issues_count": 2,
"balance": 13,
"is_penalized": false,
"weight": 0.15,
"recent_issues": [
{
"id": 123,
"title": "Fixed bug in API",
"resolved_at": "2025-01-16T15:30:00Z"
}
]
}Not Found Response:
{
"error": "Hotkey not found"
}Get detailed information for a GitHub user.
GET /github/{username}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
username |
string | GitHub username |
Response:
{
"github_username": "johndoe",
"hotkey": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"registered_at": "2025-01-10T12:00:00Z",
"valid_issues_count": 15,
"invalid_issues_count": 2,
"balance": 13,
"is_penalized": false,
"weight": 0.15,
"recent_issues": [
{
"id": 123,
"title": "Fixed bug in API",
"resolved_at": "2025-01-16T15:30:00Z"
}
]
}Not Found Response:
{
"error": "GitHub user not found"
}Get the current synchronization status for all repositories.
GET /sync/status
Response:
{
"repos": [
{
"owner": "PlatformNetwork",
"repo": "bounty-challenge",
"last_synced_at": "2025-01-17T10:00:00Z",
"issues_count": 150,
"status": "synced"
}
],
"issues_stats": {
"total_issues": 500,
"open_issues": 50,
"closed_issues": 450
}
}Trigger a manual synchronization of issues from GitHub.
POST /sync/trigger
Response (Success):
{
"success": true,
"issues_synced": 150,
"errors": []
}Response (Partial Failure):
{
"success": false,
"issues_synced": 100,
"errors": [
"PlatformNetwork/other-repo: rate limit exceeded"
]
}Get current weight calculations for all miners.
GET /get_weights
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
epoch |
integer | current | Epoch number |
Response:
{
"weights": [
{
"hotkey": "5GrwvaEF...",
"weight": 0.35
},
{
"hotkey": "5FHneW46...",
"weight": 0.25
}
],
"epoch": 12345,
"challenge_id": "bounty-challenge",
"total_miners": 15
}Get challenge configuration.
GET /config
Response:
{
"challenge_id": "bounty-challenge",
"name": "Bounty Challenge",
"version": "0.1.0",
"config_schema": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["register", "claim", "leaderboard"]
},
"github_username": {
"type": "string"
}
}
},
"features": ["github-verification", "anti-abuse"],
"limits": {
"max_submission_size": 10240,
"max_evaluation_time": 60
}
}{
"success": false,
"error": "Error message here"
}| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (invalid signature) |
| 404 | Not Found |
| 500 | Internal Server Error |
| Error Message | Cause | Solution |
|---|---|---|
Timestamp expired |
Request too old | Use current timestamp |
Invalid signature |
Wrong key used | Verify secret key |
Registration failed |
DB error | Retry or contact support |
Hotkey not found |
Not registered | Register first |
| Endpoint | Limit |
|---|---|
/register |
10/minute |
/status/* |
60/minute |
/leaderboard |
30/minute |
/stats |
30/minute |
import requests
import time
from substrateinterface import Keypair
# Create keypair from seed
keypair = Keypair.create_from_mnemonic("your mnemonic here")
# Prepare registration
timestamp = int(time.time())
message = f"register_github:johndoe:{timestamp}"
signature = keypair.sign(message.encode()).hex()
# Register
response = requests.post(
"https://chain.platform.network/api/v1/bridge/bounty-challenge/register",
json={
"hotkey": keypair.ss58_address,
"github_username": "johndoe",
"signature": f"0x{signature}",
"timestamp": timestamp
}
)
print(response.json())use sp_core::{sr25519, Pair};
use reqwest::Client;
use serde_json::json;
async fn register(secret_key: &str, github_username: &str) -> Result<(), Box<dyn std::error::Error>> {
let pair = sr25519::Pair::from_string(secret_key, None)?;
let timestamp = chrono::Utc::now().timestamp();
let message = format!("register_github:{}:{}", github_username.to_lowercase(), timestamp);
let signature = pair.sign(message.as_bytes());
let client = Client::new();
let response = client
.post("https://chain.platform.network/api/v1/bridge/bounty-challenge/register")
.json(&json!({
"hotkey": encode_ss58(&pair.public().0),
"github_username": github_username,
"signature": hex::encode(signature.0),
"timestamp": timestamp
}))
.send()
.await?;
println!("{}", response.text().await?);
Ok(())
}const { Keyring } = require('@polkadot/keyring');
const { u8aToHex } = require('@polkadot/util');
async function register(mnemonic, githubUsername) {
const keyring = new Keyring({ type: 'sr25519' });
const pair = keyring.addFromMnemonic(mnemonic);
const timestamp = Math.floor(Date.now() / 1000);
const message = `register_github:${githubUsername.toLowerCase()}:${timestamp}`;
const signature = pair.sign(message);
const response = await fetch(
'https://chain.platform.network/api/v1/bridge/bounty-challenge/register',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
hotkey: pair.address,
github_username: githubUsername,
signature: u8aToHex(signature),
timestamp: timestamp
})
}
);
console.log(await response.json());
}Coming soon: Real-time updates via WebSocket
wss://chain.platform.network/api/v1/ws/bounty-challenge
Events:
issue_validated- New issue validatedweight_updated- Weights recalculatedleaderboard_changed- Rankings changed