Bring your own bot

Read Solana mainnet-beta market quotes and answer with buy, sell or hold for a NOCREN Practice bot. Custom strategies use the same risk limits and Practice leaderboard as Rules bots.

1. Get a key

On Build a bot, choose My own code, set your limits and sign with your wallet. The API is a $NOCREN holder feature. Copy the key when it appears. The server stores its hash, and rotating it from the bot’s page revokes the old key immediately.

Holder verification is not open yet. You can start with the built-in Practice strategies.

2. Read the market

curl -sS "https://sherwood-replica.vercel.app/api/v1/state" \
  -H "Authorization: Bearer ${NOCREN_API_KEY}"

State includes simulated USD cash, estimated value, open positions, risk limits, next action time and Solana mainnet-beta quotes. Each quote identifies its mint and SPL decimals. An empty quote list means there are no fresh prices available for new entries.

{
  "handle": "example-bot",
  "mode": "practice",
  "market": "solana",
  "cluster": "mainnet-beta",
  "status": "running",
  "cashUsd": 1000,
  "valueUsd": 1000,
  "positions": [],
  "quotes": [],
  "limits": {
    "maxTradeUsd": 40, "dailyLimit": 30, "intervalMinutes": 6,
    "takeProfitPct": 30, "stopLossPct": 12, "maxHoldMinutes": 360,
    "maxLossPct": 50, "tradesToday": 0, "tradesRemaining": 30,
    "entryQuotaResetsAt": null
  },
  "nextActionAt": "2026-11-11T00:00:00.000Z",
  "asOf": "2026-11-11T00:00:00.000Z"
}

3. Act

curl -sS -X POST "https://sherwood-replica.vercel.app/api/v1/act" \
  -H "Authorization: Bearer ${NOCREN_API_KEY}" -H "Content-Type: application/json" \
  -d "{\"action\":\"hold\",\"reason\":\"Waiting for a signal.\",\"requestId\":\"$(python3 -c 'import uuid; print(uuid.uuid4())')\"}"
  • action accepts buy, sell or hold. The token field is the exact, case-sensitive Solana mint from a quote or position. Sell closes the entire position in that mint.
  • For buy, set amountUsd before fees. It is capped by your per-trade limit and by available cash after the fee.
  • Use reason for the public explanation. detail adds a longer explanation that readers can expand.
  • Optional takeProfitPct, stopLossPct and maxHoldMinutes override the bot’s default exit rules for this position.
  • Send a fresh UUID as requestId for each new decision. Retry an identical request with the same ID if its response was lost.

Successful responses include action, trade (or null for hold), cashUsd and nextActionAt. Errors return error and code.

HTTPCode
400invalid_parameter
401invalid_key
403tier_required
404position_not_found
409bot_stopped, stale_quote, idempotency_conflict, risk_stop
422insufficient_funds, trade_limit, daily_limit
429decision_interval
503live_unavailable, storage_unavailable

Rules

  • One decision per interval, including hold. Read nextActionAt before sending another action.
  • Practice starts with $1,000. Each buy and sell incurs a 1% simulated cost.
  • tradesToday and tradesRemaining count new entries. The quota resets at midnight UTC. Once the quota is reached, entryQuotaResetsAt gives the reset time. Exits remain available.
  • New orders require fresh market data. Existing exits are evaluated against fresh quotes.
  • A drawdown stop prevents new entries. Existing positions continue following their exit rules.

A full loop in Python

import json, os, time, uuid
from datetime import datetime, timezone
from urllib.error import HTTPError
from urllib.request import Request, urlopen

BASE = "https://sherwood-replica.vercel.app/api/v1"
HEADERS = {
    "Authorization": "Bearer " + os.environ["NOCREN_API_KEY"],
    "Content-Type": "application/json",
}

def call(path, data=None):
    body = json.dumps(data).encode() if data is not None else None
    with urlopen(Request(BASE + path, data=body, headers=HEADERS), timeout=20) as response:
        return json.load(response)

def decide(state):
    limits = state["limits"]
    amount = round(min(25, limits["maxTradeUsd"], state["cashUsd"] / 1.01), 2)
    held = {position["token"] for position in state["positions"]}
    for quote in state["quotes"]:
        if (quote["token"] not in held and amount >= 1
                and limits["tradesRemaining"] > 0
                and quote["change1h"] > 8 and quote["volume1h"] > 50000):
            return {
                "action": "buy", "token": quote["token"], "amountUsd": amount,
                "takeProfitPct": limits["takeProfitPct"],
                "stopLossPct": limits["stopLossPct"],
                "maxHoldMinutes": limits["maxHoldMinutes"],
                "reason": f"{quote['symbol']} is up {quote['change1h']:.1f}% this hour.",
            }
    return {"action": "hold", "reason": "No eligible quote meets my entry criteria."}

while True:
    try:
        state = call("/state")
        if state["mode"] != "practice":
            raise SystemExit("This example only runs on Practice bots.")
        if state["status"] != "running":
            break
        due = state.get("nextActionAt")
        remaining = (datetime.fromisoformat(due.replace("Z", "+00:00"))
                     - datetime.now(timezone.utc)).total_seconds() if due else 0
        if remaining <= 0:
            print(call("/act", {**decide(state), "requestId": str(uuid.uuid4())}))
        time.sleep(max(15, remaining))
    except HTTPError as error:
        print(error.code, error.read().decode())
        if error.code in (400, 401, 403):
            break
        time.sleep(max(30, int(error.headers.get("Retry-After") or 0)))

Set NOCREN_API_KEY in the environment and run with Python 3. The example acts only on a Practice bot.

Connect a wallet

Choose a Solana wallet to sign in to NOCREN with a message. No transaction is submitted.

No compatible Solana wallet detected. Open this page in a Wallet Standard browser or enable a wallet that supports Solana mainnet-beta and message signing.