npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alpha-arcade/sdk

v0.5.2

Published

TypeScript SDK for trading on Alpha Market — Algorand prediction markets. Place orders, manage positions, read orderbooks from API or chain, and build trading bots.

Readme

@alpha-arcade/sdk

TypeScript SDK for trading on Alpha Market - Algorand prediction markets.

Place orders, manage positions, stake ALPHA for fee rewards, read orderbooks from the API or chain, and build automated trading bots.

Installation

npm install @alpha-arcade/sdk algosdk @algorandfoundation/algokit-utils

algosdk and @algorandfoundation/algokit-utils are peer dependencies.

Getting an API key

An API key is optional. Without it, you can still fetch markets on-chain, place orders, and use most SDK features. With an API key, you get richer market data, liquidity rewards information, wallet order lookups, routed liquidity, and RFQ endpoints.

To get an API key:

  1. Go to alphaarcade.com and sign up with your email or Google account.
  2. Open the Account page
  3. Open the Partners tab.
  4. Click Create API key and copy the key.
  5. Add it to your environment (e.g. a .env file in the project root):
ALPHA_API_KEY=your_api_key_here

Then pass it when creating the client: apiKey: process.env.ALPHA_API_KEY.

Quick Start

import { AlphaClient } from '@alpha-arcade/sdk';
import algosdk from 'algosdk';

// 1. Setup clients
const algodClient = new algosdk.Algodv2('', 'https://mainnet-api.algonode.cloud', 443);
const indexerClient = new algosdk.Indexer('', 'https://mainnet-idx.algonode.cloud', 443);

// 2. Setup signer from mnemonic (or use any TransactionSigner)
const account = algosdk.mnemonicToSecretKey('your twenty five word mnemonic ...');
const signer = algosdk.makeBasicAccountTransactionSigner(account);

// 3. Initialize the client (no API key needed!)
const client = new AlphaClient({
  algodClient,
  indexerClient,
  signer,
  activeAddress: account.addr.toString(),
  matcherAppId: 3078581851,
  usdcAssetId: 31566704,
});

// 4. Fetch live markets (reads directly from chain)
const markets = await client.getLiveMarkets();
console.log(`Found ${markets.length} live markets`);

// 5. Place a limit buy order on the first market
const market = markets[0];
const result = await client.createLimitOrder({
  marketAppId: market.marketAppId,
  position: 1,        // 1 = Yes
  price: 500_000,     // $0.50
  quantity: 1_000_000, // 1 share
  isBuying: true,
});

console.log(`Order created! Escrow app ID: ${result.escrowAppId}`);

Examples

The repo includes runnable examples (use npx tsx examples/<script>.ts). Scripts that call the API (e.g. get-orders.ts, get-reward-markets.ts) need ALPHA_API_KEY in your .env - see Getting an API key. Trading examples also need TEST_MNEMONIC.

| Script | Description | |--------|-------------| | get-orders.ts | Fetch all open orders for a wallet via the API (getWalletOrdersFromApi) | | get-reward-markets.ts | Fetch reward markets and show liquidity reward info (getRewardMarkets) | | get-positions.ts | List token positions across markets (getPositions) | | stake-alpha.ts | Stake ALPHA into the fee-sharing pool on-chain (stakeAlpha) | | place-limit-order.ts | Place a limit order | | place-market-order.ts | Place a market order | | cancel-order.ts | Cancel an open order | | split-merge.ts | Split USDC into YES/NO and merge back | | simple-trading-bot.ts | Example bot that scans markets and places market orders | | place-rfq-trade.ts | Test cross-venue RFQ quote logic for a market | | combo-rfq-maker.ts | Run a combo RFQ maker over the platform WebSocket | | get-orderbook.ts | Retrieves and logs combined routed orderbook |

API Reference

AlphaClient

Constructor

