Files
trading_bot_v3/lib/jupiter-trigger-service.ts
mindesbunister ff4e9737fb fix: timeframe handling and progress tracking improvements
- Fix timeframe parameter handling in enhanced-screenshot API route
- Support both 'timeframe' (singular) and 'timeframes' (array) parameters
- Add proper sessionId propagation for real-time progress tracking
- Enhance MACD analysis prompt with detailed crossover definitions
- Add progress tracker service with Server-Sent Events support
- Fix Next.js build errors in chart components (module variable conflicts)
- Change dev environment port from 9000:3000 to 9001:3000
- Improve AI analysis layout detection logic
- Add comprehensive progress tracking through all service layers
2025-07-17 10:41:18 +02:00

491 lines
14 KiB
TypeScript

import { Connection, Keypair, VersionedTransaction } from '@solana/web3.js'
import fetch from 'cross-fetch'
export interface TriggerOrder {
orderId: string
inputMint: string
outputMint: string
makingAmount: string
takingAmount: string
targetPrice: number
side: 'BUY' | 'SELL'
orderType: 'STOP_LOSS' | 'TAKE_PROFIT' | 'LIMIT'
status: 'PENDING' | 'EXECUTED' | 'CANCELLED' | 'EXPIRED'
createdAt: number
executedAt?: number
txId?: string
requestId?: string
}
class JupiterTriggerService {
private connection: Connection
private keypair: Keypair | null = null
private activeOrders: TriggerOrder[] = []
// Token mint addresses
private tokens = {
SOL: 'So11111111111111111111111111111111111111112', // Wrapped SOL
USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
}
constructor() {
const rpcUrl = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
this.connection = new Connection(rpcUrl, 'confirmed')
this.initializeWallet()
}
private initializeWallet() {
try {
if (process.env.SOLANA_PRIVATE_KEY) {
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY)
this.keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray))
console.log('✅ Jupiter Trigger wallet initialized:', this.keypair.publicKey.toString())
} else {
console.warn('⚠️ No SOLANA_PRIVATE_KEY found for Jupiter Trigger')
}
} catch (error) {
console.error('❌ Failed to initialize Jupiter Trigger wallet:', error)
}
}
/**
* Create a stop loss order
* When current price drops to stopPrice, sell the token
*/
async createStopLossOrder(params: {
tokenSymbol: string
amount: number // Amount of tokens to sell
stopPrice: number // Price at which to trigger the sale
slippageBps?: number // Optional slippage (default 0 for exact execution)
expiredAt?: number // Optional expiry timestamp
}): Promise<{
success: boolean
orderId?: string
requestId?: string
transaction?: string
error?: string
}> {
if (!this.keypair) {
return { success: false, error: 'Wallet not initialized' }
}
try {
const { tokenSymbol, amount, stopPrice, slippageBps = 0, expiredAt } = params
console.log('🛑 Creating stop loss order:', params)
// Determine mint addresses
const inputMint = tokenSymbol === 'SOL' ? this.tokens.SOL : this.tokens.USDC
const outputMint = tokenSymbol === 'SOL' ? this.tokens.USDC : this.tokens.SOL
// Calculate amounts
const makingAmount = tokenSymbol === 'SOL'
? Math.floor(amount * 1_000_000_000).toString() // SOL has 9 decimals
: Math.floor(amount * 1_000_000).toString() // USDC has 6 decimals
const takingAmount = tokenSymbol === 'SOL'
? Math.floor(amount * stopPrice * 1_000_000).toString() // Convert to USDC
: Math.floor(amount / stopPrice * 1_000_000_000).toString() // Convert to SOL
const orderParams: any = {
inputMint,
outputMint,
maker: this.keypair.publicKey.toString(),
payer: this.keypair.publicKey.toString(),
params: {
makingAmount,
takingAmount,
},
computeUnitPrice: "auto"
}
// Add optional parameters
if (slippageBps > 0) {
orderParams.params.slippageBps = slippageBps.toString()
}
if (expiredAt) {
orderParams.params.expiredAt = expiredAt.toString()
}
// Create the trigger order
const response = await fetch('https://api.jup.ag/trigger/v1/createOrder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(orderParams)
})
if (!response.ok) {
const error = await response.json()
throw new Error(`Trigger API error: ${error.error || response.status}`)
}
const result = await response.json()
// Store the order locally
const order: TriggerOrder = {
orderId: result.order,
inputMint,
outputMint,
makingAmount,
takingAmount,
targetPrice: stopPrice,
side: 'SELL',
orderType: 'STOP_LOSS',
status: 'PENDING',
createdAt: Date.now(),
requestId: result.requestId
}
this.activeOrders.push(order)
console.log('✅ Stop loss order created:', result.order)
return {
success: true,
orderId: result.order,
requestId: result.requestId,
transaction: result.transaction
}
} catch (error: any) {
console.error('❌ Failed to create stop loss order:', error)
return { success: false, error: error.message }
}
}
/**
* Create a take profit order
* When current price rises to targetPrice, sell the token
*/
async createTakeProfitOrder(params: {
tokenSymbol: string
amount: number // Amount of tokens to sell
targetPrice: number // Price at which to trigger the sale
slippageBps?: number // Optional slippage (default 0 for exact execution)
expiredAt?: number // Optional expiry timestamp
}): Promise<{
success: boolean
orderId?: string
requestId?: string
transaction?: string
error?: string
}> {
if (!this.keypair) {
return { success: false, error: 'Wallet not initialized' }
}
try {
const { tokenSymbol, amount, targetPrice, slippageBps = 0, expiredAt } = params
console.log('🎯 Creating take profit order:', params)
// Determine mint addresses
const inputMint = tokenSymbol === 'SOL' ? this.tokens.SOL : this.tokens.USDC
const outputMint = tokenSymbol === 'SOL' ? this.tokens.USDC : this.tokens.SOL
// Calculate amounts
const makingAmount = tokenSymbol === 'SOL'
? Math.floor(amount * 1_000_000_000).toString() // SOL has 9 decimals
: Math.floor(amount * 1_000_000).toString() // USDC has 6 decimals
const takingAmount = tokenSymbol === 'SOL'
? Math.floor(amount * targetPrice * 1_000_000).toString() // Convert to USDC
: Math.floor(amount / targetPrice * 1_000_000_000).toString() // Convert to SOL
const orderParams: any = {
inputMint,
outputMint,
maker: this.keypair.publicKey.toString(),
payer: this.keypair.publicKey.toString(),
params: {
makingAmount,
takingAmount,
},
computeUnitPrice: "auto"
}
// Add optional parameters
if (slippageBps > 0) {
orderParams.params.slippageBps = slippageBps.toString()
}
if (expiredAt) {
orderParams.params.expiredAt = expiredAt.toString()
}
// Create the trigger order
const response = await fetch('https://api.jup.ag/trigger/v1/createOrder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(orderParams)
})
if (!response.ok) {
const error = await response.json()
throw new Error(`Trigger API error: ${error.error || response.status}`)
}
const result = await response.json()
// Store the order locally
const order: TriggerOrder = {
orderId: result.order,
inputMint,
outputMint,
makingAmount,
takingAmount,
targetPrice: targetPrice,
side: 'SELL',
orderType: 'TAKE_PROFIT',
status: 'PENDING',
createdAt: Date.now(),
requestId: result.requestId
}
this.activeOrders.push(order)
console.log('✅ Take profit order created:', result.order)
return {
success: true,
orderId: result.order,
requestId: result.requestId,
transaction: result.transaction
}
} catch (error: any) {
console.error('❌ Failed to create take profit order:', error)
return { success: false, error: error.message }
}
}
/**
* Execute (sign and send) a trigger order transaction
*/
async executeOrder(transaction: string, requestId: string): Promise<{
success: boolean
txId?: string
error?: string
}> {
if (!this.keypair) {
return { success: false, error: 'Wallet not initialized' }
}
try {
console.log('⚡ Executing trigger order transaction')
// Deserialize and sign transaction
const transactionBuf = Buffer.from(transaction, 'base64')
const versionedTransaction = VersionedTransaction.deserialize(transactionBuf)
versionedTransaction.sign([this.keypair])
// Send via Jupiter's execute endpoint
const response = await fetch('https://api.jup.ag/trigger/v1/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
requestId,
transaction: Buffer.from(versionedTransaction.serialize()).toString('base64')
})
})
if (!response.ok) {
const error = await response.json()
throw new Error(`Execute API error: ${error.error || response.status}`)
}
const result = await response.json()
console.log('✅ Trigger order executed:', result.txId)
// Update local order status
const order = this.activeOrders.find(o => o.requestId === requestId)
if (order) {
order.status = 'PENDING'
order.txId = result.txId
}
return {
success: true,
txId: result.txId
}
} catch (error: any) {
console.error('❌ Failed to execute trigger order:', error)
return { success: false, error: error.message }
}
}
/**
* Cancel a trigger order
*/
async cancelOrder(orderId: string): Promise<{
success: boolean
txId?: string
error?: string
}> {
if (!this.keypair) {
return { success: false, error: 'Wallet not initialized' }
}
try {
console.log('❌ Cancelling trigger order:', orderId)
const response = await fetch('https://api.jup.ag/trigger/v1/cancelOrder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
order: orderId,
owner: this.keypair.publicKey.toString(),
computeUnitPrice: "auto"
})
})
if (!response.ok) {
const error = await response.json()
throw new Error(`Cancel API error: ${error.error || response.status}`)
}
const result = await response.json()
// Sign and send the cancel transaction
const transactionBuf = Buffer.from(result.transaction, 'base64')
const versionedTransaction = VersionedTransaction.deserialize(transactionBuf)
versionedTransaction.sign([this.keypair])
const txId = await this.connection.sendTransaction(versionedTransaction)
const confirmation = await this.connection.confirmTransaction(txId, 'confirmed')
if (confirmation.value.err) {
throw new Error(`Cancel transaction failed: ${confirmation.value.err}`)
}
// Update local order status
const order = this.activeOrders.find(o => o.orderId === orderId)
if (order) {
order.status = 'CANCELLED'
}
console.log('✅ Trigger order cancelled:', txId)
return {
success: true,
txId
}
} catch (error: any) {
console.error('❌ Failed to cancel trigger order:', error)
return { success: false, error: error.message }
}
}
/**
* Get active trigger orders for the wallet
*/
async getTriggerOrders(): Promise<TriggerOrder[]> {
if (!this.keypair) {
return []
}
try {
const response = await fetch(`https://api.jup.ag/trigger/v1/getTriggerOrders?wallet=${this.keypair.publicKey.toString()}&active=true`)
if (!response.ok) {
throw new Error(`Get orders API error: ${response.status}`)
}
const result = await response.json()
console.log('📊 Retrieved trigger orders:', result.orders?.length || 0)
return result.orders || []
} catch (error: any) {
console.error('❌ Failed to get trigger orders:', error)
return this.activeOrders // Fallback to local orders
}
}
/**
* Create both stop loss and take profit orders for a trade
*/
async createTradingOrders(params: {
tokenSymbol: string
amount: number
stopLoss?: number
takeProfit?: number
slippageBps?: number
expiredAt?: number
}): Promise<{
success: boolean
stopLossOrder?: string
takeProfitOrder?: string
transactions?: string[]
error?: string
}> {
const { tokenSymbol, amount, stopLoss, takeProfit, slippageBps, expiredAt } = params
const results: any = { success: true, transactions: [] }
try {
// Create stop loss order
if (stopLoss) {
const slResult = await this.createStopLossOrder({
tokenSymbol,
amount,
stopPrice: stopLoss,
slippageBps,
expiredAt
})
if (slResult.success) {
results.stopLossOrder = slResult.orderId
results.transactions.push(slResult.transaction)
} else {
console.warn('⚠️ Failed to create stop loss order:', slResult.error)
}
}
// Create take profit order
if (takeProfit) {
const tpResult = await this.createTakeProfitOrder({
tokenSymbol,
amount,
targetPrice: takeProfit,
slippageBps,
expiredAt
})
if (tpResult.success) {
results.takeProfitOrder = tpResult.orderId
results.transactions.push(tpResult.transaction)
} else {
console.warn('⚠️ Failed to create take profit order:', tpResult.error)
}
}
return results
} catch (error: any) {
console.error('❌ Failed to create trading orders:', error)
return { success: false, error: error.message }
}
}
getLocalOrders(): TriggerOrder[] {
return this.activeOrders
}
isConfigured(): boolean {
return this.keypair !== null
}
}
export const jupiterTriggerService = new JupiterTriggerService()
export default JupiterTriggerService