# Introduction

An overview of the V2 exchange

<div align="left"><figure><img src="/files/C9Gpz2WKAKNOacuDemRw" alt="" width="507"><figcaption></figcaption></figure></div>

## What is GammaSwap?

GammaSwap is a high performance prediction market focused on financial assets. Users can predict whether cryptocurrencies like BTC and ETH will be up or down over short timeframes (5 minutes, 15 minutes, etc) while maintaining custody of their assets. It was engineered from the bottom up to be fast, simple and efficient.

### Key Aspects of GammaSwap

* **Non-Custodial.** You maintain full control of your assets.
* **Margin Based.** Simple and capital efficient.
* **Instant Resolutions.** No human intervention needed.
* **Off-Chain Matching.** Low latency performance.
* **On-Chain Settlement.** All matched trades settle on-chain and are public.

### High Level Exchange Mechanics

The exchange matches orders off-chain and sequences the transactions that ultimately settle on [Base](https://www.base.org/), a layer 2 blockchain created by [Coinbase](https://www.coinbase.com/).

Blockchain interactions are abstracted away for the user. They do not need gas to deposit or submit orders. Instead, the exchange pays gas on their behalf.

After signing to deposit, all trades can be performed without wallet pop ups mimicking a traditional trading experience.


# For Crypto Beginners

### Overview

GammaSwap provides the best of both worlds. The experience feels fast and simple like using a traditional broker. However, all transactions settle on the blockchain meaning trades are irreversible once they settle and you maintain custody of your assets.

### Benefits

The benefits of the blockchain are that you maintain full custody of your own assets. Our exchange cannot withdraw funds on your behalf or re-hypothecate your assets. It is impossible given the cryptographic protections of the technology.\
\
You don't need to know how wallets work or manage your own seed phrase to benefit from these advantages. You can login via Single Sign On (SSO) using your email or a social login. A third party entity, [Magic Labs](https://magic.link/), will create the wallet for you and help manage it. They have their own encrypted security set up to ensure you are the only one who has control of your funds.&#x20;

### Getting Funds

You can use an exchange like Binance or Coinbase to deposit USDC, a tokenized crypto representing US Dollars launched by [Circle](https://www.circle.com/), or use a credit card directly via our third party integration which converts your dollars for you into USDC.


# Technical Overview

### Architecture

GammaSwap utilizes an architecture similar to how an L2 operates with an L1. The protocol matches orders in an off-chain private ledger where matched orders are aggregated to be sent in batch to the public settlement chain for finality. This two step system leverages off-chain matching for speed and on chain settlement for security and transparency.\
\
Order batches are submitted to Base at predetermined intervals ranging from 10 seconds to a few minutes, depending on Base network congestion. Orders are filled based on time-price priority.&#x20;

Given V2 is a margin based exchange, a user can trade against themselves to close or flip an order's direction. This architecture also opens up cross margining opportunities for higher capital efficiency in the future.

### Advantages

Current latency expectations are around 100-200ms round trip, which if maintained in production, would make GammaSwap V2 the fastest on-chain prediction market.&#x20;

Most other on-chain prediction markets use a conditional token framework (CTF) where on-chain tokens are minted and burned each epoch. This decreases available liquidity since there's a dependency on minting new liquidity before trading is possible. The margin based architecture allows anyone with USDC in the platform to supply liquidity to the market at any time.

### Proximity

The GammaSwap matching engine is located in Singapore which is in the same region as Bybit and near major exchanges like Binance and Hyperliquid in Tokyo. This is an advantage for market makers by reducing latency and improving reaction time.


# Core Contributors

GammaSwap core contributors met at a hackathon in mid 2022, Activate x Wormhole. Since then, the team has been fully committed to building GammaSwap with various products that give users convexity onchain. The team also raised a seed round in early 2023.

Core contributors have past experience at leading companies like UBS, Meta and Figment.


# Onboarding

{% content-ref url="/pages/tmqVEwKUm3zkA1EekH4i" %}
[Getting Started with Trading](/onboarding/getting-started-with-trading)
{% endcontent-ref %}

{% content-ref url="/pages/TNXYmygYen3H3pHh04pg" %}
[Mobile Version](/onboarding/mobile-version)
{% endcontent-ref %}

{% content-ref url="/pages/55iPlseSSWXIQsrf78rj" %}
[Paper Trading / Demo Account](/onboarding/paper-trading-demo-account)
{% endcontent-ref %}


# Getting Started with Trading

To start trading you have two options:

1. Use an existing Externally Owned Account (EOA) wallet like Rabby, Metamask, etc
2. Using a social login (Gmail, Telegram, Twitter) to use a wallet managed by Magic Labs. This is simpler for those who don't have experience on chain using crypto.

### Externally Owned Account (EOA)

Connect to app.gammaswap.com by selecting "Connect Wallet" and choosing the EOA based on your preferred wallet.

1. Options include Rabby, Metamask, Coinbase Wallet. If you don't already have your own wallet, we recommend using [Rabby](https://rabby.io/).&#x20;
   1. After downloading a wallet extension for your browser, create a new wallet.
   2. Your wallet has a secret recovery phrase. Anyone with access to your private key or seed phrase can access your funds. Do not share these with anyone. Best practice is to record these and store them in a safe physical location.
2. Deposit USDC from a centralized exchange. You can deposit directly from exchanges like Coinbase and Binance by approving [Glide](https://buildwithglide.com/) as an approved third party in your exchange account. You can then send funds without having to copy the wallet address.
3. If you don't want to approve a third party or have funds onchain, you can deposit from anywhere by selecting the right token and chain (USDC on Base) then copying the transfer wallet address. Be careful, if you send the incorrect token or to an incorrect chain you could lose your funds.&#x20;
4. Once you've connected, select "Enable Trading" which handles your wallet approvals. This enables you to trade with minimal wallet interactions.

### Social Login

Connect to app.gammaswap.com by logging in via a social login (Gmail, Telegram, Twitter) after clicking the "Connect Wallet" button. Follow the instructions which may be an email confirmation, code on your telegram app, etc depending on the sign in method.

Once you sign in, your wallet will be created for you by [Magic Labs](https://magic.link/). Magic Labs uses passwordless and email-based authentication to manage non-custodial embedded wallets. To fund your Magic Wallet, either transfer USDC using the provided wallet address or use the embedded cash to crypto transfer options. You do not need to enable trading once logged in.

### How to Trade

Prediction markets allow you to choose which direction you think an asset will go, "Up" or "Down", by the market expiration. If you choose "Up", you win if the current price is equal to or greater than the price to beat by expiration. Alternatively, if you choose "Down", you win if the price is less than the price to beat by expiration. You use USDC as margin instead of buying the token itself.

*Note: many markets implement a Time Weighted Average Price (TWAP) to protect users from market manipulation and provide an improved trading experience. Please check the market description before trading.*

To open a trade:

1. Navigate to the trade page on the application.
2. In the trade tool on the right side, choose whether you want the size of your order to be denominated in contracts or in the USDC margin requirement.  Note that when choosing contracts, the contracts also represent the notional size of your oder in USDC.
3. Choose your side, "Buy/Up" or "Sell/Down",  and the amount you would like to trade. After entering an amount, you can see your average cost and potential payout along with other relevant trade details.
4. Once you are ready, click . You can skip the second click here by using the one click trading option.
5. You will see your position in the positions table underneath the chart. You can close your position at any time or wait until settlement to claim your winnings.

You claim any wins from previous trades in the position history tab below the chart on the trade page or at the bottom of the portfolio page.&#x20;


# Mobile Version

There is currently no iOS or Android mobile app. Instead you can either trade directly from a mobile browser or use a Progressive Web App (PWA). Using a PWA is highly recommended.

1. Navigate to app.gammaswap.com on mobile using the Chrome web browser.
2. On your phone, click "Connect Wallet". Login with your social login or your EOA wallet. Make sure if you login with EOA that you have the relevant wallet downloaded as an app on your phone as well.
3. If you want to use just the browser, your set up is finished.
4. If you would like to use a PWA, which is recommended for a better experience, click the menu (three dots) in the top-right corner of the browser.&#x20;
5. Select save and share or more tools from the drop-down list. Click install page as app.&#x20;
6. Name the app and click install or create to add the PWA to your mobile device.


# Paper Trading / Demo Account

Right now, GammaSwap has not launched yet in production so all trades will be executed on testnet. Testnet is a blockchain that doesn't use real money. Instead, USDC tokens for magin trading can be generated for free.

### Faucet

To get access to test/paper USDC, you will need to request tokens from the faucet in the footer. The faucet is also available in the side bar after you have successfully connected your wallet on mobile. Just click on the profile icon in the top right corner right next to the green deposit icon. The faucet will be under the account balance section.

There is also a faucet for ETH which you will need to deposit into GammaSwap on testnet. This is not required when depositing real USDC in production.

### Deposit

After requesting USDC, click the green button in the top right and select the deposit tab. Enter how much USDC you would like to trade with and click "Deposit". After the deposit clears, which could take up to 5-10 minutes depending on network congestion, you will be able to trade.&#x20;

### Enable trading

Make sure you click "Enable Trading" if you are using an EOA wallet. If you are using a social login, you can begin trading immediately.


# Markets and Trading


# Oracle & Settlement

### Oracle

Crypto markets resolve based on prices from [Pyth](https://www.pyth.network/). Pyth aggregates data from over 120 financial institutions directly reducing the chance of market manipulation and lowering latency.&#x20;

If you would like to subscribe to Pyth directly for your own trading, or verify prices on the terminal, you can do so [here](https://app.pyth.com/explore).

### Settlement

When a market expires, the market goes through a settlement process where it calculates the Time Weighed Average Price (TWAP) based on the average price of the last 60 seconds on 15 minute markets. The TWAP is used to prevent market manipulation and provide a better trading experience.\
\
There is no human intervention in settlements or disputes. Epochs are automatically processed based on the Pyth oracle price feed.


# Market Making

Market making has certain advantages including fee rebates and lower latency.

Anyone is welcome to market make.

The rebate structure and latency advantage may be subject to change in the future depending on factors such as asset type, duration or liquidity.

### Rebates

Makers earn a 30% rebate from taker fees on filled orders for the following markets:

* BTC 15 minute market
* ETH 15 minute market

The fee is rebated as soon as the fill occurs.

### Latency Advantage

There is a planned taker delay which allows makers time to react to incoming orders, creating deeper liquidity. The taker latency will likely decrease as more liquidity is built.

## Co-Location

For minimal latency as an automated trader or market maker, you can co-locate your server in Singapore with the GammaSwap matching engine. This is the same region as Bybit. It is also near major exchanges like Binance and Hyperliquid who have matching engines in Tokyo.

### Maker Support

If you have technical integration questions, it's recommended to start in the Discord channel for #api-traders: [discord.gg/gammaswaplabs](https://discord.com/invite/gammaswaplabs)


# Market Parameters

### Epoch

An epoch is a fixed, repeating time window used to organize trading, finalize outcomes, and distribute payouts. For example, in 15 minute markets, each epoch lasts 15 minutes. At the end of the 15 minute epoch the resolution price is determined, the market is settled, and payouts are distributed.

### Price to Beat

The price to beat is a rolling strike price based on the last price from the previous epoch.

### Current Price

The current price is the price of the underlying spot asset supplied by the Oracle, in this case Pyth.

### Resolution Price

The resolution price is the price of the underlying spot asset used to settle the market. In 15 minute markets, a price snapshot is taken every second in the last minute. The average of the 60 snapshots, one for each second, is used to determine settlement. If the average is above the price to beat, Up wins.  If the average is below the price to beat, Down wins.

### Contract Price

There is one contract for Up and Down. Up is long the contract price. Down is short the contract price. It resolves to $1 if Up wins and 0 if Down wins. The contract price is the midpoint of the best bid and ask on the order book. The minimum acceptable contract price in the exchange is $0.001 and the maximum acceptable contract price is $0.999. This means a bid can't be less than 0.1 cent and an offer can't be greater than 99.9 cents.

### Tick Size

The minimum price change in the exchange. The tick size for short duration markets is 0.1 cents and 1 cent or higher for longer duration markets. This means in short duration markets, prices can only be quoted at the nearest tenth of a cent.

### Margin

The amount of USD posted as collateral to open and maintain open a position. All open positions and resting limit orders are fully collateralized. Opposite side orders do not offset each other's margin requirement. This prevents the margin exchange from having to liquidate orders and ensure full payout to the winner of the contract. Since all open positions are fully collateralized, there are no margin calls.&#x20;

The formula for calculation the margin requirement of an order to open a position is as follows

*margin for long = # of Contracts \* Contract Price*

*margin for short = # of Contracts \* (1 - Contract Price)*

Therefore, losses for a position are capped at margin and % returns are measured against margin.

To post a resting limit order a user needs to have enough USD in his wallet to post margin. However, marketable orders that offset an existing position (i.e. a an order that reduces a position) uses the existing position to offset the margin requirement.


# Contracts

### Contract Explanation

The size of an order is measured in contracts and all contracts are denominated in USD. Therefore, 1 contract represents a notional value of $1, and 100 contracts represents a notional value of $100. This means that if 1 contract wins it resolves to $1 and if 100 contracts win they resolve to $100. Alternatively, if 1 or 100 contracts lose, they all resolve to $0.

### Outcomes

Buying the contract or selecting up is going long the contract. The contract wins if the underlying asset's price is at or above the price to beat by expiration. The contract loses if the asset's price is below the price to beat by expiration.

Selling the contract or selecting down is going short the contract. The contract wins if the underlying asset's price is below the price to beat by expiration. The contract loses if the underlying asset's price is at or above the price to beat by expiration.

When the market epoch ends the resolution price (price that determines if the contract won or lost) is calculated based on the underlying asset's price given by the oracle. Keep in mind that the resolution prices are based on a Time Weighted Average Price (TWAP).

### Comparison to Futures

It is similar to longing or shorting tokens on margin in a perpetual futures exchange. There are no separate contracts for up and down. There are no liquidations or funding fees, unlike perpetual futures.


# Margin

## Description

Margin is the amount of USD posted as collateral to open and maintain open a position. All open positions and resting limit orders are fully collateralized. Opposite side orders do not offset each other's margin requirement. This prevents the margin exchange from having to liquidate orders and ensure full payout to the winner of the contract as one trader's profit is an opposing trader's margin. Therefore, losses for a position are capped at margin and percent returns are measured against margin. Since all open positions are fully collateralized, there are no margin calls.

## Formula

The formula for calculation of the margin requirement of an order is as follows\
$$longMargin = round Up (C \* P)$$\
$$shortMargin = round Up (C \* (1 - P))$$

Where

*C = number of contracts*\
*P = average price of position or limit price of resting limit order*\
*roundUp = margin is rounded up to the nearest centicent*

## Reduce Only Orders

To post a resting limit order a user needs to have enough USD in his wallet to post margin. However, marketable orders that offset an existing position (i.e. an order that immediately reduces a position) use the existing position to offset the margin requirement.

## Minimum Margin

The minimum margin requirement for submitting an order is 0.1 cent. Orders that require a margin amount below this level will be rejected.


# Order Book

The order book works similar to Central Limit Order Books (CLOBs) found on many centralized exchanges, except of course that settlement is on-chain. Orders are matched in price-time priority with a tick size of 0.1 cent. Cancel orders have priority.

There is one order book per asset, epoch, and strike price (price to beat).&#x20;

### Different than other prediction markets

Unlike most on-chain prediction markets, GammaSwap does not use the Conditional Token Framework (CTF). The order book supports margin trades and tracks one contract, which people can either long or short. This reduces complexity for both takers and makers. More in [Contracts](/markets-and-trading/contracts) here.


# Order Types

Here are currently available order types:

* **Market:** An order that executes immediately at the market price based on available liquidity.&#x20;
* **Limit:** An order that executes at the selected limit price for a select number of contracts.
* **Cancel and Replace:** Cancel and replace an order in the same response. Side of replacement order must match cancelled order's side (e.g. only a long order can replace a long order). If the cancel fails, the entire order fails. If the cancel succeeds but the replacement fails (e.g. margin, ALO crossing spread, etc.), the cancel is honored. Cancel replacing an order results in a new orderId and therefore resets the client's place in the queue.

Here are currently available order options:

* **FOK (Fill or Kill):** Execute the entire order immediately or cancel it completely.
* **IOC (Immediate or Cancel):** Execute whatever portion is available immediately and cancel the unfilled remainder.
* **GTC (Good till Cancel):** Order is live until cancelled
* **ALO (Add Liquidity Only):** Order is cancelled if upon arrival it crosses the spread. Also known as post-only.


# Fees

There are only trading fees. There are no fees to claim a payout if a contract wins.

### Taker Fees

The fee we charge to takers follows a similar structure to other prediction markets.

We use the following formula \
\
$$fees = round Up (C \* feeConstant \* P \* (1 - P))$$

where \
\
*C = the number of contracts being traded*\
*P = the price of contracts in $*\
*feeConsant = the fee multiplier for the market*

roundUp = rounds up to the nearest centicent

Currently, the *feeConstan*t for all markets is 0.07.

| Contract Price | Price for 100 Contracts | Fee for 100 Contracts |
| -------------- | ----------------------- | --------------------- |
| 99¢            | $99.00                  | $0.07                 |
| 95¢            | $95.00                  | $0.34                 |
| 90¢            | $90.00                  | $0.63                 |
| 85¢            | $85.00                  | $0.90                 |
| 80¢            | $80.00                  | $1.12                 |
| 75¢            | $75.00                  | $1.32                 |
| 70¢            | $70.00                  | $1.47                 |
| 65¢            | $65.00                  | $1.60                 |
| 60¢            | $60.00                  | $1.68                 |
| 55¢            | $55.00                  | $1.74                 |
| 50¢            | $50.00                  | $1.75                 |
| 45¢            | $45.00                  | $1.74                 |
| 40¢            | $40.00                  | $1.68                 |
| 35¢            | $35.00                  | $1.60                 |
| 30¢            | $30.00                  | $1.47                 |
| 25¢            | $25.00                  | $1.32                 |
| 20¢            | $20.00                  | $1.12                 |
| 15¢            | $15.00                  | $0.90                 |
| 10¢            | $10.00                  | $0.63                 |
| 5¢             | $5.00                   | $0.34                 |
| 1¢             | $1.00                   | $0.07                 |

### Maker Fees

There are currently no maker fees. There are rebates for market making, see the [Market Making](/markets-and-trading/market-making) section for more information.


# Claiming

After a market has settled, you can claim your winnings in the position history tab found in the trade page or the portfolio page on the bottom.&#x20;

You have to submit a claim tranasction for each win. If you lost, there will be nothing to claim.


# Risks

### Smart Contract Risk

GammaSwap uses a smart contract that handles deposits, withdrawals and transfers of funds between accounts at settlement. &#x20;

The smart contract only handles user intent verification and the transfer of funds from one user to another as a result of deposits, withdrawals, trades, and payouts at contract settlement. It does not perform the matching of orders. This vastly reduces the surface area of an exploit.

Regardless, bugs or vulnerabilities in the smart contracts could result in the loss of user funds.

### Liquidity Risk

GammaSwap V2 is a new protocol and each epoch the order books have to repopulate. At times, there may be low liquidity especially during odd hours or on the weekends.&#x20;

Be aware of market slippage which could lead to a bad trading experience or substantial losses. Holding to resolution can help you avoid slippage.

### Rollback Risk

GammaSwap maintains state between the order matching engine ledger and the Base blockchain. Transactions are settled onchain as fast as 15 seconds or up to a few minutes depending on Base network congestion. When transactions settle on Base chain, they are considered finalized.

If there is a mismatch in state due to any number of reasons (base chain down, error in matching engine), trades will be rolled back to the last moment where the state was matched correctly. Any transactions past that point will be reverted. Rollbacks should not happen frequently.

### Oracle Risk

All markets currently use Pyth as the oracle source. If Pyth is compromised or manipulated, the resolution price could be affected.&#x20;

Pyth is a battletested, robust service so the probability of this is low. Pyth mitigates manipulations by taking spot bid ask spreads from a list of 120 data publishers, large market making firms and institutions, instead of directly relying on exchange prices.

We implemented a Time Weighted Average Price (TWAP) to make oracle manipulation more difficult and provide a better trading experience.

*There may be other risks not outlined here. This is not an exhaustive set of risks.*


# Audits

There has not been an audit of GammaSwap V2 yet.

**Here are the audits for V1:**

[12/19/22 | V1 Core, Strategies & Periphery | Halborn ](https://github.com/HalbornSecurity/PublicReports/blob/master/Solidity%20Smart%20Contract%20Audits/GammaSwap_Labs_Core_Strategies_and_Periphery_Smart_Contract_Security_Audit_Report_Halborn_Final.pdf)

[3/14/23 | V1 Balancer Implementation | Zellic](https://drive.google.com/file/d/1X5vfXZAP4_j7IQvVTqaBL0q-TSoxfqrn/view?usp=sharing)

[3/27/23 | V1 Core and Implementations | Zellic](https://drive.google.com/file/d/1I3g6ofwArsVXccxTms6iOKRXgLOVMyOk/view?usp=sharing)

[6/5/23 | V1 Core and Implementations | Zellic](https://drive.google.com/file/d/1fUcZGDAyaTiMWBIGmsADdKVnYqkYE2RL/view?usp=sharing)

[8/24/23 | V1 Core and Implementations | Zellic](https://drive.google.com/file/d/1foSuKDck8kJiF1dv1fDSFtaUMrctqV62/view?usp=sharing)

[9/21/23 | Staking | Zellic](https://drive.google.com/file/d/1e8AiZasbViKVDsiwSHyQklG2OyOon2lF/view?usp=sharing)

[11/7/23 | DeltaSwap | Zellic](https://drive.google.com/file/d/1QfEbGNTNHkRRjZkeSXuyTJfF9dvMlucF/view?usp=sharing)

[1/28/25 | V1 Yield Tokens | Pashov Audit Group](https://drive.google.com/file/d/112Q7aMjvHM7Qip6YHqfCmdFfj2edi82d/view?usp=sharing)


# Referrals

Referrals are not live yet. When live, anyone can sign up for a referral code. \
\
Users of a referral code get a 4% discount. The owner of the referral link earns 10% of the fees generated by the users referred.


# Points

There is a new points program for GammaSwap V2 that will commence at the start of the testnet trading competition.

You earn points for trading. The points may depend on assets traded, duration, role (maker vs taker) or volume. The points formula is not public.&#x20;

GammaSwap reserves the right to modify previous point distributions and the point formula at any time.


# Treasury & Timelocks

Treasury & Timelock Multisigs

<table><thead><tr><th width="258">Name</th><th>Address</th></tr></thead><tbody><tr><td>Ethereum Treasury</td><td><a href="https://etherscan.io/address/0x73c510b2A44B51a01A13A3539c38EB330FB9713D">0x73c510b2A44B51a01A13A3539c38EB330FB9713D</a></td></tr><tr><td>Arbitrum Treasury</td><td><a href="https://arbiscan.io/address/0x34B5870C0431158e11c68B770127FBd2cE953f7a">0x34B5870C0431158e11c68B770127FBd2cE953f7a</a></td></tr><tr><td>Arbitrum Treasury 2</td><td><a href="https://arbiscan.io/address/0xa075f1B6f50a1a02Ba22c3B43D72917a326b16c0">0xa075f1B6f50a1a02Ba22c3B43D72917a326b16c0</a></td></tr><tr><td>Base Treasury</td><td><a href="https://basescan.org/address/0xaeAAc90117fb85a7DC961522DdFe96ABB358445B">0xaeAAc90117fb85a7DC961522DdFe96ABB358445B</a></td></tr><tr><td>GS Token Timelock Controller</td><td><a href="https://arbiscan.io/address/0xc8E5A4f2b2F6F307EF8f3290E47ab2b1EEB3cE98">0xc8E5A4f2b2F6F307EF8f3290E47ab2b1EEB3cE98</a></td></tr></tbody></table>


# Token

### Distribution

The total supply for $GS tokens is 1.6B with the following breakdown of supply.

* **Treasury (51%)** — GS tokens reserved for the Treasury. They can be used for protocol or community growth.
* **Core Team (23%)** — Allocation of the core team to align incentives with the protocol and promote long term building. There is no equity company, only the foundation.
* **Investors (17%)** — Seed investors.
* **LBP (5%)** — The GS supplied in the initial Liquidity Bootstrapping Pool.
* **V1 Airdrop (3%)** —  Initial airdrop to point holders in $GS.
* **Advisors (1%)** — Allocation for current and future advisors to the protocol.

### Vesting Schedule <a href="#vesting-schedule" id="vesting-schedule"></a>

There is a vesting schedule for the Core Team, Private Investors and Advisors.

* **Core Team**: 12 month cliff, 24 months linear vesting after
* **Private Investors**: 12 month cliff, 18 months linear vesting after
* **Advisors**: 12 month cliff, 18 months linear vesting after

There is no vest for the LBP or Treasury.&#x20;

### V1 Airdrop <a href="#airdrop" id="airdrop"></a>

The V1 airdrop was be distributed over an 8 week period starting Monday, September 9th, 2024 and ending Monday, November 4th, 2024 in weekly epochs.

### Token Addresses <a href="#token-addresses" id="token-addresses"></a>

<table data-header-hidden><thead><tr><th width="204.88427734375"></th><th></th></tr></thead><tbody><tr><td><strong>Token</strong></td><td><strong>Token Address</strong></td></tr><tr><td>$GS (Ethereum)</td><td>​<a href="https://etherscan.io/token/0x64d3cae387405d91f7b0d91fb1d824a281719500">0x64d3CAe387405d91f7b0D91fb1D824A281719500</a>​</td></tr><tr><td>$GS (Arbitrum)</td><td>​<a href="https://arbiscan.io/token/0xb08d8becab1bf76a9ce3d2d5fa946f65ec1d3e83">0xb08D8BeCAB1bf76A9Ce3d2d5fa946F65EC1d3e83</a>​</td></tr><tr><td>$GS (Base)</td><td>​<a href="https://basescan.org/token/0xc4d44c155f95fd4e94600d191a4a01bb571df7df">0xc4d44c155f95FD4E94600d191a4a01bb571dF7DF</a></td></tr></tbody></table>


# Support

Support for users of the GammaSwap Interface

If you are experiencing an issue using the GammaSwap Interface or would like to report a bug, please contact <support@gammaswap.com> or file a support ticket in our [Discord](https://discord.com/invite/gammaswaplabs) and list the category as "Technical Support 🎟️"


# Getting Started

{% content-ref url="/pages/BMKOsgxODfc5axjYKRwh" %}
[Overview](/developers/getting-started/overview)
{% endcontent-ref %}

{% content-ref url="/pages/m3H7gXO4vlIFhxPqU7vZ" %}
[Installation](/developers/getting-started/installation)
{% endcontent-ref %}

{% content-ref url="/pages/InnXOwM1G3iwSsah9wAD" %}
[Imports](/developers/getting-started/imports)
{% endcontent-ref %}

{% content-ref url="/pages/bvtfcwwINz1pLC87uFFv" %}
[Connect](/developers/getting-started/connect)
{% endcontent-ref %}

{% content-ref url="/pages/HSVzuXfkw8VJfDTsZsnP" %}
[Clients](/developers/getting-started/clients)
{% endcontent-ref %}

{% content-ref url="/pages/ZLZqZjEop8MRCnKDf0dT" %}
[Quick Start](/developers/getting-started/quick-start)
{% endcontent-ref %}


# Overview

The GammaSwap V2 protocol provides HTTP, WebSocket, and on-chain interfaces for building integrations with AI agents, trading algorithms and third party tools.

The recommended integration method is the official TypeScript SDK:

```
@gammaswap/v2-exchange-sdk
```

The SDK provides five specialized clients.

<table><thead><tr><th>Client</th><th>Purpose</th><th width="180.9453125">Connects to</th></tr></thead><tbody><tr><td><code>InfoClient</code></td><td>Read exchange and account data</td><td>Exchange HTTP API</td></tr><tr><td><code>ExchangeClient</code></td><td>Submit signed exchange actions</td><td>Exchange HTTP API</td></tr><tr><td><code>DepositClient</code></td><td>Read deposit state and submit deposits</td><td>Onchain Contracts</td></tr><tr><td><code>ExchangeWebSocketClient</code></td><td>Subscribe to live market updates</td><td>Market WebSocket</td></tr><tr><td><code>OracleWebSocketClient</code></td><td>Subscribe to live oracle prices</td><td>Oracle WebSocket</td></tr></tbody></table>

### Exchange HTTP API

The Exchange HTTP API provides public endpoints for:

* Asset metadata
* Account balances
* Positions
* Order-book snapshots
* Top-of-book data
* Resolution prices
* Agent approval status
* Orders
* Cancels
* Cancel-and-replace operations
* Claims
* Withdrawals
* Agent approval and revocation

Read-only requests do not require a wallet or signature. Exchange actions are authenticated using EIP-712 signatures.

### Market WebSocket

The market WebSocket publishes real-time updates for subscribed asset IDs.

Update types include:

* Orders
* Trades
* Cancels
* Market resolutions

Applications maintaining a local order book should use the HTTP API to load an initial snapshot and use WebSocket updates to keep it current.

If the connection is interrupted or the SDK detects that resynchronization is required, reload the full order-book snapshot before processing subsequent updates.

### Oracle WebSocket

The oracle WebSocket publishes live prices for subscribed symbol IDs.

Each price update includes:

* Symbol ID
* Price
* Timestamp

After a reconnect or stale-price notification, consumers should accept the next live price update.

### Deposits

Deposits are submitted directly to the configured chain through the `DepositClient`.

The deposit client supports:

* Discovering settlement and ledger contract addresses
* Reading pending and processed balances
* Checking deposit-processing state
* Reading token balances and allowances
* Approving the deposit ledger or Permit2
* Depositing settlement tokens
* Signing and submitting permit-based deposits

A wallet, RPC URL, and matching chain ID are required for on-chain transactions.

### Agent wallets

An agent wallet is a separate EVM wallet approved to sign exchange actions for a master account.

Agent wallets can submit:

* Orders
* Cancels
* Cancel-and-replace actions
* Claims

The master account remains the owner of the positions, balances, and orders. The agent wallet is only the signer.

### Data conventions

Blockchain integers are represented as decimal strings in JSON.

SDK order and transfer inputs use human-readable decimal strings:

* `size` and `amount` support up to two decimal places.
* `price` supports up to one decimal place.
* Addresses use standard EVM hexadecimal format.
* Order IDs and action hashes use `bytes32` hexadecimal strings.
* `false` represents a buy order.
* `true` represents a sell order.

The SDK validates and converts these values before sending requests.


# Installation

Install the GammaSwap V2 TypeScript SDK from npm.

```bash
npm install @gammaswap/v2-exchange-sdk
```

### Requirements

* Node.js 20 or later
* An EVM-compatible wallet for signed exchange actions
* An RPC provider for deposits and other on-chain operations
* A WebSocket implementation when the runtime does not provide one

The SDK uses the global `fetch` and `WebSocket` implementations when they are available. Custom implementations can be supplied through the client constructors.

### Ethers

Signed exchange actions and on-chain deposits use an `ethers` wallet.

If `ethers` is not already installed in your application, install it alongside the SDK:

```bash
npm install ethers
```

### Node WebSocket support

The WebSocket clients use `globalThis.WebSocket` when the runtime provides it. In Node.js environments without a global WebSocket implementation, the SDK can use the `ws` package.

```bash
npm install ws
```

You can also provide a custom WebSocket constructor through `WebSocketCtor`.

### Environment variables

A typical server-side integration may use the following environment variables:

```bash
EXCHANGE_API_URL=https://exchange-api.gammaswap.com/api
MARKET_WS_URL=wss://exchange-api.gammaswap.com/ws/
ORACLE_WS_URL=wss://exchange-api.gammaswap.com/oracle-ws/

CHAIN_ID=84532
RPC_URL=https://your-rpc-provider.example
PRIVATE_KEY=0x...
```

Never expose a private key in client-side code, committed source files, logs, or public environment configuration.

### Verify the installation

Create a read-only client and request asset metadata:

```ts
import { createInfoClient } from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
});

const asset = await info.getAsset(
  "261336857817713630688382311349658711122006440411137",
);

console.log(asset);
```

`InfoClient` does not require a wallet because its methods only read public exchange data.

### Local SDK development

The SDK repository uses:

* Node.js 20 or later
* pnpm 10 or later

These requirements apply when developing the SDK itself. Applications consuming the published npm package do not need to use pnpm.


# Imports

All public clients can be imported from the main SDK package.

```ts
import {
  createInfoClient,
  createExchangeClient,
  createDepositClient,
  createExchangeWebSocketClient,
  createOracleWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";
```

### Client imports

#### InfoClient

```ts
import { createInfoClient } from "@gammaswap/v2-exchange-sdk";
```

Use `InfoClient` for unsigned, read-only HTTP requests.

```ts
const info = createInfoClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
});
```

#### ExchangeClient

```ts
import { createExchangeClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";
```

Use `ExchangeClient` for wallet-signed exchange actions.

```ts
const wallet = new Wallet(process.env.PRIVATE_KEY!);

const exchange = createExchangeClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
  wallet,
  chainId: "84532",
});
```

#### DepositClient

```ts
import { createDepositClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";
```

Use `DepositClient` for on-chain deposit reads and transactions.

```ts
const wallet = new Wallet(process.env.PRIVATE_KEY!);

const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: "84532",
});
```

#### ExchangeWebSocketClient

```ts
import {
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";
```

Use `ExchangeWebSocketClient` for live market updates.

```ts
const marketStream = createExchangeWebSocketClient({
  websocketUrl: "wss://exchange-api.gammaswap.com/ws/",
  onError: (error) => console.error(error),
});
```

#### OracleWebSocketClient

```ts
import {
  createOracleWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";
```

Use `OracleWebSocketClient` for live oracle prices.

```ts
const oracleStream = createOracleWebSocketClient({
  websocketUrl: "wss://exchange-api.gammaswap.com/oracle-ws/",
  onError: (error) => console.error(error),
});
```

### Submodule imports

WebSocket clients can also be imported from their dedicated submodules.

```ts
import {
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk/websocket";

import {
  createOracleWebSocketClient,
} from "@gammaswap/v2-exchange-sdk/oracle-websocket";
```

### Input utilities

Integer-input helpers are exported from:

```ts
import {
  parseUnsignedInteger,
} from "@gammaswap/v2-exchange-sdk/integer-inputs";
```

These helpers validate canonical unsigned decimal strings and `bigint` values.

They reject JavaScript numbers unless a helper explicitly supports safe JSON or runtime integers.

### String utilities

Address and hexadecimal-string helpers are exported from:

```ts
import {
  parseAddress,
} from "@gammaswap/v2-exchange-sdk/string-inputs";
```

The string utility module includes validation for:

* EVM addresses
* Non-zero addresses
* Hexadecimal data
* `bytes32` values
* Case-insensitive address comparison

### Constants

Protocol constants such as time-in-force values can be imported from:

```ts
import {
  TimeInForce,
} from "@gammaswap/v2-exchange-sdk/constants";
```

Example:

```ts
await exchange.placeOrder({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  side: false,
  price: "99.9",
  size: "10.25",
  timeInForce: TimeInForce.GTC,
});
```


# Connect

GammaSwap provides separate endpoints for HTTP requests, market updates, oracle prices, and on-chain deposit operations.

### Service URLs

<table><thead><tr><th>Service</th><th width="181.8828125">Production URL</th><th>Used by</th></tr></thead><tbody><tr><td>Exchange HTTP API</td><td><code>https://exchange-api.gammaswap.com/api</code></td><td><code>InfoClient</code>, <code>ExchangeClient</code></td></tr><tr><td>Market WebSocket</td><td><code>wss://exchange-api.gammaswap.com/ws/</code></td><td><code>ExchangeWebSocketClient</code></td></tr><tr><td>Oracle WebSocket</td><td><code>wss://exchange-api.gammaswap.com/oracle-ws/</code></td><td><code>OracleWebSocketClient</code></td></tr><tr><td>Chain RPC</td><td>Your RPC provider URL</td><td><code>DepositClient</code></td></tr></tbody></table>

Local service defaults:

| Service           | Local URL               |
| ----------------- | ----------------------- |
| Exchange HTTP API | `http://localhost:3000` |
| Market WebSocket  | `ws://127.0.0.1:4000`   |
| Oracle WebSocket  | `ws://127.0.0.1:8082`   |

{% hint style="info" %}
Production HTTP requests use the `/api` base path. Local deployments are normally served without an `/api` prefix.
{% endhint %}

### Environment configuration

A server-side application can store connection settings in environment variables:

```bash
EXCHANGE_API_URL=https://exchange-api.gammaswap.com/api
MARKET_WS_URL=wss://exchange-api.gammaswap.com/ws/
ORACLE_WS_URL=wss://exchange-api.gammaswap.com/oracle-ws/

CHAIN_ID=84532
RPC_URL=https://your-rpc-provider.example
PRIVATE_KEY=0x...
```

Do not expose `PRIVATE_KEY` in frontend code, public environment variables, logs, or source control.

### Connect to the HTTP API

Read-only requests use `InfoClient`.

```ts
import { createInfoClient } from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: process.env.EXCHANGE_API_URL!,
});
```

Signed exchange actions use `ExchangeClient`.

```ts
import { createExchangeClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.PRIVATE_KEY!);

const exchange = createExchangeClient({
  apiUrl: process.env.EXCHANGE_API_URL!,
  wallet,
  chainId: process.env.CHAIN_ID!,
});
```

### Connect to the market WebSocket

```ts
import {
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const marketStream = createExchangeWebSocketClient({
  websocketUrl: process.env.MARKET_WS_URL!,
  onError: (error) => console.error("Market stream error", error),
});

await marketStream.connect();
```

Calling `connect()` is optional before subscribing. Subscription methods establish the connection when necessary.

### Connect to the oracle WebSocket

```ts
import {
  createOracleWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const oracleStream = createOracleWebSocketClient({
  websocketUrl: process.env.ORACLE_WS_URL!,
  onError: (error) => console.error("Oracle stream error", error),
});

await oracleStream.connect();
```

### Connect to the chain

`DepositClient` connects to an EVM JSON-RPC provider.

```ts
import { createDepositClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.PRIVATE_KEY!);

const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: process.env.CHAIN_ID!,
});
```

The client verifies that the RPC network matches the configured `chainId` before performing contract reads or transactions.

### Chain IDs and contracts

The supplied SDK examples use Base Sepolia:

| Network      | Chain ID |
| ------------ | -------: |
| Base Sepolia |  `84532` |

When the SDK includes default contract addresses for a chain, it resolves those contracts automatically.

For a custom or unsupported deployment, provide contract overrides:

```ts
const exchange = createExchangeClient({
  apiUrl: process.env.EXCHANGE_API_URL!,
  wallet,
  chainId: process.env.CHAIN_ID!,
  contracts: {
    // Exchange contract overrides
  },
});
```

For deposits, the deposit-ledger address can be supplied directly:

```ts
const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: process.env.CHAIN_ID!,
  depositLedger: "0x...",
});
```

The production chain ID and production RPC provider are not specified in the supplied developer documentation.


# Clients

The SDK separates read requests, signed actions, on-chain deposits, and real-time subscriptions into five clients.

<table><thead><tr><th width="228.109375">Client</th><th width="287.0859375">Primary use</th><th align="right">Wallet required</th></tr></thead><tbody><tr><td><code>InfoClient</code></td><td>Read exchange and account data</td><td align="right">No</td></tr><tr><td><code>ExchangeClient</code></td><td>Submit signed exchange actions</td><td align="right">Yes</td></tr><tr><td><code>DepositClient</code></td><td>Read and write deposit-contract state</td><td align="right">Yes</td></tr><tr><td><code>ExchangeWebSocketClient</code></td><td>Subscribe to market updates</td><td align="right">No</td></tr><tr><td><code>OracleWebSocketClient</code></td><td>Subscribe to oracle prices</td><td align="right">No</td></tr></tbody></table>

### InfoClient

Use `InfoClient` for unsigned, read-only HTTP requests.

Available methods:

* `getAsset`
* `getResolutionPrice`
* `getLastResolutionPrice`
* `getBalance`
* `getOrderBook`
* `getBookOrders`
* <sup>`getTopOfBook`</sup>
* `getPosition`
* `getAgentApproval`
* `getAgentApprovalNonce`
* `getExchangeConfig`

Create a client:

```ts
import { createInfoClient } from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
});
```

Constructor options:

| Option    | Required | Description                        |
| --------- | -------: | ---------------------------------- |
| `apiUrl`  |      Yes | Exchange HTTP API base URL         |
| `fetch`   |       No | Replacement for `globalThis.fetch` |
| `headers` |       No | Headers added to every request     |

No wallet or signature is required.

### ExchangeClient

Use `ExchangeClient` for wallet-signed exchange actions.

Available methods:

* `placeOrder`
* `placeAgentOrder`
* `cancelOrder`
* `cancelAll`
* `cancelReplaceOrder`
* `cancelAgentOrder`
* `cancelAllAgent`
* `cancelReplaceAgentOrder`
* `claim`
* `claimAgent`
* `withdraw`
* `approveAgent`
* `revokeAgent`
* `signAgentApproval`

Create a client:

```ts
import { createExchangeClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const exchange = createExchangeClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
  wallet: new Wallet(process.env.PRIVATE_KEY!),
  chainId: "84532",
});
```

Constructor options:

| Option         | Required | Description                             |
| -------------- | -------: | --------------------------------------- |
| `apiUrl`       |      Yes | Exchange HTTP API base URL              |
| `wallet`       |      Yes | `ethers` wallet used for signing        |
| `chainId`      |      Yes | Chain ID included in EIP-712 signatures |
| `contracts`    |       No | Contract-address overrides              |
| `fetch`        |       No | Replacement for `globalThis.fetch`      |
| `headers`      |       No | Headers added to every request          |
| `infoClient`   |       No | Existing `InfoClient` instance          |
| `nonceManager` |       No | Custom action nonce manager             |

The SDK fills signing fields, hashes the action, creates the EIP-712 signature, and serializes the request body.

### DepositClient

Use `DepositClient` for direct contract reads and deposit transactions.

Available methods:

* `getSettlementToken`
* `getPermit2`
* `getAccountLedger`
* `getPendingBalance`
* `getProcessedBalance`
* `getPendingDepositCount`
* `getNextPendingDepositId`
* `getProcessedDepositIndex`
* `getMinBlockWait`
* `canProcessNext`
* `getSettlementTokenBalance`
* `getSettlementTokenAllowance`
* `approveDepositLedger`
* `approvePermit2`
* `deposit`
* `signDepositPermit`
* `depositWithPermit`
* `parseAmount`

Create a client:

```ts
import { createDepositClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet: new Wallet(process.env.PRIVATE_KEY!),
  chainId: "84532",
});
```

Constructor options:

| Option                    | Required | Description                                             |
| ------------------------- | -------: | ------------------------------------------------------- |
| `rpcUrl`                  |      Yes | EVM JSON-RPC URL                                        |
| `wallet`                  |      Yes | Wallet used for reads, approvals, and deposits          |
| `chainId`                 |      Yes | Expected RPC network chain ID                           |
| `depositLedger`           |       No | Deposit-ledger address override                         |
| `contracts`               |       No | Contract-address overrides                              |
| `settlementTokenDecimals` |       No | Settlement-token decimals; currently required to be `6` |

The client communicates directly with chain contracts rather than the Exchange HTTP API.

### ExchangeWebSocketClient

Use `ExchangeWebSocketClient` for live market events by `assetId`.

Available methods and properties:

* `connectionState`
* `connect`
* `close`
* `subscribeOrderBook`
* `unsubscribeOrderBook`

Create a client:

```ts
import {
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const marketStream = createExchangeWebSocketClient({
  websocketUrl: "wss://exchange-api.gammaswap.com/ws/",
  onError: console.error,
});
```

Subscription handlers:

* `onUpdate`
* `onOrder`
* `onTrade`
* `onCancel`
* `onResolution`
* `onError`
* `onResyncRequired`

One client can subscribe to multiple asset IDs. Multiple local handlers for the same asset share one server subscription.

### OracleWebSocketClient

Use `OracleWebSocketClient` for live oracle prices by `symbolId`.

Available methods and properties:

* `connectionState`
* `connect`
* `close`
* `subscribePrice`
* `unsubscribePrice`

Create a client:

```ts
import {
  createOracleWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const oracleStream = createOracleWebSocketClient({
  websocketUrl: "wss://exchange-api.gammaswap.com/oracle-ws/",
  stalePriceTimeoutMs: 30_000,
  onError: console.error,
});
```

Subscription handlers:

* `onPrice`
* `onError`
* `onStale`

After a reconnect or stale-price event, accept the next live price update.


# Quick Start

This guide creates the SDK clients, reads exchange data, submits a signed order, and subscribes to live updates.

### 1. Install the SDK

```bash
npm install @gammaswap/v2-exchange-sdk ethers
```

### 2. Configure the environment

```bash
EXCHANGE_API_URL=https://exchange-api.gammaswap.com/api
MARKET_WS_URL=wss://exchange-api.gammaswap.com/ws/
ORACLE_WS_URL=wss://exchange-api.gammaswap.com/oracle-ws/

CHAIN_ID=84532
RPC_URL=https://your-rpc-provider.example
PRIVATE_KEY=0x...
```

Keep private keys in a server-side secret manager. Do not include them in frontend bundles or commit them to source control.

### 3. Read asset metadata

Read-only methods use `InfoClient` and do not require a wallet.

```ts
import { createInfoClient } from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: process.env.EXCHANGE_API_URL!,
});

const assetId =
  "261336857817713630688382311349658711122006440411137";

const asset = await info.getAsset(assetId);

console.log(asset);
```

Example response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "registered": true,
  "expiration": "1730000000",
  "assetType": "2",
  "strikePrice": "999000",
  "ledger": "0x1111111111111111111111111111111111111111"
}
```

### 4. Load an order-book snapshot

```ts
const epoch = "12";

const book = await info.getOrderBook({
  assetId,
  epoch,
});

console.log(book.bids);
console.log(book.asks);
```

Example response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "ts": 1730000000,
  "seqId": 123,
  "bids": [],
  "asks": []
}
```

### 5. Create a signed exchange client

```ts
import { createExchangeClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.PRIVATE_KEY!);

const exchange = createExchangeClient({
  apiUrl: process.env.EXCHANGE_API_URL!,
  wallet,
  chainId: process.env.CHAIN_ID!,
});
```

### 6. Place an order

```ts
const order = await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
});

console.log(order);
```

Order input conventions:

<table><thead><tr><th width="207.3125">Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>side: false</code></td><td>Buy</td></tr><tr><td><code>side: true</code></td><td>Sell</td></tr><tr><td><code>price</code></td><td>Human decimal string with up to one decimal place</td></tr><tr><td><code>size</code></td><td>Human decimal string with up to two decimal places</td></tr><tr><td><code>timeInForce</code></td><td>Optional; defaults to GTC</td></tr><tr><td><code>nonce</code></td><td>Optional; generated by the SDK</td></tr></tbody></table>

Example response:

```json
{
  "orderId": "0x1111111111111111111111111111111111111111111111111111111111111111",
  "filled": "0",
  "remaining": "1025",
  "cancelled": "0",
  "status": "ACCEPTED",
  "reason": ""
}
```

### 7. Subscribe to market updates

Load the REST order-book snapshot before applying live updates.

```ts
import {
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const marketStream = createExchangeWebSocketClient({
  websocketUrl: process.env.MARKET_WS_URL!,
  onError: (error) => console.error("Market stream error", error),
});

const unsubscribeBook = await marketStream.subscribeOrderBook(assetId, {
  onUpdate: (update) => {
    console.log("Market update", update);
  },
  onOrder: (update) => {
    console.log("Order update", update);
  },
  onTrade: (update) => {
    console.log("Trade update", update);
  },
  onCancel: (update) => {
    console.log("Cancel update", update);
  },
  onResolution: (update) => {
    console.log("Resolution update", update);
  },
  onResyncRequired: async (resyncAssetId) => {
    const freshBook = await info.getOrderBook({
      assetId: resyncAssetId,
      epoch,
    });

    console.log("Reloaded order book", freshBook);
  },
});
```

When the subscription is no longer needed:

```ts
await unsubscribeBook();
marketStream.close();
```

### 8. Subscribe to oracle prices

```ts
import {
  createOracleWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const oracleStream = createOracleWebSocketClient({
  websocketUrl: process.env.ORACLE_WS_URL!,
  stalePriceTimeoutMs: 30_000,
  onError: (error) => console.error("Oracle stream error", error),
});

const unsubscribePrice = await oracleStream.subscribePrice("1", {
  onPrice: (update) => {
    console.log(
      "Oracle price",
      update.symbolId,
      update.price,
      update.ts,
    );
  },
  onStale: (symbolId) => {
    console.warn("Oracle price is stale", symbolId);
  },
});
```

When the subscription is no longer needed:

```ts
await unsubscribePrice();
oracleStream.close();
```

### 9. Create a deposit client

Deposits communicate directly with onchain contracts.

```ts
import { createDepositClient } from "@gammaswap/v2-exchange-sdk";

const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: process.env.CHAIN_ID!,
});
```

Check the connected wallet’s settlement-token balance:

```ts
const balance = await deposit.getSettlementTokenBalance();

console.log(balance);
```

Before submitting a deposit, approve the required spender using the relevant deposit-ledger or Permit2 approval method.

### Next steps

* Use `InfoClient` to read balances, positions, books, and resolutions.
* Use `ExchangeClient` to place orders, cancel orders, claim, and withdraw.
* Use an agent wallet to separate automated signing from the master wallet.
* Use `DepositClient` for token approvals and deposits.
* Use the WebSocket clients for real-time market and oracle updates.


# Concepts

{% content-ref url="/pages/odLFeQD7h5kjzXIbNE2U" %}
[Input Units](/developers/concepts/input-units)
{% endcontent-ref %}

{% content-ref url="/pages/fhx5HgtcBbyD2UrHazUv" %}
[Order Side & Time in Force](/developers/concepts/order-side-and-time-in-force)
{% endcontent-ref %}

{% content-ref url="/pages/92DPl66cdNDsgvyMonIy" %}
[Nonces](/developers/concepts/nonces)
{% endcontent-ref %}

{% content-ref url="/pages/kNluBT8yX8y4cCIfFTAf" %}
[Authentication](/developers/concepts/authentication)
{% endcontent-ref %}


# Input Units

The SDK accepts human-readable values for prices, order sizes, and transfer amounts. It validates and converts these values into protocol units before sending a request.

Blockchain integers are serialized as decimal strings in JSON.

### Value types

<table><thead><tr><th width="166.34368896484375">Value</th><th width="196.0728759765625">SDK input</th><th>Rules</th></tr></thead><tbody><tr><td><code>price</code></td><td>Human decimal string</td><td>Up to 1 decimal place</td></tr><tr><td><code>size</code></td><td>Human decimal string</td><td>Up to 2 decimal places</td></tr><tr><td><code>amount</code></td><td>Human decimal string</td><td>Up to 2 decimal places</td></tr><tr><td><code>nonce</code></td><td><code>bigint</code> or decimal string</td><td>Unsigned 64-bit integer</td></tr><tr><td><code>assetId</code></td><td><code>bigint</code> or decimal string</td><td>Canonical unsigned integer</td></tr><tr><td><code>epoch</code></td><td><code>bigint</code> or decimal string</td><td>Canonical unsigned integer</td></tr><tr><td><code>Balance values</code></td><td>Decimal string</td><td>Returned in protocol units</td></tr><tr><td><code>Address</code></td><td>EVM hexadecimal string</td><td>Valid 20-byte address</td></tr><tr><td><code>Order ID or hash</code></td><td>Hexadecimal string</td><td>Valid <code>bytes32</code> value</td></tr></tbody></table>

### Human decimal strings

Order and transfer inputs should be passed as strings rather than JavaScript numbers.

```ts
await exchange.placeOrder({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  side: false,
  price: "99.9",
  size: "10.25",
});
```

Use:

```ts
price: "99.9"
size: "10.25"
amount: "100.50"
```

Do not use:

```ts
price: 99.9
size: 10.25
amount: 100.5
```

Strings avoid floating-point rounding and allow the SDK to validate decimal precision exactly.

### Price conversion

Prices support one decimal place and are interpreted in cents before being converted into protocol units.

For example:

```
"99.9" → 999000n
```

Applications should pass the human-readable price to the SDK. Do not perform this conversion manually when using `ExchangeClient`.

### Size and amount conversion

Sizes and transfer amounts support up to two decimal places.

Examples of valid inputs:

```
"10"
"10.2"
"10.25"
```

Inputs with more than two decimal places are rejected:

```
"10.255"
```

Zero amounts, invalid precision, negative values, and out-of-range values are rejected before the SDK sends a request.

### ProtocolBigNumberish

Protocol integer fields accept `ProtocolBigNumberish`, which is:

* A `bigint`
* A canonical unsigned decimal string

Examples:

```ts
const assetId = 1n;
const epoch = "12";
const nonce = 113377280000000000n;
```

Avoid JavaScript numbers for protocol integers:

```ts
const assetId = 1; // Do not use for protocol integer inputs
```

JavaScript numbers cannot safely represent every protocol value.

### Addresses

Addresses must use standard EVM hexadecimal format:

```
0x1111111111111111111111111111111111111111
```

The SDK provides string utilities for:

* Address validation
* Non-zero address validation
* Hexadecimal-data validation
* `bytes32` validation
* Case-insensitive address comparison

```ts
import {
  parseAddress,
} from "@gammaswap/v2-exchange-sdk/string-inputs";

const account = parseAddress(
  "0x1111111111111111111111111111111111111111",
);
```

Relevant address fields are normalized before signed action hashes and signatures are validated.

### Integer utilities

Integer-input helpers are available from:

```ts
import {
  parseUnsignedInteger,
} from "@gammaswap/v2-exchange-sdk/integer-inputs";
```

These helpers accept canonical decimal strings and `bigint` values.

They intentionally reject JavaScript numbers unless a specific helper explicitly supports safe JSON or runtime integers.

### Direct API requests

The SDK accepts human decimal inputs and performs the required conversion.

Direct HTTP request bodies use converted protocol values represented as decimal strings.

For example, an SDK call may use:

```ts
{
  price: "99.9",
  size: "10.25"
}
```

The signed HTTP action contains the converted integer values:

```json
{
  "price": "999000",
  "size": "1025"
}
```

When constructing direct HTTP requests, conversion must happen before hashing and signing. Changing a value after signing invalidates the signature.


# Order Side & Time in Force

### Order side

The `side` field is a boolean.

| Value   | Direction |
| ------- | --------- |
| `false` | Buy/Up    |
| `true`  | Sell/Down |

Buy order:

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
});
```

Sell order:

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: true,
  price: "99.9",
  size: "10.25",
});
```

### Time in force

Time in force controls how an order behaves when it reaches the order book.

<table><thead><tr><th width="90.5052490234375" align="right">Value</th><th width="161.80731201171875">Constant</th><th width="178.606689453125">Name</th><th>Behavior</th></tr></thead><tbody><tr><td align="right"><code>0</code></td><td><code>TimeInForce.GTC</code></td><td>Good until cancelled</td><td>Any unfilled quantity remains open</td></tr><tr><td align="right"><code>1</code></td><td><code>TimeInForce.FOK</code></td><td>Fill or kill</td><td>The entire order must fill immediately or be cancelled</td></tr><tr><td align="right"><code>2</code></td><td><code>TimeInForce.IOC</code></td><td>Immediate or cancel</td><td>Available quantity fills immediately; the remainder is cancelled</td></tr><tr><td align="right"><code>3</code></td><td><code>TimeInForce.ALO</code></td><td>Add liquidity only</td><td>The order may only add liquidity</td></tr></tbody></table>

Import the constants from the SDK:

```ts
import {
  TimeInForce,
} from "@gammaswap/v2-exchange-sdk/constants";
```

### Good until cancelled

GTC is the default.

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  timeInForce: TimeInForce.GTC,
});
```

Since GTC is the default, it can be omitted:

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
});
```

### Fill or kill

A FOK order must fill completely. If the full requested size is unavailable, the order is cancelled.

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  timeInForce: TimeInForce.FOK,
});
```

### Immediate or cancel

An IOC order fills as much as possible immediately and cancels any remaining quantity.

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  timeInForce: TimeInForce.IOC,
});
```

### Add liquidity only

An ALO order may only add liquidity to the order book.

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  timeInForce: TimeInForce.ALO,
});
```

If the order would immediately match existing liquidity, it is rejected or cancelled with an `ALO` reason.

### Cancel and replace

For a cancel-and-replace request:

* The replacement order must use the same market as the cancelled order.
* The replacement side must match the side of the cancelled order.
* The order hash being cancelled cannot be the zero hash.
* Cancel-all behavior is not supported by cancel-and-replace.

```ts
await exchange.cancelReplaceOrder({
  assetId,
  epoch,
  cancelOrderHash: existingOrderId,
  side: false,
  price: "99.8",
  size: "10.25",
  timeInForce: TimeInForce.GTC,
});
```


# Nonces

Every signed exchange action includes a nonce. Nonces protect signed actions from being submitted more than once.

The SDK generates a nonce automatically when an action’s `nonce` field is omitted.

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  // nonce is generated automatically
});
```

### Nonce format

`NonceManager` generates unsigned 64-bit values using:

```
[48-bit timestamp in milliseconds][16-bit counter]
```

The upper 48 bits contain a logical timestamp in milliseconds. The lower 16 bits contain a counter.

### Generation behavior

When the physical clock advances:

1. The timestamp is updated.
2. The counter resets to `0`.

When another nonce is requested in the same millisecond:

1. The previous logical timestamp is retained.
2. The counter is incremented.

If the system clock moves backward:

1. The previous logical timestamp is retained.
2. The counter continues to increment.

This keeps nonces monotonically increasing within one `NonceManager` instance.

### Per-millisecond capacity

The 16-bit counter ranges from `0` through `65,535`.

This allows up to 65,536 nonces in one logical millisecond.

If more nonces are requested before the physical clock advances, the SDK:

1. Advances its logical timestamp by one millisecond.
2. Resets the counter to `0`.
3. Continues generating monotonically increasing values.

The SDK does not throw or block in this situation.

Under extremely high throughput, the logical timestamp can move ahead of the wall clock.

### Explicit nonces

Applications can provide their own nonce:

```ts
await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  nonce: 113377280000000000n,
});
```

A nonce can be a `bigint` or canonical unsigned decimal string.

```ts
nonce: 113377280000000000n
```

```ts
nonce: "113377280000000000"
```

### Multiple processes

Nonce uniqueness is local to one `NonceManager` instance.

It does not coordinate across:

* Browser tabs
* Node.js processes
* Application servers
* Devices
* Separate SDK clients

If several processes sign actions using the same address, use one of the following approaches:

* Reuse a shared `NonceManager`
* Use a centralized nonce service
* Use a shared atomic counter
* Pass coordinated explicit nonces
* Use a separate agent wallet for each independent signing process

{% hint style="warning" %}
Creating a new SDK client for every request also creates separate local nonce state unless a shared `NonceManager` is supplied.
{% endhint %}

### Nonces and request status

`NonceManager` only generates the next local value.

It does not track whether an action is:

* Pending
* Accepted
* Rejected
* Filled
* Cancelled
* Already submitted

Applications should track request status separately.

### Cancel and replace nonces

Cancel-and-replace uses two signed actions:

* A nonce for the cancel-replace action
* A nonce for the replacement order

```ts
await exchange.cancelReplaceOrder({
  assetId,
  epoch,
  cancelOrderHash,
  side: false,
  price: "99.8",
  size: "10.25",
  nonce: cancelReplaceNonce,
  replacementNonce,
});
```

When omitted, the SDK generates both values.

### Agent approval nonce

`approvalNonce` is different from an action `nonce`.

* `nonce` identifies the signed action.
* `approvalNonce` identifies the active agent approval.

Agent actions accept both:

```ts
await agentClient.placeAgentOrder({
  sender: masterAddress,
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  nonce: actionNonce,
  approvalNonce,
});
```

When `approvalNonce` is omitted, agent order, cancel, cancel-replace, and claim methods fetch it through `InfoClient`.


# Authentication

GammaSwap uses different authentication mechanisms depending on the operation.

| Operation                     | Authentication                   |
| ----------------------------- | -------------------------------- |
| Read-only HTTP request        | None                             |
| Signed exchange action        | EIP-712 wallet signature         |
| Agent exchange action         | Approved agent EIP-712 signature |
| Market WebSocket subscription | None                             |
| Oracle WebSocket subscription | None                             |
| Deposit transaction           | On-chain wallet transaction      |
| Deposit permit                | Wallet-signed permit             |

### Read-only requests

`InfoClient` methods do not require a wallet or signature.

```ts
const balance = await info.getBalance(
  "0x1111111111111111111111111111111111111111",
);
```

The queried account does not need to match any locally configured wallet.

### Signed exchange actions

`ExchangeClient` signs exchange actions using its configured `ethers` wallet.

```ts
import { createExchangeClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.PRIVATE_KEY!);

const exchange = createExchangeClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
  wallet,
  chainId: "84532",
});
```

Signed actions include:

* Orders
* Cancels
* Cancel-and-replace requests
* Claims
* Withdrawals
* Agent approvals
* Agent revocations

### EIP-712 domain

Signed exchange actions use this EIP-712 domain:

```
name: GammaSwap Exchange
version: 2
chainId: request chain ID
verifyingContract: exchange verifying contract
```

The chain ID and verifying contract prevent the same signature from being reused against another chain or exchange deployment.

### Signature types

<table><thead><tr><th width="96.45831298828125" align="right">Value</th><th width="93.953125">Type</th><th>Description</th></tr></thead><tbody><tr><td align="right"><code>0</code></td><td>EOA</td><td>The account signs its own action</td></tr><tr><td align="right"><code>4</code></td><td>Agent</td><td>An approved agent signs for a master account</td></tr></tbody></table>

For an EOA action:

* `signer` is the configured wallet.
* `sender` is normally the same wallet.
* `signatureType` is `0`.
* `approvalNonce` is `0` when the action contains that field.

For an agent action:

* `signer` is the agent wallet.
* `sender` is the master account.
* `signatureType` is `4`.
* `approvalNonce` identifies the active agent approval.

### Signed request wrapper

Most signed HTTP requests use this outer structure:

```json
{
  "action": {},
  "chainId": "84532",
  "orderHash": "0x...",
  "signature": "0x..."
}
```

The action field name depends on the endpoint:

<table><thead><tr><th width="213.97393798828125">Endpoint</th><th>Action field</th></tr></thead><tbody><tr><td><code>POST /orders</code></td><td><code>order</code></td></tr><tr><td><code>POST /cancels</code></td><td><code>cancel</code></td></tr><tr><td><code>POST /cancel-replace</code></td><td><code>cancelReplace</code></td></tr><tr><td><code>POST /withdrawals</code></td><td><code>withdrawal</code></td></tr><tr><td><code>POST /claim</code></td><td><code>claim</code></td></tr><tr><td><code>POST /agents/approve</code></td><td><code>approval</code></td></tr><tr><td><code>POST /agents/revoke</code></td><td><code>revocation</code></td></tr></tbody></table>

The field name `orderHash` is historical. It contains the EIP-712 digest of the signed action, even when the action is not an order.

It is not a hash of the outer HTTP body and does not include the signature.

### Signing process

A direct API client signs an action in this order:

1. Construct the complete action object.
2. Convert numeric fields into their exact protocol integers.
3. Construct the EIP-712 domain.
4. Calculate the action-specific struct hash.
5. Calculate the EIP-712 digest.
6. Sign the digest.
7. Send the original action, chain ID, digest, and signature.

Any change to a signed field changes the digest and invalidates the signature.

Signed fields include:

* Action type
* Nonce
* Signer
* Signature type
* Sender
* Market fields
* Amount or order values
* Chain ID
* Verifying contract

### SDK signing

The recommended approach is to let `ExchangeClient` construct and sign the action:

```ts
const result = await exchange.placeOrder({
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
});
```

The SDK fills:

* `signer`
* `sender` for EOA requests
* `signatureType`
* `chainId`
* Action-type identifier
* `nonce` when omitted
* `timeInForce` when omitted
* `orderHash`
* `signature`

### Direct signing helpers

Low-level helpers are available for applications that construct direct HTTP requests.

<table><thead><tr><th width="224.3203125">Action</th><th>Hash helper</th></tr></thead><tbody><tr><td>Order</td><td><code>hashFillOrderJS</code></td></tr><tr><td>Cancel</td><td><code>hashCancelOrderJS</code></td></tr><tr><td>Cancel-replace</td><td><code>hashCancelReplaceOrderJS</code></td></tr><tr><td>Replacement order</td><td><code>hashFillOrderJS</code></td></tr><tr><td>Withdrawal</td><td><code>hashWithdrawalOrderJS</code></td></tr><tr><td>Claim</td><td><code>hashClaimOrderJS</code></td></tr><tr><td>Agent approval</td><td><code>hashApproveAgentOrderJS</code></td></tr><tr><td>Inner agent approval</td><td><code>hashAgentApprovalJS</code></td></tr><tr><td>Agent revocation</td><td><code>hashRevokeAgentOrderJS</code></td></tr></tbody></table>

Example:

```ts
import {
  getExchangeDomain,
  hashFillOrderJS,
  signOrderJS,
} from "@gammaswap/v2-exchange-sdk";

const domain = getExchangeDomain(
  chainId,
  verifyingContract,
);

const orderHash = hashFillOrderJS(order, domain);
const signature = signOrderJS(orderHash, wallet);

const body = {
  order,
  chainId: chainId.toString(),
  orderHash,
  signature,
};
```

### Agent authentication

An agent must be approved by the master account before it can submit agent actions.

```ts
await masterClient.approveAgent({
  agent: agentWallet.address,
});
```

An agent order specifies the master account as `sender`:

```ts
await agentClient.placeAgentOrder({
  sender: masterWallet.address,
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
});
```

The SDK fetches the active approval nonce from `InfoClient` when it is omitted.

Agent approvals contain two signatures:

* An inner signature over the agent approval
* An outer signature over the approve-agent action

### Cancel-and-replace authentication

Cancel-and-replace contains two separately signed components:

* `cancelReplace`
* `replacement`

The request includes:

* Cancel-replace action hash and signature
* Replacement-order hash and signature

The cancel-replace action commits to the replacement order’s hash.

### Withdrawals

Withdrawals use EIP-712 authentication through `ExchangeClient`.

```ts
await exchange.withdraw({
  amount: "100.00",
  receiver: "0x1111111111111111111111111111111111111111",
});
```

If `receiver` is omitted, it defaults to the configured wallet address.

### WebSocket authentication

The supplied market and oracle WebSocket protocols do not require authentication.

Clients subscribe using an `assetId` or `symbolId`.

Market subscription:

```json
{
  "type": "subscribe",
  "assetId": "1"
}
```

Oracle subscription:

```json
{
  "type": "subscribe",
  "symbolId": "1"
}
```

### Deposit authentication

Deposit contract reads and transactions use the wallet configured on `DepositClient`.

Transactions are authorized through normal EVM wallet signatures.

Permit-based deposits use a separately signed deposit permit before submitting the on-chain transaction.


# API Reference

### Info Methods

Read-only methods. No wallet or signature is required.

`getAsset` `getResolutionPrice` `getLastResolutionPrice` `getBalance` `getOrderBook` `getBookOrders` `getTopOfBook` `getPosition` `getAgentApproval` `getAgentApprovalNonce getExchangeConfig`

{% content-ref url="/pages/npZWswoNtsasrgjUmCJy" %}
[Info Methods](/developers/api-reference/info-methods)
{% endcontent-ref %}

### Exchange Methods

All submitted actions are signed with the configured EOA wallet.

`placeOrder` `placeAgentOrder` `cancelOrder` `cancelAll` `cancelReplaceOrder` `cancelAgentOrder` `cancelAllAgent` `cancelReplaceAgentOrder` `claim` `claimAgent` `withdraw` `approveAgent` `revokeAgent` `signAgentApproval`

{% content-ref url="/pages/tOLQIMXOBaeYrxnaj01F" %}
[Exchange Methods](/developers/api-reference/exchange-methods)
{% endcontent-ref %}

### Deposit Methods

These methods communicate directly with onchain contracts through the configured RPC URL.

`getSettlementToken` `getPermit2` `getAccountLedger` `getPendingBalance` `getProcessedBalance` `getPendingDepositCount` `getNextPendingDepositId` `getProcessedDepositIndex getMinBlockWait` `canProcessNext` `getSettlementTokenBalance getSettlementTokenAllowance` `approveDepositLedger` `approvePermit2` `deposit` `signDepositPermit` `depositWithPermit`  `parseAmount`

{% content-ref url="/pages/EDmlQ2CSAQ4rsPbGjZ2G" %}
[Deposit Methods](/developers/api-reference/deposit-methods)
{% endcontent-ref %}

### Susbcription Methods

These methods manage the market WebSocket connection and subscribe to real-time order, trade, cancel, and resolution updates by `assetId`.

`connect` `close` `subscribeOrderBook` `unsubscribeOrderBook`

{% content-ref url="/pages/iKGrBF3VtemxKqbgvRip" %}
[Subscription Methods](/developers/api-reference/subscription-methods)
{% endcontent-ref %}

### Oracle Methods

These methods manage the oracle WebSocket connection and subscribe to real-time price updates by `symbolId`.

`connect` `close` `subscribePrice` `unsubscribePrice`

{% content-ref url="/pages/1blaZ3YC8h4ik42euljj" %}
[Oracle Methods](/developers/api-reference/oracle-methods)
{% endcontent-ref %}


# Info Methods

`InfoClient` provides read-only access to exchange, market, account, resolution, and agent data through the Exchange HTTP API.

These methods do not require a wallet or signature.

```ts
import { createInfoClient } from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
});
```

### `getAsset`

Returns registered metadata for the current state of an asset.

```ts
const asset = await info.getAsset(
  "261336857817713630688382311349658711122006440411137",
);
```

Endpoint:

```
GET /asset/:assetId
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "registered": true,
  "expiration": "1730000000",
  "assetType": "2",
  "strikePrice": "999000",
  "ledger": "0x1111111111111111111111111111111111111111"
}
```

### `getResolutionPrice`

Returns the stored resolution price for a specific asset and epoch.

```ts
const resolution = await info.getResolutionPrice({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
GET /resolve/:assetId/:epoch
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "id": 1,
  "ts": "1730000000",
  "price": "999000",
  "isNull": false
}
```

### `getLastResolutionPrice`

Returns the latest stored resolution price for an asset.

```ts
const resolution = await info.getLastResolutionPrice(
  "261336857817713630688382311349658711122006440411137",
);
```

Endpoint:

```
GET /resolve/last/epoch/:assetId
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "id": 1,
  "ts": "1730000000",
  "price": "999000",
  "isNull": false
}
```

### `getBalance`

Returns the current balance snapshot for an account.

```ts
const balance = await info.getBalance(
  "0x1111111111111111111111111111111111111111",
);
```

Endpoint:

```
GET /balance/:account
```

Response:

```json
{
  "account": "0x1111111111111111111111111111111111111111",
  "ts": 1730000000,
  "balance": "100000000",
  "pending": "5000000"
}
```

### `getOrderBook`

Returns an aggregated order-book snapshot for an asset and epoch.

```ts
const book = await info.getOrderBook({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
GET /book/:assetId/:epoch
```

The endpoint also accepts an optional `depth` query parameter. It defaults to `200` and is constrained to a value between `1` and `5000`.

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "ts": 1730000000,
  "seqId": 123,
  "bids": [
    {
      "price": "999000",
      "size": "1025",
      "orderCount": 1,
      "orders": [
        {
          "id": "0x1111111111111111111111111111111111111111111111111111111111111111",
          "size": "1025",
          "price": 999000,
          "time": 1730000000,
          "account": "0x1111111111111111111111111111111111111111"
        }
      ]
    }
  ],
  "asks": [
    {
      "price": "1000000",
      "size": "500",
      "orderCount": 1,
      "orders": [
        {
          "id": "0x2222222222222222222222222222222222222222222222222222222222222222",
          "size": "500",
          "price": 1000000,
          "time": 1730000001,
          "account": "0x2222222222222222222222222222222222222222"
        }
      ]
    }
  ]
}
```

### `getBookOrders`

Returns resting book orders owned by one account.

```ts
const orders = await info.getBookOrders({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  account: "0x1111111111111111111111111111111111111111",
});
```

Endpoint:

```
GET /book/:assetId/:epoch/:account
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "seqId": 123,
  "ts": 1730000000,
  "buys": [
    {
      "id": "0x1111111111111111111111111111111111111111111111111111111111111111",
      "size": "1025",
      "price": 999000,
      "time": 1730000000,
      "account": "0x1111111111111111111111111111111111111111"
    }
  ],
  "sells": []
}
```

### `getTopOfBook`

Returns the best bid, best ask, and last traded price for an asset and epoch.

```ts
const top = await info.getTopOfBook({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
GET /book/market/top/:assetId/:epoch
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "seqId": 123,
  "ts": 1730000000,
  "bid": {
    "price": "999000",
    "size": "1025",
    "orderCount": 1,
    "orders": []
  },
  "ask": {
    "price": "1000000",
    "size": "500",
    "orderCount": 1,
    "orders": []
  },
  "last": "999500",
  "lastTs": "1730000000"
}
```

### `getPosition`

Returns an account’s position for a specific asset and epoch.

```ts
const position = await info.getPosition({
  account: "0x1111111111111111111111111111111111111111",
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
GET /position/:account/:assetId/:epoch
```

Response:

```json
{
  "account": "0x1111111111111111111111111111111111111111",
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "ts": 1730000000,
  "size": "1025",
  "margin": "1000000",
  "balance": "5000000",
  "pnl": "250000",
  "side": false,
  "bSide": false,
  "mSide": false,
  "pSide": false
}
```

### `getAgentApproval`

Returns the current agent approval for a master account.

```ts
const approval = await info.getAgentApproval(
  "0x1111111111111111111111111111111111111111",
);
```

Endpoint:

```
GET /agents/status/:master
```

Response:

```json
{
  "agent": "0x2222222222222222222222222222222222222222",
  "nonce": "1730000300000",
  "status": "active"
}
```

Possible `status` values:

```
active
inactive
expired
```

When no approval exists, the response uses an empty agent address and a zero nonce:

```json
{
  "agent": "",
  "nonce": "0",
  "status": "inactive"
}
```

### `getAgentApprovalNonce`

Returns only the parsed agent approval nonce for a master account.

```ts
const approvalNonce =
  await info.getAgentApprovalNonce(
    "0x1111111111111111111111111111111111111111",
  );
```

Endpoint:

```
GET /agents/status/:master
```

SDK response:

```ts
1730000300000n
```

The underlying HTTP endpoint returns the complete approval object:

```json
{
  "agent": "0x2222222222222222222222222222222222222222",
  "nonce": "1730000300000",
  "status": "active"
}
```

`getAgentApprovalNonce` parses and returns only the `nonce` field.

### `getExchangeConfig`

Returns the configured exchange contract addresses for a chain.

```ts
const config = await info.getExchangeConfig("84532");
```

Endpoint:

```
GET /config/chains/:chainId
```

The SDK README identifies this method and endpoint, but the supplied HTTP API documentation does not define the response body.

An example response should not be published until the development team provides the exact returned contract fields.

### Available methods

<table><thead><tr><th width="225.07293701171875">Method</th><th>Endpoint</th></tr></thead><tbody><tr><td><code>getAsset</code></td><td><code>GET /asset/:assetId</code></td></tr><tr><td><code>getResolutionPrice</code></td><td><code>GET /resolve/:assetId/:epoch</code></td></tr><tr><td><code>getLastResolutionPrice</code></td><td><code>GET /resolve/last/epoch/:assetId</code></td></tr><tr><td><code>getBalance</code></td><td><code>GET /balance/:account</code></td></tr><tr><td><code>getOrderBook</code></td><td><code>GET /book/:assetId/:epoch</code></td></tr><tr><td><code>getBookOrders</code></td><td><code>GET /book/:assetId/:epoch/:account</code></td></tr><tr><td><code>getTopOfBook</code></td><td><code>GET /book/market/top/:assetId/:epoch</code></td></tr><tr><td><code>getPosition</code></td><td><code>GET /position/:account/:assetId/:epoch</code></td></tr><tr><td><code>getAgentApproval</code></td><td><code>GET /agents/status/:master</code></td></tr><tr><td><code>getAgentApprovalNonce</code></td><td><code>GET /agents/status/:master</code></td></tr><tr><td><code>getExchangeConfig</code></td><td><code>GET /config/chains/:chainId</code></td></tr></tbody></table>


# Exchange Methods

`ExchangeClient` signs exchange actions with the configured `ethers` wallet and submits them to the Exchange HTTP API.

The SDK constructs the EIP-712 action, generates missing nonces, calculates the action hash, creates the signature, and serializes the HTTP request.

```ts
import { createExchangeClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.PRIVATE_KEY!);

const exchange = createExchangeClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
  wallet,
  chainId: "84532",
});
```

### `placeOrder`

Places an order signed directly by the configured wallet.

```ts
const result = await exchange.placeOrder({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  side: false,
  price: "99.9",
  size: "10.25",
});
```

Endpoint:

```
POST /orders
```

Response:

```json
{
  "orderId": "0x1111111111111111111111111111111111111111111111111111111111111111",
  "filled": "0",
  "remaining": "1025",
  "cancelled": "0",
  "status": "ACCEPTED",
  "reason": ""
}
```

Possible `status` values:

```
ACCEPTED
CANCELLED
REJECTED
FILLED
PARTIALLY_FILLED
```

Possible rejection reasons include:

```
IOC
FOK
MARGIN
INVALID_ORDER
MARKET_RESOLVED
INTERNAL_ERROR
ALO
UNKNOWN
```

### `placeAgentOrder`

Places an order signed by an approved agent for a master account.

```ts
const result = await exchange.placeAgentOrder({
  sender: "0x1111111111111111111111111111111111111111",
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  side: false,
  price: "99.9",
  size: "10.25",
});
```

Endpoint:

```
POST /orders
```

Response:

```json
{
  "orderId": "0x2222222222222222222222222222222222222222222222222222222222222222",
  "filled": "500",
  "remaining": "525",
  "cancelled": "0",
  "status": "PARTIALLY_FILLED",
  "reason": ""
}
```

The configured wallet is the agent signer. The `sender` field is the master account that owns the order.

If `approvalNonce` is omitted, the SDK fetches it through `InfoClient`.

### `cancelOrder`

Cancels one order signed directly by the configured wallet.

```ts
const result = await exchange.cancelOrder({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  orderHash:
    "0x1111111111111111111111111111111111111111111111111111111111111111",
});
```

Endpoint:

```
POST /cancels
```

Response:

```json
{
  "id": "0x3333333333333333333333333333333333333333333333333333333333333333",
  "orderIds": [
    "0x1111111111111111111111111111111111111111111111111111111111111111"
  ],
  "status": "CANCELLED"
}
```

Possible `status` values:

```
CANCELLED
CANCEL_FAILED
CANCEL_NOT_COMMITTED
```

### `cancelAll`

Cancels all orders owned by the configured wallet for an asset and epoch.

```ts
const result = await exchange.cancelAll({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
POST /cancels
```

The SDK uses the zero hash internally to request cancel-all.

Response:

```json
{
  "id": "0x4444444444444444444444444444444444444444444444444444444444444444",
  "orderIds": [
    "0x1111111111111111111111111111111111111111111111111111111111111111",
    "0x2222222222222222222222222222222222222222222222222222222222222222"
  ],
  "status": "CANCELLED"
}
```

### `cancelAgentOrder`

Cancels one master-account order as an approved agent.

```ts
const result = await exchange.cancelAgentOrder({
  sender: "0x1111111111111111111111111111111111111111",
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  orderHash:
    "0x2222222222222222222222222222222222222222222222222222222222222222",
});
```

Endpoint:

```
POST /cancels
```

Response:

```json
{
  "id": "0x5555555555555555555555555555555555555555555555555555555555555555",
  "orderIds": [
    "0x2222222222222222222222222222222222222222222222222222222222222222"
  ],
  "status": "CANCELLED"
}
```

If `approvalNonce` is omitted, the SDK fetches it through `InfoClient`.

### `cancelAllAgent`

Cancels all master-account orders for an asset and epoch as an approved agent.

```ts
const result = await exchange.cancelAllAgent({
  sender: "0x1111111111111111111111111111111111111111",
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
POST /cancels
```

Response:

```json
{
  "id": "0x6666666666666666666666666666666666666666666666666666666666666666",
  "orderIds": [
    "0x1111111111111111111111111111111111111111111111111111111111111111",
    "0x2222222222222222222222222222222222222222222222222222222222222222"
  ],
  "status": "CANCELLED"
}
```

### `cancelReplaceOrder`

Cancels one order and submits a replacement order in the same request.

```ts
const result = await exchange.cancelReplaceOrder({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  cancelOrderHash:
    "0x1111111111111111111111111111111111111111111111111111111111111111",
  side: false,
  price: "99.8",
  size: "10.25",
  allOrNothing: false,
});
```

Endpoint:

```
POST /cancel-replace
```

Response:

```json
{
  "id": "0x7777777777777777777777777777777777777777777777777777777777777777",
  "cancel": {
    "id": "0x8888888888888888888888888888888888888888888888888888888888888888",
    "orderIds": [
      "0x1111111111111111111111111111111111111111111111111111111111111111"
    ],
    "status": "CANCELLED"
  },
  "replacement": {
    "orderId": "0x9999999999999999999999999999999999999999999999999999999999999999",
    "filled": "0",
    "remaining": "1025",
    "cancelled": "0",
    "status": "ACCEPTED",
    "reason": ""
  },
  "status": "SUCCESS"
}
```

Possible top-level `status` values:

```
CANCEL_FAILED
REPLACEMENT_FAILED
CANCEL_COMMITTED_REPLACEMENT_FAILED
SUCCESS
```

The replacement must use the same market and side as the cancelled order. Cancel-all is not supported by cancel-and-replace.

### `cancelReplaceAgentOrder`

Cancels and replaces a master-account order as an approved agent.

```ts
const result = await exchange.cancelReplaceAgentOrder({
  sender: "0x1111111111111111111111111111111111111111",
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  cancelOrderHash:
    "0x2222222222222222222222222222222222222222222222222222222222222222",
  side: false,
  price: "99.8",
  size: "10.25",
  allOrNothing: false,
});
```

Endpoint:

```
POST /cancel-replace
```

Response:

```json
{
  "id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "cancel": {
    "id": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "orderIds": [
      "0x2222222222222222222222222222222222222222222222222222222222222222"
    ],
    "status": "CANCELLED"
  },
  "replacement": {
    "orderId": "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
    "filled": "0",
    "remaining": "1025",
    "cancelled": "0",
    "status": "ACCEPTED",
    "reason": ""
  },
  "status": "SUCCESS"
}
```

If `approvalNonce` is omitted, the SDK fetches it through `InfoClient`.

### `claim`

Claims available funds for a resolved asset epoch.

```ts
const result = await exchange.claim({
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
POST /claim
```

Response:

```json
{
  "id": "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
  "status": "CLAIMED"
}
```

Possible `status` values:

```
CLAIMED
CLAIM_FAILED
```

### `claimAgent`

Claims available funds for a master account as an approved agent.

```ts
const result = await exchange.claimAgent({
  sender: "0x1111111111111111111111111111111111111111",
  assetId: "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Endpoint:

```
POST /claim
```

Response:

```json
{
  "id": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
  "status": "CLAIMED"
}
```

If `approvalNonce` is omitted, the SDK fetches it through `InfoClient`.

### `withdraw`

Submits a signed withdrawal request.

```ts
const result = await exchange.withdraw({
  amount: "100.00",
  receiver: "0x2222222222222222222222222222222222222222",
});
```

Endpoint:

```
POST /withdrawals
```

Response:

```json
{
  "id": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
  "status": "WITHDRAWN"
}
```

Possible `status` values:

```
WITHDRAWN
WITHDRAWAL_FAILED
```

If `receiver` is omitted, it defaults to the configured wallet address.

### `approveAgent`

Approves an agent wallet to sign exchange actions for the master account.

```ts
const result = await exchange.approveAgent({
  agent: "0x2222222222222222222222222222222222222222",
});
```

Endpoint:

```
POST /agents/approve
```

Response:

```json
{
  "id": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "status": "SUCCESS"
}
```

Possible `status` values:

```
SUCCESS
FAIL
```

The agent must differ from the configured master wallet.

If `approvalNonce` is omitted, the SDK creates the required approval value.

### `revokeAgent`

Revokes the master account’s current agent approval.

```ts
const result = await exchange.revokeAgent({});
```

Endpoint:

```
POST /agents/revoke
```

Response:

```json
{
  "id": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd",
  "status": "SUCCESS"
}
```

Possible `status` values:

```
SUCCESS
FAIL
```

### `signAgentApproval`

Creates the inner agent-approval signature without submitting an HTTP request.

```ts
const signature = await exchange.signAgentApproval({
  agent: "0x2222222222222222222222222222222222222222",
  approvalNonce: "1730000300000",
});
```

SDK response:

```ts
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b"
```

This is a local signing operation. It does not approve the agent by itself. Use `approveAgent` to sign and submit the complete agent-approval action.

### Available methods

| Method                    | Endpoint or action     |
| ------------------------- | ---------------------- |
| `placeOrder`              | `POST /orders`         |
| `placeAgentOrder`         | `POST /orders`         |
| `cancelOrder`             | `POST /cancels`        |
| `cancelAll`               | `POST /cancels`        |
| `cancelReplaceOrder`      | `POST /cancel-replace` |
| `cancelAgentOrder`        | `POST /cancels`        |
| `cancelAllAgent`          | `POST /cancels`        |
| `cancelReplaceAgentOrder` | `POST /cancel-replace` |
| `claim`                   | `POST /claim`          |
| `claimAgent`              | `POST /claim`          |
| `withdraw`                | `POST /withdrawals`    |
| `approveAgent`            | `POST /agents/approve` |
| `revokeAgent`             | `POST /agents/revoke`  |
| `signAgentApproval`       | Local signing          |


# Deposit Methods

`DepositClient` communicates directly with on-chain contracts through the configured RPC URL.

Use it to read deposit state, inspect token balances and allowances, submit approvals, sign permits, and deposit settlement tokens.

```ts
import { createDepositClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.PRIVATE_KEY!);

const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: "84532",
});
```

The client verifies that the RPC network matches the configured `chainId` before performing contract reads or transactions.

On-chain integers are returned as TypeScript `bigint` values rather than JSON numbers.

### `getSettlementToken`

Returns the settlement-token contract address.

```ts
const settlementToken =
  await deposit.getSettlementToken();
```

SDK response:

```ts
"0x1111111111111111111111111111111111111111"
```

### `getPermit2`

Returns the Permit2 contract address used by the deposit system.

```ts
const permit2 = await deposit.getPermit2();
```

SDK response:

```ts
"0x2222222222222222222222222222222222222222"
```

### `getAccountLedger`

Returns the account-ledger contract address.

```ts
const accountLedger =
  await deposit.getAccountLedger();
```

SDK response:

```ts
"0x3333333333333333333333333333333333333333"
```

### `getPendingBalance`

Returns the connected account’s pending deposit balance.

```ts
const pendingBalance =
  await deposit.getPendingBalance();
```

SDK response:

```ts
100250000n
```

With six settlement-token decimals, this represents `100.25` tokens.

### `getProcessedBalance`

Returns the connected account’s processed deposit balance.

```ts
const processedBalance =
  await deposit.getProcessedBalance();
```

SDK response:

```ts
50000000n
```

With six settlement-token decimals, this represents `50` tokens.

### `getPendingDepositCount`

Returns the number of pending deposits.

```ts
const pendingCount =
  await deposit.getPendingDepositCount();
```

SDK response:

```ts
3n
```

### `getNextPendingDepositId`

Returns the next pending deposit ID.

```ts
const nextDepositId =
  await deposit.getNextPendingDepositId();
```

SDK response:

```ts
42n
```

### `getProcessedDepositIndex`

Returns the processed-deposit index.

```ts
const processedIndex =
  await deposit.getProcessedDepositIndex();
```

SDK response:

```ts
38n
```

### `getMinBlockWait`

Returns the minimum number of blocks that must pass before the next deposit can be processed.

```ts
const minBlockWait =
  await deposit.getMinBlockWait();
```

SDK response:

```ts
5n
```

### `canProcessNext`

Checks whether the next pending deposit can be processed.

```ts
const canProcess =
  await deposit.canProcessNext();
```

SDK response:

```ts
true
```

If processing requirements have not been met:

```ts
false
```

### `getSettlementTokenBalance`

Returns a settlement-token balance.

When no owner is supplied, the configured wallet address is used.

```ts
const balance =
  await deposit.getSettlementTokenBalance();
```

SDK response:

```ts
250000000n
```

With six settlement-token decimals, this represents `250` tokens.

An owner address can also be supplied:

```ts
const balance =
  await deposit.getSettlementTokenBalance(
    "0x4444444444444444444444444444444444444444",
  );
```

SDK response:

```ts
100250000n
```

### `getSettlementTokenAllowance`

Returns the settlement-token allowance for a spender.

```ts
const allowance =
  await deposit.getSettlementTokenAllowance();
```

SDK response:

```ts
1000000000n
```

Optional spender and owner addresses can be supplied:

```ts
const allowance =
  await deposit.getSettlementTokenAllowance(
    "0x2222222222222222222222222222222222222222",
    "0x4444444444444444444444444444444444444444",
  );
```

SDK response:

```ts
500000000n
```

### `approveDepositLedger`

Approves the deposit ledger to spend settlement tokens.

```ts
const result =
  await deposit.approveDepositLedger({
    // Approval input
  });
```

This method submits an on-chain transaction.

The supplied SDK documentation does not publish its complete input fields or return type. The exact response object should be copied from the SDK’s exported TypeScript definition before publishing a response example.

### `approvePermit2`

Approves Permit2 to spend settlement tokens.

```ts
const result =
  await deposit.approvePermit2({
    // Approval input
  });
```

This method submits an on-chain transaction.

### `deposit`

Submits an on-chain deposit transaction.

```ts
const result = await deposit.deposit({
  // Deposit input
});
```

The method logs the deposit transaction ID by default.

To suppress the log:

```ts
const result = await deposit.deposit({
  // Deposit input
  logTxId: false,
});
```

The SDK confirms that the method submits the transaction and logs the deposit `txId` by default.

### `signDepositPermit`

Creates a signed Permit2 deposit authorization without submitting an on-chain transaction.

```ts
const permit =
  await deposit.signDepositPermit({
    // Permit input
  });
```

### `depositWithPermit`

Submits an on-chain deposit using a signed Permit2 authorization.

```ts
const result =
  await deposit.depositWithPermit({
    // Deposit and permit input
  });
```

### `parseAmount`

Converts a human decimal amount into settlement-token base units.

```ts
const amount = deposit.parseAmount("100.25");
```

SDK response:

```ts
100250000n
```

The settlement token uses six decimals.

Additional examples:

```ts
deposit.parseAmount("1");
```

Response:

```ts
1000000n
```

```ts
deposit.parseAmount("0.01");
```

Response:

```ts
10000n
```

Invalid precision, zero amounts, negative values, and out-of-range values are rejected before a transaction is submitted.

### Available methods

| Method                        | Operation                                     |
| ----------------------------- | --------------------------------------------- |
| `getSettlementToken`          | Read settlement-token address                 |
| `getPermit2`                  | Read Permit2 address                          |
| `getAccountLedger`            | Read account-ledger address                   |
| `getPendingBalance`           | Read pending deposit balance                  |
| `getProcessedBalance`         | Read processed deposit balance                |
| `getPendingDepositCount`      | Read pending-deposit count                    |
| `getNextPendingDepositId`     | Read next pending deposit ID                  |
| `getProcessedDepositIndex`    | Read processed-deposit index                  |
| `getMinBlockWait`             | Read minimum block wait                       |
| `canProcessNext`              | Check whether the next deposit is processable |
| `getSettlementTokenBalance`   | Read settlement-token balance                 |
| `getSettlementTokenAllowance` | Read token allowance                          |
| `approveDepositLedger`        | Approve the deposit ledger                    |
| `approvePermit2`              | Approve Permit2                               |
| `deposit`                     | Submit a deposit                              |
| `signDepositPermit`           | Sign a deposit permit                         |
| `depositWithPermit`           | Deposit using a permit                        |
| `parseAmount`                 | Convert a human amount into base units        |


# Subscription Methods

`ExchangeWebSocketClient` manages the market WebSocket connection and subscribes to real-time order, trade, cancel, and resolution updates by `assetId`.

```ts
import {
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const marketStream = createExchangeWebSocketClient({
  websocketUrl: "wss://exchange-api.gammaswap.com/ws/",
  onError: (error) => console.error(error),
});
```

### `connectionState`

Returns the current state of the market WebSocket connection.

```ts
const state = marketStream.connectionState;
```

SDK response:

```ts
state
```

### `connect`

Opens the market WebSocket connection.

```ts
const result = await marketStream.connect();
```

After the socket opens, the WebSocket service sends a `connected` control message:

```json
{
  "type": "connected",
  "message": "Send {\"type\":\"subscribe\",\"assetId\":\"...\"} to receive market updates"
}
```

Calling `connect()` before subscribing is optional. `subscribeOrderBook` establishes the connection when necessary.

### `subscribeOrderBook`

Subscribes to market updates for an `assetId`.

```ts
const unsubscribe =
  await marketStream.subscribeOrderBook(
    "261336857817713630688382311349658711122006440411137",
    {
      onUpdate: (update) => {
        console.log("Market update", update);
      },
      onOrder: (update) => {
        console.log("Order update", update);
      },
      onTrade: (update) => {
        console.log("Trade update", update);
      },
      onCancel: (update) => {
        console.log("Cancel update", update);
      },
      onResolution: (update) => {
        console.log("Resolution update", update);
      },
      onError: (error) => {
        console.error("Subscription error", error);
      },
      onResyncRequired: (assetId) => {
        console.log("Reload REST order book", assetId);
      },
    },
  );
```

SDK response:

```ts
async () => {
  // Removes this handler.
}
```

The resolved value is an asynchronous unsubscribe function:

```ts
await unsubscribe();
```

The underlying WebSocket subscription message is:

```json
{
  "type": "subscribe",
  "assetId": "261336857817713630688382311349658711122006440411137"
}
```

After the subscription is accepted, the service sends:

```json
{
  "type": "subscribed",
  "assetId": "261336857817713630688382311349658711122006440411137"
}
```

### `unsubscribeOrderBook`

Removes all local handlers for an `assetId` and unsubscribes from that asset’s server-side market feed.

```ts
const result =
  await marketStream.unsubscribeOrderBook(
    "261336857817713630688382311349658711122006440411137",
  );
```

SDK response:

```ts
undefined
```

The underlying WebSocket unsubscribe message is:

```json
{
  "type": "unsubscribe",
  "assetId": "261336857817713630688382311349658711122006440411137"
}
```

After the subscription is removed, the service sends:

```json
{
  "type": "unsubscribed",
  "assetId": "261336857817713630688382311349658711122006440411137"
}
```

`unsubscribeOrderBook` removes every local handler for the asset.

The unsubscribe function returned by `subscribeOrderBook` removes only the handler associated with that call.

### `close`

Closes the market WebSocket connection.

```ts
const result = marketStream.close();
```

An optional WebSocket close code and reason can be supplied:

```ts
marketStream.close(1000, "Client shutdown");
```

The client does not reconnect after an intentional close unless a new connection or subscription is started.

### Order updates

Order updates are delivered to `onOrder` and `onUpdate`.

```json
{
  "type": "order",
  "seqId": 123,
  "data": {}
}
```

### Trade updates

Trade updates are delivered to `onTrade` and `onUpdate`.

```json
{
  "type": "trade",
  "seqId": 124,
  "data": {}
}
```

### Cancel updates

Cancel updates are delivered to `onCancel` and `onUpdate`.

```json
{
  "type": "cancel",
  "seqId": 125,
  "data": {}
}
```

### Resolution updates

Resolution updates are delivered to `onResolution` and `onUpdate`.

```json
{
  "type": "resolution",
  "seqId": 126,
  "data": {}
}
```

### Error messages

If a subscription message cannot be processed, the service sends an error message:

```json
{
  "type": "error",
  "message": "AssetId 123 is not available"
}
```

Errors are delivered to:

* The subscription’s `onError` handler
* The client-level `onError` handler, where applicable

### Resynchronization

Market updates include a sequence ID.

If the connection drops or the client determines that the socket is unhealthy, active subscriptions receive:

```ts
onResyncRequired(assetId);
```

Reload the complete order-book snapshot through `InfoClient` before applying subsequent updates:

```ts
const freshBook = await info.getOrderBook({
  assetId,
  epoch,
});
```

### Subscription behavior

One client can subscribe to multiple asset IDs.

When multiple handlers subscribe to the same asset:

1. The client creates one server subscription.
2. Updates are distributed to every local handler.
3. The returned unsubscribe function removes only its handler.
4. The server subscription is removed after the last handler unsubscribes.

### Reconnection defaults

| Option                | Default |
| --------------------- | ------: |
| `reconnect`           |  `true` |
| `reconnectDelayMs`    |  `1000` |
| `maxReconnectDelayMs` | `30000` |
| `ackTimeoutMs`        | `15000` |

### Heartbeats

The server sends protocol-level WebSocket ping frames.

Browser WebSockets and the Node `ws` client respond with protocol pong frames automatically.

Do not send an application-level pong message:

```json
{
  "type": "pong"
}
```

### Available methods and properties

<table><thead><tr><th width="216.5625">Name</th><th>SDK response</th></tr></thead><tbody><tr><td><code>connectionState</code></td><td>Current connection-state value</td></tr><tr><td><code>connect</code></td><td><code>Promise&#x3C;void></code></td></tr><tr><td><code>close</code></td><td><code>void</code></td></tr><tr><td><code>subscribeOrderBook</code></td><td><code>Promise&#x3C;unsubscribe function></code></td></tr><tr><td><code>unsubscribeOrderBook</code></td><td><code>Promise&#x3C;void></code></td></tr></tbody></table>


# Oracle Methods

`OracleWebSocketClient` manages the oracle WebSocket connection and subscribes to real-time price updates by `symbolId`.

```ts
import {
  createOracleWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const oracleStream = createOracleWebSocketClient({
  websocketUrl: "wss://exchange-api.gammaswap.com/oracle-ws/",
  stalePriceTimeoutMs: 30_000,
  onError: (error) => console.error(error),
});
```

### `connectionState`

Returns the current state of the oracle WebSocket connection.

```ts
const state = oracleStream.connectionState;
```

### `connect`

Opens the oracle WebSocket connection.

```ts
const result = await oracleStream.connect();
```

After the socket opens, the WebSocket service sends a `connected` control message:

```json
{
  "type": "connected",
  "message": "Send {\"type\":\"subscribe\",\"symbolId\":\"...\"} to receive prices"
}
```

Calling `connect()` before subscribing is optional. `subscribePrice` establishes the connection when necessary.

### `subscribePrice`

Subscribes to live oracle prices for a `symbolId`.

```ts
const unsubscribe =
  await oracleStream.subscribePrice("1", {
    onPrice: (update) => {
      console.log(
        "Price update",
        update.symbolId,
        update.price,
        update.ts,
      );
    },
    onError: (error) => {
      console.error("Subscription error", error);
    },
    onStale: (symbolId) => {
      console.warn("Oracle price is stale", symbolId);
    },
  });
```

The resolved value is an asynchronous unsubscribe function:

```ts
await unsubscribe();
```

The underlying WebSocket subscription message is:

```json
{
  "type": "subscribe",
  "symbolId": "1"
}
```

After the subscription is accepted, the service sends:

```json
{
  "type": "subscribed",
  "symbolId": "1"
}
```

### Price updates

Price updates are delivered to the subscription’s `onPrice` handler.

```json
{
  "type": "price",
  "symbolId": "1",
  "price": "123456789",
  "ts": 1730000000
}
```

The update contains:

<table><thead><tr><th width="114.5859375">Field</th><th width="97.921875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td>string</td><td>Always <code>price</code> for a price update</td></tr><tr><td><code>symbolId</code></td><td>string</td><td>Subscribed oracle symbol ID</td></tr><tr><td><code>price</code></td><td>string</td><td>Current oracle price</td></tr><tr><td><code>ts</code></td><td>number</td><td>Price timestamp</td></tr></tbody></table>

Example handler:

```ts
onPrice: (update) => {
  if (update.symbolId === "1") {
    console.log(update.price);
  }
}
```

Handler input:

```ts
{
  type: "price",
  symbolId: "1",
  price: "123456789",
  ts: 1730000000,
}
```

### `unsubscribePrice`

Removes all local handlers for a `symbolId` and unsubscribes from that symbol’s server-side price feed.

```ts
const result =
  await oracleStream.unsubscribePrice("1");
```

The underlying WebSocket unsubscribe message is:

```json
{
  "type": "unsubscribe",
  "symbolId": "1"
}
```

After the subscription is removed, the service sends:

```json
{
  "type": "unsubscribed",
  "symbolId": "1"
}
```

`unsubscribePrice` removes every local handler for the symbol.

The unsubscribe function returned by `subscribePrice` removes only the handler associated with that call.

### Unavailable symbols

If a symbol becomes unavailable after subscription, the service can send:

```json
{
  "type": "unsubscribed",
  "symbolId": "1",
  "reason": "symbol unavailable"
}
```

The symbol is no longer subscribed after this message.

### `close`

Closes the oracle WebSocket connection.

```ts
const result = oracleStream.close();
```

An optional WebSocket close code and reason can be supplied:

```ts
oracleStream.close(1000, "Client shutdown");
```

The client does not reconnect after an intentional close unless a new connection or subscription is started.

### Error messages

If a subscription message cannot be processed, the service sends an error message:

```json
{
  "type": "error",
  "message": "SymbolId 123 is not available"
}
```

Errors are delivered to:

* The subscription’s `onError` handler
* The client-level `onError` handler, where applicable

### Stale prices

`stalePriceTimeoutMs` defines the maximum time the client waits without receiving a price update for a subscribed symbol.

The default is:

```ts
30_000
```

If no price arrives before the timeout, the client calls:

```ts
onStale("1");
```

The handler receives the stale `symbolId`:

```ts
"1"
```

The client also:

1. Emits an error.
2. Abandons the unhealthy socket.
3. Reconnects when active subscriptions remain.

The oracle stream does not provide sequence IDs or REST catch-up.

After reconnecting or receiving `onStale`, accept the next live price update for the symbol.

### Subscription behavior

One client can subscribe to multiple symbol IDs.

When multiple handlers subscribe to the same symbol:

1. The client creates one server subscription.
2. Price updates are distributed to every local handler.
3. The returned unsubscribe function removes only its handler.
4. The server subscription is removed after the last handler unsubscribes.

### Reconnection defaults

| Option                | Default |
| --------------------- | ------: |
| `reconnect`           |  `true` |
| `reconnectDelayMs`    |  `1000` |
| `maxReconnectDelayMs` | `30000` |
| `ackTimeoutMs`        | `15000` |
| `stalePriceTimeoutMs` | `30000` |

### Heartbeats

The server sends protocol-level WebSocket ping frames.

Browser WebSockets and the Node `ws` client respond with protocol pong frames automatically.

Do not send an application-level pong message:

```json
{
  "type": "pong"
}
```

### Available methods and properties

<table><thead><tr><th width="181.1953125">Name</th><th>SDK response</th></tr></thead><tbody><tr><td><code>connectionState</code></td><td>Current connection-state value</td></tr><tr><td><code>connect</code></td><td><code>Promise&#x3C;void></code></td></tr><tr><td><code>close</code></td><td><code>void</code></td></tr><tr><td><code>subscribePrice</code></td><td><code>Promise&#x3C;unsubscribe function></code></td></tr><tr><td><code>unsubscribePrice</code></td><td><code>Promise&#x3C;void></code></td></tr></tbody></table>


# Guides

{% content-ref url="/pages/m82msOcmJ1uHmqbVaykt" %}
[Agent Wallets](/developers/guides/agent-wallets)
{% endcontent-ref %}

{% content-ref url="/pages/bqpGuUsqreRG7DVWpfKe" %}
[Deposits](/developers/guides/deposits)
{% endcontent-ref %}

{% content-ref url="/pages/x7OBqQ6L0etHAanzwsot" %}
[Signing Direct Requests](/developers/guides/signing-direct-requests)
{% endcontent-ref %}

{% content-ref url="/pages/EVS8QVu0r6PbTBXCCgGq" %}
[Maintaining an Order Book](/developers/guides/maintaining-an-order-book)
{% endcontent-ref %}

{% content-ref url="/pages/JIYvCokLw2l0AzbhJKGx" %}
[WebSocket Reconnection](/developers/guides/websocket-reconnection)
{% endcontent-ref %}


# Agent Wallets

An agent wallet is a separate EVM wallet that is approved to sign exchange actions for a master account.

Agent wallets allow applications and automated trading systems to sign actions without using the master account’s private key for every request.

The master account continues to own:

* Balances
* Positions
* Orders
* Claims
* Withdrawable funds

The agent wallet is only the signer for supported exchange actions.

### Supported agent actions

An approved agent can submit:

* Orders
* Cancels
* Cancel-all requests
* Cancel-and-replace requests
* Claims

Agent withdrawals are not supported by the documented SDK methods.

### Generate an agent wallet

Use `Wallet.createRandom()` to generate a new EVM wallet.

```ts
import { Wallet } from "ethers";

const agentWallet = Wallet.createRandom();

console.log("Agent address:", agentWallet.address);
```

The generated private key is available as:

```ts
agentWallet.privateKey
```

Store the private key in a server-side secret manager.

Do not:

* Commit the private key to source control
* Include it in a frontend bundle
* Store it in browser local storage
* Print it in production logs
* Share it with the master wallet’s users

### Create the master client

The master wallet must approve and revoke agents.

```ts
import { createExchangeClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const masterWallet = new Wallet(
  process.env.MASTER_PRIVATE_KEY!,
);

const masterClient = createExchangeClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
  wallet: masterWallet,
  chainId: "84532",
});
```

### Approve the agent

Approve the generated agent address with the master wallet.

```ts
const result = await masterClient.approveAgent({
  agent: agentWallet.address,
});
```

Response:

```json
{
  "id": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "status": "SUCCESS"
}
```

Possible statuses:

```
SUCCESS
FAIL
```

The SDK:

1. Validates the master and agent addresses.
2. Ensures the agent differs from the master.
3. Creates the inner agent-approval signature.
4. Creates the signed outer approval action.
5. Submits the approval to `POST /agents/approve`.

An explicit approval nonce can also be provided:

```ts
await masterClient.approveAgent({
  agent: agentWallet.address,
  approvalNonce: "1730000300000",
});
```

### Create an agent client

Create a separate `ExchangeClient` using the agent wallet.

```ts
const agentClient = createExchangeClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
  wallet: agentWallet,
  chainId: "84532",
});
```

The configured wallet is now the agent signer.

For agent methods, pass the master account as `sender`.

### Place an agent order

```ts
const result = await agentClient.placeAgentOrder({
  sender: masterWallet.address,
  assetId:
    "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  side: false,
  price: "99.9",
  size: "10.25",
});
```

Response:

```json
{
  "orderId": "0x1111111111111111111111111111111111111111111111111111111111111111",
  "filled": "0",
  "remaining": "1025",
  "cancelled": "0",
  "status": "ACCEPTED",
  "reason": ""
}
```

### Cancel an order as an agent

```ts
const result = await agentClient.cancelAgentOrder({
  sender: masterWallet.address,
  assetId:
    "261336857817713630688382311349658711122006440411137",
  epoch: "12",
  orderHash:
    "0x1111111111111111111111111111111111111111111111111111111111111111",
});
```

Response:

```json
{
  "id": "0x2222222222222222222222222222222222222222222222222222222222222222",
  "orderIds": [
    "0x1111111111111111111111111111111111111111111111111111111111111111"
  ],
  "status": "CANCELLED"
}
```

### Cancel all orders as an agent

```ts
const result = await agentClient.cancelAllAgent({
  sender: masterWallet.address,
  assetId:
    "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

The SDK uses the zero order hash internally to request cancel-all.

### Cancel and replace as an agent

```ts
const result =
  await agentClient.cancelReplaceAgentOrder({
    sender: masterWallet.address,
    assetId:
      "261336857817713630688382311349658711122006440411137",
    epoch: "12",
    cancelOrderHash:
      "0x1111111111111111111111111111111111111111111111111111111111111111",
    side: false,
    price: "99.8",
    size: "10.25",
  });
```

The replacement order must use the same market and side as the cancelled order.

### Claim as an agent

```ts
const result = await agentClient.claimAgent({
  sender: masterWallet.address,
  assetId:
    "261336857817713630688382311349658711122006440411137",
  epoch: "12",
});
```

Response:

```json
{
  "id": "0x3333333333333333333333333333333333333333333333333333333333333333",
  "status": "CLAIMED"
}
```

### Approval nonces

Agent actions contain both:

* An action `nonce`
* An agent `approvalNonce`

The action nonce identifies the signed action.

The approval nonce identifies the active agent approval.

When `approvalNonce` is omitted, the SDK fetches it through `InfoClient`.

```ts
const result = await agentClient.placeAgentOrder({
  sender: masterWallet.address,
  assetId,
  epoch,
  side: false,
  price: "99.9",
  size: "10.25",
  approvalNonce: "1730000300000",
});
```

### Check agent status

Create an `InfoClient` to check the current approval.

```ts
import { createInfoClient } from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
});

const approval =
  await info.getAgentApproval(masterWallet.address);
```

Response:

```json
{
  "agent": "0x2222222222222222222222222222222222222222",
  "nonce": "1730000300000",
  "status": "active"
}
```

Possible statuses:

```
active
inactive
expired
```

Get only the parsed approval nonce:

```ts
const approvalNonce =
  await info.getAgentApprovalNonce(
    masterWallet.address,
  );
```

SDK response:

```ts
1730000300000n
```

### Revoke the agent

Agent revocation must be signed by the master wallet.

```ts
const result =
  await masterClient.revokeAgent({});
```

Response:

```json
{
  "id": "0x4444444444444444444444444444444444444444444444444444444444444444",
  "status": "SUCCESS"
}
```

After revocation, the agent can no longer submit actions for the master account.

### Signing without submitting

Use `signAgentApproval` to create the inner agent-approval signature without submitting the complete approval request.

```ts
const signature =
  await masterClient.signAgentApproval({
    agent: agentWallet.address,
    approvalNonce: "1730000300000",
  });
```

This does not approve the agent by itself.

Use `approveAgent` to submit the complete approval.

### Multiple trading processes

Nonce uniqueness is local to one `NonceManager` instance.

If several processes use the same agent wallet, coordinate their action nonces with:

* A shared `NonceManager`
* A centralized nonce service
* A shared atomic counter
* Explicit coordinated nonces

For independent trading processes, use a separate agent wallet for each process when possible.


# Deposits

Deposits are submitted directly to on-chain contracts through `DepositClient`.

Unlike orders and other exchange actions, deposits do not use the Exchange HTTP API.

A deposit integration requires:

* An EVM wallet
* A JSON-RPC provider
* The correct chain ID
* A configured or discoverable deposit-ledger contract
* Settlement tokens in the connected wallet

### Create a deposit client

```ts
import { createDepositClient } from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(
  process.env.PRIVATE_KEY!,
);

const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: "84532",
});
```

The client connects the wallet to the configured RPC provider.

Before performing reads or transactions, it checks that the RPC network matches `chainId`.

### Contract configuration

The deposit-ledger address can be resolved from the SDK’s default contracts for the selected chain.

It can also be supplied directly:

```ts
const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: "84532",
  depositLedger:
    "0x1111111111111111111111111111111111111111",
});
```

Custom contract overrides can be supplied with `contracts`:

```ts
const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: "84532",
  contracts: {
    // Chain-specific contract overrides
  },
});
```

Settlement-token decimals are currently required to be six:

```ts
const deposit = createDepositClient({
  rpcUrl: process.env.RPC_URL!,
  wallet,
  chainId: "84532",
  settlementTokenDecimals: 6,
});
```

### Discover deposit contracts

Read the settlement-token address:

```ts
const settlementToken =
  await deposit.getSettlementToken();
```

Response:

```ts
"0x2222222222222222222222222222222222222222"
```

Read the Permit2 address:

```ts
const permit2 = await deposit.getPermit2();
```

Response:

```ts
"0x3333333333333333333333333333333333333333"
```

Read the account-ledger address:

```ts
const accountLedger =
  await deposit.getAccountLedger();
```

Response:

```ts
"0x4444444444444444444444444444444444444444"
```

### Parse a deposit amount

Deposit amounts use human decimal strings.

```ts
const amount = deposit.parseAmount("100.25");
```

Response:

```ts
100250000n
```

The settlement token uses six decimal places.

Additional examples:

```ts
deposit.parseAmount("1");
```

Response:

```ts
1000000n
```

```ts
deposit.parseAmount("0.01");
```

Response:

```ts
10000n
```

### Check the token balance

Read the connected wallet’s settlement-token balance:

```ts
const balance =
  await deposit.getSettlementTokenBalance();
```

Response:

```ts
250000000n
```

This represents `250` settlement tokens when the token uses six decimals.

Read another owner’s balance:

```ts
const balance =
  await deposit.getSettlementTokenBalance(
    "0x5555555555555555555555555555555555555555",
  );
```

### Check token allowance

```ts
const allowance =
  await deposit.getSettlementTokenAllowance();
```

Response:

```ts
1000000000n
```

An owner and spender can also be supplied explicitly.

```ts
const allowance =
  await deposit.getSettlementTokenAllowance(
    "0x3333333333333333333333333333333333333333",
    wallet.address,
  );
```

### Approve the deposit ledger

The deposit ledger must have sufficient token allowance before it can transfer settlement tokens.

```ts
const result =
  await deposit.approveDepositLedger({
    // Approval input
  });
```

This method submits an on-chain token-approval transaction.

The exact approval input and returned transaction type are not included in the supplied SDK documentation. These fields should be copied from the SDK’s exported TypeScript definitions.

### Approve Permit2

Permit-based deposits require an appropriate Permit2 token allowance.

```ts
const result =
  await deposit.approvePermit2({
    // Approval input
  });
```

This method submits an on-chain token-approval transaction.

The exact approval input and returned transaction type are not included in the supplied SDK documentation.

### Submit a deposit

```ts
const result = await deposit.deposit({
  // Deposit input
});
```

The method logs the deposit `txId` by default.

Disable transaction-ID logging with:

```ts
const result = await deposit.deposit({
  // Deposit input
  logTxId: false,
});
```

The exact deposit input and return type are not included in the supplied SDK documentation. They should be documented from the SDK’s exported TypeScript definitions.

### Sign a deposit permit

`signDepositPermit` creates a signed Permit2 authorization without submitting a transaction.

```ts
const permit =
  await deposit.signDepositPermit({
    // Permit input
  });
```

Keep the signed permit with the deposit request that will consume it.

The exact returned permit structure is not included in the supplied SDK documentation.

### Deposit with a permit

```ts
const result =
  await deposit.depositWithPermit({
    // Deposit and signed permit input
  });
```

This method submits an on-chain deposit using the signed Permit2 authorization.

The exact input and return type should be copied from the SDK’s exported TypeScript definitions.

### Check deposit state

Read the pending balance:

```ts
const pendingBalance =
  await deposit.getPendingBalance();
```

Read the processed balance:

```ts
const processedBalance =
  await deposit.getProcessedBalance();
```

Read the number of pending deposits:

```ts
const pendingCount =
  await deposit.getPendingDepositCount();
```

Read the next pending deposit ID:

```ts
const nextDepositId =
  await deposit.getNextPendingDepositId();
```

Read the processed-deposit index:

```ts
const processedIndex =
  await deposit.getProcessedDepositIndex();
```

Read the minimum required block wait:

```ts
const minBlockWait =
  await deposit.getMinBlockWait();
```

Check whether the next deposit can be processed:

```ts
const canProcess =
  await deposit.canProcessNext();
```

Response:

```ts
true
```

### Recommended deposit flow

1. Create `DepositClient`.
2. Verify the RPC network and chain ID.
3. Read the settlement-token and deposit-ledger addresses.
4. Parse the human deposit amount.
5. Check the wallet’s settlement-token balance.
6. Check the required token allowance.
7. Approve the deposit ledger or Permit2 if necessary.
8. Submit `deposit` or `depositWithPermit`.
9. Record the transaction hash or deposit ID returned by the SDK.
10. Monitor pending and processed deposit state.

### Error handling

Deposit operations can fail because of:

* RPC connection failures
* Incorrect chain ID
* Insufficient token balance
* Insufficient allowance
* Invalid amount precision
* Wallet signature rejection
* Contract reverts
* Permit expiration or invalid permit data

Handle transaction failures through the error objects returned by the configured EVM provider and wallet.


# Signing Direct Requests

The recommended way to submit signed exchange actions is through `ExchangeClient`.

Use direct signing only when implementing a custom HTTP client or integrating the Exchange API without the SDK client wrapper.

Direct signing requires exact agreement on:

* Action type
* Field names
* Field order
* Solidity integer widths
* Addresses
* Numeric conversion
* Chain ID
* Verifying contract
* EIP-712 domain

Changing any signed field after calculating the digest invalidates the signature.

### EIP-712 domain

Signed exchange actions use this domain:

```
name: GammaSwap Exchange
version: 2
chainId: request chain ID
verifyingContract: exchange verifying contract
```

The domain prevents a signature from being reused against another chain or exchange deployment.

### Signed request wrapper

Most signed HTTP endpoints use this outer structure:

```json
{
  "action": {},
  "chainId": "84532",
  "orderHash": "0x...",
  "signature": "0x..."
}
```

The action field depends on the endpoint:

| Endpoint               | Action field    |
| ---------------------- | --------------- |
| `POST /orders`         | `order`         |
| `POST /cancels`        | `cancel`        |
| `POST /cancel-replace` | `cancelReplace` |
| `POST /withdrawals`    | `withdrawal`    |
| `POST /claim`          | `claim`         |
| `POST /agents/approve` | `approval`      |
| `POST /agents/revoke`  | `revocation`    |

The field name `orderHash` is historical.

It contains the EIP-712 digest of the signed action, even when the action is a cancel, withdrawal, claim, approval, or revocation.

It is not a hash of the outer HTTP request body and does not include `signature`.

### Signature types

<table><thead><tr><th width="86.890625" align="right">Value</th><th width="97.890625">Type</th><th>Meaning</th></tr></thead><tbody><tr><td align="right"><code>0</code></td><td>EOA</td><td>The sender signs directly</td></tr><tr><td align="right"><code>4</code></td><td>Agent</td><td>An approved agent signs for a master account</td></tr></tbody></table>

### Signing process

A direct client signs an action in this order:

1. Construct the complete action object.
2. Convert human inputs into exact protocol integers.
3. Construct the EIP-712 domain.
4. Calculate the action-specific struct hash.
5. Calculate the final EIP-712 digest.
6. Sign the digest.
7. Serialize bigint fields as decimal strings.
8. Send the action, chain ID, digest, and signature.

The final digest is constructed as:

```
keccak256("\x19\x01" || domainSeparator || actionStructHash)
```

### Hash helpers

The SDK exports helpers for each action type.

<table><thead><tr><th width="172.13800048828125">Action</th><th width="133.99212646484375">Endpoint</th><th>Hash helper</th></tr></thead><tbody><tr><td>Order</td><td><code>POST /orders</code></td><td><code>hashFillOrderJS</code></td></tr><tr><td>Cancel</td><td><code>POST /cancels</code></td><td><code>hashCancelOrderJS</code></td></tr><tr><td>Cancel-replace</td><td><code>POST /cancel-replace</code></td><td><code>hashCancelReplaceOrderJS</code></td></tr><tr><td>Replacement order</td><td><code>POST /cancel-replace</code></td><td><code>hashFillOrderJS</code></td></tr><tr><td>Withdrawal</td><td><code>POST /withdrawals</code></td><td><code>hashWithdrawalOrderJS</code></td></tr><tr><td>Claim</td><td><code>POST /claim</code></td><td><code>hashClaimOrderJS</code></td></tr><tr><td>Agent approval</td><td><code>POST /agents/approve</code></td><td><code>hashApproveAgentOrderJS</code></td></tr><tr><td>Inner agent approval</td><td><code>POST /agents/approve</code></td><td><code>hashAgentApprovalJS</code></td></tr><tr><td>Agent revocation</td><td><code>POST /agents/revoke</code></td><td><code>hashRevokeAgentOrderJS</code></td></tr></tbody></table>

### Sign an order

```ts
import {
  getExchangeDomain,
  hashFillOrderJS,
  signOrderJS,
} from "@gammaswap/v2-exchange-sdk";
import { Wallet } from "ethers";

const wallet = new Wallet(
  process.env.PRIVATE_KEY!,
);

const chainId = 84532n;

const verifyingContract =
  "0x1111111111111111111111111111111111111111";

const assetId =
  261336857817713630688382311349658711122006440411137n;

const epoch = 12n;
const nonce = 113377280000000000n;

const order = {
  typ: 2n,
  nonce,
  signer: wallet.address,
  signatureType: 0n,
  sender: wallet.address,
  epoch,
  side: false,
  assetId,
  size: 1025n,
  price: 999000n,
  timeInForce: 0n,
  approvalNonce: 0n,
};

const domain = getExchangeDomain(
  chainId,
  verifyingContract,
);

const orderHash =
  hashFillOrderJS(order, domain);

const signature =
  signOrderJS(orderHash, wallet);
```

### Serialize the request

Bigint values must be serialized as decimal strings before JSON encoding.

```ts
const body = {
  order: {
    typ: order.typ.toString(),
    nonce: order.nonce.toString(),
    signer: order.signer,
    signatureType:
      order.signatureType.toString(),
    sender: order.sender,
    epoch: order.epoch.toString(),
    side: order.side,
    assetId: order.assetId.toString(),
    size: order.size.toString(),
    price: order.price.toString(),
    timeInForce:
      order.timeInForce.toString(),
    approvalNonce:
      order.approvalNonce.toString(),
  },
  chainId: chainId.toString(),
  orderHash,
  signature,
};
```

Serialized request body:

```json
{
  "order": {
    "typ": "2",
    "nonce": "113377280000000000",
    "signer": "0x2222222222222222222222222222222222222222",
    "signatureType": "0",
    "sender": "0x2222222222222222222222222222222222222222",
    "epoch": "12",
    "side": false,
    "assetId": "261336857817713630688382311349658711122006440411137",
    "size": "1025",
    "price": "999000",
    "timeInForce": "0",
    "approvalNonce": "0"
  },
  "chainId": "84532",
  "orderHash": "0x...",
  "signature": "0x..."
}
```

### Submit the request

```ts
const response = await fetch(
  "https://exchange-api.gammaswap.com/api/orders",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  },
);

const result = await response.json();
```

Successful response:

```json
{
  "orderId": "0x3333333333333333333333333333333333333333333333333333333333333333",
  "filled": "0",
  "remaining": "1025",
  "cancelled": "0",
  "status": "ACCEPTED",
  "reason": ""
}
```

### Cancel-and-replace signing

Cancel-and-replace contains two separately signed actions:

* The replacement order
* The cancel-replace action

Sign the replacement order first:

```ts
const replacementOrderHash =
  hashFillOrderJS(replacement, domain);

const replacementSignature =
  signOrderJS(
    replacementOrderHash,
    wallet,
  );
```

The cancel-replace action includes `replacementOrderHash`.

```ts
const cancelReplace = {
  // Cancel-replace fields
  replacementOrderHash,
};
```

Calculate and sign the cancel-replace digest:

```ts
const orderHash =
  hashCancelReplaceOrderJS(
    cancelReplace,
    domain,
  );

const signature =
  signOrderJS(orderHash, wallet);
```

The HTTP body includes both signatures:

```json
{
  "cancelReplace": {},
  "replacement": {},
  "chainId": "84532",
  "orderHash": "0x...",
  "signature": "0x...",
  "replacementOrderHash": "0x...",
  "replacementSignature": "0x..."
}
```

### Agent approval signing

Agent approval contains two signatures:

1. An inner signature over the agent approval.
2. An outer signature over the approve-agent action.

Use:

```
hashAgentApprovalJS
```

for the inner agent approval and:

```
hashApproveAgentOrderJS
```

for the outer approval action.

### Address handling

Normalize relevant address fields consistently before hashing and sending requests.

The API normalizes signed address fields to lowercase when validating hashes and signatures.

A difference in address representation can produce a different digest if the direct signing implementation does not follow the same normalization rules.

### Numeric handling

Do not use floating-point JavaScript numbers when building signed actions.

Use exact integer values:

```ts
const price = 999000n;
const size = 1025n;
```

Do not sign:

```ts
const price = 99.9;
const size = 10.25;
```

Human decimal inputs must be converted before hashing.

### Common signature failures

A request can fail authentication if:

* The wrong chain ID is used.
* The wrong verifying contract is used.
* A signed field changes before submission.
* Human decimal values are signed instead of protocol integers.
* Fields are encoded in the wrong order.
* An incorrect Solidity integer width is used.
* The submitted digest does not match the action.
* The recovered signer does not match the action’s `signer`.
* An agent approval is missing, inactive, or expired.
* The wrong approval nonce is used.
* The signature type does not match the signing flow.

Common API errors include:

```
ORDER_HASH_MISMATCH
CANCEL_ORDER_HASH_MISMATCH
CANCEL_REPLACE_ORDER_HASH_MISMATCH
WITHDRAWAL_ORDER_HASH_MISMATCH
APPROVE_AGENT_ORDER_HASH_MISMATCH
REVOKE_ORDER_HASH_MISMATCH
INVALID_SIGNATURE
INVALID_APPROVAL_SIGNATURE
```

For most integrations, use `ExchangeClient` instead of constructing direct signatures manually.


# Maintaining an Order Book

Use the HTTP API to load a complete order-book snapshot and the market WebSocket to apply real-time changes.

The order-book snapshot and every market update include a sequence ID.

A reliable client should use the sequence ID to:

* Ignore events already included in the snapshot
* Apply updates in order
* Detect missing events
* Determine when a complete resynchronization is required

### Order-book data flow

```
Subscribe to market updates
        ↓
Buffer incoming updates
        ↓
Load the HTTP order-book snapshot
        ↓
Discard buffered updates at or below the snapshot sequence ID
        ↓
Apply later updates in sequence
        ↓
Reload the snapshot after a gap or resync notification
```

### Create the clients

```ts
import {
  createInfoClient,
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
});

const marketStream =
  createExchangeWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/ws/",
    onError: (error) => {
      console.error("Market stream error", error);
    },
  });
