| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- 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
- }
- }
|