Scarce
BTC–On the curve–Graduated–Solana

Developers · API v1

Build on Scarce Markets

Every Scarce Markets coin has 21,000,000 supply, trades against BTC on a Meteora bonding curve that halves across four eras, and graduates to a Meteora DAMM v2 pool. This API reads those coins and builds their transactions, so you can put them in a bot, a wallet, a dashboard or your own trading front end without learning Meteora's SDKs.

  • No API key. JSON over HTTPS, open to every origin (CORS).
  • Your users keep their keys. Trades and launches come back as unsigned transactions; the user's wallet signs and sends them.
  • Curve or pool, handled. Buys and sells route to the bonding curve or, after graduation, the DAMM v2 pool. Buys can pay in SOL.
  • Live events. One stream for new coins, halvings, graduations and burns.
Base URLhttps://scarce.markets/api/v1
NetworkSolana: real BTC (cbBTC) is at stake
OpenAPI/api/v1/openapi.json
StatusA hackathon submission (Colosseum Crypto World's Fair, Meteora DBC side track). The v1 contract is stable; the service has no uptime guarantee.

Quickstart

Three calls from nothing to a signed buy.

1. Find a coin

curl "https://scarce.markets/api/v1/coins?sort=progress&graduated=false&limit=5"

2. Quote it

curl "https://scarce.markets/api/v1/quote?mint=<mint>&side=buy&amount=0.001"

3. Build the transaction, sign it in the wallet, send it

TypeScript · @solana/web3.js + wallet adapter
import { Transaction, VersionedTransaction } from "@solana/web3.js";

const res = await fetch("https://scarce.markets/api/v1/tx/buy", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ wallet: wallet.publicKey.toBase58(), mint, btc: 0.001, slippageBps: 200 }),
}).then((r) => r.json());
if (res.error) throw new Error(res.error);

const bytes = Uint8Array.from(atob(res.transaction), (c) => c.charCodeAt(0));
const tx = res.version === 0 ? VersionedTransaction.deserialize(bytes) : Transaction.from(bytes);
const signature = await wallet.sendTransaction(tx, connection);

Concepts

Units

QuantityUnit in the APIOn chain
BTC amountsBTC, a decimal number8 decimals (1 BTC = 10^8 atoms = 100,000,000 sats)
Coin amountsWhole coins, a decimal number6 decimals, 21,000,000 supply per coin
PricesSats per coin (priceSats)Read from the pool's sqrt price
Fully diluted valueBTC (fdvBtc)priceSats × 21,000,000 ÷ 10^8
TimesUnix seconds (event timestamps at: Unix ms)—

Numbers are JSON floats, fine for display and quotes. The transactions themselves use exact integer amounts, and out.min is what the chain enforces.

A coin's life

  1. Launch. A creator picks a curve tier: satoshi (0.1 BTC), hal (0.25 BTC), whitepaper (0.5 BTC), bitcoin (1 BTC). The tier sets how much BTC the curve raises before graduation, and with it every price on the curve. Supply, eras and fees are the same in every tier.
  2. Bonding curve, four eras. Genesis, Halving I, Halving II, Final Era. In each era the price doubles and the coins sold halve, so each era raises the same BTC. Entering the next era is a halving: era.index goes up and you get a coin.halving event. Each trade pays a 1% fee in BTC.
  3. Graduation. When btcRaised reaches graduationBtc, the coin moves to a Meteora DAMM v2 pool (migrated: true, dammPool set, a coin.graduated event). Trading continues there; the API routes to it automatically and quotes show venue: "dammV2".

Which network

This deployment serves Solana. Every response carries an X-Cluster header (mainnet-beta), and GET /api/v1 returns cluster. Check it before asking a user to sign, and send the transaction to an RPC on the same network.

Signing transactions

  • transaction is base64. Decode it, then Transaction.from(bytes) when version is "legacy", or VersionedTransaction.deserialize(bytes) when it is 0 (buys paid in SOL).
  • The fee payer is wallet. Nothing is signed on our side. Launches also need the new mint keypair's signature (see the launch recipe).
  • The transaction has a recent blockhash: sign and send within about 60 seconds, or build a new one.
  • Slippage protection is on chain: if the price moves past slippageBps, the transaction fails and nothing is spent but the network fee.
  • Simulate before sending if you want an early error (insufficient BTC, for example); wallets usually do this for you.