```

### Identify the market

Order books are identified by:

* `assetId`
* `epoch`

```ts
const assetId =
  "261336857817713630688382311349658711122006440411137";

const epoch = "12";
```

The market WebSocket subscription uses `assetId`.

The HTTP order-book snapshot uses both `assetId` and `epoch`.

### Subscribe before loading the snapshot

Subscribe first and temporarily buffer updates.

This prevents updates that occur during the HTTP request from being missed.

```ts
const bufferedUpdates: MarketUpdate[] = [];
let synchronized = false;

const unsubscribe =
  await marketStream.subscribeOrderBook(
    assetId,
    {
      onUpdate: (update) => {
        if (!synchronized) {
          bufferedUpdates.push(update);
          return;
        }

        applyUpdate(update);
      },
      onResyncRequired: () => {
        void resynchronize();
      },
      onError: (error) => {
        console.error(
          "Order-book subscription error",
          error,
        );
      },
    },
  );
```

`MarketUpdate` represents the market-update type exported by the SDK. Use the SDK’s actual exported type name in the implementation.

### Load the initial snapshot

```ts
let book = await info.getOrderBook({
  assetId,
  epoch,
});
```

Example response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "ts": 1730000000,
  "seqId": 123,
  "bids": [
    {
      "price": "999000",
      "size": "1025",
      "orderCount": 1,
      "orders": []
    }
  ],
  "asks": [
    {
      "price": "1000000",
      "size": "500",
      "orderCount": 1,
      "orders": []
    }
  ]
}
```

