TypeScript helpers for interacting with the Ambient Ember on-chain program.
npm install @crocswap-libs/ambient-emberTypeScript/JavaScript SDK for interacting with the Ambient Ember perpetual futures DEX on Solana.
``bash`
npm install @crocswap-libs/ambient-emberor
yarn add @crocswap-libs/ambient-emberor
pnpm add @crocswap-libs/ambient-ember
`typescript
import { Connection, PublicKey } from "@solana/web3.js";
import * as ember from "@crocswap-libs/ambient-ember";
const connection = new Connection("https://testnet.fogo.io");
const user = new PublicKey("YourWalletAddress");
// Place a market order
const tx = await ember.buildMarketBuyOrder(
connection,
64n, // Market ID (64 = BTC)
BigInt(Date.now()), // Order ID
100000000n, // 1.0 in 10^8 scale
user
);
`
- Key Concepts
- Order Management
- Position Management
- Margin and Collateral
- Querying Data
- Risk Management
- Types and Constants
- Examples
- API Reference
The SDK uses fixed-point arithmetic with specific decimal scales:
- Prices: Scaled by 10^6 (e.g., $100.50 = 100500000)
- Quantities: Scaled by 10^8 (e.g., 1.5 BTC = 150000000)
- Collateral/USD: Scaled by 10^6 (e.g., $1000 = 1000000000)
- Basis Points (bps): 1 bps = 0.01% (e.g., 500 bps = 5%)
- CMA (Cross Margin Account): User's main trading account that holds margin buckets
- Margin Bucket: Per-market collateral and position tracking
- Order Details: Storage for a user's open orders in a market
- Market Order Log: Append-only log of all market events
`typescript
// Program ID (can be overridden with EMBER_PROGRAM_ID env var)
export const EMBER_PROGRAM_ID = new PublicKey("ambi3LHRUzmU187u4rP46rX6wrYrLtU1Bmi5U2yCTGE");
// Default USD mint
export const USD_MINT = new PublicKey("fUSDNGgHkZfwckbr5RLLvRbvqvRcTLdH9hcHJiq4jry");
// Default market
export const DFLT_EMBER_MARKET = {
mktId: 64,
ticker: "BTC",
name: "Bitcoin"
};
`
Market orders are IOC (Immediate or Cancel) orders with price=0 convention.
`typescript
// Market Buy
const buyTx = await ember.buildMarketBuyOrder(
connection,
64n, // Market ID
orderId, // Unique order ID (bigint)
100000000n, // 1.0 BTC in 10^8 scale
userPublicKey
);
// Market Sell
const sellTx = await ember.buildMarketSellOrder(
connection,
64n,
orderId,
100000000n,
userPublicKey
);
`
`typescript
// Limit Buy Order
const buyTx = await ember.buildBuyOrder(
connection,
64n, // Market ID
orderId, // Unique order ID
100000000n, // Quantity: 1.0 BTC
50000000000n, // Price: $50,000
userPublicKey,
{ type: ember.TimeInForce.GTC }, // Good Till Cancelled
1000 // Optional: 10% user initial margin
);
// Limit Sell with Time Expiry
const sellTx = await ember.buildSellOrder(
connection,
64n,
orderId,
50000000n, // 0.5 BTC
51000000000n, // $51,000
userPublicKey,
{
type: ember.TimeInForce.GTT,
timestamp: BigInt(Date.now() / 1000 + 3600) // Expires in 1 hour
}
);
`
`typescript`
const tx = await ember.buildOrderEntryTransaction(connection, {
marketId: 64n,
orderId: uniqueOrderId,
side: ember.OrderSide.Bid, // 0 = Buy, 1 = Sell
qty: 100000000n, // 1.0 BTC
price: 50000000000n, // $50,000
tif: { type: ember.TimeInForce.GTC },
user: userPublicKey,
actor: signerPublicKey, // Optional: delegated trading
userSetImBps: 1000, // Optional: 10% initial margin
});
`typescript`
const cancelTx = await ember.buildCancelOrderTransaction(connection, {
marketId: 64n,
orderId: orderToCancel,
user: userPublicKey,
tombstone: ember.OrderTombstone.UserCancel
});
To close a position, place an order in the opposite direction:
`typescript
// Close a long position with market order
const closeLongTx = await ember.buildMarketSellOrder(
connection,
marketId,
orderId,
positionSize, // Your current position size
userPublicKey
);
// Close with limit order
const closeTx = await ember.buildSellOrder(
connection,
marketId,
orderId,
positionSize,
targetPrice,
userPublicKey
);
`
`typescript`
// Deposit and commit to margin in one transaction
const depositTx = await ember.buildDepositMarginTx(
connection,
{
user: userPublicKey,
mint: ember.USD_MINT,
amount: 1000000000n, // $1000 in 10^6 scale
marketId: 64n // Target market
}
);
`typescript`
// Uncommit and withdraw in one transaction
const withdrawTx = await ember.buildWithdrawMarginTx(
connection,
{
user: userPublicKey,
mint: ember.USD_MINT,
amount: 500000000n, // $500 in 10^6 scale
marketId: 64n
}
);
`typescript`
// Set custom initial margin (must be >= market minimum)
const setMarginTx = await ember.createSetUserMarginTransaction({
connection,
user: userPublicKey,
marketId: 64n,
userSetImBps: 2000 // 20% initial margin
});
`typescript
// Get full market data
const market = await ember.getMarketData(connection, 64n);
if (market) {
console.log({
lastBid: Number(market.lastBid) / 1e6,
lastAsk: Number(market.lastAsk) / 1e6,
lastTradePrice: Number(market.lastTradePrice) / 1e6,
lastMarkPrice: Number(market.lastMarkPrice) / 1e6,
spread: Number(market.spread) / 1e6,
isActive: market.isActive,
imBps: market.imBps, // Initial margin
mmBps: market.mmBps, // Maintenance margin
tickSize: market.tickSize,
minOrderSize: market.minOrderSize
});
}
// Get just prices
const prices = await ember.getMarketPrices(connection, 64n);
`
`typescript
// Get margin bucket with all calculations
const marginBucket = await ember.getUserMarginBucket(
connection,
userPublicKey,
64n, // Market ID
ember.USD_MINT // Collateral token
);
if (marginBucket) {
// Position info
console.log({
netPosition: Number(marginBucket.netPosition) / 1e8,
avgEntryPrice: Number(marginBucket.avgEntryPrice) / 1e6,
committedCollateral: Number(marginBucket.committedCollateral) / 1e6,
});
// P&L calculations
console.log({
markPrice: Number(marginBucket.markPrice) / 1e6,
unrealizedPnl: Number(marginBucket.unrealizedPnl) / 1e6,
equity: Number(marginBucket.equity) / 1e6,
});
// Available balances
console.log({
availToBuy: Number(marginBucket.availToBuy) / 1e6,
availToSell: Number(marginBucket.availToSell) / 1e6,
availToWithdraw: Number(marginBucket.availToWithdraw) / 1e6,
});
// Margin requirements
console.log({
userSetImBps: marginBucket.userSetImBps,
marketImBps: marginBucket.marketImBps,
effectiveImBps: marginBucket.effectiveImBps // max(user, market)
});
}
`
`typescript
// Current position liquidation price
const liqPrice = ember.calcLiqPrice(
1000, // Collateral: $1000
{
qty: 0.1, // 0.1 BTC long
entryPrice: 50000 // Entry at $50k
},
0.05 // 5% maintenance margin
);
// Liquidation price after a new order
const newLiqPrice = ember.calcLiqPriceOnNewOrder(
1000, // Current collateral
{ qty: 0.1, entryPrice: 50000 }, // Current position
{ qty: 0.05, entryPrice: 51000 }, // New order
0.05 // Maintenance margin
);
`
`typescript
const result = await ember.validateOrder(
connection,
{
user: userPublicKey,
marketId: 64n,
side: ember.OrderSide.Bid,
quantity: 100000000n,
price: 50000000000n,
orderType: 'limit'
}
);
if (result.isValid) {
// Order can be placed
} else {
console.error('Validation failed:', result.errors);
// Possible errors:
// - INSUFFICIENT_MARGIN
// - EXCEEDS_POSITION_LIMIT
// - BELOW_MIN_ORDER_SIZE
// - INVALID_PRICE_TICK
}
`
`typescript
// Order side
enum OrderSide {
Bid = 0, // Buy
Ask = 1 // Sell
}
// Time in force options
enum TimeInForce {
IOC = 'IOC', // Immediate or Cancel
FOK = 'FOK', // Fill or Kill
GTC = 'GTC', // Good Till Cancelled
ALO = 'ALO', // Add Liquidity Only
GTT = 'GTT' // Good Till Time
}
// Time in force variants
type TimeInForceVariant =
| { type: TimeInForce.IOC }
| { type: TimeInForce.FOK }
| { type: TimeInForce.GTC }
| { type: TimeInForce.ALO }
| { type: TimeInForce.GTT; timestamp: bigint };
// Order cancellation reasons
enum OrderTombstone {
UserCancel = 0,
Liquidation = 1,
Expiry = 2
}
`
`typescript
// Base margin bucket
interface MarginBucket {
scope: MarginScope;
marketId: bigint;
mint: PublicKey;
committedCollateral: bigint;
netPosition: bigint;
openBidQty: bigint;
openAskQty: bigint;
avgEntryPrice: bigint;
userSetImBps: number;
}
// With pricing calculations
type MarginBucketPriced = MarginBucket & {
markPrice: bigint;
equity: bigint;
unrealizedPnl: bigint;
marketImBps: number;
effectiveImBps: number;
}
// With available balance calculations
type MarginBucketAvail = MarginBucketPriced & {
availToBuy: bigint;
availToSell: bigint;
availToWithdraw: bigint;
}
`
`typescript`
interface MarketData {
marketId: bigint;
lastBid: bigint;
lastAsk: bigint;
lastTradePrice: bigint;
lastMarkPrice: bigint;
midPrice: bigint;
spread: bigint;
isActive: boolean;
imBps: number;
mmBps: number;
tickSize: bigint;
minOrderSize: bigint;
oracle: PublicKey;
baseToken: PublicKey;
}
`typescript
import * as ember from '@crocswap-libs/ambient-ember';
import { Connection, PublicKey, sendAndConfirmTransaction } from '@solana/web3.js';
async function executeTrade(
connection: Connection,
user: Keypair,
side: 'buy' | 'sell',
quantity: number, // Human readable (e.g., 0.1 BTC)
price: number // Human readable (e.g., 50000)
) {
// Convert to scaled values
const scaledQty = BigInt(Math.floor(quantity * 1e8));
const scaledPrice = BigInt(Math.floor(price * 1e6));
const marketId = 64n; // BTC market
// Check market status
const market = await ember.getMarketData(connection, marketId);
if (!market?.isActive) {
throw new Error('Market is not active');
}
// Check user margin
const margin = await ember.getUserMarginBucket(
connection,
user.publicKey,
marketId
);
const available = side === 'buy'
? margin?.availToBuy || 0n
: margin?.availToSell || 0n;
const notional = (scaledQty * scaledPrice) / 100000000n;
const required = (notional * BigInt(market.imBps)) / 10000n;
if (available < required) {
throw new Error(Insufficient margin. Need: $${Number(required)/1e6});`
}
// Build and send order
const orderId = BigInt(Date.now());
const tx = side === 'buy'
? await ember.buildBuyOrder(
connection, marketId, orderId, scaledQty, scaledPrice, user.publicKey
)
: await ember.buildSellOrder(
connection, marketId, orderId, scaledQty, scaledPrice, user.publicKey
);
const sig = await sendAndConfirmTransaction(connection, tx, [user]);
console.log('Order placed:', sig);
return sig;
}
`typescript`
async function manageMargin(
connection: Connection,
user: Keypair,
action: 'deposit' | 'withdraw',
amount: number // USD amount
) {
const scaledAmount = BigInt(Math.floor(amount * 1e6));
if (action === 'deposit') {
// Deposit and commit to margin
const tx = await ember.buildDepositMarginTx(connection, {
user: user.publicKey,
mint: ember.USD_MINT,
amount: scaledAmount,
marketId: 64n
});
const sig = await sendAndConfirmTransaction(connection, tx, [user]);
console.log('Deposited:', sig);
} else {
// Check available to withdraw
const margin = await ember.getUserMarginBucket(
connection,
user.publicKey,
64n
);
if (!margin || margin.availToWithdraw < scaledAmount) {
throw new Error('Insufficient available balance');
}
// Withdraw
const tx = await ember.buildWithdrawMarginTx(connection, {
user: user.publicKey,
mint: ember.USD_MINT,
amount: scaledAmount,
marketId: 64n
});
const sig = await sendAndConfirmTransaction(connection, tx, [user]);
console.log('Withdrawn:', sig);
}
}
- buildOrderEntryTransaction() - Full control order entrybuildBuyOrder()
- - Convenience for limit buy ordersbuildSellOrder()
- - Convenience for limit sell ordersbuildMarketBuyOrder()
- - Market buy orderbuildMarketSellOrder()
- - Market sell orderbuildCancelOrderTransaction()
- - Cancel an orderbuildDepositMarginTx()
- - Deposit and commit marginbuildWithdrawMarginTx()
- - Uncommit and withdraw margincreateSetUserMarginTransaction()
- - Set custom margin requirements
- getMarketData() - Full market informationgetMarketPrices()
- - Just bid/ask/trade pricesgetUserMarginBucket()
- - User position and margin infogetMultipleMarketData()
- - Batch market queries
- calcLiqPrice() - Calculate liquidation pricecalcLiqPriceOnNewOrder()
- - Liquidation price after new orderpriceMarginBucketPnl()
- - Calculate P&L for margin bucketcalcMarginAvail()
- - Calculate available balances
- orderEntryIx() - Order entry instructioncancelOrderIx()
- - Cancel order instructiondepositIx()
- - Deposit instructionwithdrawIx()
- - Withdraw instructioncommitCollateralIx()
- - Commit collateral instructionuncommitCollateralIx()
- - Uncommit collateral instructionsetUserMarginIx()
- - Set user margin instructioninitCMAIx()
- - Initialize CMA instructioninitMarketIx()
- - Initialize market instruction
- cmaPda() - Cross Margin Account addressmarketPda()
- - Market state addressorderDetailsPda()
- - Order details storage addressmarketOrderLogPda()
- - Market order log address
`typescript`
try {
const tx = await ember.buildOrderEntryTransaction(connection, params);
} catch (error) {
if (error.message.includes('Market') && error.message.includes('not found')) {
// Market doesn't exist
} else if (error.message.includes('CMA account')) {
// User account not initialized
} else if (error.message.includes('Insufficient margin')) {
// Not enough collateral
}
}
1. Always use BigInt for numeric values to avoid precision loss
2. Check market data before placing orders to ensure the market is active
3. Validate orders using the risk module before submission
4. Handle account initialization - the SDK automatically adds init instructions when needed
5. Monitor margin requirements - effective IM is max(user-set, market minimum)
6. Use appropriate commitment levels - 'confirmed' for queries, 'finalized' for critical operations
7. Batch operations when possible to reduce transaction costs
The SDK includes example scripts in the scripts/ directory:
- deposit.js - Deposit and commit marginwithdraw.js
- - Uncommit and withdraw marginorderEntry.js
- - Place orderscancelOrder.js
- - Cancel ordersqueryMarginBucket.js
- - Query user positionsqueryMarketData.js
- - Query market information
Run scripts with:
`bash`
node scripts/scriptName.js [args]
- EMBER_PROGRAM_ID - Override default program IDEMBER_USD_MINT` - Override default USD mint
-
UNLICENSED - Proprietary software of Crocodile Labs
For issues and questions:
- GitHub Issues: [Report bugs and feature requests]
- Documentation: [Full API documentation]
- Contact: legal@crocodilelabs.io