Errors and limits

Errors are JSON: { "error": "slippageBps: an integer from 0 to 5000" }. The message says what to fix.

StatusMeaning
400Bad input: a parameter is missing or invalid.
404Not a Scarce Markets coin on this network.
409Launch: that mint already exists.
413 / 415Metadata: the image is too big, or not PNG, JPG or GIF.
429Rate limited. retryAfterSecs says when to retry.
502A chain read or route failed upstream. Safe to retry.
503Not deployed on this network yet, or the event stream is full.

Limits per IP address:

BucketLimitEndpoints
read120 per 1 min/, /coins, /coins/{mint}, /curves, /burns, /btc-usd
quote60 per 1 min/quote
tx30 per 1 min/tx/buy, /tx/sell
launch5 per 10 min/tx/launch
metadata10 per 10 min/metadata
stream10 per 1 min/events

Need more? Cache the read endpoints (their Cache-Control headers say for how long) and use the event stream instead of polling.

API reference

Market

API index

GET/api/v1

The network this deployment serves, every curve tier with its DBC config, the BTC quote mint, token rules, fees, program IDs, the keeper and treasury, and the endpoint list. Read it once at startup and check cluster.

Limit 120 reads per 1 min per IP · cached 1 min

Request
curl https://scarce.markets/api/v1
Response 200
{
  "name": "Scarce Markets API",
  "version": "1",
  "cluster": "mainnet-beta",
  "deployed": true,
  "token": {
    "supply": 21000000,
    "decimals": 6,
    "mintAuthority": null,
    "freezeAuthority": null
  },
  "quote": {
    "mint": "<btc mint>",
    "decimals": 8
  },
  "tiers": [
    {
      "id": "satoshi",
      "name": "Satoshi",
      "graduationBtc": 0.1,
      "config": "<config>"
    },
    "…"
  ],
  "eras": [
    {
      "index": 0,
      "name": "Genesis"
    },
    {
      "index": 1,
      "name": "Halving I"
    },
    {
      "index": 2,
      "name": "Halving II"
    },
    {
      "index": 3,
      "name": "Final Era"
    }
  ],
  "fees": {
    "tradingFeeBps": 100,
    "protocolShareBps": 2000,
    "creatorShareBps": 7500,
    "buybackShareOfFees": 0.15,
    "treasuryShareOfFees": 0.05
  },
  "programs": {
    "dbc": "<program>",
    "dammV2": "<program>"
  },
  "keeper": "<address>",
  "treasury": "<address>",
  "nativeCoin": {
    "symbol": "21",
    "mint": "<mint>"
  },
  "endpoints": {
    "GET /api/v1/coins": "…"
  }
}

List coins

GET/api/v1/coins

Every coin with its live state and metadata. The list is refreshed every few seconds; page through it with limit and offset.

Limit 120 reads per 1 min per IP · cached 3 s

Query

sortstring
Newest first, largest fully diluted value first, or closest to graduation first.One of new, fdv, progress.Default new.
graduatedboolean
true: only coins trading on DAMM v2. false: only coins still on the bonding curve.
tierstring
Only coins on this curve tier.One of satoshi, hal, whitepaper, bitcoin.
limitinteger
Page size, 1 to 200.Default 50.
offsetinteger
Coins to skip.Default 0.
Request
curl "https://scarce.markets/api/v1/coins?sort=progress&graduated=false&limit=10"
Response 200
{
  "total": 42,
  "offset": 0,
  "limit": 10,
  "coins": [
    {
      "mint": "<mint>",
      "pool": "<dbc pool>",
      "config": "<config>",
      "tier": "bitcoin",
      "creator": "<wallet>",
      "activatedAt": 1791103356,
      "priceSats": 3.3362,
      "fdvBtc": 0.7006,
      "btcRaised": 0.201,
      "graduationBtc": 1,
      "progress": 0.201,
      "era": {
        "index": 0,
        "name": "Genesis",
        "progress": 0.8033,
        "coinsToHalving": 1390686.34
      },
      "coinsSold": 8030111.07,
      "migrated": false,
      "dammPool": null,
      "partnerFeeBtcUnclaimed": 1.1e-7,
      "meta": {
        "name": "Satoshi Cat",
        "symbol": "SCAT",
        "uri": "https://…/metadata.json",
        "image": "https://…/image.png",
        "description": "…",
        "socials": {
          "twitter": "https://x.com/…"
        }
      },
      "isNative": false
    },
    "…"
  ]
}
  • 400 A parameter is missing or invalid; error names it.

