index.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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(() => {
  45. console.log(`[${Date.now()}] 复制仓位成功`)
  46. // 发送Discord通知,使用embeds
  47. const solscanBaseUrl = 'https://solscan.io'
  48. const embed = {
  49. title: '✅ 仓位复制成功',
  50. description: `成功复制仓位:**${mintASymbol}/${mintBSymbol}**`,
  51. color: 0x00ff00, // 绿色
  52. fields: [
  53. {
  54. name: '💰 币对',
  55. value: `${mintASymbol}/${mintBSymbol}`,
  56. inline: true
  57. },
  58. {
  59. name: '💵 总存款',
  60. value: `$${Number(totalDeposit).toFixed(2)}`,
  61. inline: true
  62. },
  63. {
  64. name: '📊 复制金额',
  65. value: `$${maxUsdValue.toFixed(2)}`,
  66. inline: true
  67. },
  68. {
  69. name: '👤 提供者地址',
  70. value: `[${providerAddress.slice(0, 4)}...${providerAddress.slice(-4)}](${solscanBaseUrl}/account/${providerAddress})`,
  71. inline: false
  72. },
  73. {
  74. name: '📍 仓位地址',
  75. value: `[${positionAddress.slice(0, 8)}...${positionAddress.slice(-8)}](${solscanBaseUrl}/account/${positionAddress})`,
  76. inline: false
  77. },
  78. {
  79. name: '🎨 NFT Mint',
  80. value: `[${nftMintAddress.slice(0, 8)}...${nftMintAddress.slice(-8)}](${solscanBaseUrl}/token/${nftMintAddress})`,
  81. inline: false
  82. }
  83. ],
  84. timestamp: new Date().toISOString(),
  85. footer: {
  86. text: 'ByReal Auto Trading'
  87. }
  88. }
  89. // 如果有价格信息,添加到字段中
  90. if (mintA?.price && mintB?.price) {
  91. embed.fields.push({
  92. name: '💹 价格信息',
  93. value: `${mintASymbol}: $${Number(mintA.price).toFixed(6)}\n${mintBSymbol}: $${Number(mintB.price).toFixed(6)}`,
  94. inline: false
  95. })
  96. }
  97. return fetch('https://discord.com/api/webhooks/1457714616636280978/YFMGaZEj2gJwUjINpFfJIkagG1I3SLZRwz9bGpc2OlGFWBVa88r73cMIkBpX3iGpSIjV', {
  98. method: 'POST',
  99. headers: {
  100. 'Content-Type': 'application/json',
  101. },
  102. body: JSON.stringify({
  103. embeds: [embed]
  104. }),
  105. })
  106. }).catch(err => {
  107. // 发送失败通知
  108. const errorEmbed = {
  109. title: '❌ 仓位复制失败',
  110. description: `复制仓位失败:**${mintASymbol}/${mintBSymbol}**`,
  111. color: 0xff0000, // 红色
  112. fields: [
  113. {
  114. name: '💰 币对',
  115. value: `${mintASymbol}/${mintBSymbol}`,
  116. inline: true
  117. },
  118. {
  119. name: '💵 总存款',
  120. value: `$${Number(totalDeposit).toFixed(2)}`,
  121. inline: true
  122. },
  123. {
  124. name: '📍 仓位地址',
  125. value: `[${positionAddress.slice(0, 8)}...${positionAddress.slice(-8)}](https://solscan.io/account/${positionAddress})`,
  126. inline: false
  127. },
  128. {
  129. name: '⚠️ 错误信息',
  130. value: `\`\`\`${err instanceof Error ? err.message : String(err)}\`\`\``,
  131. inline: false
  132. }
  133. ],
  134. timestamp: new Date().toISOString(),
  135. footer: {
  136. text: 'ByReal Auto Trading'
  137. }
  138. }
  139. return fetch('https://discord.com/api/webhooks/1457714616636280978/YFMGaZEj2gJwUjINpFfJIkagG1I3SLZRwz9bGpc2OlGFWBVa88r73cMIkBpX3iGpSIjV', {
  140. method: 'POST',
  141. headers: {
  142. 'Content-Type': 'application/json',
  143. },
  144. body: JSON.stringify({
  145. embeds: [errorEmbed]
  146. }),
  147. }).catch(notifyErr => {
  148. console.error(`[${Date.now()}] 发送Discord通知失败: ${notifyErr}`)
  149. })
  150. })
  151. }