new AlphaClient(config: AlphaClientConfig)

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | algodClient | algosdk.Algodv2 | Yes | Algorand algod client | | indexerClient | algosdk.Indexer | Yes | Algorand indexer client | | signer | TransactionSigner | Yes | Transaction signer | | activeAddress | string | Yes | Your Algorand address | | matcherAppId | number | Yes | Matcher contract app ID (mainnet: 3078581851) | | usdcAssetId | number | Yes | USDC ASA ID (mainnet: 31566704) | | apiKey | string | No | Alpha API key. If provided, getLiveMarkets() and related API methods use the platform for richer data (images, categories, volume, reward markets, wallet orders). If omitted, markets are discovered on-chain. | | apiBaseUrl | string | No | API base URL (default: https://platform.alphaarcade.com/api) | | marketCreatorAddress | string | No | Market creator address for on-chain discovery (defaults to Alpha Arcade mainnet) | | stakingAppId | number | No | ALPHA staking pool app ID (mainnet default: 3626756314) | | alphaAssetId | number | No | ALPHA ASA ID (mainnet default: 2726252423) |


Trading

createLimitOrder(params)

Creates a limit order that sits on the orderbook at your price.

const result = await client.createLimitOrder({
  marketAppId: 123456789,
  position: 1,          // 1 = Yes, 0 = No
  price: 500_000,       // $0.50 in microunits
  quantity: 2_000_000,  // 2 shares in microunits
  isBuying: true,
});
// result: { escrowAppId, txIds, confirmedRound, matchedQuantity?, matchedPrice? }

createMarketOrder(params)

Creates a market order that auto-matches against the orderbook.

const result = await client.createMarketOrder({
  marketAppId: 123456789,
  position: 1,
  price: 550_000,       // willing to pay up to $0.55
  quantity: 1_000_000,
  isBuying: true,
  slippage: 50_000,     // $0.05 slippage tolerance
});
// result: { escrowAppId, txIds, confirmedRound, matchedQuantity, matchedPrice }

createFokOrder(params) and buildFokOrder(params)

Fill-or-kill (FOK) uses the existing native escrow contracts: escrow creation and exact fills execute in one atomic group. A failed match rejects the whole group, including escrow creation. There is no resting unfilled remainder on success.

// Buy exactly 100 YES shares at $0.60 or better, excluding trading fees.
const result = await client.createFokOrder({
  marketAppId: 123456789,
  position: 1,
  isBuying: true,
  quantity: 100_000_000,
  price: 600_000,
});
// { escrowAppId, txIds, confirmedRound, matchedQuantity, estimatedMatchedPrice }

price is a maximum for buys and a minimum for sells. All prices and quantities use integer microunits. The method uses zero slippage, never widens the limit, and never automatically retries a rejected group. YES and NO orders support direct and complementary native matching. Automatic selection takes the best prices first and excludes the active wallet's own escrows.

To select counterparties yourself, supply matchingOrders. Their quantities must total exactly the order quantity. The SDK revalidates each escrow's owner, side, available quantity, and effective price against the native orderbook. Caller-supplied price metadata does not override current prices.

const built = await client.buildFokOrder({
  marketAppId: 123456789,
  position: 1,
  isBuying: true,
  quantity: 100_000_000,
  price: 600_000,
  matchingOrders: [
    { escrowAppId: makerAId, owner: makerAAddress, quantity: 40_000_000 },
    { escrowAppId: makerBId, owner: makerBAddress, quantity: 60_000_000 },
  ],
});
// Omit matchingOrders to select counterparties automatically.
console.log(Buffer.from(built.groupId).toString('base64'));
const unsignedBytes = built.transactions.map(txn => algosdk.encodeUnsignedTransaction(txn));
// Pass the entire unsigned group to your wallet, then submit all signed transactions together.

buildFokOrder() does not sign or submit. It returns unsigned transactions, groupId, createEscrowTxnIndex, matchingOrders, matchedQuantity, and estimatedMatchedPrice. Here matchedQuantity is planned coverage, not a confirmed trade. Do not remove or modify transactions after building the group.

Limits and execution details:

  • Native escrow liquidity only; routed/RFQ liquidity is not included.
  • At most six maker escrows fit with the current transaction layout, including one required asset opt-in. Orders exceeding this limit reject before signing. Best-price selection does not search for a worse-price combination with fewer makers.
  • Insufficient depth, invalid selected fills, and invalid parameters reject before signing.
  • Liquidity can change after the orderbook read. The on-chain exact-quantity and price checks then decide whether the entire group succeeds.
  • estimatedMatchedPrice is the quoted weighted average, not a receipt of the actual fill price. Maker amendments can change execution prices within the fixed limit.
  • The SDK funds escrow creation and trading fees using its existing native-order builder. Successful fills can leave a fully filled escrow and unused funding for later cleanup with cancelOrder().
  • FOK does not require a contract upgrade, but it does require deployments compatible with the SDK's generated market and matcher clients.

cancelOrder(params)

Cancels an open order and returns funds to the owner.

const result = await client.cancelOrder({
  marketAppId: 123456789,
  escrowAppId: 987654321,
  orderOwner: 'ALGO_ADDRESS...',
});
// result: { success, txIds }

amendOrder(params)

Edits an existing unfilled order in-place - cheaper and faster than cancel + recreate. The escrow contract adjusts collateral automatically: sends you a refund if the new value is lower, or requires extra funds (sent automatically) if higher.

Only works on orders with zero quantity filled.

// Get your open orders to find the escrowAppId
const orders = await client.getOpenOrders(123456789);
const order = orders[0];

// Amend the order to a new price and quantity
const result = await client.amendOrder({
  marketAppId: 123456789,
  escrowAppId: order.escrowAppId,
  price: 600_000,       // new price: $0.60
  quantity: 3_000_000,  // new quantity: 3 shares
});
// result: { success, txIds, confirmedRound }

proposeMatch(params)

Manually matches an existing maker order against a taker.

const result = await client.proposeMatch({
  marketAppId: 123456789,
  makerEscrowAppId: 987654321,
  makerAddress: 'MAKER_ALGO_ADDRESS...',
  quantityMatched: 500_000,
});
// result: { success, txIds }

Positions

splitShares(params)

Splits USDC into equal YES + NO tokens. 1 USDC = 1 YES + 1 NO.

const result = await client.splitShares({
  marketAppId: 123456789,
  amount: 5_000_000, // $5.00 USDC
});
// You now hold 5 YES + 5 NO tokens for this market

mergeShares(params)

Merges equal YES + NO tokens back into USDC.

const result = await client.mergeShares({
  marketAppId: 123456789,
  amount: 3_000_000, // Merge 3 YES + 3 NO = $3.00 USDC
});

claim(params)

Claims USDC from a resolved market.

const result = await client.claim({
  marketAppId: 123456789,
  assetId: 111222333, // The YES or NO token ASA ID
});

getPositions(walletAddress?)

Gets all token positions across all markets.

const positions = await client.getPositions();
for (const pos of positions) {
  console.log(`Market ${pos.marketAppId}: ${pos.yesBalance / 1e6} YES, ${pos.noBalance / 1e6} NO`);
}

Staking (ALPHA fee-sharing pool)

Stake ALPHA to earn a share of trading fees routed to the pool as USDC. These methods are fully on-chain (algod only) — no Alpha platform API key required.

Mainnet defaults (overridable via stakingAppId / alphaAssetId on the client):

| Constant | Value | |----------|-------| | Staking app ID | 3626756314 | | ALPHA ASA | 2726252423 (6 decimals) | | Reward asset | USDC 31566704 |

Transaction group for stakeAlpha: optional app opt_in() → ALPHA axfer into the pool → stake(). The ALPHA transfer must immediately precede the stake app call. First-time stakers need ~0.2385 ALGO free for app local-state MBR + fees.

stakeAlpha(params)

// Stake 10 ALPHA (amounts are microunits)
const result = await client.stakeAlpha({ amount: 10_000_000 });
console.log(result.txIds, result.confirmedRound);

unstakeAlpha(params)

const result = await client.unstakeAlpha({ amount: 5_000_000 });

claimStakingRewards()

Claims accrued USDC rewards. Adds a USDC ASA opt-in when needed.

const result = await client.claimStakingRewards();

getStakingPosition(walletAddress?)

Reads on-chain stake + claimable USDC for a wallet.

const pos = await client.getStakingPosition();
console.log(`Staked: ${pos.staked / 1e6} ALPHA`);
console.log(`Claimable: $${pos.claimable / 1e6} USDC`);
console.log(`Pool share: ${pos.poolShareBps / 100}%`);

See examples/stake-alpha.ts for a runnable script (TEST_MNEMONIC required).

Disclaimer: $ALPHA staking and fee distributions involve risks. Staking returns depend on trading activity on staking-enabled markets and are not guaranteed. This documentation does not constitute an offer or solicitation to purchase $ALPHA or any other digital asset. $ALPHA has not been registered under the U.S. Securities Act of 1933 or any state securities law.


Community Resolution (Oracle-Lite)

Propose, dispute, finalize, and claim on community-resolved markets (the markets shown on /governance — oracleAppId on the market row). Actions are fully on-chain (algod only); the two list reads use the REST API.

Proposing is bonded: you post the base bond (25 USDC on current markets) with your assertion. If nobody disputes within the window, finalization returns your bond and pays you the proposer reward (50 ALPHA) in the same transaction — the SDK handles the reward mechanics automatically, including an ALPHA opt-in at propose time if your wallet lacks one. A lost dispute forfeits the bond, so only propose outcomes you can defend.

import { RESOLUTION_OUTCOME } from '@alpha-arcade/sdk';

// What can I resolve?
const markets = await client.getResolutionMarkets();

// Full on-chain state of one market's oracle
const state = await client.getResolutionState(markets[0].oracleAppId);
// state.status: 0 none, 1 proposed, 2 disputed, 3 resolved
// state.rewardsAppId !== 0 -> proposer rewards are active on this market

// Propose the outcome (posts the USDC bond)
await client.proposeResolution({
  oracleAppId: markets[0].oracleAppId,
  outcome: RESOLUTION_OUTCOME.YES, // 0 No, 1 Yes, 2 fifty/fifty
});

// Disagree with someone's live proposal? Challenge it under a 2x bond —
// the market goes to the arbiter. KEEP_OPEN = "not resolvable yet".
await client.disputeResolution({ oracleAppId, outcome: RESOLUTION_OUTCOME.NO });

// After the window lapses undisputed, anyone may finalize (the keeper cron
// also does this) — the proposer gets bond + ALPHA reward:
await client.finalizeResolution({ oracleAppId });

// Pull your settled bond (or any bonder's — payout always goes to the bonder)
await client.claimResolutionBond({ oracleAppId });

// Your live + claimable bonds across all markets
const bonds = await client.getWalletResolutionBonds();

| Method | Description | |--------|-------------| | getResolutionMarkets() | Lite-resolved markets (REST) | | getResolutionState(oracleAppId) | Full on-chain oracle state | | proposeResolution({ oracleAppId, outcome }) | Bonded outcome proposal (auto ALPHA opt-in on reward-armed markets) | | disputeResolution({ oracleAppId, outcome }) | 2x-bond challenge; KEEP_OPEN (3) allowed | | finalizeResolution({ oracleAppId }) | Permissionless settle; pays the proposer reward on armed markets | | claimResolutionBond({ oracleAppId, bonder? }) | Pull a settled bond payout | | getWalletResolutionBonds(wallet?) | A wallet's bonds (REST) |


Orderbook

getOrderbook(marketAppId)

Fetches the full on-chain orderbook for a single market app.

const book = await client.getOrderbook(123456789);

console.log('Yes bids:', book.yes.bids.length);
console.log('Yes asks:', book.yes.asks.length);
console.log('No bids:', book.no.bids.length);
console.log('No asks:', book.no.asks.length);

// Best yes bid
if (book.yes.bids.length > 0) {
  const best = book.yes.bids.sort((a, b) => b.price - a.price)[0];
  console.log(`Best Yes bid: $${best.price / 1e6} for ${best.quantity / 1e6} shares`);
}

getFullOrderbookFromApi(marketId)

Fetches the full processed orderbook snapshot for a market from the Alpha REST API. Requires apiKey.

This returns the same shape as websocket orderbook_changed.orderbook: a record keyed by marketAppId, where each value includes:

  • top-level aggregated bids, asks, and spread
  • detailed yes and no bid/ask orders with escrowAppId and owner
const snapshot = await client.getFullOrderbookFromApi('market-uuid-here');

for (const [appId, book] of Object.entries(snapshot)) {
  console.log(`App ${appId}: spread=${book.spread}`);
  console.log('Top-level bids:', book.bids);
  console.log('Detailed YES bids:', book.yes.bids);
}

getRoutedOrderbook(marketId)

Fetches the API-backed orderbook with native Alpha Arcade liquidity and routed Polymarket liquidity. Requires apiKey.

Use this when you want to show liquidity that can be matched on demand through the cross-venue flow. The native book remains unchanged under native; routed entries are source-tagged so they cannot be confused with real escrow orders.

const routed = await client.getRoutedOrderbook('market-uuid-here');

for (const [appId, routedBook] of Object.entries(routed.orderbook)) {
  console.log(`App ${appId}`);

  for (const ask of routedBook.merged.asks) {
    if (ask.source === 'alpha') {
      console.log(`AA ask ${ask.escrowAppId}: $${ask.price / 1e6}`);
    } else {
      console.log(
        `Routed ask via ${ask.polyTokenId}: display $${ask.displayPriceMicro / 1e6}, source $${ask.polySourcePriceMicro / 1e6}`,
      );
    }
  }
}

Routed entries have source: 'polymarket' and execution: 'crossVenue'. They intentionally do not have escrowAppId, because the escrow does not exist until the cross-venue transaction group is signed and submitted.

Do not pass routed entries into createMarketOrder() or calculateMatchingOrders(). Those functions only match existing Alpha Arcade escrow orders. Use requestRfqQuote() for routed liquidity.

Cross-venue config and RFQ quotes

These methods wrap the routed-liquidity API. They require apiKey.

const config = await client.getCrossVenueConfig();
console.log(`Cross-venue matcher app: ${config.matcherAppId}`);

const quote = await client.requestRfqQuote({
  marketId: 'market-uuid-here',
  marketAppId: 123456789,      // recommended for multi-choice markets
  userAddress: account.addr.toString(),
  userPosition: 1,             // 1 = YES, 0 = NO
  isBuying: true,
  quantity: 2_000_000,         // 2 shares
});

if (!quote.ok) {
  console.log(`No routed quote: ${quote.reason} ${quote.detail ?? ''}`);
} else {
  console.log(`Quote id: ${quote.quoteId}`);
  console.log(`Display price: $${quote.displayPriceMicro! / 1e6}`);
  console.log(`External source price: $${quote.polySourcePriceMicro! / 1e6}`);
  console.log(`Expires at: ${new Date(quote.expiresAt!).toISOString()}`);
  console.log(`User needs opt-in: ${quote.userNeedsOptIn}`);
  console.log(`MM needs opt-in: ${quote.mmNeedsOptIn}`);
}

requestRfqQuote() is a fresh quote for display and transaction construction. It is not final fill authorization. The backend re-fetches Polymarket liquidity and re-runs cross-venue validation when submitRoutedOrder() is called, then byte-compares the wallet-signed user legs before the market-maker signs.

submitRoutedOrder(params)

Submits a wallet-signed cross-venue order to the backend for final validation, market-maker signing, and on-chain submission.

const result = await client.submitRoutedOrder({
  userAddress: account.addr.toString(),
  marketId: 'market-uuid-here',
  marketAppId: 123456789,
  userPosition: 1,
  isBuying: true,
  quantity: quote.quantity!,
  polyQuotedPriceMicro: quote.displayPriceMicro!,
  yesAssetId: quote.yesAssetId!,
  noAssetId: quote.noAssetId!,
  mmNeedsOptIn: quote.mmNeedsOptIn ?? false,
  userNeedsOptIn: quote.userNeedsOptIn ?? false,
  crossVenueTakerSlippageMicro: quote.takerSlippageMicro!,
  suggestedParams: {
    firstValid: 0,
    lastValid: 0,
    genesisHash: 'base64-genesis-hash',
    genesisID: 'mainnet-v1.0',
    fee: 0,
    minFee: 1000,
  },
  nonce: 'base64-8-byte-nonce',
  signedUserTxns: [
    'base64-signed-user-opt-in-or-payment',
    'base64-signed-user-funding',
    'base64-signed-user-create-escrow',
    'base64-signed-user-propose-match',
  ],
});

The SDK currently provides the HTTP wrapper for submit. Your wallet integration must build the canonical cross-venue group and collect the user signatures that submit-for-wallet expects. If the backend sees a stale price, wrong asset id, unexpected opt-in state, or any byte mismatch in the user-signed transactions, it rejects before the market-maker signs.

getOpenOrders(marketAppId, walletAddress?)

Gets open orders for a wallet on a specific market (from on-chain data).

const orders = await client.getOpenOrders(123456789);
for (const order of orders) {
  const side = order.side === 1 ? 'BUY' : 'SELL';
  const pos = order.position === 1 ? 'YES' : 'NO';
  console.log(`${side} ${pos} @ $${order.price / 1e6} - ${order.quantity / 1e6} shares`);
}

getWalletOrdersFromApi(walletAddress)

Gets all open orders for a wallet across every live market via the Alpha REST API. Requires apiKey. Paginates automatically.

const orders = await client.getWalletOrdersFromApi('ALGO_ADDRESS...');
for (const order of orders) {
  console.log(`Market ${order.marketAppId} | Escrow ${order.escrowAppId} | ${order.quantityFilled / 1e6} filled`);
}

Markets

Markets can be loaded on-chain (default, no API key) or via the REST API (richer data, requires API key).

getLiveMarkets() / getMarket(marketId)

Smart defaults - uses the API if apiKey is set, otherwise reads from chain.

const markets = await client.getLiveMarkets();
for (const m of markets) {
  console.log(`${m.title} - App ID: ${m.marketAppId}, source: ${m.source}`);
}

const market = await client.getMarket('12345'); // app ID string for on-chain, UUID for API

getMarketsOnChain() / getMarketOnChain(marketAppId)

Always reads from the blockchain. No API key needed. Returns core data: title, asset IDs, resolution time, fees.

const markets = await client.getMarketsOnChain();
const market = await client.getMarketOnChain(3012345678);

getLiveMarketsFromApi() / getMarketFromApi(marketId)

Always uses the REST API. Requires apiKey. Returns richer data: images, categories, volume, probabilities.

const markets = await client.getLiveMarketsFromApi();
const market = await client.getMarketFromApi('uuid-here');

getRewardMarkets()

Fetches all markets with USDC or ALPHA liquidity reward pools, including rewarded child outcomes. Requires apiKey. client.getRewardMarkets() returns Market[]. The standalone function supports the same read without a wallet or signer:

import { getRewardMarkets, type AlphaLpRewards } from '@alpha-arcade/sdk';

const markets = await getRewardMarkets({ apiKey: process.env.ALPHA_API_KEY });
for (const market of markets) {
  // Each executable child has its own pool. Do not multiply the parent pool.
  const outcomes = market.options?.length ? market.options : [market];
  for (const outcome of outcomes) {
    const alpha: AlphaLpRewards | undefined = outcome.alphaLpRewards;
    console.log(`${market.title} / ${outcome.title}`);
    console.log('USDC pool:', (outcome.totalRewards ?? 0) / 1e6);
    console.log('USDC pregame/day:', (outcome.totalPregameRewards ?? 0) / 1e6);
    console.log('ALPHA/day:', (alpha?.dailyMicro ?? 0) / 1e6);
    console.log('ALPHA pregame/day:', (alpha?.pregameDailyMicro ?? 0) / 1e6);
    console.log('ALPHA game pool:', (alpha?.inGameMicro ?? 0) / 1e6);
    if (alpha?.startsAt) console.log('ALPHA starts:', new Date(alpha.startsAt).toISOString());
  }
}

Token amounts and periods

  • USDC keeps the existing fields: totalRewards, totalPregameRewards, rewardsPaidOut, lastRewardAmount, and lastRewardTs. USDC amounts use six decimal places. totalRewards is a daily budget for non-sports markets and a fixed game pool for sports. totalPregameRewards is a pregame daily budget.
  • Optional alphaLpRewards uses the exported AlphaLpRewards type. dailyMicro is a non-sports daily budget; pregameDailyMicro is a pregame daily budget; inGameMicro is a fixed game pool. All amounts are micro-ALPHA: divide by 1,000,000 to display ALPHA. Never label them as dollars or add them to USDC totals.
  • startsAt is Unix milliseconds. Operator changes take effect at the next hour. An existing hourly block keeps its frozen budget. An absent, empty, or all-zero ALPHA configuration means no ALPHA campaign; a future start is scheduled, not active.
  • getLiveMarketsFromApi() and getMarketFromApi() also preserve this configuration. On-chain market discovery cannot provide these operator-managed LP budgets.

Scoring and opt-in

Both tokens use the existing order size, age, spread, and market liquidity rules. rewardsSpreadDistance, pregameRewardsSpreadDistance, and rewardsMinContracts still describe those rules. Both tokens pay hourly; transfers can arrive separately.

ALPHA adds an asset opt-in requirement. Each sample includes only opted-in, unfrozen ALPHA holdings in its ALPHA score denominator. Wallets without ALPHA opt-in still earn USDC. A later opt-in never earns ALPHA for earlier samples. Samples without ALPHA-eligible wallets leave that sample's allocation unspent. Failed holding checks also leave that sample's ALPHA allocation unspent. If a wallet opts out after earning, its ALPHA entitlement stays pending until it can receive ALPHA again.

Opt in to the ALPHA asset on the same wallet that provides liquidity. Buying or staking ALPHA is not required. Mainnet ALPHA is ASA 2726252423. For other deployments, read the asset ID from the backend's /get-lp-reward-config endpoint; do not assume the mainnet asset ID works on testnet. This read method does not sign an opt-in transaction.

Pool sizes are market budgets, not personal earnings estimates. A wallet's ALPHA share can differ from its USDC share because each token has a separate denominator. Do not estimate ALPHA earnings by multiplying the wallet's USDC share by the ALPHA pool.

See examples/get-reward-markets.ts for a runnable example that prints both tokens for each outcome.


WebSocket Streams

Real-time data streams via WebSocket. No API key or auth required. Replaces polling with push-based updates.

The SDK connects to the public platform websocket at wss://platform-wss.alphaarcade.com. The first subscription is sent in the connection query string, and any later subscribe or unsubscribe calls use the server's control-message envelope:

{
  "id": "request-id",
  "method": "SUBSCRIBE",
  "params": [
    { "stream": "get-orderbook", "slug": "will-btc-hit-100k" }
  ]
}

Supported public streams:

  • get-live-markets
  • get-market with slug
  • get-orderbook with slug
  • get-wallet-orders with wallet
import { AlphaWebSocket } from '@alpha-arcade/sdk';

// Node.js 22+ and browsers - native WebSocket, nothing extra needed
const ws = new AlphaWebSocket();

// Node.js < 22 - install `ws` and pass it in:
// npm install ws
import WebSocket from 'ws';
const ws = new AlphaWebSocket({ WebSocket });

// Subscribe to orderbook updates (~5s snapshots)
const unsub = ws.subscribeOrderbook('will-btc-hit-100k', (event) => {
  console.log('Orderbook:', event.orderbook);
});

// Unsubscribe when done
unsub();

// Close the connection
ws.close();

subscribeLiveMarkets(callback)

Receive incremental diffs whenever market probabilities change.

ws.subscribeLiveMarkets((event) => {
  console.log('Markets changed at', event.ts, event);
});

subscribeMarket(slug, callback)

Receive change events for a single market. Uses the market slug (not marketAppId) - see note on subscribeOrderbook below.

ws.subscribeMarket('will-btc-hit-100k', (event) => {
  console.log('Market update:', event);
});

subscribeOrderbook(slug, callback)

Receive full orderbook snapshots on every change (~5s interval). The payload matches getFullOrderbookFromApi(marketId).

Note: The WebSocket API uses market slugs (URL-friendly names like "will-btc-hit-100k"), not marketAppId numbers. You can get a market's slug from the slug field on Market objects returned by getLiveMarkets() or getMarket().

ws.subscribeOrderbook('will-btc-hit-100k', (event) => {
  // Top-level bids/asks use decimal prices (cents)
  // Nested yes/no use raw microunit prices with escrowAppId and owner
  for (const [appId, book] of Object.entries(event.orderbook)) {
    console.log(`App ${appId}: spread=${book.spread}`);
    console.log('  Bids:', book.bids);
    console.log('  Yes bids:', book.yes.bids);
  }
});

subscribeWalletOrders(wallet, callback)

Receive updates when orders for a wallet are created or modified.

ws.subscribeWalletOrders('MMU6X...', (event) => {
  console.log('Wallet orders changed:', event);
});

Unsubscribing

Each subscribe* method returns an unsubscribe function. Call it to stop receiving events for that stream:

const unsub = ws.subscribeOrderbook('my-market', (event) => { /* ... */ });

// Later, stop listening
unsub();

Control Methods

// List active subscriptions on this connection
const subs = await ws.listSubscriptions();

// Query server properties (`heartbeat` or `limits`)
const props = await ws.getProperty('heartbeat');

Configuration

import WebSocket from 'ws'; // Only needed on Node.js < 22

const ws = new AlphaWebSocket({
  WebSocket,                                  // Pass `ws` on Node.js < 22 (not needed in browsers or Node 22+)
  url: 'wss://custom-endpoint.example.com',   // Override default URL
  reconnect: true,                            // Auto-reconnect (default: true)
  maxReconnectAttempts: 10,                   // Give up after 10 retries (default: Infinity)
  heartbeatIntervalMs: 60_000,                // Ping interval in ms (default: 60000)
});

Connection Details

| Setting | Value | |---------|-------| | Heartbeat | 60s (auto-handled) | | Idle timeout | 180s | | Rate limit | 5 messages/sec/connection | | Reconnect | Exponential backoff (1s → 30s max) |

The client automatically responds to server pings, sends keepalive pings, and reconnects with exponential backoff on unexpected disconnects. All active subscriptions are restored after reconnect.


Combo RFQ

Competitive quotes for AND/OR combo purchases and combo cash-outs. Your API key is required. Alpha always quotes as the house; connected partner makers can compete over the same platform WebSocket used for public streams.

This is separate from single-market cross-venue RFQ (requestRfqQuote / submitRoutedOrder).

Buy a combo (taker)

  1. Request a quote with your combo tree and stake.
  2. Sign the returned user legs.
  3. Submit. If an external maker won, they get a short final look before the group lands on chain.
import { signComboRfqTransactions, type ComboRfqTree } from '@alpha-arcade/sdk';

const tree: ComboRfqTree = {
  groups: [
    {
      op: 'AND',
      legs: [
        // AA-native market legs (esports, tennis, MLB, futures, …).
        { source: 'aa', marketId: 'market-uuid-1', selection: 'yes' },
        { source: 'aa', marketId: 'market-uuid-2', selection: 'no' },
        // Same-game (SGP) legs are also supported — identified by graderId +
        // the BlazeBuilder sgp token (from the /parlay/sgp/markets feed):
        // {
        //   source: 'sgp',
        //   graderId: 'DraftKings#<eventId>#Moneyline#<Team>',
        //   sgp: '<blazebuilder-token>',
        //   league: 'mlb',
        //   eventId: '<eventId>',
        // },
      ],
    },
  ],
  connectors: [], // op between consecutive groups; length = groups.length - 1
};

const quote = await client.requestComboRfqQuote({
  tree,
  grossStakeMicro: 10_000_000, // $10
  userAddress: account.addr.toString(),
});

console.log(quote.makerKind);      // "alpha" | "external"
console.log(quote.pricedYesMicro); // YES price in microunits (500_000 = $0.50)

if (!quote.unsignedUserTxns?.length) {
  throw new Error('Quote missing user legs. Pass userAddress on the quote request.');
}

const signedTakerTxns = await signComboRfqTransactions(
  quote.unsignedUserTxns,
  signer, // same TransactionSigner you passed to AlphaClient
);

const result = await client.submitComboRfqWallet({
  quoteId: quote.quoteId,
  userAddress: account.addr.toString(),
  signedTakerTxns,
});

console.log('Combo filled:', result.txId);

Important taker notes:

  • Prices and stake use microunits (1_000_000 = $1.00).
  • After you sign, the fill is bound to the chosen maker. Decline or timeout means re-quote; there is no silent rematch.
  • Common submit errors: MAKER_DECLINED, MAKER_TIMEOUT, RFQ_EXPIRED, RFQ_DISABLED, NO_QUOTES.

Quote combos as a maker

Connected partner makers compete on both taker directions:

  • buy RFQ — the taker is buying combo YES. This is a reverse auction: the lowest YES price wins, and you only beat Alpha by quoting cheaper than its hidden house quote.
  • sell RFQ — the taker is cashing out an existing combo YES position. This is a forward auction: the highest YES buy price wins, and you only beat Alpha by bidding above alphaPriceMicro (broadcast on sell requests).

Alpha is always the backstop, so quoting is optional: you only win when you send the most competitive price and then confirm the fill in time.

Prerequisites

  • A partner API key (contact the Alpha Arcade team to be provisioned).
  • A funded Algorand maker wallet — makerAddress. It quotes and signs the fills, and is checked for capacity on every win: fund it with USDC (to post your side of fills) plus a little ALGO (fees + asset opt-ins). A maker that can't cover a fill is briefly auto-paused, not errored.
  • The maker wallet is independent of the API key's account — pass it explicitly.

Minimal maker loop

import algosdk from 'algosdk';
import { AlphaWebSocket } from '@alpha-arcade/sdk';

const maker = algosdk.mnemonicToSecretKey(process.env.TEST_MNEMONIC!);
const signer = algosdk.makeBasicAccountTransactionSigner(maker);
const MIN_EDGE_MICRO = 5_000;

const ws = new AlphaWebSocket({
  apiKey: process.env.ALPHA_API_KEY!,     // your partner key
  // On Node < 22: also pass WebSocket from the `ws` package.
});

const session = await ws.openComboRfqMakerSession({
  makerAddress: maker.addr.toString(),    // funded USDC + ALGO wallet
  signer,                                 // used by confirm() to sign maker legs
});

for await (const event of session) {
  if (event.type === 'combo_rfq_request') {
    // When fairPriceMicro is missing, Alpha could not live-price the combo —
    // you must price `event.tree` yourself (AA books / SGP feed). This snippet
    // has no local model, so it skips those RFQs.
    if (event.fairPriceMicro == null) continue;

    const side = event.side ?? 'buy';
    if (side === 'sell') {
      // SELL RFQ: taker cashes out YES, you BUY it. Higher bid wins. When Alpha
      // has an offer (`alphaPriceMicro`), you must beat it upward.
      const alpha = event.alphaPriceMicro;
      const priceMicro = event.fairPriceMicro - MIN_EDGE_MICRO;
      if (alpha != null && priceMicro <= alpha) continue;
      await session.quote(event, { priceMicro });
    } else {
      // BUY RFQ: taker buys YES, you lay NO. Lower YES price wins.
      const priceMicro = event.fairPriceMicro + MIN_EDGE_MICRO;
      await session.quote(event, { priceMicro });
    }
    continue;
  }

  if (event.type === 'combo_rfq_fill_request') {
    // You won the auction — sign your maker legs within confirmBy (~2s).
    if (Date.now() > event.confirmBy) {
      await session.decline(event, 'expired');
      continue;
    }
    await session.confirm(event);         // BUY signs NO-lay legs; SELL signs YES-buy legs
  }
}

Pricing the combo

Most combo_rfq_requests carry fairPriceMicro — the whole-combo FAIR probability (pre-edge, in microunits). This is your anchor: it's computable by any maker with the underlying odds (so it leaks none of Alpha's margin) and lets you quote without a round trip to price the tree.