Get a coin

GET/api/v1/coins/{mint}

One coin's live state and metadata, the same object as in the list.

Limit 120 reads per 1 min per IP · cached 2 s

Path

mintstringrequired
The coin's mint address.
Request
curl https://scarce.markets/api/v1/coins/<mint>
Response 200
{
  "mint": "<mint>",
  "pool": "<dbc pool>",
  "config": "<config>",
  "tier": "bitcoin",
  "creator": "<wallet>",
  "activatedAt": 1791103356,
  "priceSats": 3.3362,
  "fdvBtc": 0.7006,
  "btcRaised": 0.201,
  "graduationBtc": 1,
  "progress": 0.201,
  "era": {
    "index": 0,
    "name": "Genesis",
    "progress": 0.8033,
    "coinsToHalving": 1390686.34
  },
  "coinsSold": 8030111.07,
  "migrated": false,
  "dammPool": null,
  "partnerFeeBtcUnclaimed": 1.1e-7,
  "meta": {
    "name": "Satoshi Cat",
    "symbol": "SCAT",
    "uri": "https://…/metadata.json",
    "image": "https://…/image.png",
    "description": "…",
    "socials": {
      "twitter": "https://x.com/…"
    }
  },
  "isNative": false
}
  • 400 A parameter is missing or invalid; error names it.
  • 404 The mint is not a coin on any of this network's curve tiers.

Halving curves

GET/api/v1/curves

Each tier's curve: the four eras with their BTC and price ranges and coins sold, sampled points for charting, and the coins that go into the DAMM v2 pool at graduation. Curves are immutable on chain, so cache this as long as you like.

Limit 120 reads per 1 min per IP · cached 60 min

Request
curl https://scarce.markets/api/v1/curves
Response 200
{
  "satoshi": {
    "tier": "satoshi",
    "graduationBtc": 0.1,
    "eras": [
      {
        "index": 0,
        "name": "Genesis",
        "fromBtc": 0,
        "toBtc": 0.025025,
        "fromSats": 0.1878,
        "toSats": 0.3757,
        "coins": 9420797.41
      },
      {
        "index": 1,
        "name": "Halving I",
        "fromBtc": 0.025025,
        "toBtc": 0.05005,
        "fromSats": 0.3757,
        "toSats": 0.7513,
        "coins": 4710398.7
      },
      "…"
    ],
    "points": [
      {
        "coins": 0,
        "sats": 0.1878,
        "btc": 0,
        "era": 0
      },
      {
        "coins": 329661.73,
        "sats": 0.1917,
        "btc": 0.00062562,
        "era": 0
      },
      "…"
    ],
    "lpCoins": 3335232.78
  },
  "hal": "…"
}

Buyback & burn

GET/api/v1/burns

