Skip to Content
BackendFrontend Integration Guide

Frontend Integration Guide

Complete reference for integrating a frontend client with the BattlesBit GraphQL API. All examples use the actual schema — copy-paste ready.


0. Connection Setup

HTTP (Queries & Mutations)

SettingValue
Endpoint/graphql
MethodPOST
Content-Typeapplication/json
Auth headerAuthorization: Bearer {accessToken}

WebSocket (Subscriptions)

SettingValue
Endpoint/graphql (use ws:// or wss://)
Protocolgraphql-ws
AuthPass token in connectionParams
import { createClient } from "graphql-ws"; const wsClient = createClient({ url: "wss://api.battlesbit.com/graphql", connectionParams: { authorization: "Bearer " + accessToken, }, });

Reconnect Strategy

Use exponential backoff for both HTTP retries and WebSocket reconnections:

1s → 2s → 4s → 8s → 16s → 30s (cap)
const wsClient = createClient({ url: "wss://api.battlesbit.com/graphql", connectionParams: { authorization: "Bearer " + accessToken, }, retryAttempts: Infinity, retryWait: (retries) => { const delay = Math.min(1000 * Math.pow(2, retries), 30000); return new Promise((resolve) => setTimeout(resolve, delay)); }, });

1. Authentication

Request Email Verification Code

mutation RequestCode($email: String!) { requestEmailVerificationCode(email: $email) { expiresIn } }

Returns expiresIn (seconds until the code expires) — not a success boolean.

Login with Email (OTP)

mutation LoginEmail($email: String!, $code: String!) { loginWithEmail(email: $email, verificationCode: $code) { accessToken refreshToken expiresIn } }

Login with Password

mutation LoginPassword($email: String!, $password: String!) { loginWithPassword(email: $email, password: $password) { accessToken refreshToken expiresIn } }

Refresh Token

mutation Refresh($refreshToken: String!) { refreshToken(refreshToken: $refreshToken) { accessToken refreshToken expiresIn } }

Token Storage

TokenStorageReason
accessTokenIn-memory variableShort-lived, never persisted to disk
refreshTokenSecure storage (Keychain / EncryptedSharedPreferences)Long-lived, used to obtain new access tokens

Set up an HTTP interceptor that automatically calls refreshToken when the access token expires and retries the failed request.


2. Profile

Get Current Profile

query Profile { profile { id nickName avatar email level exp cup balance isEmailVerified isSuspended wallets { id currency balance locked } } }

Update Profile

mutation UpdateProfile($input: UpdateProfileInput!) { updateProfile(updateProfileInput: $input) { id nickName avatar } }

Variables:

{ "input": { "nickName": "TraderJoe", "avatar": "avatar_03" } }

3. Browse Challenges

Challenges (games) are templates that define the rules for a match room.

query BrowseChallenges($page: Pagination, $order: GameOrder, $filter: GameFilter) { gamesPage(paginateBy: $page, orderBy: $order, filterBy: $filter) { data { id title description entryFee rewardPrice currency duration minPlayers maxPlayers difficulty cover allowedSymbols maxLeverage allowedDirections maxDailyDrawdownPct maxTotalDrawdownPct profitTargetPct maxLossPerTradePct maxPositions startVBalance cup winExp loseExp minTrades maxTradesPerMinute } total } }

Variables:

{ "page": { "limit": 20, "offset": 0 }, "order": { "field": "CREATED_AT", "direction": "DESC" }, "filter": { "isActive": true } }

Key Fields

FieldDescription
entryFeeCost to join (deducted from wallet)
rewardPricePrize for the winner
currencyUSDT or SOL
durationMatch length (e.g. "30m", "1h")
difficultybeginner, intermediate, expert
allowedSymbolsTradable assets (empty = all)
maxLeverageMax leverage allowed (null = global default)
allowedDirectionsboth, long_only, short_only
maxDailyDrawdownPctDaily drawdown limit as %
maxTotalDrawdownPctTotal drawdown limit as %
profitTargetPctAuto-finish when profit target reached

4. Browse Match Rooms

Match rooms are instances of a challenge where players compete.

query BrowseMatches($page: Pagination, $order: GameMatchOrder, $filter: GameMatchFilter) { gameMatchesPage(paginateBy: $page, orderBy: $order, filterBy: $filter) { data { id status startTime endTime gameID tradingLocked winnerID game { title entryFee rewardPrice currency duration minPlayers maxPlayers } participants { id userID vBalance isDisqualified user { nickName avatar } } } total } }

Match Statuses

StatusMeaningJoinable?
waitingWaiting for playersYes
openTrading in progressNo
pendingLegacy / transitionalNo
closedMatch finished, results availableNo
canceledMatch canceled, fees refundedNo
archivedArchived matchNo

Display rooms with status: "waiting" as joinable.


5. Join a Match

mutation JoinMatch($matchID: ID!) { joinMatch(matchID: $matchID) { id userID vBalance gameMatchID currency } }

The entry fee is automatically deducted from the player’s wallet.

Error Handling

Error messageCauseUI suggestion
"insufficient balance"Wallet balance too lowShow deposit prompt
"match is full"maxPlayers reachedRefresh room list
"not in waiting status"Match already startedRefresh room list
"user already in match"Duplicate join attemptNavigate to match
"suspended"Account is suspendedShow suspension notice

6. Live Trading

Open a Position

mutation OpenPosition($input: CreateGameMatchPositionInput!) { createGameMatchPosition(createGameMatchPositionInput: $input) { id status baseToken entryPrice margin leverage side type size liquidationPrice stopLoss targetPoint } }

Variables:

{ "input": { "matchID": "match-uuid", "baseToken": "bitcoin", "entryPrice": 95000.50, "margin": 10.0, "leverage": 5, "side": "buy", "type": "market", "stopLoss": 90000.0, "targetPoint": 100000.0 } }

Important: The argument name is createGameMatchPositionInput: — not input:.

Position Input Fields

FieldTypeRequiredDescription
matchIDID!YesThe match to trade in
baseTokenString!YesAsset ID (e.g. "bitcoin")
entryPriceFloat!YesDesired entry price
marginFloat!YesAmount of virtual balance to use
leverageUint8!YesLeverage multiplier
sidebuy | sellYesTrade direction
typemarket | limitYesOrder type
stopLossFloatNoStop loss price
targetPointFloatNoTake profit price

Position Creation Errors

Error messageCause
"insufficient balance"Not enough vBalance
"max positions exceeded"Hit maxPositions limit
"symbol not allowed"baseToken not in game’s allowedSymbols
"leverage exceeds"Over game’s maxLeverage
"direction not allowed"Wrong side for long_only / short_only game
"trading is locked"Admin locked trading on this match
"disqualified"Participant was disqualified by risk rules
"max loss per trade"Margin exceeds maxLossPerTradePct of starting balance
"trading frequency"Exceeded maxTradesPerMinute

Update Stop Loss / Take Profit

mutation UpdatePosition($id: ID!, $input: UpdateGameMatchPositionInput!) { updateGameMatchPosition(id: $id, updateGameMatchPositionInput: $input) { id stopLoss targetPoint } }

Variables:

{ "id": "position-uuid", "input": { "stopLoss": 92000.0, "targetPoint": 105000.0 } }

Important: The argument name is updateGameMatchPositionInput: — not input:.

To clear a stop loss or target point, use the clear fields:

{ "id": "position-uuid", "input": { "clearStopLoss": true, "clearTargetPoint": true } }

Close a Position (Full or Partial)

mutation ClosePosition($id: ID!, $percentage: Float!) { closeGameMatchPosition(id: $id, percentage: $percentage) { id status profit closePrice closedAt } }
PercentageResult
100.0Full close
50.0Partial close — original position stays open with reduced size, a new closed position record is created
1.099.0Partial close at that percentage

Cancel a Pending Order

Cancel a limit order that has not yet been filled:

mutation CancelOrder($id: ID!) { cancelGameMatchPosition(id: $id) }

Returns Boolean!true on success.

Get Participant Equity

query Equity($matchID: ID!) { participantEquity(matchID: $matchID) { vBalance unrealizedPnL equity usedMargin freeMargin } }
FieldDescription
vBalanceCurrent virtual balance
unrealizedPnLPnL from open positions
equityvBalance + unrealizedPnL
usedMarginMargin locked in open positions
freeMarginequity - usedMargin

Poll this query or update locally when you receive PnL notifications.


7. Real-Time Subscriptions

Global Notifications

Receive account-level notifications (wallet updates, achievement unlocks, app announcements):

subscription GlobalNotifications { NotificationGlobal { target id message type action event data createdAt } }

Note: The subscription name is PascalCase: NotificationGlobal.

Match Notifications

Receive match-specific notifications (position PnL updates, position closed, match events):

subscription MatchNotifications($matchId: ID!) { NotificationMatch(matchId: $matchId) { target id message type action event data createdAt } }

Note: The parameter is matchId (lowercase d) — not matchID.

Notification Events

EventDescription
MATCH_CREATEDNew match room available
MATCH_CANCELEDMatch was canceled
MATCH_CLOSEDMatch finished
POSITION_UPDATEDPosition PnL changed
POSITION_CANCELEDLimit order canceled
POSITION_LIQUIDATEDPosition hit liquidation price
POSITION_CLOSEDPosition was closed
WALLET_BALANCE_UPDATEDWallet balance changed
APP_NOTIFICATIONGeneral app notification
ACHIEVEMENT_UNLOCKEDPlayer unlocked an achievement

Parsing the data Field

The data field is a JSON string. Parse it to extract PnL and other dynamic values:

const notification = subscriptionData.NotificationMatch; if (notification.event === "POSITION_UPDATED") { const data = JSON.parse(notification.data); // data.pnl — absolute PnL value // data["pnl%"] — PnL percentage updatePositionPnL(notification.id, data.pnl, data["pnl%"]); }

Live Asset Prices

Subscribe to real-time price updates for a single asset:

subscription AssetPrice($id: String!) { assetUpdated(id: $id) { id priceUSD } }

8. Post-Match Results

Match Summary (Player-Facing)

Use matchSummary for player-facing post-match stats:

query MatchSummary($matchID: ID!) { matchSummary(matchID: $matchID) { matchID totalTrades totalParticipants bestTradePnL worstTradePnL maxDrawdown duration winnerID } }

Note: gameMatch(id) requires the read:gameMatches permission (admin only). Use matchSummary for regular players.

Access Match Data via Participants

Since gameMatch is admin-only, players can still access match details through the gameMatchesPage query (which is public) to see participants and final standings:

query MatchResults($filter: GameMatchFilter) { gameMatchesPage( paginateBy: { limit: 1 } filterBy: $filter ) { data { id status winnerID game { title rewardPrice currency } participants { userID vBalance peakVBalance isDisqualified disqualificationReason user { nickName avatar } } } } }

9. Wallet Management

Create a Wallet

First-time users need to create a wallet:

mutation CreateWallet($input: CreateWalletInput!) { createWallet(createWalletInput: $input) { id currency balance locked address } }

Variables:

{ "input": { "currency": "USDT" } }

Important: The argument name is createWalletInput: — not input:.

Supported currencies: USDT, SOL.

Check Balance

Wallets are available on the profile query:

query MyWallets { profile { wallets { id currency balance locked address } } }

Note: walletsPage requires the read:wallets permission (admin only). Use profile.wallets for regular players.

Request Withdrawal

mutation Withdraw($input: CreateWithdrawalInput!) { createWithdrawRequest(createWithdrawalInput: $input) { id createdAt } }

Variables:

{ "input": { "amount": 50000, "currency": "USDT", "address": "0x..." } }

Withdrawals are created as pending transactions and require admin approval.


10. Market Data

List All Tradable Assets

query AllAssets { assets { id name symbol priceUSD changePercent24Hr marketCapUSD volumeUSD24Hr iconUrl rank } }

Get a Single Asset

query SingleAsset($id: String!) { asset(id: $id) { id name symbol priceUSD changePercent24Hr supply maxSupply marketCapUSD volumeUSD24Hr vwap24Hr iconUrl rank } }

Subscribe to Live Prices

Single asset:

subscription AssetPrice($id: String!) { assetUpdated(id: $id) { id priceUSD } }

All assets (bulk updates):

subscription AllAssetPrices { assetsUpdated { id priceUSD } }

Favorite Assets

# Add to favorites mutation { createFavoriteAsset(assetID: "bitcoin") } # Remove from favorites mutation { deleteFavoriteAsset(assetID: "bitcoin") }

Both return Boolean!.


11. Social Features

Send a Friend Request

mutation SendRequest($friendID: ID!) { sendFriendRequest(friendID: $friendID) { id status userID friendID } }

Accept a Friend Request

mutation AcceptRequest($requestID: ID!) { acceptFriendRequest(requestID: $requestID) { id status } }

Reject a Friend Request

mutation RejectRequest($requestID: ID!) { rejectFriendRequest(requestID: $requestID) { id status } }

Remove a Friend

mutation RemoveFriend($friendshipID: ID!) { removeFriend(friendshipID: $friendshipID) }

Returns Boolean!.

List Friends

query MyFriends($first: Int) { friends(first: $first) { edges { node { id userID friendID status user { nickName avatar } friend { nickName avatar } } } } }

List Pending Friend Requests

query PendingRequests($first: Int) { friendRequests(first: $first) { edges { node { id userID friendID status user { nickName avatar } } } } }

12. Notifications

Notification Center (Persistent)

query MyNotifications($first: Int) { myNotifications(first: $first) { edges { node { id message event type target targetID isRead createdAt } } } }
FieldDescription
targetWhat the notification is about (e.g. "MATCH", "POSITION", "WALLET")
targetIDUUID of the related entity
isReadWhether the user has seen it
eventEvent type string for icon/color mapping
typeINFO, WARNING, or ERROR

Unread Count (Badge)

query { unreadNotificationCount }

Mark as Read

# Single notification mutation MarkRead($id: ID!) { markNotificationRead(id: $id) { id isRead } } # All notifications mutation { markAllNotificationsRead }

13. Leaderboard

query Leaderboard($page: Pagination, $period: String) { leaderboard(paginateBy: $page, period: $period) { data { userID nickName avatar cup exp level } total } }

Period Values

ValueMeaning
nullAll-time leaderboard
"weekly"Current week
"monthly"Current month

Variables example:

{ "page": { "limit": 50, "offset": 0 }, "period": "weekly" }

14. Store

Browse Store Items

query StoreItems { storeItems(first: 50, filterBy: { isActive: true }) { edges { node { id title description price paymentType currency isActive consumable { id name description avatar type value } } } } }

Get a Single Store Item

query StoreItem($id: ID!) { storeItem(id: $id) { id title description price paymentType currency consumable { id name avatar type value } } }

Purchase a Store Item

mutation BuyItem($id: ID!) { requestPurchaseStoreItem(id: $id) }

Returns Boolean!.

Browse Store Packages

query StorePackages { storePackages(first: 50, filterBy: { isActive: true }) { edges { node { id title description avatar price paymentType currency isActive items { id quantity consumable { id name avatar type value } } } } } }

Get a Single Store Package

query StorePackage($id: ID!) { storePackage(id: $id) { id title description avatar price paymentType currency items { id quantity consumable { id name avatar type value } } } }

Purchase a Store Package

mutation BuyPackage($id: ID!) { requestPurchaseStorePackage(id: $id) }

Returns Boolean!.


15. Achievements

Browse Achievements

query AllAchievements { achievements(first: 100) { edges { node { id key name description avatar isActive isHidden expiresAt rules { id name field target comparator weight } rewards { id name avatar type value } } } } }

Get a Single Achievement

query Achievement($id: ID!) { achievement(id: $id) { id key name description avatar isActive isHidden rules { id name field target comparator weight } rewards { id name avatar type value } } }

Set Favorite Achievements (Profile Showcase)

mutation SetFavorites($favorites: [String!]!) { updateProfileFavoriteAchievement(favoriteAchievement: $favorites) { id achievementID } }

Variables:

{ "favorites": ["achievement-key-1", "achievement-key-2", "achievement-key-3"] }

16. Push Notifications

Register Device Token

mutation RegisterDevice($input: CreateFCMTokenInput!) { createFCMToken(createFCMTokenInput: $input) { id token platform } }

Variables:

{ "input": { "token": "fcm-device-token-here", "platform": "ios" } }

Important: The argument name is createFCMTokenInput: — not input:.

Supported Platforms

ValuePlatform
iosApple iOS
androidAndroid
webWeb browser (PWA)

Register the FCM token after login and whenever it refreshes.


Full Game Flow Summary

┌──────────────────────────────────────────────────────────────────────┐ │ AUTHENTICATION │ │ requestEmailVerificationCode ──► loginWithEmail ──► Token │ │ OR loginWithPassword ──► Token │ │ Store accessToken (memory) + refreshToken (secure storage) │ └──────────────────────┬───────────────────────────────────────────────┘ ┌──────────────┼──────────────┐ ▼ ▼ ▼ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ │ profile │ │ createWallet │ │ createFCMToken│ │ (setup) │ │ (first time) │ │ (push notif) │ └───────────┘ └──────────────┘ └───────────────┘ ┌──────────────────────────────────────────────────────────────────────┐ │ BROWSE & DISCOVER │ │ gamesPage ──► See available challenges (rules, fees, prizes) │ │ gameMatchesPage ──► See match rooms (status: waiting = joinable) │ │ assets ──► Browse tradable assets + assetUpdated subscription │ │ leaderboard ──► See top players │ │ storeItems / storePackages ──► Browse the store │ └──────────────────────┬──────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────┐ │ JOIN MATCH │ │ joinMatch(matchID) ──► GameMatchParticipant │ │ Entry fee auto-deducted from wallet │ │ Subscribe: NotificationMatch(matchId) for live updates │ └──────────────────────┬──────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────┐ │ LIVE TRADING │ │ │ │ createGameMatchPosition ──► Open position (market or limit) │ │ updateGameMatchPosition ──► Adjust SL/TP │ │ closeGameMatchPosition ──► Full or partial close │ │ cancelGameMatchPosition ──► Cancel pending limit order │ │ participantEquity ──► Check vBalance, PnL, margin │ │ │ │ Subscriptions: │ │ NotificationMatch ──► PnL updates, position events │ │ assetUpdated ──► Live price feed for charts │ └──────────────────────┬──────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────┐ │ POST-MATCH │ │ matchSummary(matchID) ──► Stats, winner, trade counts │ │ gameMatchesPage (filter by ID) ──► Participants, final balances │ │ NotificationGlobal ──► Achievement unlocked, wallet updated │ └──────────────────────────────────────────────────────────────────────┘

Quick Reference: Argument Names

A common source of bugs is using input: instead of the full argument name. Here is the correct mapping:

MutationArgument Name
createGameMatchPositioncreateGameMatchPositionInput:
updateGameMatchPositionupdateGameMatchPositionInput:
updateProfileupdateProfileInput:
createWalletcreateWalletInput:
createWithdrawRequestcreateWithdrawalInput:
createFCMTokencreateFCMTokenInput:

Never use input: as a shorthand — the server will reject the request.

Last updated on