index.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { loadConfig } from '../config.js'
  2. import type { OpenPositionEvent } from '../solana/openPositionListener.js'
  3. const cfg = loadConfig()
  4. export async function copyPosition(positionDetails: OpenPositionEvent['positionDetails']) {
  5. if (!positionDetails) return
  6. const mintA = positionDetails?.mintA || undefined
  7. const mintB = positionDetails?.mintB || undefined
  8. const mintASymbol = mintA?.symbol || undefined
  9. const mintBSymbol = mintB?.symbol || undefined
  10. const totalDeposit = positionDetails?.totalDeposit || undefined
  11. // const upperTick = positionDetails?.upperTick || undefined
  12. // const lowerTick = positionDetails?.lowerTick || undefined
  13. const positionAddress = positionDetails?.positionAddress || ''
  14. const providerAddress = positionDetails?.providerAddress || ''
  15. const nftMintAddress = positionDetails?.nftMintAddress || ''
  16. const copyConfig = cfg?.positionCopy?.config?.[`${mintASymbol}/${mintBSymbol}`] || undefined
  17. let maxUsdValue = 0.2
  18. if (!copyConfig) {
  19. return 'no copy config'
  20. }
  21. const minimumDeposit = copyConfig?.minimumDeposit || 0
  22. if (Number(totalDeposit) < minimumDeposit) {
  23. return
  24. }
  25. if (Number(totalDeposit) > 1000) {
  26. maxUsdValue = copyConfig?.copyMount?.[1000] || 2
  27. } else if (Number(totalDeposit) > 500) {
  28. maxUsdValue = copyConfig?.copyMount?.[500] || 1
  29. } else if (Number(totalDeposit) > minimumDeposit) {
  30. maxUsdValue = copyConfig?.copyMount?.[minimumDeposit] || 0.2
  31. }
  32. console.log(`[${Date.now()}] 复制仓位: ${providerAddress.slice(0, 4)}...${providerAddress.slice(-4)} ${mintASymbol}/${mintBSymbol} ${maxUsdValue}$`)
  33. return fetch(cfg?.positionCopy?.url, {
  34. method: 'POST',
  35. headers: {
  36. 'Content-Type': 'application/json',
  37. 'Authorization': 'Basic YWRtaW46YzU4ODk5Njc='
  38. },
  39. body: JSON.stringify({
  40. nftMintAddress,
  41. positionAddress,
  42. maxUsdValue,
  43. }),
  44. }).then(res => res.json()).then((response: unknown) => {
  45. const data = response as { success?: boolean, error?: string }
  46. if (!data.success) {
  47. throw new Error(data.error ?? 'Failed to copy position')
  48. }
  49. console.log(`[${Date.now()}] 复制仓位成功`)
  50. // 发送Discord通知,使用embeds
  51. const solscanBaseUrl = 'https://solscan.io'
  52. const embed = {
  53. title: '✅ 仓位复制成功',
  54. description: `成功复制仓位:**${mintASymbol}/${mintBSymbol}**`,
  55. color: 0x00ff00, // 绿色
  56. fields: [
  57. {
  58. name: '💰 币对',
  59. value: `${mintASymbol}/${mintBSymbol}`,
  60. inline: true
  61. },
  62. {
  63. name: '💵 总存款',
  64. value: `$${Number(totalDeposit).toFixed(2)}`,
  65. inline: true
  66. },
  67. {
  68. name: '📊 复制金额',
  69. value: `$${maxUsdValue.toFixed(2)}`,
  70. inline: true
  71. },
  72. {
  73. name: '👤 提供者地址',
  74. value: `[${providerAddress.slice(0, 4)}...${providerAddress.slice(-4)}](${solscanBaseUrl}/account/${providerAddress})`,
  75. inline: false
  76. },
  77. {
  78. name: '📍 仓位地址',
  79. value: `[${positionAddress.slice(0, 8)}...${positionAddress.slice(-8)}](${solscanBaseUrl}/account/${positionAddress})`,
  80. inline: false
  81. },
  82. {
  83. name: '🎨 NFT Mint',
  84. value: `[${nftMintAddress.slice(0, 8)}...${nftMintAddress.slice(-8)}](${solscanBaseUrl}/token/${nftMintAddress})`,
  85. inline: false
  86. }
  87. ],
  88. timestamp: new Date().toISOString(),
  89. footer: {
  90. text: 'ByReal Auto Trading'
  91. }
  92. }
  93. // 如果有价格信息,添加到字段中
  94. if (mintA?.price && mintB?.price) {
  95. embed.fields.push({
  96. name: '💹 价格信息',
  97. value: `${mintASymbol}: $${Number(mintA.price).toFixed(6)}\n${mintBSymbol}: $${Number(mintB.price).toFixed(6)}`,
  98. inline: false
  99. })
  100. }
  101. return fetch('https://discord.com/api/webhooks/1457714616636280978/YFMGaZEj2gJwUjINpFfJIkagG1I3SLZRwz9bGpc2OlGFWBVa88r73cMIkBpX3iGpSIjV', {
  102. method: 'POST',
  103. headers: {
  104. 'Content-Type': 'application/json',
  105. },
  106. body: JSON.stringify({
  107. embeds: [embed]
  108. }),
  109. })
  110. }).catch(err => {
  111. // 发送失败通知
  112. const errorEmbed = {
  113. title: '❌ 仓位复制失败',
  114. description: `复制仓位失败:**${mintASymbol}/${mintBSymbol}**`,
  115. color: 0xff0000, // 红色
  116. fields: [
  117. {
  118. name: '💰 币对',
  119. value: `${mintASymbol}/${mintBSymbol}`,
  120. inline: true
  121. },
  122. {
  123. name: '💵 总存款',
  124. value: `$${Number(totalDeposit).toFixed(2)}`,
  125. inline: true
  126. },
  127. {
  128. name: '📍 仓位地址',
  129. value: `[${positionAddress.slice(0, 8)}...${positionAddress.slice(-8)}](https://solscan.io/account/${positionAddress})`,
  130. inline: false
  131. },
  132. {
  133. name: '⚠️ 错误信息',
  134. value: `\`\`\`${err instanceof Error ? err.message : String(err)}\`\`\``,
  135. inline: false
  136. }
  137. ],
  138. timestamp: new Date().toISOString(),
  139. footer: {
  140. text: 'ByReal Auto Trading'
  141. }
  142. }
  143. return fetch('https://discord.com/api/webhooks/1457714616636280978/YFMGaZEj2gJwUjINpFfJIkagG1I3SLZRwz9bGpc2OlGFWBVa88r73cMIkBpX3iGpSIjV', {
  144. method: 'POST',
  145. headers: {
  146. 'Content-Type': 'application/json',
  147. },
  148. body: JSON.stringify({
  149. embeds: [errorEmbed]
  150. }),
  151. }).catch(notifyErr => {
  152. console.error(`[${Date.now()}] 发送Discord通知失败: ${notifyErr}`)
  153. })
  154. })
  155. }