The native coin's circulating and burned supply (21,000,000 minus the mint's supply), the treasury's BTC balance, and the keeper's buyback & burn transactions, newest first. why is buyback or leftover (a graduated coin's unsold curve remainder).

Limit 120 reads per 1 min per IP · cached 15 s

Request
curl https://scarce.markets/api/v1/burns
Response 200
{
  "nativeMint": "<mint>",
  "nativeSymbol": "21",
  "keeper": "<address>",
  "burnedCoins": 21955.54,
  "supplyCoins": 20978044.46,
  "treasury": {
    "address": "<address>",
    "btc": 0.00005112
  },
  "burns": [
    {
      "signature": "<signature>",
      "time": 1791140807,
      "mint": "<mint>",
      "coins": 10.2039,
      "why": "buyback"
    },
    "…"
  ]
}

BTC/USD

GET/api/v1/btc-usd

The BTC/USD rate the site uses for dollar figures; null when every price source is down. Multiply a BTC amount by it, or priceSats / 1e8 × usd for a coin's dollar price.

Limit 120 reads per 1 min per IP · cached 30 s

Request
curl https://scarce.markets/api/v1/btc-usd
Response 200
{
  "usd": 85811
}

Trading

Quote a trade

GET/api/v1/quote

What a buy or sell would return right now, without building a transaction. On the bonding curve it also previews the era after the trade: whether it crosses a halving, or completes the curve and graduates. That preview uses the BTC raised after the 1% fee; treat it as an estimate.

Limit 60 quotes per 1 min per IP · cached 2 s

Query

mintstringrequired
The coin.
sidestring
Buy coins, or sell coins for BTC.One of buy, sell.Default buy.
amountnumberrequired
Buys: BTC to spend (or SOL with in=sol). Sells: coins to sell.
instring
Buys only: pay in BTC, or in SOL swapped to BTC in the same transaction.One of btc, sol.Default btc.
slippageBpsinteger
Sets out.min, 0 to 5000.Default 200.
Request
curl "https://scarce.markets/api/v1/quote?mint=<mint>&side=buy&amount=0.03"
Response 200
{
  "mint": "<mint>",
  "side": "buy",
  "in": {
    "asset": "btc",
    "amount": 0.03
  },
  "out": {
    "asset": "coin",
    "expected": 858566.35,
    "min": 841395.02
  },
  "viaBtc": null,
  "slippageBps": 200,
  "spotPriceSats": 3.3362,
  "avgPriceSats": 3.4942,
  "priceImpact": 0.0474,
  "venue": "curve",
  "era": {
    "before": 0,
    "after": 1,
    "crossesHalving": true,
    "graduates": false
  }
}
  • 400 A parameter is missing or invalid; error names it.
  • 404 The mint is not a coin on any of this network's curve tiers.

Build a buy

POST/api/v1/tx/buy

An unsigned transaction that buys the coin for wallet. Send exactly one of btc or sol. With sol, one v0 transaction swaps SOL to BTC and buys with the BTC that swap guarantees; any extra BTC stays in the wallet. Graduated coins are bought on their DAMM v2 pool.

Limit 30 buy / sell transactions per 1 min per IP · not cached

JSON body

walletstringrequired
The buyer; signs and pays fees.
mintstringrequired
The coin.
btcnumber
BTC to spend.
solnumber
SOL to spend instead of BTC.
slippageBpsinteger
Maximum slippage in basis points, 0 to 5000. 200 = 2%.Default 200.
Request
curl -X POST https://scarce.markets/api/v1/tx/buy -H "Content-Type: application/json" \
  -d '{"wallet":"<wallet>","mint":"<mint>","btc":0.001,"slippageBps":200}'
Response 200
{
  "transaction": "<base64>",
  "version": "legacy",
  "mint": "<mint>",
  "wallet": "<wallet>",
  "in": {
    "asset": "btc",
    "amount": 0.001
  },
  "expectedCoins": 29711.4,
  "minCoins": 29117.17,
  "note": "Sign with the wallet and send within ~60 s (the blockhash expires)."
}
  • 400 A parameter is missing or invalid; error names it.
  • 404 The mint is not a coin on any of this network's curve tiers.
  • 502 The chain read or the SOL route failed (for SOL buys: try a different amount, or pay in BTC).

Build a sell

POST/api/v1/tx/sell

An unsigned transaction that sells coins from wallet for BTC, on the bonding curve or the DAMM v2 pool.

Limit 30 buy / sell transactions per 1 min per IP · not cached

JSON body

walletstringrequired
The seller; signs and pays fees.
mintstringrequired
The coin.
coinsnumberrequired
Coins to sell.
slippageBpsinteger
Maximum slippage in basis points, 0 to 5000. 200 = 2%.Default 200.
Request
curl -X POST https://scarce.markets/api/v1/tx/sell -H "Content-Type: application/json" \
  -d '{"wallet":"<wallet>","mint":"<mint>","coins":1000}'
Response 200
{
  "transaction": "<base64>",
  "version": "legacy",
  "mint": "<mint>",
  "wallet": "<wallet>",
  "in": {
    "asset": "coin",
    "amount": 1000
  },
  "expectedBtc": 0.00031037,
  "minBtc": 0.00030416,
  "note": "Sign with the wallet and send within ~60 s (the blockhash expires)."
}
  • 400 A parameter is missing or invalid; error names it.
  • 404 The mint is not a coin on any of this network's curve tiers.

Launch

Upload metadata

POST/api/v1/metadata

Stores the coin's image and a Metaplex-style metadata JSON on permanent storage and returns its uri for the launch. Send the image as a file, or link one with imageUrl.

Limit 10 metadata uploads per 10 min per IP · not cached

Form fields (multipart/form-data)

namestringrequired
Up to 32 characters.
symbolstringrequired
2 to 10 letters or digits; uppercased.
imagefile
PNG, JPG or GIF, at most 1 MB.
imageUrlstring
An http(s) image URL instead of a file.
descriptionstring
Up to 500 characters.
twitterstring
http(s) URL.
telegramstring
http(s) URL.
websitestring
http(s) URL.
Request
curl -X POST https://scarce.markets/api/v1/metadata \
  -F name="Satoshi Cat" -F symbol=SCAT -F image=@cat.png -F twitter=https://x.com/satoshicat
Response 200
{
  "uri": "https://…/metadata.json",
  "image": "https://…/image.png",
  "provider": "…"
}
  • 400 A parameter is missing or invalid; error names it.
  • 413 The image is over 1 MB.
  • 415 The image is not a PNG, JPG or GIF.
  • 502 The storage provider failed.

Build a launch

POST/api/v1/tx/launch

A transaction that creates the coin on the chosen curve tier, with an optional first buy in the same transaction so nobody can buy ahead of the creator. Generate a fresh keypair for the mint and send only its public key: the transaction needs the wallet's and the mint keypair's signatures. Supply, decimals and the revoked mint and freeze authorities come from the tier's config and can't be changed.

Limit 5 launch transactions per 10 min per IP · not cached

JSON body

walletstringrequired
The creator; signs, pays fees and receives the creator's share of trading fees.
mintstringrequired
Public key of a fresh keypair you generated.
tierstring
The curve's graduation target: 0.1, 0.25, 0.5 or 1 BTC. Permanent.One of satoshi, hal, whitepaper, bitcoin.Default satoshi.
namestringrequired
Up to 32 characters.
symbolstringrequired
2 to 10 letters or digits.
uristringrequired
The metadata JSON URL, from POST /api/v1/metadata.
firstBuyBtcnumber
BTC the creator buys with in the launch transaction.Default 0.
Request
curl -X POST https://scarce.markets/api/v1/tx/launch -H "Content-Type: application/json" \
  -d '{"wallet":"<wallet>","mint":"<new mint>","tier":"satoshi","name":"Satoshi Cat",
       "symbol":"SCAT","uri":"https://…/metadata.json","firstBuyBtc":0.001}'