When fairPriceMicro is missing, Alpha could not live-price the combo (typically a SELL / cash-out whose legs have no Polymarket order book). You must price tree yourself from AA books / your SGP feed. alphaPriceMicro is omitted in the same case — there is no house reserve to beat, so any valid bid can win.

  • On buy requests, quote a little above fair (fair + edge) and compete by going lower than other makers.
  • On sell requests, quote a little below fair (fair - edge) and compete by going higher than other makers. When alphaPriceMicro is present, you must also beat that reserve.

To price independently instead of anchoring, each leg tells you what it is:

  • AA legs — { source: 'aa', marketId, marketAppId, selection, description }. Read the on-chain order book directly by marketAppId, or call /combo/price.
  • SGP legs — { source: 'sgp', graderId, sgp, league, eventId, description }. Price from your own odds feed (OddsBlaze); same-game correlation uses the BlazeBuilder sgp token. graderId is Book#eventId#Market#Selection.
  • Tree shape — each tree.groups[] combines its legs by op (AND/OR); tree.connectors[] join consecutive groups (length = groups.length - 1). Every taker flow broadcasts RFQs — AND/OR combos, classic flat parlays, and same-game (SGP/mixed) tickets. Flat parlays arrive as a single ALL-must-win group (groups.length === 1, op: 'AND', no connectors), so don't assume multiple groups.
  • Sell-side extras — sell requests also carry quantityMicro, marketId, marketAppId, and yesAssetId so makers can size the cash-out and hedge it. alphaPriceMicro is Alpha's cash-out reserve when Alpha could live-price; it is omitted when you must price the tree yourself.
  • description on each leg is a plain-english label (e.g. "NFL Champion 2027 — Baltimore Ravens") for logging/UI.