Record the snapshot sequence ID:

```ts
let lastSeqId = book.seqId;
```

### Apply buffered updates

Discard updates that are already represented by the snapshot.

```ts
const pendingUpdates = bufferedUpdates
  .filter((update) => update.seqId > lastSeqId)
  .sort((a, b) => a.seqId - b.seqId);
```

Apply the remaining events in sequence:

```ts
for (const update of pendingUpdates) {
  applyUpdate(update);
}

bufferedUpdates.length = 0;
synchronized = true;
```

### Validate sequence IDs

Before applying an update, confirm that it follows the last applied update.

```ts
function applyUpdate(update: MarketUpdate) {
  if (update.seqId <= lastSeqId) {
    return;
  }

  const expectedSeqId = lastSeqId + 1;

  if (update.seqId !== expectedSeqId) {
    void resynchronize();
    return;
  }

  applyMarketEvent(book, update);
  lastSeqId = update.seqId;
}
```

This example assumes sequence IDs increase by one for each applicable market event. Confirm that behavior against the SDK’s exported market-update contract before enforcing it in production.

At minimum, clients should reject updates that move backward and resynchronize whenever continuity cannot be established.

### Market update types

The market WebSocket publishes four update types.

#### Order

```json
{
  "type": "order",
  "seqId": 124,
  "data": {}
}
```

