Stable Points (SP)

Plug‑and‑play Commerce & Rewards rails for any Web3 project — powered by Spree Finance's 2M+ merchant network.

Spree API (EVM) Technical Reference

Introduction

Stable Points (SP) are commerce‑stable reward points, pegged at $1.00 ≈ 100 SP, accepted across a network of 2M+ merchants out of the box. Brands can also issue Branded SP (their own label of stable points) while inheriting Spree’s compliance controls (whitelists, roles, freeze, fee policies).

The public /build API constructs unsigned transactions you sign and broadcast. This keeps custody and signing with you (or your users) and makes it ideal for wallets, dapps, and servers that manage their own keys.

Key operational guarantees:

  • Walled‑garden controls: mint, transfer, and redeem are gated by whitelists/roles to support compliant usage.

  • Multi‑chain: uniform patterns across EVM and Solana.

  • Composable: pair /build with read‑only /evm routes to check balances, allowances, and pending redemptions.

Base URL & patterns

  • Staging base: https://api.stg.spree.finance

  • Production: provided during onboarding.

Route families (this guide focuses on the first):

  • 🔓 /build/* — build unsigned tx for client signing & broadcasting (no auth)

  • 📖 /evm/* — read‑only state for EVM (optional pairing for UX, helpful for reading on-chain metrics)

Address formats

  • EVM: 0x‑prefixed hex.

  • Solana: Base58.

Request/response shape (build routes)

Request (POST/PUT/DELETE):

{
  "input": {
    /* route‑specific parameters */
  },
  "key": "<signerAddress>"
}

key must equal the wallet address that will sign the transaction.

Success response (build):

{
  "success": true,
  "transaction": "<unsigned tx>",
  "message": "Transaction built successfully. Sign and broadcast this transaction.",
  "tx": { /* optional tx params object for some EVM routes */ }
}
  • EVM: transaction may be a 0x‑hex serialized unsigned tx, or tx may contain a params object (to, data, value, gas, ...).

  • Solana: transaction is base64 (VersionedTransaction/Message) for @solana/web3.js.

Error response:

{ "success": false, "error": "Error description", "code": "MACHINE_READABLE_CODE" }

Prerequisites & conventions

  • Whitelists: Users and programs must be on the relevant mint, transfer, and redeem whitelists.

  • Roles: Admins manage whitelists/fees; executor finalizes redemptions; freezer can freeze/unfreeze.

  • Allowances (EVM): For mint/redeem flows, users must approve the factory/points as spender for the relevant token.

  • Units: Always pass amounts as integer base units (e.g., wei for EVM; token decimals for Solana mints).

  • Basket mode (EVM): Set expectedBasketMode to true only if the factory is configured for basket mint/redeem.


EVM — Public /build endpoints

Base prefix: /build/evm/chain/{chainId}

5.1 Core token operations

Approve (ERC‑20)

POST /build/evm/chain/{chainId}/token/{tokenAddr}/approve
Body {
  "input": { "spender": "0x...", "amount": "1000000000000000000" },
  "key": "0x<owner>"
}

Transfer (ERC‑20)

POST /build/evm/chain/{chainId}/token/{tokenAddr}/transfer
Body { "input": { "to": "0x...", "amount": "500" }, "key": "0x<owner>" }

Transfer‑from (ERC‑20)

POST /build/evm/chain/{chainId}/token/{tokenAddr}/transfer-from
Body { "input": { "from": "0x...", "to": "0x...", "amount": "250" }, "key": "0x<spender>" }

5.2 Factory — Mint & Redeem

Mint

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/asset/{assetAddr}/mint
Body {
  "input": { "to": "0x<receiver>", "amount": "1000", "expectedBasketMode": false },
  "key": "0x<minter>"
}

Redeem — create

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/asset/{assetAddr}/redeem/create
Body {
  "input": { "to": "0x<receiver>", "amount": "250", "expectedBasketMode": false },
  "key": "0x<requester>"
}

Redeem — cancel

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/asset/{assetAddr}/redeem/cancel
Body { "input": {}, "key": "0x<requester>" }

Redeem — finalize (executor)

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/asset/{assetAddr}/redeem/finalize
Body {
  "input": { "userAddress": "0x<requester>", "amount": "250", "expectedBasketMode": false },
  "key": "0x<executor>"
}

