import { randomUUID } from 'crypto' export class PacificaOrdersAdapter { constructor(client) { this.client = client } normalizeSymbol(input) { if (!input) return input const s = String(input).toUpperCase() if (s.includes('-')) return s.split('-')[0] if (s.endsWith('USDT')) return s.replace('USDT', '') if (s.endsWith('USD')) return s.replace('USD', '') return s } async createMarketOrder(payload) { // For market orders, only use required fields per API docs const marketPayload = { account: payload.account, symbol: payload.symbol, amount: payload.amount, side: payload.side, reduceOnly: payload.reduceOnly, slippagePercent: payload.slippagePercent || '0.5', } return await this.sendSigned(this.client.endpoints.orderCreateMarket, 'create_market_order', marketPayload) } async createLimitOrder(payload) { return await this.sendSigned(this.client.endpoints.orderCreateLimit, 'create_order', payload) } async createStopOrder(payload) { return await this.sendSigned(this.client.endpoints.orderCreateStop, 'create_stop_order', payload) } async cancelOrder(payload) { return await this.sendSigned(this.client.endpoints.orderCancel, 'cancel_order', payload) } async cancelAll(payload) { const account = this.client.requireAccount() const data = { all_symbols: payload.allSymbols ?? !payload.symbol, exclude_reduce_only: payload.excludeReduceOnly ?? false, symbol: this.normalizeSymbol(payload.symbol), } const { signature, timestamp, expiryWindow } = await this.client.signOperation('cancel_all_orders', data, 30000) const body = { account, ...data, signature, timestamp, expiry_window: expiryWindow } return await this.client.post(this.client.endpoints.orderCancelAll, body, { skipHeaderSig: true }) } async openOrders(symbol, account) { const query = { account: this.client.requireAccount(account) } if (symbol) query.symbol = symbol return await this.client.get(this.client.endpoints.openOrders, query) } async batch(actions) { const signedActions = await Promise.all( actions.map(async action => { if (action.type === 'Create') { const raw = action.data // Normalize incoming fields from external callers (reduce_only -> reduceOnly) const payload = { account: raw.account, symbol: raw.symbol, amount: raw.amount, side: raw.side, reduceOnly: raw.reduceOnly !== undefined ? raw.reduceOnly : raw.reduce_only ?? false, clientOrderId: raw.clientOrderId ?? raw.client_order_id, tif: raw.tif, price: raw.price, slippagePercent: raw.slippagePercent ?? raw.slippage_percent, takeProfit: raw.takeProfit, stopLoss: raw.stopLoss, agentWallet: raw.agentWallet ?? raw.agent_wallet, expiryWindow: raw.expiryWindow, } const transformed = this.transformCreatePayload( payload, this.client.cfg.agentPrivateKey ? this.client.requireAgentWallet(payload.agentWallet) : undefined, ) const dataToSign = this.pickOrderSignFields('create_order', transformed) const { signature, timestamp, expiryWindow } = await this.client.signOperation( 'create_order', dataToSign, payload.expiryWindow ?? 30000, ) return { type: 'Create', data: { ...transformed, signature, timestamp, expiry_window: expiryWindow } } } const cp = action.data const transformedCancel = this.transformCancelPayload(cp) const cancelSign = this.pickCancelSignFields(transformedCancel) const { signature, timestamp, expiryWindow } = await this.client.signOperation( 'cancel_order', cancelSign, cp.expiryWindow ?? 30000, ) return { type: 'Cancel', data: { ...transformedCancel, signature, timestamp, expiry_window: expiryWindow } } }), ) return await this.client.post(this.client.endpoints.orderBatch, { actions: signedActions }, { skipHeaderSig: true }) } transformCreatePayload(payload, agentWallet, isMarketOrder = false) { const clientOrderId = payload.clientOrderId || randomUUID() // For market orders, use minimal required fields per API error messages if (isMarketOrder) { return { account: payload.account, symbol: this.normalizeSymbol(payload.symbol), amount: payload.amount, side: payload.side, reduce_only: payload.reduceOnly, slippage_percent: payload.slippagePercent || '1.0', agent_wallet: agentWallet ?? payload.agentWallet, } } // For limit orders and others, include all fields const takeProfit = payload.takeProfit ? { stop_price: payload.takeProfit.stopPrice, limit_price: payload.takeProfit.limitPrice, client_order_id: payload.takeProfit.clientOrderId, } : undefined const stopLoss = payload.stopLoss ? { stop_price: payload.stopLoss.stopPrice, limit_price: payload.stopLoss.limitPrice, client_order_id: payload.stopLoss.clientOrderId, } : undefined return { account: payload.account, symbol: this.normalizeSymbol(payload.symbol), amount: payload.amount, side: payload.side, reduce_only: payload.reduceOnly, client_order_id: clientOrderId, tif: payload.tif ?? 'GTC', price: payload.price, slippage_percent: payload.slippagePercent, take_profit: takeProfit, stop_loss: stopLoss, agent_wallet: agentWallet ?? payload.agentWallet, } } async buildSignedPayload(operation, data, expiryWindow = 30000, useAgent = false) { const signer = useAgent ? this.client.signOperationWithAgent.bind(this.client) : this.client.signOperation.bind(this.client) const { signature, timestamp, expiryWindow: ew } = await signer(operation, data, expiryWindow) return { ...data, signature, timestamp, expiry_window: ew, } } async sendSigned(path, operation, payload) { const isOrder = operation.startsWith('create_') const useAgent = isOrder && !!this.client.cfg.agentPrivateKey if (operation === 'cancel_order') { const transformed = this.transformCancelPayload(payload) const expiryWindow = payload.expiryWindow ?? 30000 const dataToSign = this.pickCancelSignFields(transformed) const { signature, timestamp, expiryWindow: ew, } = await this.client.signOperation(operation, dataToSign, expiryWindow) const finalBody = { ...transformed, signature, timestamp, expiry_window: ew } if (process.env.PACIFICA_DEBUG === '1') { // eslint-disable-next-line no-console console.log('[pacifica.debug] op=', operation) // eslint-disable-next-line no-console console.log('[pacifica.debug] sign_data=', JSON.stringify(dataToSign)) // eslint-disable-next-line no-console console.log( '[pacifica.debug] final_body=', JSON.stringify({ ...finalBody, signature: `b58(${String(finalBody.signature).slice(0, 8)}...)` }), ) } return await this.client.post(path, finalBody, { skipHeaderSig: true }) } // create_* flow: sign ONLY the operation fields per docs, NOT account/agent_wallet const transformed = operation === 'create_stop_order' ? (() => { const p = payload const clientId = p.clientOrderId || randomUUID() return { account: p.account, symbol: this.normalizeSymbol(p.symbol), side: p.side, reduce_only: p.reduceOnly, stop_order: { stop_price: p.stopLoss?.stopPrice, limit_price: p.stopLoss?.limitPrice, client_order_id: clientId, amount: p.amount, }, agent_wallet: useAgent ? this.client.requireAgentWallet(p.agentWallet) : p.agentWallet, } })() : this.transformCreatePayload( payload, useAgent ? this.client.requireAgentWallet(payload.agentWallet) : undefined, operation === 'create_market_order', ) const expiryWindow = payload.expiryWindow ?? 5000 const dataToSign = this.pickOrderSignFields(operation, transformed) const signer = useAgent ? this.client.signOperationWithAgent.bind(this.client) : this.client.signOperation.bind(this.client) const { signature, timestamp, expiryWindow: ew } = await signer(operation, dataToSign, expiryWindow) const finalBody = { ...transformed, timestamp, expiry_window: ew, signature, } if (process.env.PACIFICA_DEBUG === '1') { // eslint-disable-next-line no-console console.log('[pacifica.debug] op=', operation) // eslint-disable-next-line no-console console.log('[pacifica.debug] sign_data=', JSON.stringify(dataToSign)) // eslint-disable-next-line no-console console.log( '[pacifica.debug] final_body=', JSON.stringify({ ...finalBody, signature: `b58(${String(finalBody.signature).slice(0, 8)}...)` }), ) } return await this.client.post(path, finalBody, { skipHeaderSig: true }) } pickOrderSignFields(operation, transformed) { const base = { symbol: transformed.symbol, reduce_only: transformed.reduce_only, side: transformed.side, } if (operation === 'create_market_order') { // Market order fields per API requirements base.amount = transformed.amount if (transformed.slippage_percent !== undefined) { base.slippage_percent = transformed.slippage_percent } return base } // For other orders base.amount = transformed.amount || transformed.quantity // Add client_order_id for other order types base.client_order_id = transformed.client_order_id if (operation === 'create_stop_order') { base.side = transformed.side base.reduce_only = transformed.reduce_only base.stop_order = { stop_price: transformed.stop_order?.stop_price, limit_price: transformed.stop_order?.limit_price, amount: transformed.stop_order?.amount, client_order_id: transformed.stop_order?.client_order_id, } return base } if (operation === 'create_stop_order') { if (transformed.stop_price !== undefined) base.stop_price = transformed.stop_price if (transformed.limit_price !== undefined) base.limit_price = transformed.limit_price return base } // create_order (limit) if (transformed.tif !== undefined) base.tif = transformed.tif if (transformed.price !== undefined) base.price = transformed.price return base } transformCancelPayload(payload) { if (!payload.orderId && !payload.clientOrderId) { throw new Error('PacificaOrdersAdapter: orderId or clientOrderId required') } return { account: payload.account, symbol: this.normalizeSymbol(payload.symbol), order_id: payload.orderId, client_order_id: payload.clientOrderId, } } pickCancelSignFields(transformed) { const base = { symbol: transformed.symbol } if (transformed.order_id !== undefined && transformed.order_id !== null) base.order_id = transformed.order_id else if (transformed.client_order_id) base.client_order_id = transformed.client_order_id return base } }