#### Trade

```json
{
  "type": "trade",
  "seqId": 125,
  "data": {}
}
```

#### Cancel

```json
{
  "type": "cancel",
  "seqId": 126,
  "data": {}
}
```

#### Resolution

```json
{
  "type": "resolution",
  "seqId": 127,
  "data": {}
}
```

The development team’s WebSocket document does not define the fields inside `data`.

The exact order-book mutation logic should be implemented from the SDK’s exported event types rather than inferred from the empty examples.

### Use specific handlers

Applications can use one general handler:

```ts
onUpdate: (update) => {
  applyUpdate(update);
}
```

They can also use event-specific handlers:

```ts
await marketStream.subscribeOrderBook(
  assetId,
  {
    onOrder: (update) => {
      console.log("Order", update);
    },
    onTrade: (update) => {
      console.log("Trade", update);
    },
    onCancel: (update) => {
      console.log("Cancel", update);
    },
    onResolution: (update) => {
      console.log("Resolution", update);
    },
  },
);
```

Do not apply the same update through both `onUpdate` and an event-specific handler.

Use `onUpdate` for one centralized order-book reducer, or use the specific handlers without also processing the general callback.

### Resynchronize the order book

Reload the entire snapshot when:

* `onResyncRequired` is called
* A sequence gap is detected
* The connection reconnects
* An update cannot be applied
* The local book fails an integrity check
* The market epoch changes