5.3 Whitelist management (factory & points)

  • Mint whitelist

    • PUT /build/evm/chain/{chainId}/factory/{factoryAddr}/whitelist/mint/user/{userAddr}

    • DELETE /build/evm/chain/{chainId}/factory/{factoryAddr}/whitelist/mint/user/{userAddr}

  • Redeem whitelist

    • PUT /build/evm/chain/{chainId}/factory/{factoryAddr}/whitelist/redeem/user/{userAddr}

    • DELETE /build/evm/chain/{chainId}/factory/{factoryAddr}/whitelist/redeem/user/{userAddr}

  • Transfer whitelist (points)

    • PUT /build/evm/chain/{chainId}/points/{pointsAddr}/whitelist/transfer/user/{userAddr}

    • DELETE /build/evm/chain/{chainId}/points/{pointsAddr}/whitelist/transfer/user/{userAddr}

All calls use:

Body { "input": {}, "key": "0x<whitelist-admin>" }

5.4 Roles & controls

Grant role

PUT /build/evm/chain/{chainId}/factory/{factoryAddr}/role/{roleType}
Body { "input": { "userAddress": "0x..." }, "key": "0x<role-admin>" }

Revoke role

DELETE /build/evm/chain/{chainId}/factory/{factoryAddr}/role/{roleType}
Body { "input": { "userAddress": "0x..." }, "key": "0x<role-admin>" }

Consideration: roleType ∈ { freezer, manager, executor }

Freeze / Unfreeze

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/freeze
POST /build/evm/chain/{chainId}/factory/{factoryAddr}/unfreeze
Body { "input": {}, "key": "0x<freezer>" }

5.5 Fees & vaults

Update mint fee (bps)

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/asset/{assetAddr}/fees/mint
Body { "input": { "feeBps": 100 }, "key": "0x<fee-admin>" }

Update redeem fee (bps)

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/asset/{assetAddr}/fees/redeem
Body { "input": { "feeBps": 200 }, "key": "0x<fee-admin>" }

Create vault

POST /build/evm/chain/{chainId}/factory/{factoryAddr}/vault
Body { "input": { "assetAddress": "0x..." }, "key": "0x<vault-admin>" }

6) Solana — Public /build endpoints

Base prefix: /build/solana

6.1 Core token operations

Mint

POST /build/solana/mint
Body { "input": { "to": "<Base58>", "amount": 1000 }, "key": "<mint-authority Base58>" }

Transfer

POST /build/solana/transfer
Body { "input": { "to": "<Base58>", "amount": 500 }, "key": "<owner Base58>" }

6.2 Redemption

Create

POST /build/solana/redeem/create
Body { "input": { "to": "<Base58>", "amount": 250 }, "key": "<requester Base58>" }

Cancel

POST /build/solana/redeem/cancel
Body { "input": {}, "key": "<requester Base58>" }

Finalize (executor)

POST /build/solana/redeem/finalize
Body { "input": { "requesterKey": "<Base58>" }, "key": "<executor Base58>" }

6.3 Whitelists

PUT    /build/solana/whitelist/{mint|transfer|redeem}/user/{userAddr}
DELETE /build/solana/whitelist/{mint|transfer|redeem}/user/{userAddr}
Body { "input": {}, "key": "<whitelist-authority Base58>" }

6.4 Authority updates

PUT /build/solana/whitelist/{mint|transfer|redeem}/authority/{userAddr}
PUT /build/solana/fee/authority/{userAddr}
PUT /build/solana/freeze/authority/{userAddr}
PUT /build/solana/redemption-executor/authority/{userAddr}
PUT /build/solana/redemption-executor/{userAddr}
Body { "input": {}, "key": "<current-authority Base58>" }

6.5 Freeze / Unfreeze

POST /build/solana/freeze/{all|mint|transfer|redeem}
POST /build/solana/unfreeze/{all|mint|transfer|redeem}
Body { "input": {}, "key": "<freeze-authority Base58>" }

6.6 Fees

POST /build/solana/fees
Body { "input": { "mintFee": 100, "transferFee": 50, "redeemFee": 200 }, "key": "<fee-authority Base58>" }

7) End‑to‑end flows (EVM & Solana)

A) EVM — USDC → SP mint, then transfer

  1. Approve USDC

POST /build/evm/.../token/{USDC}/approve
Body { "input": { "spender": "{factory}", "amount": "1000000" }, "key": "{user}" }
  1. Mint SP

POST /build/evm/.../factory/{factory}/asset/{USDC}/mint
Body { "input": { "to": "{user}", "amount": "1000000", "expectedBasketMode": false }, "key": "{user}" }
  1. Transfer SP (if needed)

POST /build/evm/.../token/{SP}/transfer
Body { "input": { "to": "{recipient}", "amount": "500000" }, "key": "{user}" }

Ensure both sender & recipient are on the transfer whitelist for the points contract.

B) EVM — Redeem SP back to USDC

  1. Approve SP to factory.

  2. Create redeem request