Latency — you have ~1s to quote and ~2s to sign the fill. WebSocket delivery + your price + the round trip must fit the first window, so serious makers price from a local model/cache (or the fair anchor) rather than a live API probe, and run close to us-east-1.

Settlement is non-custodial and tamper-proof. You sign only your own maker legs, from your own wallet. On submit the server rebuilds the transaction group byte-for-byte from the pinned quote and rejects any mismatch, then settles the whole combo as one atomic group in USDC on Algorand.

  • On buy fills, you fund the NO-lay side of the trader's purchase — roughly (1e6 - priceMicro) per contract.
  • On sell fills, you fund the YES buy side of the trader's cash-out — roughly floor(quantity * priceMicro / 1e6) + fee.

Maker helpers on the session:

| Method | When | |--------|------| | quote(event, { priceMicro }) | Respond during the ~1s auction | | cancel(event) | Withdraw a quote you already sent | | confirm(event) | Win final look: sign maker legs (~2s) | | decline(event, reason?) | Refuse the fill |

Runnable example: examples/combo-rfq-maker.ts

ALPHA_API_KEY=... TEST_MNEMONIC=... npx tsx examples/combo-rfq-maker.ts

Perps (ALGO/USD LP-vault perpetual DEX)

Leveraged long/short ALGO/USD against an LP vault, priced by the Folks Feed Oracle. Fully on-chain (algod only). Prices are FFO raw units; sizes are µALGO base; collateral is µUSDC.