```ts
let resyncing = false;

async function resynchronize() {
  if (resyncing) {
    return;
  }

  resyncing = true;
  synchronized = false;
  bufferedUpdates.length = 0;

  try {
    const freshBook =
      await info.getOrderBook({
        assetId,
        epoch,
      });

    book = freshBook;
    lastSeqId = freshBook.seqId;

    const pendingUpdates = bufferedUpdates
      .filter(
        (update) =>
          update.seqId > lastSeqId,
      )
      .sort(
        (a, b) =>
          a.seqId - b.seqId,
      );

    for (const update of pendingUpdates) {
      applyUpdate(update);
    }

    bufferedUpdates.length = 0;
    synchronized = true;
  } catch (error) {
    console.error(
      "Order-book resynchronization failed",
      error,
    );
  } finally {
    resyncing = false;
  }
}
```

In production, retain updates received while the snapshot request is in progress. Do not clear the same buffer after new events have been added to it.

A safer implementation swaps active buffers:

```ts
let updateBuffer: MarketUpdate[] = [];

async function loadSnapshotWithBuffer() {
  synchronized = false;

  const activeBuffer = updateBuffer;
  updateBuffer = [];

  const snapshot =
    await info.getOrderBook({
      assetId,
      epoch,
    });

  const allBufferedUpdates = [
    ...activeBuffer,
    ...updateBuffer,
  ];

  book = snapshot;
  lastSeqId = snapshot.seqId;

  const pendingUpdates = allBufferedUpdates
    .filter(
      (update) =>
        update.seqId > lastSeqId,
    )
    .sort(
      (a, b) =>
        a.seqId - b.seqId,
    );

  updateBuffer = [];

  for (const update of pendingUpdates) {
    applyUpdate(update);
  }

  synchronized = true;
}
```