Response 200
{
  "transaction": "<base64>",
  "version": "legacy",
  "mint": "<mint>",
  "wallet": "<wallet>",
  "tier": "satoshi",
  "config": "<config>",
  "pool": "<dbc pool>",
  "firstBuyBtc": 0.001,
  "signers": [
    "<wallet>",
    "<mint>"
  ],
  "note": "Sign with the wallet and the mint keypair, then send within ~60 s (the blockhash expires)."
}
  • 400 A parameter is missing or invalid; error names it.
  • 409 That mint already has a pool: generate a fresh keypair.

Events

Event stream

GET/api/v1/events

Server-sent events (text/event-stream) for new coins, halvings, graduations and burns, a few seconds after they land on chain. Each event has an id; reconnect with the Last-Event-ID header (browsers' EventSource does this for you) or ?since=<id> to receive what you missed. The server keeps the last 200 events since it started.

Limit 10 event stream connections per 1 min per IP · not cached

Query

sinceinteger
Replay buffered events after this id.
Request
curl -N https://scarce.markets/api/v1/events
Stream
event: ready
data: {"cluster":"mainnet-beta"}

id: 7
event: coin.halving
data: {"mint":"<mint>","symbol":"SCAT","name":"Satoshi Cat","tier":"satoshi","fromEra":0,"toEra":1,"eraName":"Halving I","priceSats":0.4494,"at":1791147084827}

: ping
  • 503 Too many open streams on the server; retry after the Retry-After seconds.

Objects

Coin

Returned by /coins and /coins/{mint}.

mintstring
The coin's mint address.
poolstring
Its Meteora DBC pool.
configstring
The DBC config it launched on (one per tier).
tierstring
satoshi, hal, whitepaper, bitcoin
creatorstring
The wallet that launched it.
activatedAtinteger
Unix seconds trading opened.
priceSatsnumber
Current price in sats per coin, from the curve or, once graduated, the DAMM v2 pool.
fdvBtcnumber
Fully diluted value in BTC: priceSats × 21,000,000 ÷ 10^8.
btcRaisednumber
BTC on the bonding curve.
graduationBtcnumber
BTC raised at which the coin graduates (the tier's target).
progressnumber
btcRaised ÷ graduationBtc, 0 to 1.
era.indexinteger
0 Genesis, 1 Halving I, 2 Halving II, 3 Final Era
era.namestring
The era's name.
era.progressnumber
0 to 1 through the current era, by BTC raised.
era.coinsToHalvingnumber
Coins left to buy before the next halving.
coinsSoldnumber
Coins bought off the curve so far.
migratedboolean
Graduated: now trades on Meteora DAMM v2.
dammPoolstring | null
The DAMM v2 pool once graduated.
partnerFeeBtcUnclaimednumber
Platform fees accrued and not yet claimed for buyback & burn.
metaobject
name, symbol, uri, image, description and socials (twitter, telegram, website).
isNativeboolean
This is the platform's native coin, the one fees buy back and burn.

Quote

inobject
What goes in: asset (btc, sol or coin) and amount.
out.expectednumber
Coins (buys) or BTC (sells) out at the current state.
out.minnumber
The least the trade can return with your slippage; the transaction fails below it.
viaBtcnumber | null
SOL buys: the BTC the SOL swap guarantees, which then buys the coin.
spotPriceSatsnumber
Price before the trade, sats per coin.
avgPriceSatsnumber
BTC paid or received ÷ coins, in sats per coin, fee included.
priceImpactnumber
avgPriceSats ÷ spotPriceSats − 1: positive on buys, negative on sells.
venuestring
curve (Meteora DBC) or dammV2 (graduated).
eraobject | null
Curve only: before, after (null = graduates), crossesHalving, graduates.

Transaction response

transactionstring
The serialized transaction, base64.
version"legacy" | 0
Deserialize with Transaction.from (legacy) or VersionedTransaction.deserialize (0).
expectedCoins / expectedBtcnumber
What the trade should return.
minCoins / minBtcnumber
The least it can return; below that it fails and nothing is spent.
notestring
Signing reminder.

Event types

From GET /api/v1/events. Every payload has at, the Unix ms the server saw it. The stream also sends a ready event on connect and a comment ping every 15 seconds.

coin.created

A coin launched.

{
  "mint": "<mint>",
  "symbol": "SCAT",
  "name": "Satoshi Cat",
  "tier": "satoshi",
  "creator": "<wallet>",
  "pool": "<dbc pool>",
  "activatedAt": 1791147066,
  "at": 1791147073843
}

coin.halving

A coin on the bonding curve entered its next era: the price has doubled since the era began and each BTC now buys half as many coins.

{
  "mint": "<mint>",
  "symbol": "SCAT",
  "name": "Satoshi Cat",
  "tier": "satoshi",
  "fromEra": 0,
  "toEra": 1,
  "eraName": "Halving I",
  "priceSats": 0.4494,
  "at": 1791147084827
}

coin.graduated

A coin raised its tier's target and moved to its Meteora DAMM v2 pool.

{
  "mint": "<mint>",
  "symbol": "SCAT",
  "name": "Satoshi Cat",
  "tier": "satoshi",
  "dammPool": "<damm v2 pool>",
  "priceSats": 2.9983,
  "at": 1791150000000
}

burn

A keeper buyback & burn (or a graduated coin's leftover burn) landed.

{
  "signature": "<signature>",
  "time": 1791140807,
  "mint": "<mint>",
  "coins": 10.2039,
  "why": "buyback",
  "at": 1791140812000
}

Recipes

Launch a coin from your app

TypeScript · browser
import { Keypair, Transaction } from "@solana/web3.js";

// 1. Image + metadata -> uri
const form = new FormData();
form.set("name", "Satoshi Cat");
form.set("symbol", "SCAT");
form.set("image", file); // a File from an <input type="file">
const meta = await fetch("https://scarce.markets/api/v1/metadata", { method: "POST", body: form }).then((r) => r.json());
if (meta.error) throw new Error(meta.error);

// 2. A fresh keypair becomes the coin's mint
const mint = Keypair.generate();
const res = await fetch("https://scarce.markets/api/v1/tx/launch", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
    mint: mint.publicKey.toBase58(),
    tier: "satoshi",
    name: "Satoshi Cat",
    symbol: "SCAT",
    uri: meta.uri,
    firstBuyBtc: 0.001,
  }),
}).then((r) => r.json());
if (res.error) throw new Error(res.error);