Restricted jurisdictions. Perps are not available in the US, Canada, UK, Australia, New Zealand, Singapore, Hong Kong, Japan, China, India, Southeast Asia, or sanctioned jurisdictions (Cuba, Iran, North Korea, Syria, Russia, Belarus). For that reason the SDK deliberately does not offer opening positions or depositing LP liquidity — those exist only in the geofenced app. The SDK covers exits and monitoring: closing positions, withdrawing liquidity, liquidations (keepers), and every market/position read — so no one's funds are ever trapped. Trading from a restricted jurisdiction violates the Alpha Arcade terms of service.

// Market snapshot (OI, skew, funding, params) and mark price
const market = await client.getPerpsMarket();
const mark = await client.getPerpsMark();

// Live position with PnL / liq price
const view = await client.getPerpPositionView();

// Close in full at the skew-adjusted exec price
await client.closePerpPosition({ limitPrice: Number(mark) - 1_000 });

// Withdraw LP liquidity from the vault
const shares = await client.getPerpsLpShares();
await client.perpsLpWithdraw({ shares: Number(shares), minAmountOutMicro: 0 });

| Method | Description | |--------|-------------| | closePerpPosition({ limitPrice }) | Close your position in full | | getPerpsMarket() / getPerpsMark() | Market snapshot / mark price | | getPerpPosition(wallet?) / getPerpPositionView(wallet?) | Raw / enriched position | | getAllPerpPositions() | Every open position (keeper tooling) | | liquidatePerp(trader) | Permissionless liquidation of an underwater trader | | perpsLpWithdraw({ shares, minAmountOutMicro }) / getPerpsLpShares(wallet?) | LP vault exits | | perpsPoke() / perpsReportOracleDown() | Keeper maintenance calls |