### Top-of-book applications

Applications that only need the best bid and ask can use:

```ts
const top = await info.getTopOfBook({
  assetId,
  epoch,
});
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "seqId": 123,
  "ts": 1730000000,
  "bid": {
    "price": "999000",
    "size": "1025",
    "orderCount": 1,
    "orders": []
  },
  "ask": {
    "price": "1000000",
    "size": "500",
    "orderCount": 1,
    "orders": []
  },
  "last": "999500",
  "lastTs": "1730000000"
}
```

The same resynchronization principles apply: reload the HTTP value after a disconnect or sequence failure.

### Account-owned orders

Use `getBookOrders` to load resting orders for one account.

```ts
const accountOrders =
  await info.getBookOrders({
    assetId,
    epoch,
    account:
      "0x1111111111111111111111111111111111111111",
  });
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "seqId": 123,
  "ts": 1730000000,
  "buys": [],
  "sells": []
}
```

Reload this snapshot after a resynchronization event if the application maintains a separate account-order view.

### Unsubscribe

The function returned by `subscribeOrderBook` removes only the handler created by that subscription call.

```ts
await unsubscribe();
```

To remove every local handler for an asset:

```ts
await marketStream.unsubscribeOrderBook(
  assetId,
);
```

Close the socket when the application no longer needs market updates:

```ts
marketStream.close();
```

### Recommended safeguards

A production order-book consumer should:

* Buffer events during snapshot loading
* Track the last applied sequence ID
* Ignore duplicate or stale events
* Detect sequence discontinuity
* Allow only one resynchronization at a time
* Reload after `onResyncRequired`
* Validate that bid and ask levels remain sorted
* Validate that sizes do not become negative
* Replace the local state atomically after a new snapshot
* Record resynchronization failures and retry with backoff


# WebSocket Reconnection

The market and oracle WebSocket clients reconnect automatically by default.

```ts
const marketStream =
  createExchangeWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/ws/",
    reconnect: true,
  });

const oracleStream =
  createOracleWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/oracle-ws/",
    reconnect: true,
  });
```

Reconnection keeps the transport available, but each stream has different recovery requirements.

### Default reconnect configuration

<table><thead><tr><th width="263.7890625">Option</th><th align="right">Default</th></tr></thead><tbody><tr><td><code>reconnect</code></td><td align="right"><code>true</code></td></tr><tr><td><code>reconnectDelayMs</code></td><td align="right"><code>1000</code></td></tr><tr><td><code>maxReconnectDelayMs</code></td><td align="right"><code>30000</code></td></tr><tr><td><code>ackTimeoutMs</code></td><td align="right"><code>15000</code></td></tr><tr><td>Oracle <code>stalePriceTimeoutMs</code></td><td align="right"><code>30000</code></td></tr></tbody></table>