// 3. Wallet + mint sign; the coin page is live once it confirms
const tx = Transaction.from(Uint8Array.from(atob(res.transaction), (c) => c.charCodeAt(0)));
const signature = await wallet.sendTransaction(tx, connection, { signers: [mint] });
console.log("https://scarce.markets/c/" + res.mint);

A halving alert bot

Node 22 or later, no dependencies: read the stream with fetch and post wherever you like.

JavaScript · Node
const res = await fetch("https://scarce.markets/api/v1/events");
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buf = "";
for (;;) {
  const { value, done } = await reader.read();
  if (done) break; // reconnect here with ?since=<last id>
  buf += value;
  let end;
  while ((end = buf.indexOf("\n\n")) >= 0) {
    const block = buf.slice(0, end);
    buf = buf.slice(end + 2);
    const type = /^event: (.*)$/m.exec(block)?.[1];
    const data = /^data: (.*)$/m.exec(block)?.[1];
    if (type === "coin.halving") {
      const e = JSON.parse(data);
      console.log(`$${e.symbol} entered ${e.eraName} at ${e.priceSats.toFixed(2)} sats https://scarce.markets/c/${e.mint}`);
    }
  }
}

In a browser it is shorter:

JavaScript · browser
const es = new EventSource("https://scarce.markets/api/v1/events");
es.addEventListener("coin.graduated", (e) => console.log("graduated", JSON.parse(e.data).symbol));