Utility Functions

These are exported for advanced users:

import { calculateFee, calculateMatchingOrders, getMarketGlobalState } from '@alpha-arcade/sdk';

// Fee calculation
const fee = calculateFee(1_000_000, 500_000, 70_000); // quantity, price, feeBase

// Read market state directly
const state = await getMarketGlobalState(algodClient, marketAppId);

Units & Conventions

| Concept | Unit | Example | |---------|------|---------| | Prices | Microunits (1M = $1.00) | 500_000 = $0.50 | | Quantities | Microunits (1M = 1 share) | 2_000_000 = 2 shares | | Position | 1 = Yes, 0 = No | position: 1 | | Side | 1 = Buy, 0 = Sell | Order side | | Fee base | Microunits | 70_000 = 7% |


Building a Trading Bot

import { AlphaClient } from '@alpha-arcade/sdk';
import algosdk from 'algosdk';

const setup = () => {
  const algodClient = new algosdk.Algodv2('', 'https://mainnet-api.algonode.cloud', 443);
  const indexerClient = new algosdk.Indexer('', 'https://mainnet-idx.algonode.cloud', 443);
  const account = algosdk.mnemonicToSecretKey(process.env.MNEMONIC!);

  return new AlphaClient({
    algodClient,
    indexerClient,
    signer: algosdk.makeBasicAccountTransactionSigner(account),
    activeAddress: account.addr.toString(),
    matcherAppId: 3078581851,
    usdcAssetId: 31566704,
  });
};