Configure the reconnect delay when creating a client:

```ts
const marketStream =
  createExchangeWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/ws/",
    reconnect: true,
    reconnectDelayMs: 1_000,
    maxReconnectDelayMs: 30_000,
    ackTimeoutMs: 15_000,
    onError: (error) => {
      console.error(
        "Market WebSocket error",
        error,
      );
    },
  });
```

### Market WebSocket recovery

Market updates include sequence IDs.

After the connection drops or the SDK determines that the socket is unhealthy, active handlers receive:

```ts
onResyncRequired(assetId);
```

Example:

```ts
const unsubscribe =
  await marketStream.subscribeOrderBook(
    assetId,
    {
      onUpdate: (update) => {
        applyMarketUpdate(update);
      },
      onResyncRequired: async (
        resyncAssetId,
      ) => {
        const freshBook =
          await info.getOrderBook({
            assetId: resyncAssetId,
            epoch,
          });

        replaceLocalBook(freshBook);
      },
      onError: (error) => {
        console.error(
          "Market subscription error",
          error,
        );
      },
    },
  );
```

Do not continue applying market updates to the old local order book after `onResyncRequired` is called.

Instead:

1. Mark the local order book as unsynchronized.
2. Buffer or pause new events.
3. Load a fresh HTTP order-book snapshot.
4. Replace the local state.
5. Apply only events newer than the snapshot sequence ID.
6. Resume normal processing.

### Market reconnect flow

```
Connection becomes unhealthy
        ↓
onResyncRequired(assetId)
        ↓
SDK reconnects when subscriptions remain
        ↓
Reload GET /book/:assetId/:epoch
        ↓
Replace the local order book
        ↓
Resume live updates
```

### Oracle WebSocket recovery

Oracle price updates do not include sequence IDs.

The oracle stream also does not provide a REST catch-up operation.

After reconnecting, accept the next live price update.

```ts
const unsubscribe =
  await oracleStream.subscribePrice(
    "1",
    {
      onPrice: (update) => {
        setCurrentPrice(
          update.symbolId,
          update.price,
          update.ts,
        );
      },
      onStale: (symbolId) => {
        markPriceAsStale(symbolId);
      },
      onError: (error) => {
        console.error(
          "Oracle subscription error",
          error,
        );
      },
    },
  );
```

### Stale oracle prices

`stalePriceTimeoutMs` controls how long the client waits without a price update for a subscribed symbol.

```ts
const oracleStream =
  createOracleWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/oracle-ws/",
    stalePriceTimeoutMs: 30_000,
  });
```

If the timeout expires, the client:

1. Calls `onStale(symbolId)`.
2. Emits an error.
3. Abandons the unhealthy socket.
4. Reconnects when active subscriptions remain.

Example stale callback:

```ts
onStale: (symbolId) => {
  console.warn(
    "Oracle price is stale",
    symbolId,
  );

  disablePriceDependentActions(symbolId);
}
```

When the next price arrives:

```ts
onPrice: (update) => {
  clearStaleState(update.symbolId);
  setCurrentPrice(
    update.symbolId,
    update.price,
    update.ts,
  );
}
```

### Subscription acknowledgement timeout

The clients wait for subscribe and unsubscribe acknowledgements.

The default timeout is:

```ts
15_000
```

Configure it with `ackTimeoutMs`:

```ts
const marketStream =
  createExchangeWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/ws/",
    ackTimeoutMs: 20_000,
  });
```

A failed subscription acknowledgement rejects the subscription operation or emits an error through the configured handlers.

### Unsubscribe failures

Unsubscribe acknowledgements are best-effort.

If an unsubscribe acknowledgement fails:

* The local subscription is still removed.
* The error is emitted.
* The client reconnects only when other active subscriptions remain.

The application should treat the local handler as removed even if the server acknowledgement is not received.

### Multiple subscriptions

One WebSocket client can manage multiple asset or symbol subscriptions.

Market example:

```ts
const unsubscribeAssetOne =
  await marketStream.subscribeOrderBook(
    assetOne,
    assetOneHandlers,
  );

const unsubscribeAssetTwo =
  await marketStream.subscribeOrderBook(
    assetTwo,
    assetTwoHandlers,
  );
```

Oracle example:

```ts
const unsubscribeSymbolOne =
  await oracleStream.subscribePrice(
    "1",
    symbolOneHandlers,
  );

const unsubscribeSymbolTwo =
  await oracleStream.subscribePrice(
    "2",
    symbolTwoHandlers,
  );
```

The client maintains active local handlers across transport failures. Recovery logic must still restore application state:

* Reload market order books after market reconnects.
* Accept the next live price after oracle reconnects.

### Shared server subscriptions

Multiple local handlers for the same asset or symbol share one server subscription.

```ts
const unsubscribeA =
  await marketStream.subscribeOrderBook(
    assetId,
    handlersA,
  );

const unsubscribeB =
  await marketStream.subscribeOrderBook(
    assetId,
    handlersB,
  );
```

Calling:

```ts
await unsubscribeA();
```

removes only `handlersA`.

The server subscription remains active while `handlersB` is still registered.

Calling:

```ts
await marketStream.unsubscribeOrderBook(
  assetId,
);
```

removes all local handlers for the asset.

### Intentional close

Calling `close()` intentionally closes the connection.

```ts
marketStream.close(
  1000,
  "Application shutdown",
);

oracleStream.close(
  1000,
  "Application shutdown",
);
```

An intentional close should be part of the application’s shutdown process.

Run returned unsubscribe functions before closing when practical:

```ts
await unsubscribe();
marketStream.close();
```

### Browser and Node.js behavior

Browser WebSocket implementations support `close()` but do not provide a forceful termination method.

The Node `ws` implementation can provide `terminate()`.

When a socket is already considered unhealthy, the SDK:

* Uses `terminate()` when the WebSocket implementation provides it
* Falls back to `close()` in browser-compatible environments
* Ignores events from abandoned sockets so stale events do not affect a newer connection

If an abandoned browser socket does not close cleanly, the server heartbeat or TCP timeout eventually removes it.

### Heartbeats

The services send protocol-level WebSocket ping frames.

Standard browser WebSockets and the Node `ws` package automatically respond with protocol pong frames.

Do not send an application-level pong:

```json
{
  "type": "pong"
}
```

### Error handling

Configure a client-level error handler:

```ts
const marketStream =
  createExchangeWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/ws/",
    onError: (error) => {
      console.error(
        "Market WebSocket client error",
        error,
      );
    },
  });
```

Also configure subscription-level error handlers:

```ts
await marketStream.subscribeOrderBook(
  assetId,
  {
    onUpdate: applyMarketUpdate,
    onError: (error) => {
      console.error(
        "Asset subscription error",
        error,
      );
    },
    onResyncRequired: () => {
      void reloadOrderBook();
    },
  },
);
```

### Disable automatic reconnection

Automatic reconnection can be disabled:

```ts
const marketStream =
  createExchangeWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/ws/",
    reconnect: false,
  });
```

When disabled, the application is responsible for:

* Detecting connection failure
* Opening a new connection
* Recreating subscriptions
* Reloading market state
* Marking oracle prices as stale

### Recommended reconnect behavior

For market subscriptions:

1. Enable automatic reconnection.
2. Implement `onResyncRequired`.
3. Pause or buffer market events during resynchronization.
4. Reload the full HTTP order-book snapshot.
5. Validate sequence continuity before resuming.

For oracle subscriptions:

1. Enable automatic reconnection.
2. Configure `stalePriceTimeoutMs`.
3. Implement `onStale`.
4. Mark stale prices as unusable.
5. Accept the next live price after reconnecting.

For both clients:

* Log errors without exposing secrets.
* Monitor repeated reconnect attempts.
* Avoid creating a new client for every subscription.
* Close clients during application shutdown.
* Keep client and subscription error handlers lightweight.


# About

GammaSwap V1 was built to bring convexity onchain through AMMs without liquidations or oracle dependencies. Users could borrow liquidity from AMMs to hedge Impermanent Loss or to speculate on volatility or price direction of any token.&#x20;

The team believes prediction markets enable the same type of risk exposure in a much more capital efficient and easier to understand format. Therefore the team redirected its efforts to improve the experience of trading prediction markets for cryptocurrency assets on-chain.

*Note: the core team is no longer actively developing V1 or incentivizing it. All development and growth is focused on V2.*


# Add / Remove Liquidity

### Add Liquidity

You can add liquidity to a pool by navigating to the earn page, clicking on the pool you want to provide liquidity into and then adding in the two tokens of the pool in a 50:50 ratio. You can also zap in with one token. The zap features uses multiple DEXs to swap your token into the two tokens of the pool. There may be price impact, however.

### Remove Liquidity

To remove liquidity from a pool, navigate to the portfolio page and click on the current supplied position that you would like to remove.

Navigate to the withdraw tab in the pool page. You can withdraw using zap or by doing it manually.&#x20;

Zapping uses the GammaSwap DEX router to find the best prices and swaps your position into one token for you. If price impact is high or you prefer exiting into both tokens of the pool, you can withdraw manually by toggling the zap button off.

### Closing a Perpetual Option

Navigate to the trade page for the market of the position you would look to close and select the close button.&#x20;

You can also close a position from the portfolio page by going to the borrowed tab.

### Exit Farm

To withdraw liquidity, you must first unstake your LP on the farm page and then withdraw your liquidity from the pool.&#x20;

After unstaking, you can withdraw your liquidity on the pool page by adding the amount of GSLP you would like to withdraw and then click withdraw.


# Vesting

### Vesting Staking Rewards

To vest esGS to GS on the staking page, you must click the vest button under the vesting section. Vesting convers esGS to GS linearly over 30 day. When initiating vesting, you must reserve the aggregate amount of GS, esGS and MP points used to earn the esGS. The UI will tell you how much is needed.

### Vesting Farm Rewards

To vest esGS to GS from a farm, you must click the vest button in the farm that you earned the esGS in. You will need to stake your GSLP position while you are vesting.

You can claim the rewards from the vesting vault at any time by withdrawing just be aware that it will also restart the vesting schedule for both staking and farming.


