Slipstream API integration guide
Slipstream takes a signed raw transaction over HTTPS, checks it against the avonpool betanet node and a fee rule, and broadcasts it so the pool mines it. Base URL: https://slipstream.beta.avonpool.xyz. No API key, JSON responses, CORS open to any origin.
Endpoints
All paths are relative to https://slipstream.beta.avonpool.xyz. Plain HTTP redirects to HTTPS with a 301.
| Method | Path | Returns |
|---|---|---|
| POST | /api/tx (alias /tx) | Submits one raw transaction. {accepted, txid, status, ...} or {accepted: false, reject_reason} |
| GET | /api/tx/{txid} | {tx, events}: current state plus every status change. 404 for an unknown txid |
| GET | /api/txs?status=&limit= | {txs: [...]}, newest first. limit 1–500, default 50 |
| GET | /api/fees | {min_submission_rate, mineable_rate, required_rate, template_height, template_weight, updated_at} |
| GET | /info.json (alias /api/info) | The pool description. Also served at https://pool.beta.avonpool.xyz/info.json |
| GET | /healthz | {ok, template_age_s, enforcer_error}. 200 when healthy, 503 when the node's template source is down |
| GET | / | The list of endpoints |
Submitting a transaction
Send the fully signed transaction as hex in one POST. The service runs testmempoolaccept first, so nothing is broadcast unless the node and the fee rule both accept it. Two body formats are accepted:
Content-Type: text/plain(or none): the body is the hex string itself.Content-Type: application/json:{"hex": "0200..."}. A bare JSON string or{"tx": "..."}also works.
curl -s -X POST --data-binary "$RAW_TX_HEX" \
https://slipstream.beta.avonpool.xyz/api/tx
const BASE = 'https://slipstream.beta.avonpool.xyz';
async function submitTx(hex) {
const res = await fetch(`${BASE}/api/tx`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ hex }),
});
const body = await res.json();
if (res.status === 429) throw new Error('rate-limited: retry next minute');
if (res.status >= 500) throw new Error(`slipstream unavailable: ${body.reject_reason ?? res.status}`);
return body; // check body.accepted, then body.reject_reason
}
An accepted response carries the stored record, abridged here (the full field list is under Tracking a transaction):
{
"accepted": true,
"txid": "3f1c...",
"wtxid": "9a2e...",
"status": "pending",
"vsize": 141,
"weight": 561,
"fee_sats": 282,
"fee_rate": 2,
"required_fee_rate": 1,
"submitted_height": 970802
}
Submitting the same transaction again is safe. It returns accepted: true with already_tracked: true and the current record, instead of an error.
Responses and reject reasons
Always read accepted from the body: a rejection by the node or the fee rule comes back as HTTP 200 with accepted: false. Only malformed input, rate limiting and outages use other status codes.
| HTTP | reject_reason | Meaning | Caller should |
|---|---|---|---|
| 200 | (none, accepted: true) | Broadcast and tracked | Store the txid, start polling |
| 200 | fee-rate-too-low | Below the required rate. Body adds fee_rate and required_fee_rate | Rebuild at or above required_fee_rate |
| 200 | already-confirmed | Already in a block | Treat as done |
| 200 | node reason, e.g. missing-inputs, bad-txns-inputs-missingorspent, insufficient fee, scriptpubkey, mandatory-script-verify-flag-failed (...) | The node refused it as it stands | Fix the transaction; do not retry unchanged |
| 400 | invalid-hex | Empty, odd length or non-hex body | Fix the encoding |
| 400 | tx-decode-failed | Hex that is not a transaction | Fix the serialization |
| 400 / 413 | tx-size | Over 1 MB of transaction (2 MB of hex) | Split or shrink it |
| 400 | invalid-body | JSON that does not parse | Fix the request |
| 429 | rate-limited | Over 30 submissions per minute from this IP | Back off until the next minute |
| 502 | node-unavailable | The pool's node did not answer | Retry later with backoff |
Tracking a transaction
Poll GET /api/tx/{txid} until tx.status is confirmed or dropped. The service rechecks every transaction on each new block template, so polling every 30–60 seconds is enough.
status | Meaning | Final? |
|---|---|---|
pending | In the node's mempool, not in the pool's current template | No |
in_template | In the template the pool's miners are working on now | No |
mined | In a block, fewer than 6 confirmations. mined_by_pool is 1 if avonpool mined it, 0 if another pool did | No |
confirmed | 6 or more confirmations | Yes |
dropped | Left the mempool unmined and the node refused it on resend. status_reason gives the node's reason | Yes, unless resubmitted |
The usual path is pending → in_template → mined → confirmed. Two moves go backwards: a block that is orphaned returns its transaction to pending, and a transaction that leaves the mempool is resent automatically, ending in pending or dropped. A dropped transaction can be submitted again once it is valid.
curl -s https://slipstream.beta.avonpool.xyz/api/tx/$TXID
The tx object has these fields: txid, wtxid, vsize, weight, fee_sats, fee_rate, required_fee_rate, submitted_at, submitted_height, submitter, status, status_reason, status_at, first_in_template_at, last_in_template_at, mined_block_hash, mined_height, mined_at, mined_by_pool, confirmations, confirmed_at, resubmissions. Times are Unix seconds; rates are sat/vB. events is the history as [{ts, event, detail}], oldest first.
GET /api/txs?status=in_template&limit=100 lists recent transactions across all submitters, without submitter.
Fees
Build the transaction at or above required_rate from GET /api/fees, or it is refused with fee-rate-too-low. The required rate is the higher of the pool's floor (1 sat/vB) and the current mineable rate. The mineable rate equals the floor while the block template has room, and becomes the cheapest included transaction's rate once the template is full.
curl -s https://slipstream.beta.avonpool.xyz/api/fees
{"min_submission_rate":1,"mineable_rate":1,"required_rate":1,"template_height":970802,"template_weight":3766,"updated_at":1790866502}
The fee rate is checked as fee in sats divided by virtual size, so read it just before signing and add a margin if blocks are filling up.
info.json
GET /info.json describes the pool for pool directories and is cached for 60 seconds. Mode, fee, coinbase tag and addresses are read from the running pool, so they always match what the coinbase pays. The same document is served at https://pool.beta.avonpool.xyz/info.json.
{
"name": "avonpool_beta",
"operator": "avonpool",
"chain": "betanet",
"mode": "solo",
"fee_bps": 100,
"coinbase_tag": "/avonpool/",
"stratum_url": "stratum+tcp://pool.beta.avonpool.xyz:3334",
"dashboard_url": "https://pool.beta.avonpool.xyz",
"status_url": "https://pool.beta.avonpool.xyz/api/status",
"slipstream_url": "https://slipstream.beta.avonpool.xyz",
"operator_address": "bc1qljvzxk0tp6qtrunt590z5rtdhs8jhkkn4ny4rm",
"pool_btc_address": null,
"payout": "The stratum username is your BTC address; each block's coinbase pays the miner who found it.",
"software": "simplepool",
"version": "0.1.0",
"logo": null,
"contact": "pool@ecash.com"
}
fee_bps is in basis points (100 = 1%). status_url returns live pool statistics (hashrate, workers, blocks).
Limits and caveats
- Standard transactions only. The betanet node reports its chain as
main, where Bitcoin Core does not allow non-standard transactions. A non-standard output is refused withscriptpubkey. BIP300 deposits are standard and are accepted. - Not exclusive to avonpool. An accepted transaction is relayed like any other, so another pool may mine it first.
mined_by_poolsays which. - Rate limit: 30 submissions per minute per client IP, counted in fixed one-minute windows. Reads are limited by nginx with a burst of 20.
- Size: at most 1 MB of transaction per request.
- Your IP is stored. Each submission records the client IP as
submitter, andGET /api/tx/{txid}returns it to anyone who knows the txid. - Health check: before a batch of submissions,
GET /healthzreturns 503 if the pool cannot build templates, and submissions may then fail withnode-unavailable.