const run = async () => {
  const client = setup();
  const markets = await client.getLiveMarkets(); // Loads from chain, no API key needed

  for (const market of markets) {
    const book = await client.getOrderbook(market.marketAppId);

    // Simple strategy: buy Yes if best ask < $0.30
    const bestAsk = book.yes.asks.sort((a, b) => a.price - b.price)[0];
    if (bestAsk && bestAsk.price < 300_000) {
      console.log(`Buying Yes on "${market.title}" at $${bestAsk.price / 1e6}`);

      await client.createMarketOrder({
        marketAppId: market.marketAppId,
        position: 1,
        price: bestAsk.price,
        quantity: 1_000_000,
        isBuying: true,
        slippage: 20_000, // $0.02 slippage
      });
    }
  }
};

run().catch(console.error);

Network Configuration

Mainnet (default)

const algodClient = new algosdk.Algodv2('', 'https://mainnet-api.algonode.cloud', 443);
const indexerClient = new algosdk.Indexer('', 'https://mainnet-idx.algonode.cloud', 443);

Testnet

const algodClient = new algosdk.Algodv2('', 'https://testnet-api.algonode.cloud', 443);
const indexerClient = new algosdk.Indexer('', 'https://testnet-idx.algonode.cloud', 443);

Error Handling

All methods throw on failure. Wrap calls in try/catch:

try {
  const result = await client.createLimitOrder({ ... });
} catch (error) {
  if (error.message.includes('balance')) {
    console.error('Insufficient funds');
  } else {
    console.error('Order failed:', error.message);
  }
}

Disclaimer

This SDK, the included examples, and any related scripts are provided as tools for informational and development purposes only. Nothing in this repository constitutes financial, investment, legal, or tax advice.

You are solely responsible for reviewing, testing, and understanding any code, transactions, strategies, or automations you choose to run. Use of these tools is at your own risk. The authors and maintainers disclaim liability for any losses, damages, or other consequences arising from the use or misuse of this software.

Always do your own research before using these tools with real funds, production systems, or live markets.

License

MIT