POST /build/evm/.../factory/{factory}/asset/{USDC}/redeem/create
Body { "input": { "to": "{user}", "amount": "750000", "expectedBasketMode": false }, "key": "{user}" }
  1. Finalize (executor)

POST /build/evm/.../factory/{factory}/asset/{USDC}/redeem/finalize
Body { "input": { "userAddress": "{user}", "amount": "750000", "expectedBasketMode": false }, "key": "{executor}" }

C) Solana — Mint → Transfer → Redeem

POST /build/solana/mint            { key: <mint‑authority>, input: { to, amount } }
POST /build/solana/transfer        { key: <owner>,          input: { to, amount } }
POST /build/solana/redeem/create   { key: <requester>,      input: { to, amount } }
POST /build/solana/redeem/finalize { key: <executor>,       input: { requesterKey } }

8) Signing & broadcasting examples

8.1 EVM (ethers v6)

import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);

const res = await fetch(
  `https://api.stg.spree.finance/build/evm/chain/${chainId}/factory/${factory}/asset/${asset}/mint`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      input: { to: wallet.address, amount: "1000", expectedBasketMode: false },
      key: wallet.address,
    }),
  }
);
const json = await res.json();
if (!json.success) throw new Error(json.error || "build failed");

let txHash: string;
if (json.transaction) {
  const unsigned = ethers.Transaction.from(json.transaction);
  const signed = await wallet.signTransaction(unsigned);
  txHash = (await provider.broadcastTransaction(signed)).hash;
} else if (json.tx) {
  txHash = (await wallet.sendTransaction(json.tx)).hash;
} else {
  throw new Error("Unexpected build response shape");
}
console.log("tx hash", txHash);

8.2 Solana (web3.js)

import { Connection, VersionedTransaction } from "@solana/web3.js";

const connection = new Connection(process.env.SOLANA_RPC!);

const res = await fetch("https://api.stg.spree.finance/build/solana/mint", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ input: { to: recipient, amount: 1000 }, key: signerPubkey }),
});
const json = await res.json();
if (!json.success) throw new Error(json.error || "build failed");

const tx = VersionedTransaction.deserialize(Buffer.from(json.transaction, "base64"));
// Attach signers and sign, e.g. tx.sign([keypair])
const sig = await connection.sendTransaction(tx, { skipPreflight: false });
console.log("sig", sig);

9) Errors & troubleshooting

Tips

  • Always re‑check allowance, whitelists, and freeze status before submitting.

  • If broadcast fails with underpriced gas or blockhash expired, rebuild the tx.

  • Keep amount as integer base units; don’t send decimals.


10) Roles & glossary

  • manager — manages whitelists and fees.

  • executor — finalizes redemption requests.

  • freezer — can freeze/unfreeze contracts.

  • points — the ERC‑20/SPL points token (e.g., SP).

  • factory — contract managing mints/redeems, vaults, fees, and roles.

  • basket mode — factory setting enabling basket‑based mint/redeem.


11) Copy‑paste cURL (staging)

Approve (EVM)

curl -X POST \
  "https://api.stg.spree.finance/build/evm/chain/137/token/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606e48/approve" \
  -H "Content-Type: application/json" \
  -d '{
    "input": { "spender": "0xFactory...", "amount": "1000000" },
    "key": "0xUser..."
  }'

Mint (EVM)

curl -X POST \
  "https://api.stg.spree.finance/build/evm/chain/137/factory/0xFactory.../asset/0xA0b86991.../mint" \
  -H "Content-Type: application/json" \
  -d '{
    "input": { "to": "0xUser...", "amount": "1000", "expectedBasketMode": false },
    "key": "0xUser..."
  }'

Mint (Solana)

curl -X POST \
  "https://api.stg.spree.finance/build/solana/mint" \
  -H "Content-Type: application/json" \
  -d '{
    "input": { "to": "2ZVzCH4fH6QMGCs8rVEocaTxhCdxHFrbyJU8BVF1k6yH", "amount": 1000 },
    "key": "Di1hUr8ALUMV1wqJk5otzEyWANDT2NB1VEtWsYZNEhKq"
  }'

Compliance & business notes

  • Why whitelists? They enforce a controlled, compliant rewards perimeter (mint/transfer/redeem) aligned with merchant acceptance and brand policies.

  • Executor custody: Redemption finalization is an admin path that coordinates vault movements and fiat settlement for merchant payouts.

  • Branded SP: Partners can issue their own label on top of SP/stable collateral, reuse the same /build flows, and inherit the network acceptance footprint.

For production launch (domains, chainlists, and operational thresholds), the Spree team will provide environment and policy details.

Last updated