Coins about to graduate

Python · requests
import requests

r = requests.get("https://scarce.markets/api/v1/coins", params={"sort": "progress", "graduated": "false", "limit": 10})
for c in r.json()["coins"]:
    print(f'{c["meta"]["symbol"]:>8}  {c["progress"]:6.1%}  {c["era"]["name"]:<10}  {c["priceSats"]:.2f} sats')

Price in dollars

TypeScript
const [{ usd }, coin] = await Promise.all([
  fetch("https://scarce.markets/api/v1/btc-usd").then((r) => r.json()),
  fetch("https://scarce.markets/api/v1/coins/<mint>").then((r) => r.json()),
]);
const priceUsd = (coin.priceSats / 1e8) * usd;

Straight from the chain

There is no Scarce Markets program. A coin belongs to Scarce Markets exactly when its Meteora DBC pool uses one of these configs, so you can index or trade without this API using @meteora-ag/dynamic-bonding-curve-sdk and @meteora-ag/cp-amm-sdk.

WhatAddress
Satoshi config (0.1 BTC)D1hYgnrGvwDYJa7rn5oZSU4p9Apb88ZZZDDRtF2jBf8c
Hal config (0.25 BTC)ESPEBb4FSwfoh2zqXdZc2FRxKnbYnSPLJcNPDbqMqdBD
Whitepaper config (0.5 BTC)69VWqqNqBuEk6CQaLvRCRnB6qkc4mUr6VRbygRA1uuyW
Bitcoin config (1 BTC)FU6fJRHHjuC51FEn2BVWoe8RBTSbnpmEA7WG5f617wrt
BTC quote mintcbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij
DBC programdbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN
DAMM v2 programcpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG

A coin's pool is deriveDbcPoolAddress(btcMint, mint, config). Era boundaries follow from the config's curve: four constant-product segments, one per era, each raising a quarter of the graduation target.

Versioning

  • Within /api/v1, responses only gain fields. Ignore fields you don't know. Removing or renaming anything means /api/v2.
  • The unversioned /api/* routes serve this site and can change without notice. Build on /api/v1.
  • Fees bought back and burned describe what the platform does with its share; nothing here is a promise about price or returns.