├── .DS_Store ├── .gitattributes ├── README.md ├── package-lock.json ├── package.json ├── src ├── .DS_Store ├── dlmm-chain-pools-manager │ ├── .DS_Store │ ├── README.md │ ├── package.json │ ├── src │ │ ├── config.ts │ │ ├── display.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── models.ts │ │ ├── services.ts │ │ └── utils.ts │ └── tsconfig.json ├── scripts │ └── encrypt-key.ts └── utils │ └── crypto.ts └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptocj520/meteora/4d5741bab5b31c9ecb3bb910a7eebe7755c394ab/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DLMM串联池流动性管理系统 2 | 3 | ## 项目概述 4 | 5 | DLMM串联池流动性管理系统是一个专为Solana区块链上的DLMM(Discrete Liquidity Market Maker)池子设计的自动化管理工具。该系统能够实时监控并维护多个串联池子的流动性分布,确保流动性按照BidAsk模型正确分布,从而优化交易效率和减少滑点。 6 | 7 | 当市场价格变动,跨越不同头寸范围时,系统会自动检测并调整不符合BidAsk模型的头寸,确保市场流动性始终保持最优状态。这种自动化管理大大降低了流动性提供者的操作负担,同时提高了市场的整体效率。 8 | 9 | ## 核心功能 10 | 11 | ### 1. 动态池子管理 12 | 13 | - **自动发现池子**:系统启动时会自动扫描并识别用户的所有DLMM池子头寸,无需手动配置 14 | - **多池子串联管理**:支持同时管理多个串联的DLMM流动性池,确保它们协同工作 15 | - **头寸数据实时分析**:持续收集和分析每个头寸的bin数据和流动性分布 16 | 17 | ### 2. BidAsk模型合规性检查 18 | 19 | - **流动性分布验证**:检查每个头寸的流动性分布是否符合BidAsk模型要求 20 | - **升序/降序分布检测**:高于当前价格的区域应为升序分布,低于当前价格的区域应为降序分布 21 | - **合规性报告**:生成详细的头寸合规性报告,显示不符合要求的具体原因 22 | 23 | ### 3. 自动调整策略 24 | 25 | - **流动性移除**:对不符合BidAsk模型的头寸自动移除100%流动性 26 | - **重新添加流动性**:使用正确的BidAsk策略重新添加流动性,确保符合模型要求 27 | - **渐进式调整**:系统会一次调整一个头寸,确保调整过程平稳进行 28 | 29 | ### 4. 实时价格监控 30 | 31 | - **活跃Bin监控**:持续监控每个池子的活跃Bin变化 32 | - **价格变动检测**:实时检测X/Y代币价格变动 33 | - **池子交叉检测**:当价格跨越不同头寸范围时自动触发检查 34 | 35 | ### 5. 安全与私钥管理 36 | 37 | - **私钥加密存储**:支持使用密码加密私钥,提高系统安全性 38 | - **多级私钥加载**:支持从加密文件或配置文件加载私钥 39 | - **用户身份验证**:在操作关键功能前进行身份验证 40 | 41 | ### 6. 交易优化 42 | 43 | - **优先级费用设置**:支持设置交易优先级费用,提高交易成功率 44 | - **交易自动重试**:遇到临时错误时自动重试,最多尝试5次 45 | - **计算单元优化**:可配置计算单元限制,优化交易执行 46 | 47 | ### 7. 可视化与日志 48 | 49 | - **终端实时显示**:在终端实时显示池子状态和调整过程 50 | - **详细日志记录**:记录系统的每一步操作,便于问题排查 51 | - **状态消息更新**:直观显示当前系统状态和操作进度 52 | 53 | ## 系统架构 54 | 55 | DLMM串联池流动性管理系统采用简化的模块化架构设计,将系统分为以下几个主要部分: 56 | 57 | ``` 58 | dlmm-chain-pools-manager/ 59 | ├── src/ 60 | │ ├── models.ts # 数据模型定义 61 | │ ├── services.ts # 核心服务功能 62 | │ ├── utils.ts # 工具函数 63 | │ ├── logger.ts # 日志模块 64 | │ ├── display.ts # 显示模块 65 | │ ├── config.ts # 配置文件 66 | │ └── index.ts # 主程序入口 67 | ├── package.json # 项目依赖 68 | └── tsconfig.json # TypeScript配置 69 | ``` 70 | 71 | ### 核心模块说明 72 | 73 | 1. **数据模型 (models.ts)**: 74 | - 定义系统中的核心数据结构,包括池子、头寸和池子链等 75 | - 包含数据处理和验证逻辑 76 | - 实现BidAsk模型合规性检查的基础函数 77 | 78 | 2. **服务功能 (services.ts)**: 79 | - 整合所有核心业务逻辑到一个文件中 80 | - 包含连接管理、钱包服务、池子发现和价格监控功能 81 | - 实现流动性调整的关键算法 82 | - 处理与区块链交互的全部逻辑 83 | 84 | 3. **工具函数 (utils.ts)**: 85 | - 提供通用辅助功能和算法 86 | - 实现错误重试、数据格式化等功能 87 | - 包含通用的时间和计算工具 88 | 89 | 4. **日志模块 (logger.ts)**: 90 | - 管理全系统的日志记录 91 | - 支持多级别日志(DEBUG, INFO, WARNING, ERROR) 92 | - 提供结构化和格式化的日志输出 93 | 94 | 5. **显示模块 (display.ts)**: 95 | - 负责终端界面显示和用户交互 96 | - 实现池子状态和操作进度的可视化 97 | - 提供彩色编码的状态提示 98 | 99 | 6. **配置文件 (config.ts)**: 100 | - 集中管理所有系统参数和设置 101 | - 包含网络连接、监控和调整策略的配置项 102 | 103 | 7. **主程序 (index.ts)**: 104 | - 系统入口点 105 | - 协调各模块工作并实现主要工作流程 106 | - 处理启动、循环执行和优雅退出 107 | 108 | ## 安装指南 109 | 110 | ### 前置要求 111 | 112 | - Node.js v14.0.0 或更高版本 113 | - npm v6.0.0 或更高版本 114 | - 一个有效的Solana钱包(用于管理流动性) 115 | 116 | ### 安装步骤 117 | 118 | 1. **克隆项目** 119 | 120 | ```bash 121 | git clone https://github.com/yourusername/dlmm-chain-pools-manager.git 122 | cd dlmm-chain-pools-manager 123 | ``` 124 | 125 | 2. **安装依赖** 126 | 127 | ```bash 128 | npm install 129 | ``` 130 | 131 | 3. **配置系统** 132 | 133 | 编辑 `src/config.ts` 文件,设置关键参数: 134 | 135 | ```typescript 136 | // 基本配置 137 | export const CONFIG = { 138 | // Solana RPC端点URL 139 | RPC_ENDPOINT: "https://api.mainnet-beta.solana.com", 140 | // 刷新间隔(毫秒) 141 | REFRESH_INTERVAL_MS: 10000, 142 | // 是否在终端显示界面 143 | DISPLAY_ENABLED: true, 144 | // 日志级别: 'debug' | 'info' | 'warn' | 'error' 145 | LOG_LEVEL: "info", 146 | }; 147 | 148 | // 钱包配置 149 | export const WALLET_CONFIG = { 150 | // 私钥(Base58编码)- 生产环境建议使用环境变量或加密文件 151 | PRIVATE_KEY: "你的私钥", 152 | }; 153 | 154 | // 交易配置 155 | export const TRANSACTION_CONFIG = { 156 | // 是否启用优先级费用 157 | ENABLE_PRIORITY_FEE: true, 158 | // 优先级费用(microLamports) 159 | PRIORITY_FEE_MICROLAMPORTS: 200000, 160 | }; 161 | ``` 162 | 163 | 4. **编译项目** 164 | 165 | ```bash 166 | npm run build 167 | ``` 168 | 169 | ## 使用方法 170 | 171 | ### 开发模式 172 | 173 | 开发模式下,系统会实时编译并运行: 174 | 175 | ```bash 176 | npm run dev 177 | ``` 178 | 179 | ### 生产模式 180 | 181 | 在生产环境中,先构建项目然后运行: 182 | 183 | ```bash 184 | npm run build 185 | npm start 186 | ``` 187 | 188 | ### 退出程序 189 | 190 | 按 `Ctrl+C` 安全退出程序。 191 | 192 | ## 工作流程 193 | 194 | 系统遵循以下工作流程: 195 | 196 | 1. **初始化**: 197 | - 加载配置文件 198 | - 连接到Solana网络 199 | - 初始化钱包 200 | - 扫描并发现用户的所有DLMM头寸 201 | 202 | 2. **监控**: 203 | - 定期轮询活跃Bin和价格变动 204 | - 当检测到活跃Bin变化时,触发池子交叉检测 205 | - 检查相邻头寸是否符合BidAsk模型要求 206 | 207 | 3. **调整**: 208 | - 对不符合BidAsk模型的头寸,移除其全部流动性 209 | - 根据BidAsk模型重新添加流动性 210 | - 刷新头寸数据,确保调整生效 211 | 212 | 4. **显示**: 213 | - 实时更新终端显示,展示当前状态 214 | - 记录详细日志,以便后续分析 215 | 216 | ## 配置项说明 217 | 218 | ### 主要配置参数 219 | 220 | | 配置项 | 说明 | 默认值 | 221 | |----------------------------|----------------------------|-----------------------------------| 222 | | RPC_ENDPOINT | Solana网络RPC端点 | https://api.mainnet-beta.solana.com | 223 | | REFRESH_INTERVAL_MS | 数据刷新间隔(毫秒) | 10000 | 224 | | DISPLAY_ENABLED | 是否启用终端显示界面 | true | 225 | | LOG_LEVEL | 日志级别 | info | 226 | | PRIORITY_FEE_MICROLAMPORTS | 优先级费用(microLamports)| 200000 | 227 | | MAX_RETRIES | 交易最大重试次数 | 5 | 228 | 229 | ## BidAsk模型说明 230 | 231 | BidAsk模型是一种优化流动性分布的策略,核心理念是: 232 | 233 | 1. **高于当前价格(Ask侧)**:流动性呈**升序**分布 234 | - 例:bin 100有10个代币,bin 101有20个代币,bin 102有30个代币... 235 | 236 | 2. **低于当前价格(Bid侧)**:流动性呈**降序**分布 237 | - 例:bin 99有30个代币,bin 98有20个代币,bin 97有10个代币... 238 | 239 | 这种分布方式确保流动性集中在最可能被交易的价格区间,提高资本效率。 240 | 241 | ## 私钥加密 242 | 243 | 为了提高系统安全性,本项目支持对私钥进行加密存储,避免在配置文件中明文保存私钥。 244 | 245 | ### 加密私钥步骤 246 | 247 | 1. **运行加密命令** 248 | 249 | ```bash 250 | npm run encrypt-key 251 | ``` 252 | 253 | 2. **按提示输入私钥和密码** 254 | 255 | ``` 256 | 请输入需要加密的私钥 (Base58格式): <输入你的私钥> 257 | 请设置加密密码: <输入密码> 258 | 请再次输入密码确认: <再次输入密码> 259 | ``` 260 | 261 | 3. **确认加密文件创建成功** 262 | 263 | 成功后,系统会创建一个加密的私钥文件,默认保存在项目根目录的 `.key` 文件中。同时,系统会自动修改 `config.ts` 文件中的相关配置: 264 | 265 | ```typescript 266 | export const WALLET_CONFIG = { 267 | USE_ENCRYPTED_KEY: true, 268 | // 私钥字段将被清空 269 | PRIVATE_KEY: "", 270 | // 可选:自定义加密文件路径 271 | KEY_FILE_PATH: "./.key" 272 | }; 273 | ``` 274 | 275 | ### 使用加密私钥 276 | 277 | 启动程序时,如果配置中启用了加密私钥(`USE_ENCRYPTED_KEY: true`),系统会提示输入密码: 278 | 279 | ```bash 280 | npm start 281 | # 系统会提示:请输入密码解锁私钥: 282 | ``` 283 | 284 | 输入正确密码后,系统会解密私钥并继续启动流程。 285 | 286 | ### 安全注意事项 287 | 288 | - 密码应当足够复杂,包含大小写字母、数字和特殊符号 289 | - 定期更换密码和私钥 290 | - 加密文件(.key)包含敏感信息,请勿分享或提交到版本控制系统 291 | - 建议将加密文件添加到 `.gitignore` 中 292 | 293 | ## 故障排除 294 | 295 | ### 常见问题解决方案 296 | 297 | 1. **连接错误** 298 | - 检查RPC_ENDPOINT配置是否正确 299 | - 确认网络连接正常 300 | - 尝试使用备用RPC端点 301 | 302 | 2. **交易失败** 303 | - 增加优先级费用值 304 | - 检查钱包余额是否足够 305 | - 查看日志了解详细错误信息 306 | 307 | 3. **配置加载错误** 308 | - 确认config.ts文件存在且格式正确 309 | - 检查文件权限 310 | 311 | ### 日志分析 312 | 313 | 系统日志包含详细的操作记录和错误信息,可帮助诊断问题: 314 | 315 | - `[INFO]` - 一般信息,正常操作 316 | - `[WARN]` - 警告信息,可能需要注意 317 | - `[ERROR]` - 错误信息,需要排查 318 | - `[DEBUG]` - 调试信息,包含详细内部操作 319 | 320 | ## 安全建议 321 | 322 | 1. **私钥安全** 323 | - 不要在配置文件中存储明文私钥 324 | - 考虑使用环境变量存储私钥 325 | - 定期更换私钥 326 | 327 | 2. **权限控制** 328 | - 为流动性管理创建专用钱包 329 | - 不要在该钱包中存储大量资金 330 | 331 | 3. **网络安全** 332 | - 使用可靠的RPC端点 333 | - 避免在不安全的网络上运行程序 334 | 335 | ## 许可证 336 | 337 | 本项目采用 MIT 许可证 338 | 339 | --- 340 | 341 | 感谢使用DLMM串联池流动性管理系统! -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlmm-project", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "dlmm-project", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@meteora-ag/dlmm": "^1.5.0", 13 | "@solana/web3.js": "^1.91.1", 14 | "axios": "^1.9.0" 15 | }, 16 | "devDependencies": { 17 | "@types/bn.js": "^5.1.5", 18 | "@types/bs58": "^4.0.4", 19 | "@types/node": "^20.12.9", 20 | "ts-node": "^10.9.2", 21 | "typescript": "^5.4.2" 22 | } 23 | }, 24 | "node_modules/@babel/runtime": { 25 | "version": "7.27.1", 26 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", 27 | "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", 28 | "license": "MIT", 29 | "engines": { 30 | "node": ">=6.9.0" 31 | } 32 | }, 33 | "node_modules/@coral-xyz/anchor": { 34 | "version": "0.28.0", 35 | "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.28.0.tgz", 36 | "integrity": "sha512-kQ02Hv2ZqxtWP30WN1d4xxT4QqlOXYDxmEd3k/bbneqhV3X5QMO4LAtoUFs7otxyivOgoqam5Il5qx81FuI4vw==", 37 | "license": "(MIT OR Apache-2.0)", 38 | "dependencies": { 39 | "@coral-xyz/borsh": "^0.28.0", 40 | "@solana/web3.js": "^1.68.0", 41 | "base64-js": "^1.5.1", 42 | "bn.js": "^5.1.2", 43 | "bs58": "^4.0.1", 44 | "buffer-layout": "^1.2.2", 45 | "camelcase": "^6.3.0", 46 | "cross-fetch": "^3.1.5", 47 | "crypto-hash": "^1.3.0", 48 | "eventemitter3": "^4.0.7", 49 | "js-sha256": "^0.9.0", 50 | "pako": "^2.0.3", 51 | "snake-case": "^3.0.4", 52 | "superstruct": "^0.15.4", 53 | "toml": "^3.0.0" 54 | }, 55 | "engines": { 56 | "node": ">=11" 57 | } 58 | }, 59 | "node_modules/@coral-xyz/borsh": { 60 | "version": "0.28.0", 61 | "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.28.0.tgz", 62 | "integrity": "sha512-/u1VTzw7XooK7rqeD7JLUSwOyRSesPUk0U37BV9zK0axJc1q0nRbKFGFLYCQ16OtdOJTTwGfGp11Lx9B45bRCQ==", 63 | "license": "Apache-2.0", 64 | "dependencies": { 65 | "bn.js": "^5.1.2", 66 | "buffer-layout": "^1.2.0" 67 | }, 68 | "engines": { 69 | "node": ">=10" 70 | }, 71 | "peerDependencies": { 72 | "@solana/web3.js": "^1.68.0" 73 | } 74 | }, 75 | "node_modules/@cspotcode/source-map-support": { 76 | "version": "0.8.1", 77 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 78 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 79 | "dev": true, 80 | "license": "MIT", 81 | "dependencies": { 82 | "@jridgewell/trace-mapping": "0.3.9" 83 | }, 84 | "engines": { 85 | "node": ">=12" 86 | } 87 | }, 88 | "node_modules/@jridgewell/resolve-uri": { 89 | "version": "3.1.2", 90 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 91 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 92 | "dev": true, 93 | "license": "MIT", 94 | "engines": { 95 | "node": ">=6.0.0" 96 | } 97 | }, 98 | "node_modules/@jridgewell/sourcemap-codec": { 99 | "version": "1.5.0", 100 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 101 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 102 | "dev": true, 103 | "license": "MIT" 104 | }, 105 | "node_modules/@jridgewell/trace-mapping": { 106 | "version": "0.3.9", 107 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 108 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 109 | "dev": true, 110 | "license": "MIT", 111 | "dependencies": { 112 | "@jridgewell/resolve-uri": "^3.0.3", 113 | "@jridgewell/sourcemap-codec": "^1.4.10" 114 | } 115 | }, 116 | "node_modules/@meteora-ag/dlmm": { 117 | "version": "1.5.0", 118 | "resolved": "https://registry.npmjs.org/@meteora-ag/dlmm/-/dlmm-1.5.0.tgz", 119 | "integrity": "sha512-d8DwhSrSSuIPfw6+9LLu2aXylKc7tS6FTnQzZcQYlGhrKIlABn97muasizBAcgMp80AR/bKEa6qtFCoQt4pqwA==", 120 | "license": "ISC", 121 | "dependencies": { 122 | "@coral-xyz/anchor": "^0.28.0", 123 | "@coral-xyz/borsh": "^0.28.0", 124 | "@solana/buffer-layout": "^4.0.1", 125 | "@solana/spl-token": "^0.4.6", 126 | "@solana/web3.js": "^1.91.6", 127 | "bn.js": "^5.2.1", 128 | "decimal.js": "^10.4.2", 129 | "express": "^4.19.2", 130 | "gaussian": "^1.3.0" 131 | } 132 | }, 133 | "node_modules/@noble/curves": { 134 | "version": "1.9.0", 135 | "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz", 136 | "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==", 137 | "license": "MIT", 138 | "dependencies": { 139 | "@noble/hashes": "1.8.0" 140 | }, 141 | "engines": { 142 | "node": "^14.21.3 || >=16" 143 | }, 144 | "funding": { 145 | "url": "https://paulmillr.com/funding/" 146 | } 147 | }, 148 | "node_modules/@noble/hashes": { 149 | "version": "1.8.0", 150 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", 151 | "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", 152 | "license": "MIT", 153 | "engines": { 154 | "node": "^14.21.3 || >=16" 155 | }, 156 | "funding": { 157 | "url": "https://paulmillr.com/funding/" 158 | } 159 | }, 160 | "node_modules/@solana/buffer-layout": { 161 | "version": "4.0.1", 162 | "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", 163 | "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", 164 | "license": "MIT", 165 | "dependencies": { 166 | "buffer": "~6.0.3" 167 | }, 168 | "engines": { 169 | "node": ">=5.10" 170 | } 171 | }, 172 | "node_modules/@solana/buffer-layout-utils": { 173 | "version": "0.2.0", 174 | "resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz", 175 | "integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==", 176 | "license": "Apache-2.0", 177 | "dependencies": { 178 | "@solana/buffer-layout": "^4.0.0", 179 | "@solana/web3.js": "^1.32.0", 180 | "bigint-buffer": "^1.1.5", 181 | "bignumber.js": "^9.0.1" 182 | }, 183 | "engines": { 184 | "node": ">= 10" 185 | } 186 | }, 187 | "node_modules/@solana/codecs": { 188 | "version": "2.0.0-rc.1", 189 | "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.0.0-rc.1.tgz", 190 | "integrity": "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==", 191 | "license": "MIT", 192 | "dependencies": { 193 | "@solana/codecs-core": "2.0.0-rc.1", 194 | "@solana/codecs-data-structures": "2.0.0-rc.1", 195 | "@solana/codecs-numbers": "2.0.0-rc.1", 196 | "@solana/codecs-strings": "2.0.0-rc.1", 197 | "@solana/options": "2.0.0-rc.1" 198 | }, 199 | "peerDependencies": { 200 | "typescript": ">=5" 201 | } 202 | }, 203 | "node_modules/@solana/codecs-core": { 204 | "version": "2.0.0-rc.1", 205 | "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz", 206 | "integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==", 207 | "license": "MIT", 208 | "dependencies": { 209 | "@solana/errors": "2.0.0-rc.1" 210 | }, 211 | "peerDependencies": { 212 | "typescript": ">=5" 213 | } 214 | }, 215 | "node_modules/@solana/codecs-data-structures": { 216 | "version": "2.0.0-rc.1", 217 | "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz", 218 | "integrity": "sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==", 219 | "license": "MIT", 220 | "dependencies": { 221 | "@solana/codecs-core": "2.0.0-rc.1", 222 | "@solana/codecs-numbers": "2.0.0-rc.1", 223 | "@solana/errors": "2.0.0-rc.1" 224 | }, 225 | "peerDependencies": { 226 | "typescript": ">=5" 227 | } 228 | }, 229 | "node_modules/@solana/codecs-numbers": { 230 | "version": "2.0.0-rc.1", 231 | "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz", 232 | "integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==", 233 | "license": "MIT", 234 | "dependencies": { 235 | "@solana/codecs-core": "2.0.0-rc.1", 236 | "@solana/errors": "2.0.0-rc.1" 237 | }, 238 | "peerDependencies": { 239 | "typescript": ">=5" 240 | } 241 | }, 242 | "node_modules/@solana/codecs-strings": { 243 | "version": "2.0.0-rc.1", 244 | "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz", 245 | "integrity": "sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==", 246 | "license": "MIT", 247 | "dependencies": { 248 | "@solana/codecs-core": "2.0.0-rc.1", 249 | "@solana/codecs-numbers": "2.0.0-rc.1", 250 | "@solana/errors": "2.0.0-rc.1" 251 | }, 252 | "peerDependencies": { 253 | "fastestsmallesttextencoderdecoder": "^1.0.22", 254 | "typescript": ">=5" 255 | } 256 | }, 257 | "node_modules/@solana/errors": { 258 | "version": "2.0.0-rc.1", 259 | "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz", 260 | "integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==", 261 | "license": "MIT", 262 | "dependencies": { 263 | "chalk": "^5.3.0", 264 | "commander": "^12.1.0" 265 | }, 266 | "bin": { 267 | "errors": "bin/cli.mjs" 268 | }, 269 | "peerDependencies": { 270 | "typescript": ">=5" 271 | } 272 | }, 273 | "node_modules/@solana/options": { 274 | "version": "2.0.0-rc.1", 275 | "resolved": "https://registry.npmjs.org/@solana/options/-/options-2.0.0-rc.1.tgz", 276 | "integrity": "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==", 277 | "license": "MIT", 278 | "dependencies": { 279 | "@solana/codecs-core": "2.0.0-rc.1", 280 | "@solana/codecs-data-structures": "2.0.0-rc.1", 281 | "@solana/codecs-numbers": "2.0.0-rc.1", 282 | "@solana/codecs-strings": "2.0.0-rc.1", 283 | "@solana/errors": "2.0.0-rc.1" 284 | }, 285 | "peerDependencies": { 286 | "typescript": ">=5" 287 | } 288 | }, 289 | "node_modules/@solana/spl-token": { 290 | "version": "0.4.13", 291 | "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.13.tgz", 292 | "integrity": "sha512-cite/pYWQZZVvLbg5lsodSovbetK/eA24gaR0eeUeMuBAMNrT8XFCwaygKy0N2WSg3gSyjjNpIeAGBAKZaY/1w==", 293 | "license": "Apache-2.0", 294 | "dependencies": { 295 | "@solana/buffer-layout": "^4.0.0", 296 | "@solana/buffer-layout-utils": "^0.2.0", 297 | "@solana/spl-token-group": "^0.0.7", 298 | "@solana/spl-token-metadata": "^0.1.6", 299 | "buffer": "^6.0.3" 300 | }, 301 | "engines": { 302 | "node": ">=16" 303 | }, 304 | "peerDependencies": { 305 | "@solana/web3.js": "^1.95.5" 306 | } 307 | }, 308 | "node_modules/@solana/spl-token-group": { 309 | "version": "0.0.7", 310 | "resolved": "https://registry.npmjs.org/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz", 311 | "integrity": "sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==", 312 | "license": "Apache-2.0", 313 | "dependencies": { 314 | "@solana/codecs": "2.0.0-rc.1" 315 | }, 316 | "engines": { 317 | "node": ">=16" 318 | }, 319 | "peerDependencies": { 320 | "@solana/web3.js": "^1.95.3" 321 | } 322 | }, 323 | "node_modules/@solana/spl-token-metadata": { 324 | "version": "0.1.6", 325 | "resolved": "https://registry.npmjs.org/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz", 326 | "integrity": "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==", 327 | "license": "Apache-2.0", 328 | "dependencies": { 329 | "@solana/codecs": "2.0.0-rc.1" 330 | }, 331 | "engines": { 332 | "node": ">=16" 333 | }, 334 | "peerDependencies": { 335 | "@solana/web3.js": "^1.95.3" 336 | } 337 | }, 338 | "node_modules/@solana/web3.js": { 339 | "version": "1.98.2", 340 | "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.2.tgz", 341 | "integrity": "sha512-BqVwEG+TaG2yCkBMbD3C4hdpustR4FpuUFRPUmqRZYYlPI9Hg4XMWxHWOWRzHE9Lkc9NDjzXFX7lDXSgzC7R1A==", 342 | "license": "MIT", 343 | "dependencies": { 344 | "@babel/runtime": "^7.25.0", 345 | "@noble/curves": "^1.4.2", 346 | "@noble/hashes": "^1.4.0", 347 | "@solana/buffer-layout": "^4.0.1", 348 | "@solana/codecs-numbers": "^2.1.0", 349 | "agentkeepalive": "^4.5.0", 350 | "bn.js": "^5.2.1", 351 | "borsh": "^0.7.0", 352 | "bs58": "^4.0.1", 353 | "buffer": "6.0.3", 354 | "fast-stable-stringify": "^1.0.0", 355 | "jayson": "^4.1.1", 356 | "node-fetch": "^2.7.0", 357 | "rpc-websockets": "^9.0.2", 358 | "superstruct": "^2.0.2" 359 | } 360 | }, 361 | "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { 362 | "version": "2.1.0", 363 | "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.1.0.tgz", 364 | "integrity": "sha512-SR7pKtmJBg2mhmkel2NeHA1pz06QeQXdMv8WJoIR9m8F/hw80K/612uaYbwTt2nkK0jg/Qn/rNSd7EcJ4SBGjw==", 365 | "license": "MIT", 366 | "dependencies": { 367 | "@solana/errors": "2.1.0" 368 | }, 369 | "engines": { 370 | "node": ">=20.18.0" 371 | }, 372 | "peerDependencies": { 373 | "typescript": ">=5" 374 | } 375 | }, 376 | "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { 377 | "version": "2.1.0", 378 | "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.1.0.tgz", 379 | "integrity": "sha512-XMu4yw5iCgQnMKsxSWPPOrGgtaohmupN3eyAtYv3K3C/MJEc5V90h74k5B1GUCiHvcrdUDO9RclNjD9lgbjFag==", 380 | "license": "MIT", 381 | "dependencies": { 382 | "@solana/codecs-core": "2.1.0", 383 | "@solana/errors": "2.1.0" 384 | }, 385 | "engines": { 386 | "node": ">=20.18.0" 387 | }, 388 | "peerDependencies": { 389 | "typescript": ">=5" 390 | } 391 | }, 392 | "node_modules/@solana/web3.js/node_modules/@solana/errors": { 393 | "version": "2.1.0", 394 | "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.1.0.tgz", 395 | "integrity": "sha512-l+GxAv0Ar4d3c3PlZdA9G++wFYZREEbbRyAFP8+n8HSg0vudCuzogh/13io6hYuUhG/9Ve8ARZNamhV7UScKNw==", 396 | "license": "MIT", 397 | "dependencies": { 398 | "chalk": "^5.3.0", 399 | "commander": "^13.1.0" 400 | }, 401 | "bin": { 402 | "errors": "bin/cli.mjs" 403 | }, 404 | "engines": { 405 | "node": ">=20.18.0" 406 | }, 407 | "peerDependencies": { 408 | "typescript": ">=5" 409 | } 410 | }, 411 | "node_modules/@solana/web3.js/node_modules/commander": { 412 | "version": "13.1.0", 413 | "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", 414 | "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", 415 | "license": "MIT", 416 | "engines": { 417 | "node": ">=18" 418 | } 419 | }, 420 | "node_modules/@solana/web3.js/node_modules/superstruct": { 421 | "version": "2.0.2", 422 | "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", 423 | "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", 424 | "license": "MIT", 425 | "engines": { 426 | "node": ">=14.0.0" 427 | } 428 | }, 429 | "node_modules/@swc/helpers": { 430 | "version": "0.5.17", 431 | "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", 432 | "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", 433 | "license": "Apache-2.0", 434 | "dependencies": { 435 | "tslib": "^2.8.0" 436 | } 437 | }, 438 | "node_modules/@tsconfig/node10": { 439 | "version": "1.0.11", 440 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", 441 | "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", 442 | "dev": true, 443 | "license": "MIT" 444 | }, 445 | "node_modules/@tsconfig/node12": { 446 | "version": "1.0.11", 447 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 448 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 449 | "dev": true, 450 | "license": "MIT" 451 | }, 452 | "node_modules/@tsconfig/node14": { 453 | "version": "1.0.3", 454 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 455 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 456 | "dev": true, 457 | "license": "MIT" 458 | }, 459 | "node_modules/@tsconfig/node16": { 460 | "version": "1.0.4", 461 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 462 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 463 | "dev": true, 464 | "license": "MIT" 465 | }, 466 | "node_modules/@types/bn.js": { 467 | "version": "5.1.6", 468 | "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.6.tgz", 469 | "integrity": "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==", 470 | "dev": true, 471 | "license": "MIT", 472 | "dependencies": { 473 | "@types/node": "*" 474 | } 475 | }, 476 | "node_modules/@types/bs58": { 477 | "version": "4.0.4", 478 | "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.4.tgz", 479 | "integrity": "sha512-0IEpMFXXQi2zXaXl9GJ3sRwQo0uEkD+yFOv+FnAU5lkPtcu6h61xb7jc2CFPEZ5BUOaiP13ThuGc9HD4R8lR5g==", 480 | "dev": true, 481 | "license": "MIT", 482 | "dependencies": { 483 | "@types/node": "*", 484 | "base-x": "^3.0.6" 485 | } 486 | }, 487 | "node_modules/@types/connect": { 488 | "version": "3.4.38", 489 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 490 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 491 | "license": "MIT", 492 | "dependencies": { 493 | "@types/node": "*" 494 | } 495 | }, 496 | "node_modules/@types/node": { 497 | "version": "20.17.32", 498 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.32.tgz", 499 | "integrity": "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw==", 500 | "license": "MIT", 501 | "dependencies": { 502 | "undici-types": "~6.19.2" 503 | } 504 | }, 505 | "node_modules/@types/uuid": { 506 | "version": "8.3.4", 507 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", 508 | "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", 509 | "license": "MIT" 510 | }, 511 | "node_modules/@types/ws": { 512 | "version": "7.4.7", 513 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", 514 | "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", 515 | "license": "MIT", 516 | "dependencies": { 517 | "@types/node": "*" 518 | } 519 | }, 520 | "node_modules/accepts": { 521 | "version": "1.3.8", 522 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 523 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 524 | "license": "MIT", 525 | "dependencies": { 526 | "mime-types": "~2.1.34", 527 | "negotiator": "0.6.3" 528 | }, 529 | "engines": { 530 | "node": ">= 0.6" 531 | } 532 | }, 533 | "node_modules/acorn": { 534 | "version": "8.14.1", 535 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", 536 | "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", 537 | "dev": true, 538 | "license": "MIT", 539 | "bin": { 540 | "acorn": "bin/acorn" 541 | }, 542 | "engines": { 543 | "node": ">=0.4.0" 544 | } 545 | }, 546 | "node_modules/acorn-walk": { 547 | "version": "8.3.4", 548 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", 549 | "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", 550 | "dev": true, 551 | "license": "MIT", 552 | "dependencies": { 553 | "acorn": "^8.11.0" 554 | }, 555 | "engines": { 556 | "node": ">=0.4.0" 557 | } 558 | }, 559 | "node_modules/agentkeepalive": { 560 | "version": "4.6.0", 561 | "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", 562 | "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", 563 | "license": "MIT", 564 | "dependencies": { 565 | "humanize-ms": "^1.2.1" 566 | }, 567 | "engines": { 568 | "node": ">= 8.0.0" 569 | } 570 | }, 571 | "node_modules/arg": { 572 | "version": "4.1.3", 573 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 574 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 575 | "dev": true, 576 | "license": "MIT" 577 | }, 578 | "node_modules/array-flatten": { 579 | "version": "1.1.1", 580 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 581 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 582 | "license": "MIT" 583 | }, 584 | "node_modules/asynckit": { 585 | "version": "0.4.0", 586 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 587 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 588 | "license": "MIT" 589 | }, 590 | "node_modules/axios": { 591 | "version": "1.9.0", 592 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", 593 | "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", 594 | "license": "MIT", 595 | "dependencies": { 596 | "follow-redirects": "^1.15.6", 597 | "form-data": "^4.0.0", 598 | "proxy-from-env": "^1.1.0" 599 | } 600 | }, 601 | "node_modules/base-x": { 602 | "version": "3.0.11", 603 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", 604 | "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", 605 | "license": "MIT", 606 | "dependencies": { 607 | "safe-buffer": "^5.0.1" 608 | } 609 | }, 610 | "node_modules/base64-js": { 611 | "version": "1.5.1", 612 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 613 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 614 | "funding": [ 615 | { 616 | "type": "github", 617 | "url": "https://github.com/sponsors/feross" 618 | }, 619 | { 620 | "type": "patreon", 621 | "url": "https://www.patreon.com/feross" 622 | }, 623 | { 624 | "type": "consulting", 625 | "url": "https://feross.org/support" 626 | } 627 | ], 628 | "license": "MIT" 629 | }, 630 | "node_modules/bigint-buffer": { 631 | "version": "1.1.5", 632 | "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", 633 | "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", 634 | "hasInstallScript": true, 635 | "license": "Apache-2.0", 636 | "dependencies": { 637 | "bindings": "^1.3.0" 638 | }, 639 | "engines": { 640 | "node": ">= 10.0.0" 641 | } 642 | }, 643 | "node_modules/bignumber.js": { 644 | "version": "9.3.0", 645 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", 646 | "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", 647 | "license": "MIT", 648 | "engines": { 649 | "node": "*" 650 | } 651 | }, 652 | "node_modules/bindings": { 653 | "version": "1.5.0", 654 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 655 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 656 | "license": "MIT", 657 | "dependencies": { 658 | "file-uri-to-path": "1.0.0" 659 | } 660 | }, 661 | "node_modules/bn.js": { 662 | "version": "5.2.2", 663 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", 664 | "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", 665 | "license": "MIT" 666 | }, 667 | "node_modules/body-parser": { 668 | "version": "1.20.3", 669 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 670 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 671 | "license": "MIT", 672 | "dependencies": { 673 | "bytes": "3.1.2", 674 | "content-type": "~1.0.5", 675 | "debug": "2.6.9", 676 | "depd": "2.0.0", 677 | "destroy": "1.2.0", 678 | "http-errors": "2.0.0", 679 | "iconv-lite": "0.4.24", 680 | "on-finished": "2.4.1", 681 | "qs": "6.13.0", 682 | "raw-body": "2.5.2", 683 | "type-is": "~1.6.18", 684 | "unpipe": "1.0.0" 685 | }, 686 | "engines": { 687 | "node": ">= 0.8", 688 | "npm": "1.2.8000 || >= 1.4.16" 689 | } 690 | }, 691 | "node_modules/borsh": { 692 | "version": "0.7.0", 693 | "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", 694 | "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", 695 | "license": "Apache-2.0", 696 | "dependencies": { 697 | "bn.js": "^5.2.0", 698 | "bs58": "^4.0.0", 699 | "text-encoding-utf-8": "^1.0.2" 700 | } 701 | }, 702 | "node_modules/bs58": { 703 | "version": "4.0.1", 704 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", 705 | "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", 706 | "license": "MIT", 707 | "dependencies": { 708 | "base-x": "^3.0.2" 709 | } 710 | }, 711 | "node_modules/buffer": { 712 | "version": "6.0.3", 713 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", 714 | "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 715 | "funding": [ 716 | { 717 | "type": "github", 718 | "url": "https://github.com/sponsors/feross" 719 | }, 720 | { 721 | "type": "patreon", 722 | "url": "https://www.patreon.com/feross" 723 | }, 724 | { 725 | "type": "consulting", 726 | "url": "https://feross.org/support" 727 | } 728 | ], 729 | "license": "MIT", 730 | "dependencies": { 731 | "base64-js": "^1.3.1", 732 | "ieee754": "^1.2.1" 733 | } 734 | }, 735 | "node_modules/buffer-layout": { 736 | "version": "1.2.2", 737 | "resolved": "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz", 738 | "integrity": "sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==", 739 | "license": "MIT", 740 | "engines": { 741 | "node": ">=4.5" 742 | } 743 | }, 744 | "node_modules/bufferutil": { 745 | "version": "4.0.9", 746 | "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", 747 | "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", 748 | "hasInstallScript": true, 749 | "license": "MIT", 750 | "optional": true, 751 | "dependencies": { 752 | "node-gyp-build": "^4.3.0" 753 | }, 754 | "engines": { 755 | "node": ">=6.14.2" 756 | } 757 | }, 758 | "node_modules/bytes": { 759 | "version": "3.1.2", 760 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 761 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 762 | "license": "MIT", 763 | "engines": { 764 | "node": ">= 0.8" 765 | } 766 | }, 767 | "node_modules/call-bind-apply-helpers": { 768 | "version": "1.0.2", 769 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 770 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 771 | "license": "MIT", 772 | "dependencies": { 773 | "es-errors": "^1.3.0", 774 | "function-bind": "^1.1.2" 775 | }, 776 | "engines": { 777 | "node": ">= 0.4" 778 | } 779 | }, 780 | "node_modules/call-bound": { 781 | "version": "1.0.4", 782 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 783 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 784 | "license": "MIT", 785 | "dependencies": { 786 | "call-bind-apply-helpers": "^1.0.2", 787 | "get-intrinsic": "^1.3.0" 788 | }, 789 | "engines": { 790 | "node": ">= 0.4" 791 | }, 792 | "funding": { 793 | "url": "https://github.com/sponsors/ljharb" 794 | } 795 | }, 796 | "node_modules/camelcase": { 797 | "version": "6.3.0", 798 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 799 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 800 | "license": "MIT", 801 | "engines": { 802 | "node": ">=10" 803 | }, 804 | "funding": { 805 | "url": "https://github.com/sponsors/sindresorhus" 806 | } 807 | }, 808 | "node_modules/chalk": { 809 | "version": "5.4.1", 810 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", 811 | "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", 812 | "license": "MIT", 813 | "engines": { 814 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 815 | }, 816 | "funding": { 817 | "url": "https://github.com/chalk/chalk?sponsor=1" 818 | } 819 | }, 820 | "node_modules/combined-stream": { 821 | "version": "1.0.8", 822 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 823 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 824 | "license": "MIT", 825 | "dependencies": { 826 | "delayed-stream": "~1.0.0" 827 | }, 828 | "engines": { 829 | "node": ">= 0.8" 830 | } 831 | }, 832 | "node_modules/commander": { 833 | "version": "12.1.0", 834 | "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 835 | "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 836 | "license": "MIT", 837 | "engines": { 838 | "node": ">=18" 839 | } 840 | }, 841 | "node_modules/content-disposition": { 842 | "version": "0.5.4", 843 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 844 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 845 | "license": "MIT", 846 | "dependencies": { 847 | "safe-buffer": "5.2.1" 848 | }, 849 | "engines": { 850 | "node": ">= 0.6" 851 | } 852 | }, 853 | "node_modules/content-type": { 854 | "version": "1.0.5", 855 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 856 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 857 | "license": "MIT", 858 | "engines": { 859 | "node": ">= 0.6" 860 | } 861 | }, 862 | "node_modules/cookie": { 863 | "version": "0.7.1", 864 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 865 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 866 | "license": "MIT", 867 | "engines": { 868 | "node": ">= 0.6" 869 | } 870 | }, 871 | "node_modules/cookie-signature": { 872 | "version": "1.0.6", 873 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 874 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 875 | "license": "MIT" 876 | }, 877 | "node_modules/create-require": { 878 | "version": "1.1.1", 879 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 880 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 881 | "dev": true, 882 | "license": "MIT" 883 | }, 884 | "node_modules/cross-fetch": { 885 | "version": "3.2.0", 886 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", 887 | "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", 888 | "license": "MIT", 889 | "dependencies": { 890 | "node-fetch": "^2.7.0" 891 | } 892 | }, 893 | "node_modules/crypto-hash": { 894 | "version": "1.3.0", 895 | "resolved": "https://registry.npmjs.org/crypto-hash/-/crypto-hash-1.3.0.tgz", 896 | "integrity": "sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==", 897 | "license": "MIT", 898 | "engines": { 899 | "node": ">=8" 900 | }, 901 | "funding": { 902 | "url": "https://github.com/sponsors/sindresorhus" 903 | } 904 | }, 905 | "node_modules/debug": { 906 | "version": "2.6.9", 907 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 908 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 909 | "license": "MIT", 910 | "dependencies": { 911 | "ms": "2.0.0" 912 | } 913 | }, 914 | "node_modules/decimal.js": { 915 | "version": "10.5.0", 916 | "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", 917 | "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", 918 | "license": "MIT" 919 | }, 920 | "node_modules/delay": { 921 | "version": "5.0.0", 922 | "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", 923 | "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", 924 | "license": "MIT", 925 | "engines": { 926 | "node": ">=10" 927 | }, 928 | "funding": { 929 | "url": "https://github.com/sponsors/sindresorhus" 930 | } 931 | }, 932 | "node_modules/delayed-stream": { 933 | "version": "1.0.0", 934 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 935 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 936 | "license": "MIT", 937 | "engines": { 938 | "node": ">=0.4.0" 939 | } 940 | }, 941 | "node_modules/depd": { 942 | "version": "2.0.0", 943 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 944 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 945 | "license": "MIT", 946 | "engines": { 947 | "node": ">= 0.8" 948 | } 949 | }, 950 | "node_modules/destroy": { 951 | "version": "1.2.0", 952 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 953 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 954 | "license": "MIT", 955 | "engines": { 956 | "node": ">= 0.8", 957 | "npm": "1.2.8000 || >= 1.4.16" 958 | } 959 | }, 960 | "node_modules/diff": { 961 | "version": "4.0.2", 962 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 963 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 964 | "dev": true, 965 | "license": "BSD-3-Clause", 966 | "engines": { 967 | "node": ">=0.3.1" 968 | } 969 | }, 970 | "node_modules/dot-case": { 971 | "version": "3.0.4", 972 | "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", 973 | "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", 974 | "license": "MIT", 975 | "dependencies": { 976 | "no-case": "^3.0.4", 977 | "tslib": "^2.0.3" 978 | } 979 | }, 980 | "node_modules/dunder-proto": { 981 | "version": "1.0.1", 982 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 983 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 984 | "license": "MIT", 985 | "dependencies": { 986 | "call-bind-apply-helpers": "^1.0.1", 987 | "es-errors": "^1.3.0", 988 | "gopd": "^1.2.0" 989 | }, 990 | "engines": { 991 | "node": ">= 0.4" 992 | } 993 | }, 994 | "node_modules/ee-first": { 995 | "version": "1.1.1", 996 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 997 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 998 | "license": "MIT" 999 | }, 1000 | "node_modules/encodeurl": { 1001 | "version": "2.0.0", 1002 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 1003 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 1004 | "license": "MIT", 1005 | "engines": { 1006 | "node": ">= 0.8" 1007 | } 1008 | }, 1009 | "node_modules/es-define-property": { 1010 | "version": "1.0.1", 1011 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 1012 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 1013 | "license": "MIT", 1014 | "engines": { 1015 | "node": ">= 0.4" 1016 | } 1017 | }, 1018 | "node_modules/es-errors": { 1019 | "version": "1.3.0", 1020 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 1021 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 1022 | "license": "MIT", 1023 | "engines": { 1024 | "node": ">= 0.4" 1025 | } 1026 | }, 1027 | "node_modules/es-object-atoms": { 1028 | "version": "1.1.1", 1029 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 1030 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 1031 | "license": "MIT", 1032 | "dependencies": { 1033 | "es-errors": "^1.3.0" 1034 | }, 1035 | "engines": { 1036 | "node": ">= 0.4" 1037 | } 1038 | }, 1039 | "node_modules/es-set-tostringtag": { 1040 | "version": "2.1.0", 1041 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 1042 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 1043 | "license": "MIT", 1044 | "dependencies": { 1045 | "es-errors": "^1.3.0", 1046 | "get-intrinsic": "^1.2.6", 1047 | "has-tostringtag": "^1.0.2", 1048 | "hasown": "^2.0.2" 1049 | }, 1050 | "engines": { 1051 | "node": ">= 0.4" 1052 | } 1053 | }, 1054 | "node_modules/es6-promise": { 1055 | "version": "4.2.8", 1056 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 1057 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", 1058 | "license": "MIT" 1059 | }, 1060 | "node_modules/es6-promisify": { 1061 | "version": "5.0.0", 1062 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 1063 | "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", 1064 | "license": "MIT", 1065 | "dependencies": { 1066 | "es6-promise": "^4.0.3" 1067 | } 1068 | }, 1069 | "node_modules/escape-html": { 1070 | "version": "1.0.3", 1071 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1072 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 1073 | "license": "MIT" 1074 | }, 1075 | "node_modules/etag": { 1076 | "version": "1.8.1", 1077 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1078 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 1079 | "license": "MIT", 1080 | "engines": { 1081 | "node": ">= 0.6" 1082 | } 1083 | }, 1084 | "node_modules/eventemitter3": { 1085 | "version": "4.0.7", 1086 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 1087 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 1088 | "license": "MIT" 1089 | }, 1090 | "node_modules/express": { 1091 | "version": "4.21.2", 1092 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 1093 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 1094 | "license": "MIT", 1095 | "dependencies": { 1096 | "accepts": "~1.3.8", 1097 | "array-flatten": "1.1.1", 1098 | "body-parser": "1.20.3", 1099 | "content-disposition": "0.5.4", 1100 | "content-type": "~1.0.4", 1101 | "cookie": "0.7.1", 1102 | "cookie-signature": "1.0.6", 1103 | "debug": "2.6.9", 1104 | "depd": "2.0.0", 1105 | "encodeurl": "~2.0.0", 1106 | "escape-html": "~1.0.3", 1107 | "etag": "~1.8.1", 1108 | "finalhandler": "1.3.1", 1109 | "fresh": "0.5.2", 1110 | "http-errors": "2.0.0", 1111 | "merge-descriptors": "1.0.3", 1112 | "methods": "~1.1.2", 1113 | "on-finished": "2.4.1", 1114 | "parseurl": "~1.3.3", 1115 | "path-to-regexp": "0.1.12", 1116 | "proxy-addr": "~2.0.7", 1117 | "qs": "6.13.0", 1118 | "range-parser": "~1.2.1", 1119 | "safe-buffer": "5.2.1", 1120 | "send": "0.19.0", 1121 | "serve-static": "1.16.2", 1122 | "setprototypeof": "1.2.0", 1123 | "statuses": "2.0.1", 1124 | "type-is": "~1.6.18", 1125 | "utils-merge": "1.0.1", 1126 | "vary": "~1.1.2" 1127 | }, 1128 | "engines": { 1129 | "node": ">= 0.10.0" 1130 | }, 1131 | "funding": { 1132 | "type": "opencollective", 1133 | "url": "https://opencollective.com/express" 1134 | } 1135 | }, 1136 | "node_modules/eyes": { 1137 | "version": "0.1.8", 1138 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 1139 | "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", 1140 | "engines": { 1141 | "node": "> 0.1.90" 1142 | } 1143 | }, 1144 | "node_modules/fast-stable-stringify": { 1145 | "version": "1.0.0", 1146 | "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", 1147 | "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", 1148 | "license": "MIT" 1149 | }, 1150 | "node_modules/fastestsmallesttextencoderdecoder": { 1151 | "version": "1.0.22", 1152 | "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", 1153 | "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", 1154 | "license": "CC0-1.0", 1155 | "peer": true 1156 | }, 1157 | "node_modules/file-uri-to-path": { 1158 | "version": "1.0.0", 1159 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 1160 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 1161 | "license": "MIT" 1162 | }, 1163 | "node_modules/finalhandler": { 1164 | "version": "1.3.1", 1165 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 1166 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 1167 | "license": "MIT", 1168 | "dependencies": { 1169 | "debug": "2.6.9", 1170 | "encodeurl": "~2.0.0", 1171 | "escape-html": "~1.0.3", 1172 | "on-finished": "2.4.1", 1173 | "parseurl": "~1.3.3", 1174 | "statuses": "2.0.1", 1175 | "unpipe": "~1.0.0" 1176 | }, 1177 | "engines": { 1178 | "node": ">= 0.8" 1179 | } 1180 | }, 1181 | "node_modules/follow-redirects": { 1182 | "version": "1.15.9", 1183 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 1184 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 1185 | "funding": [ 1186 | { 1187 | "type": "individual", 1188 | "url": "https://github.com/sponsors/RubenVerborgh" 1189 | } 1190 | ], 1191 | "license": "MIT", 1192 | "engines": { 1193 | "node": ">=4.0" 1194 | }, 1195 | "peerDependenciesMeta": { 1196 | "debug": { 1197 | "optional": true 1198 | } 1199 | } 1200 | }, 1201 | "node_modules/form-data": { 1202 | "version": "4.0.2", 1203 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", 1204 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", 1205 | "license": "MIT", 1206 | "dependencies": { 1207 | "asynckit": "^0.4.0", 1208 | "combined-stream": "^1.0.8", 1209 | "es-set-tostringtag": "^2.1.0", 1210 | "mime-types": "^2.1.12" 1211 | }, 1212 | "engines": { 1213 | "node": ">= 6" 1214 | } 1215 | }, 1216 | "node_modules/forwarded": { 1217 | "version": "0.2.0", 1218 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1219 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 1220 | "license": "MIT", 1221 | "engines": { 1222 | "node": ">= 0.6" 1223 | } 1224 | }, 1225 | "node_modules/fresh": { 1226 | "version": "0.5.2", 1227 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1228 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 1229 | "license": "MIT", 1230 | "engines": { 1231 | "node": ">= 0.6" 1232 | } 1233 | }, 1234 | "node_modules/function-bind": { 1235 | "version": "1.1.2", 1236 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1237 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1238 | "license": "MIT", 1239 | "funding": { 1240 | "url": "https://github.com/sponsors/ljharb" 1241 | } 1242 | }, 1243 | "node_modules/gaussian": { 1244 | "version": "1.3.0", 1245 | "resolved": "https://registry.npmjs.org/gaussian/-/gaussian-1.3.0.tgz", 1246 | "integrity": "sha512-rYQ0ESfB+z0t7G95nHH80Zh7Pgg9A0FUYoZqV0yPec5WJZWKIHV2MPYpiJNy8oZAeVqyKwC10WXKSCnUQ5iDVg==", 1247 | "license": "MIT", 1248 | "engines": { 1249 | "node": ">= 0.6.0" 1250 | } 1251 | }, 1252 | "node_modules/get-intrinsic": { 1253 | "version": "1.3.0", 1254 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 1255 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1256 | "license": "MIT", 1257 | "dependencies": { 1258 | "call-bind-apply-helpers": "^1.0.2", 1259 | "es-define-property": "^1.0.1", 1260 | "es-errors": "^1.3.0", 1261 | "es-object-atoms": "^1.1.1", 1262 | "function-bind": "^1.1.2", 1263 | "get-proto": "^1.0.1", 1264 | "gopd": "^1.2.0", 1265 | "has-symbols": "^1.1.0", 1266 | "hasown": "^2.0.2", 1267 | "math-intrinsics": "^1.1.0" 1268 | }, 1269 | "engines": { 1270 | "node": ">= 0.4" 1271 | }, 1272 | "funding": { 1273 | "url": "https://github.com/sponsors/ljharb" 1274 | } 1275 | }, 1276 | "node_modules/get-proto": { 1277 | "version": "1.0.1", 1278 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 1279 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1280 | "license": "MIT", 1281 | "dependencies": { 1282 | "dunder-proto": "^1.0.1", 1283 | "es-object-atoms": "^1.0.0" 1284 | }, 1285 | "engines": { 1286 | "node": ">= 0.4" 1287 | } 1288 | }, 1289 | "node_modules/gopd": { 1290 | "version": "1.2.0", 1291 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1292 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1293 | "license": "MIT", 1294 | "engines": { 1295 | "node": ">= 0.4" 1296 | }, 1297 | "funding": { 1298 | "url": "https://github.com/sponsors/ljharb" 1299 | } 1300 | }, 1301 | "node_modules/has-symbols": { 1302 | "version": "1.1.0", 1303 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1304 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1305 | "license": "MIT", 1306 | "engines": { 1307 | "node": ">= 0.4" 1308 | }, 1309 | "funding": { 1310 | "url": "https://github.com/sponsors/ljharb" 1311 | } 1312 | }, 1313 | "node_modules/has-tostringtag": { 1314 | "version": "1.0.2", 1315 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 1316 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 1317 | "license": "MIT", 1318 | "dependencies": { 1319 | "has-symbols": "^1.0.3" 1320 | }, 1321 | "engines": { 1322 | "node": ">= 0.4" 1323 | }, 1324 | "funding": { 1325 | "url": "https://github.com/sponsors/ljharb" 1326 | } 1327 | }, 1328 | "node_modules/hasown": { 1329 | "version": "2.0.2", 1330 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1331 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1332 | "license": "MIT", 1333 | "dependencies": { 1334 | "function-bind": "^1.1.2" 1335 | }, 1336 | "engines": { 1337 | "node": ">= 0.4" 1338 | } 1339 | }, 1340 | "node_modules/http-errors": { 1341 | "version": "2.0.0", 1342 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1343 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1344 | "license": "MIT", 1345 | "dependencies": { 1346 | "depd": "2.0.0", 1347 | "inherits": "2.0.4", 1348 | "setprototypeof": "1.2.0", 1349 | "statuses": "2.0.1", 1350 | "toidentifier": "1.0.1" 1351 | }, 1352 | "engines": { 1353 | "node": ">= 0.8" 1354 | } 1355 | }, 1356 | "node_modules/humanize-ms": { 1357 | "version": "1.2.1", 1358 | "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", 1359 | "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", 1360 | "license": "MIT", 1361 | "dependencies": { 1362 | "ms": "^2.0.0" 1363 | } 1364 | }, 1365 | "node_modules/iconv-lite": { 1366 | "version": "0.4.24", 1367 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1368 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1369 | "license": "MIT", 1370 | "dependencies": { 1371 | "safer-buffer": ">= 2.1.2 < 3" 1372 | }, 1373 | "engines": { 1374 | "node": ">=0.10.0" 1375 | } 1376 | }, 1377 | "node_modules/ieee754": { 1378 | "version": "1.2.1", 1379 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1380 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 1381 | "funding": [ 1382 | { 1383 | "type": "github", 1384 | "url": "https://github.com/sponsors/feross" 1385 | }, 1386 | { 1387 | "type": "patreon", 1388 | "url": "https://www.patreon.com/feross" 1389 | }, 1390 | { 1391 | "type": "consulting", 1392 | "url": "https://feross.org/support" 1393 | } 1394 | ], 1395 | "license": "BSD-3-Clause" 1396 | }, 1397 | "node_modules/inherits": { 1398 | "version": "2.0.4", 1399 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1400 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1401 | "license": "ISC" 1402 | }, 1403 | "node_modules/ipaddr.js": { 1404 | "version": "1.9.1", 1405 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1406 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1407 | "license": "MIT", 1408 | "engines": { 1409 | "node": ">= 0.10" 1410 | } 1411 | }, 1412 | "node_modules/isomorphic-ws": { 1413 | "version": "4.0.1", 1414 | "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", 1415 | "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", 1416 | "license": "MIT", 1417 | "peerDependencies": { 1418 | "ws": "*" 1419 | } 1420 | }, 1421 | "node_modules/jayson": { 1422 | "version": "4.2.0", 1423 | "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.2.0.tgz", 1424 | "integrity": "sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg==", 1425 | "license": "MIT", 1426 | "dependencies": { 1427 | "@types/connect": "^3.4.33", 1428 | "@types/node": "^12.12.54", 1429 | "@types/ws": "^7.4.4", 1430 | "commander": "^2.20.3", 1431 | "delay": "^5.0.0", 1432 | "es6-promisify": "^5.0.0", 1433 | "eyes": "^0.1.8", 1434 | "isomorphic-ws": "^4.0.1", 1435 | "json-stringify-safe": "^5.0.1", 1436 | "stream-json": "^1.9.1", 1437 | "uuid": "^8.3.2", 1438 | "ws": "^7.5.10" 1439 | }, 1440 | "bin": { 1441 | "jayson": "bin/jayson.js" 1442 | }, 1443 | "engines": { 1444 | "node": ">=8" 1445 | } 1446 | }, 1447 | "node_modules/jayson/node_modules/@types/node": { 1448 | "version": "12.20.55", 1449 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", 1450 | "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", 1451 | "license": "MIT" 1452 | }, 1453 | "node_modules/jayson/node_modules/commander": { 1454 | "version": "2.20.3", 1455 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 1456 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 1457 | "license": "MIT" 1458 | }, 1459 | "node_modules/js-sha256": { 1460 | "version": "0.9.0", 1461 | "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", 1462 | "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", 1463 | "license": "MIT" 1464 | }, 1465 | "node_modules/json-stringify-safe": { 1466 | "version": "5.0.1", 1467 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 1468 | "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", 1469 | "license": "ISC" 1470 | }, 1471 | "node_modules/lower-case": { 1472 | "version": "2.0.2", 1473 | "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", 1474 | "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", 1475 | "license": "MIT", 1476 | "dependencies": { 1477 | "tslib": "^2.0.3" 1478 | } 1479 | }, 1480 | "node_modules/make-error": { 1481 | "version": "1.3.6", 1482 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 1483 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 1484 | "dev": true, 1485 | "license": "ISC" 1486 | }, 1487 | "node_modules/math-intrinsics": { 1488 | "version": "1.1.0", 1489 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1490 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1491 | "license": "MIT", 1492 | "engines": { 1493 | "node": ">= 0.4" 1494 | } 1495 | }, 1496 | "node_modules/media-typer": { 1497 | "version": "0.3.0", 1498 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1499 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1500 | "license": "MIT", 1501 | "engines": { 1502 | "node": ">= 0.6" 1503 | } 1504 | }, 1505 | "node_modules/merge-descriptors": { 1506 | "version": "1.0.3", 1507 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 1508 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 1509 | "license": "MIT", 1510 | "funding": { 1511 | "url": "https://github.com/sponsors/sindresorhus" 1512 | } 1513 | }, 1514 | "node_modules/methods": { 1515 | "version": "1.1.2", 1516 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1517 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1518 | "license": "MIT", 1519 | "engines": { 1520 | "node": ">= 0.6" 1521 | } 1522 | }, 1523 | "node_modules/mime": { 1524 | "version": "1.6.0", 1525 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1526 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1527 | "license": "MIT", 1528 | "bin": { 1529 | "mime": "cli.js" 1530 | }, 1531 | "engines": { 1532 | "node": ">=4" 1533 | } 1534 | }, 1535 | "node_modules/mime-db": { 1536 | "version": "1.52.0", 1537 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1538 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1539 | "license": "MIT", 1540 | "engines": { 1541 | "node": ">= 0.6" 1542 | } 1543 | }, 1544 | "node_modules/mime-types": { 1545 | "version": "2.1.35", 1546 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1547 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1548 | "license": "MIT", 1549 | "dependencies": { 1550 | "mime-db": "1.52.0" 1551 | }, 1552 | "engines": { 1553 | "node": ">= 0.6" 1554 | } 1555 | }, 1556 | "node_modules/ms": { 1557 | "version": "2.0.0", 1558 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1559 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 1560 | "license": "MIT" 1561 | }, 1562 | "node_modules/negotiator": { 1563 | "version": "0.6.3", 1564 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1565 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1566 | "license": "MIT", 1567 | "engines": { 1568 | "node": ">= 0.6" 1569 | } 1570 | }, 1571 | "node_modules/no-case": { 1572 | "version": "3.0.4", 1573 | "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", 1574 | "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", 1575 | "license": "MIT", 1576 | "dependencies": { 1577 | "lower-case": "^2.0.2", 1578 | "tslib": "^2.0.3" 1579 | } 1580 | }, 1581 | "node_modules/node-fetch": { 1582 | "version": "2.7.0", 1583 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 1584 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 1585 | "license": "MIT", 1586 | "dependencies": { 1587 | "whatwg-url": "^5.0.0" 1588 | }, 1589 | "engines": { 1590 | "node": "4.x || >=6.0.0" 1591 | }, 1592 | "peerDependencies": { 1593 | "encoding": "^0.1.0" 1594 | }, 1595 | "peerDependenciesMeta": { 1596 | "encoding": { 1597 | "optional": true 1598 | } 1599 | } 1600 | }, 1601 | "node_modules/node-gyp-build": { 1602 | "version": "4.8.4", 1603 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", 1604 | "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", 1605 | "license": "MIT", 1606 | "optional": true, 1607 | "bin": { 1608 | "node-gyp-build": "bin.js", 1609 | "node-gyp-build-optional": "optional.js", 1610 | "node-gyp-build-test": "build-test.js" 1611 | } 1612 | }, 1613 | "node_modules/object-inspect": { 1614 | "version": "1.13.4", 1615 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 1616 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 1617 | "license": "MIT", 1618 | "engines": { 1619 | "node": ">= 0.4" 1620 | }, 1621 | "funding": { 1622 | "url": "https://github.com/sponsors/ljharb" 1623 | } 1624 | }, 1625 | "node_modules/on-finished": { 1626 | "version": "2.4.1", 1627 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1628 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1629 | "license": "MIT", 1630 | "dependencies": { 1631 | "ee-first": "1.1.1" 1632 | }, 1633 | "engines": { 1634 | "node": ">= 0.8" 1635 | } 1636 | }, 1637 | "node_modules/pako": { 1638 | "version": "2.1.0", 1639 | "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", 1640 | "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", 1641 | "license": "(MIT AND Zlib)" 1642 | }, 1643 | "node_modules/parseurl": { 1644 | "version": "1.3.3", 1645 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1646 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1647 | "license": "MIT", 1648 | "engines": { 1649 | "node": ">= 0.8" 1650 | } 1651 | }, 1652 | "node_modules/path-to-regexp": { 1653 | "version": "0.1.12", 1654 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 1655 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 1656 | "license": "MIT" 1657 | }, 1658 | "node_modules/proxy-addr": { 1659 | "version": "2.0.7", 1660 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1661 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1662 | "license": "MIT", 1663 | "dependencies": { 1664 | "forwarded": "0.2.0", 1665 | "ipaddr.js": "1.9.1" 1666 | }, 1667 | "engines": { 1668 | "node": ">= 0.10" 1669 | } 1670 | }, 1671 | "node_modules/proxy-from-env": { 1672 | "version": "1.1.0", 1673 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1674 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 1675 | "license": "MIT" 1676 | }, 1677 | "node_modules/qs": { 1678 | "version": "6.13.0", 1679 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1680 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1681 | "license": "BSD-3-Clause", 1682 | "dependencies": { 1683 | "side-channel": "^1.0.6" 1684 | }, 1685 | "engines": { 1686 | "node": ">=0.6" 1687 | }, 1688 | "funding": { 1689 | "url": "https://github.com/sponsors/ljharb" 1690 | } 1691 | }, 1692 | "node_modules/range-parser": { 1693 | "version": "1.2.1", 1694 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1695 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1696 | "license": "MIT", 1697 | "engines": { 1698 | "node": ">= 0.6" 1699 | } 1700 | }, 1701 | "node_modules/raw-body": { 1702 | "version": "2.5.2", 1703 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1704 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1705 | "license": "MIT", 1706 | "dependencies": { 1707 | "bytes": "3.1.2", 1708 | "http-errors": "2.0.0", 1709 | "iconv-lite": "0.4.24", 1710 | "unpipe": "1.0.0" 1711 | }, 1712 | "engines": { 1713 | "node": ">= 0.8" 1714 | } 1715 | }, 1716 | "node_modules/rpc-websockets": { 1717 | "version": "9.1.1", 1718 | "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.1.1.tgz", 1719 | "integrity": "sha512-1IXGM/TfPT6nfYMIXkJdzn+L4JEsmb0FL1O2OBjaH03V3yuUDdKFulGLMFG6ErV+8pZ5HVC0limve01RyO+saA==", 1720 | "license": "LGPL-3.0-only", 1721 | "dependencies": { 1722 | "@swc/helpers": "^0.5.11", 1723 | "@types/uuid": "^8.3.4", 1724 | "@types/ws": "^8.2.2", 1725 | "buffer": "^6.0.3", 1726 | "eventemitter3": "^5.0.1", 1727 | "uuid": "^8.3.2", 1728 | "ws": "^8.5.0" 1729 | }, 1730 | "funding": { 1731 | "type": "paypal", 1732 | "url": "https://paypal.me/kozjak" 1733 | }, 1734 | "optionalDependencies": { 1735 | "bufferutil": "^4.0.1", 1736 | "utf-8-validate": "^5.0.2" 1737 | } 1738 | }, 1739 | "node_modules/rpc-websockets/node_modules/@types/ws": { 1740 | "version": "8.18.1", 1741 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", 1742 | "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", 1743 | "license": "MIT", 1744 | "dependencies": { 1745 | "@types/node": "*" 1746 | } 1747 | }, 1748 | "node_modules/rpc-websockets/node_modules/eventemitter3": { 1749 | "version": "5.0.1", 1750 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", 1751 | "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", 1752 | "license": "MIT" 1753 | }, 1754 | "node_modules/rpc-websockets/node_modules/ws": { 1755 | "version": "8.18.1", 1756 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", 1757 | "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", 1758 | "license": "MIT", 1759 | "engines": { 1760 | "node": ">=10.0.0" 1761 | }, 1762 | "peerDependencies": { 1763 | "bufferutil": "^4.0.1", 1764 | "utf-8-validate": ">=5.0.2" 1765 | }, 1766 | "peerDependenciesMeta": { 1767 | "bufferutil": { 1768 | "optional": true 1769 | }, 1770 | "utf-8-validate": { 1771 | "optional": true 1772 | } 1773 | } 1774 | }, 1775 | "node_modules/safe-buffer": { 1776 | "version": "5.2.1", 1777 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1778 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1779 | "funding": [ 1780 | { 1781 | "type": "github", 1782 | "url": "https://github.com/sponsors/feross" 1783 | }, 1784 | { 1785 | "type": "patreon", 1786 | "url": "https://www.patreon.com/feross" 1787 | }, 1788 | { 1789 | "type": "consulting", 1790 | "url": "https://feross.org/support" 1791 | } 1792 | ], 1793 | "license": "MIT" 1794 | }, 1795 | "node_modules/safer-buffer": { 1796 | "version": "2.1.2", 1797 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1798 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1799 | "license": "MIT" 1800 | }, 1801 | "node_modules/send": { 1802 | "version": "0.19.0", 1803 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1804 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1805 | "license": "MIT", 1806 | "dependencies": { 1807 | "debug": "2.6.9", 1808 | "depd": "2.0.0", 1809 | "destroy": "1.2.0", 1810 | "encodeurl": "~1.0.2", 1811 | "escape-html": "~1.0.3", 1812 | "etag": "~1.8.1", 1813 | "fresh": "0.5.2", 1814 | "http-errors": "2.0.0", 1815 | "mime": "1.6.0", 1816 | "ms": "2.1.3", 1817 | "on-finished": "2.4.1", 1818 | "range-parser": "~1.2.1", 1819 | "statuses": "2.0.1" 1820 | }, 1821 | "engines": { 1822 | "node": ">= 0.8.0" 1823 | } 1824 | }, 1825 | "node_modules/send/node_modules/encodeurl": { 1826 | "version": "1.0.2", 1827 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1828 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1829 | "license": "MIT", 1830 | "engines": { 1831 | "node": ">= 0.8" 1832 | } 1833 | }, 1834 | "node_modules/send/node_modules/ms": { 1835 | "version": "2.1.3", 1836 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1837 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1838 | "license": "MIT" 1839 | }, 1840 | "node_modules/serve-static": { 1841 | "version": "1.16.2", 1842 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1843 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1844 | "license": "MIT", 1845 | "dependencies": { 1846 | "encodeurl": "~2.0.0", 1847 | "escape-html": "~1.0.3", 1848 | "parseurl": "~1.3.3", 1849 | "send": "0.19.0" 1850 | }, 1851 | "engines": { 1852 | "node": ">= 0.8.0" 1853 | } 1854 | }, 1855 | "node_modules/setprototypeof": { 1856 | "version": "1.2.0", 1857 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1858 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1859 | "license": "ISC" 1860 | }, 1861 | "node_modules/side-channel": { 1862 | "version": "1.1.0", 1863 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1864 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1865 | "license": "MIT", 1866 | "dependencies": { 1867 | "es-errors": "^1.3.0", 1868 | "object-inspect": "^1.13.3", 1869 | "side-channel-list": "^1.0.0", 1870 | "side-channel-map": "^1.0.1", 1871 | "side-channel-weakmap": "^1.0.2" 1872 | }, 1873 | "engines": { 1874 | "node": ">= 0.4" 1875 | }, 1876 | "funding": { 1877 | "url": "https://github.com/sponsors/ljharb" 1878 | } 1879 | }, 1880 | "node_modules/side-channel-list": { 1881 | "version": "1.0.0", 1882 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1883 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1884 | "license": "MIT", 1885 | "dependencies": { 1886 | "es-errors": "^1.3.0", 1887 | "object-inspect": "^1.13.3" 1888 | }, 1889 | "engines": { 1890 | "node": ">= 0.4" 1891 | }, 1892 | "funding": { 1893 | "url": "https://github.com/sponsors/ljharb" 1894 | } 1895 | }, 1896 | "node_modules/side-channel-map": { 1897 | "version": "1.0.1", 1898 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1899 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1900 | "license": "MIT", 1901 | "dependencies": { 1902 | "call-bound": "^1.0.2", 1903 | "es-errors": "^1.3.0", 1904 | "get-intrinsic": "^1.2.5", 1905 | "object-inspect": "^1.13.3" 1906 | }, 1907 | "engines": { 1908 | "node": ">= 0.4" 1909 | }, 1910 | "funding": { 1911 | "url": "https://github.com/sponsors/ljharb" 1912 | } 1913 | }, 1914 | "node_modules/side-channel-weakmap": { 1915 | "version": "1.0.2", 1916 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1917 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1918 | "license": "MIT", 1919 | "dependencies": { 1920 | "call-bound": "^1.0.2", 1921 | "es-errors": "^1.3.0", 1922 | "get-intrinsic": "^1.2.5", 1923 | "object-inspect": "^1.13.3", 1924 | "side-channel-map": "^1.0.1" 1925 | }, 1926 | "engines": { 1927 | "node": ">= 0.4" 1928 | }, 1929 | "funding": { 1930 | "url": "https://github.com/sponsors/ljharb" 1931 | } 1932 | }, 1933 | "node_modules/snake-case": { 1934 | "version": "3.0.4", 1935 | "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", 1936 | "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", 1937 | "license": "MIT", 1938 | "dependencies": { 1939 | "dot-case": "^3.0.4", 1940 | "tslib": "^2.0.3" 1941 | } 1942 | }, 1943 | "node_modules/statuses": { 1944 | "version": "2.0.1", 1945 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1946 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1947 | "license": "MIT", 1948 | "engines": { 1949 | "node": ">= 0.8" 1950 | } 1951 | }, 1952 | "node_modules/stream-chain": { 1953 | "version": "2.2.5", 1954 | "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", 1955 | "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", 1956 | "license": "BSD-3-Clause" 1957 | }, 1958 | "node_modules/stream-json": { 1959 | "version": "1.9.1", 1960 | "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", 1961 | "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", 1962 | "license": "BSD-3-Clause", 1963 | "dependencies": { 1964 | "stream-chain": "^2.2.5" 1965 | } 1966 | }, 1967 | "node_modules/superstruct": { 1968 | "version": "0.15.5", 1969 | "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", 1970 | "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==", 1971 | "license": "MIT" 1972 | }, 1973 | "node_modules/text-encoding-utf-8": { 1974 | "version": "1.0.2", 1975 | "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", 1976 | "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" 1977 | }, 1978 | "node_modules/toidentifier": { 1979 | "version": "1.0.1", 1980 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1981 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1982 | "license": "MIT", 1983 | "engines": { 1984 | "node": ">=0.6" 1985 | } 1986 | }, 1987 | "node_modules/toml": { 1988 | "version": "3.0.0", 1989 | "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", 1990 | "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", 1991 | "license": "MIT" 1992 | }, 1993 | "node_modules/tr46": { 1994 | "version": "0.0.3", 1995 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1996 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 1997 | "license": "MIT" 1998 | }, 1999 | "node_modules/ts-node": { 2000 | "version": "10.9.2", 2001 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 2002 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 2003 | "dev": true, 2004 | "license": "MIT", 2005 | "dependencies": { 2006 | "@cspotcode/source-map-support": "^0.8.0", 2007 | "@tsconfig/node10": "^1.0.7", 2008 | "@tsconfig/node12": "^1.0.7", 2009 | "@tsconfig/node14": "^1.0.0", 2010 | "@tsconfig/node16": "^1.0.2", 2011 | "acorn": "^8.4.1", 2012 | "acorn-walk": "^8.1.1", 2013 | "arg": "^4.1.0", 2014 | "create-require": "^1.1.0", 2015 | "diff": "^4.0.1", 2016 | "make-error": "^1.1.1", 2017 | "v8-compile-cache-lib": "^3.0.1", 2018 | "yn": "3.1.1" 2019 | }, 2020 | "bin": { 2021 | "ts-node": "dist/bin.js", 2022 | "ts-node-cwd": "dist/bin-cwd.js", 2023 | "ts-node-esm": "dist/bin-esm.js", 2024 | "ts-node-script": "dist/bin-script.js", 2025 | "ts-node-transpile-only": "dist/bin-transpile.js", 2026 | "ts-script": "dist/bin-script-deprecated.js" 2027 | }, 2028 | "peerDependencies": { 2029 | "@swc/core": ">=1.2.50", 2030 | "@swc/wasm": ">=1.2.50", 2031 | "@types/node": "*", 2032 | "typescript": ">=2.7" 2033 | }, 2034 | "peerDependenciesMeta": { 2035 | "@swc/core": { 2036 | "optional": true 2037 | }, 2038 | "@swc/wasm": { 2039 | "optional": true 2040 | } 2041 | } 2042 | }, 2043 | "node_modules/tslib": { 2044 | "version": "2.8.1", 2045 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2046 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2047 | "license": "0BSD" 2048 | }, 2049 | "node_modules/type-is": { 2050 | "version": "1.6.18", 2051 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 2052 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 2053 | "license": "MIT", 2054 | "dependencies": { 2055 | "media-typer": "0.3.0", 2056 | "mime-types": "~2.1.24" 2057 | }, 2058 | "engines": { 2059 | "node": ">= 0.6" 2060 | } 2061 | }, 2062 | "node_modules/typescript": { 2063 | "version": "5.8.3", 2064 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 2065 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 2066 | "license": "Apache-2.0", 2067 | "bin": { 2068 | "tsc": "bin/tsc", 2069 | "tsserver": "bin/tsserver" 2070 | }, 2071 | "engines": { 2072 | "node": ">=14.17" 2073 | } 2074 | }, 2075 | "node_modules/undici-types": { 2076 | "version": "6.19.8", 2077 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 2078 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 2079 | "license": "MIT" 2080 | }, 2081 | "node_modules/unpipe": { 2082 | "version": "1.0.0", 2083 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2084 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 2085 | "license": "MIT", 2086 | "engines": { 2087 | "node": ">= 0.8" 2088 | } 2089 | }, 2090 | "node_modules/utf-8-validate": { 2091 | "version": "5.0.10", 2092 | "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", 2093 | "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", 2094 | "hasInstallScript": true, 2095 | "license": "MIT", 2096 | "optional": true, 2097 | "dependencies": { 2098 | "node-gyp-build": "^4.3.0" 2099 | }, 2100 | "engines": { 2101 | "node": ">=6.14.2" 2102 | } 2103 | }, 2104 | "node_modules/utils-merge": { 2105 | "version": "1.0.1", 2106 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2107 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 2108 | "license": "MIT", 2109 | "engines": { 2110 | "node": ">= 0.4.0" 2111 | } 2112 | }, 2113 | "node_modules/uuid": { 2114 | "version": "8.3.2", 2115 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 2116 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 2117 | "license": "MIT", 2118 | "bin": { 2119 | "uuid": "dist/bin/uuid" 2120 | } 2121 | }, 2122 | "node_modules/v8-compile-cache-lib": { 2123 | "version": "3.0.1", 2124 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 2125 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 2126 | "dev": true, 2127 | "license": "MIT" 2128 | }, 2129 | "node_modules/vary": { 2130 | "version": "1.1.2", 2131 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2132 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 2133 | "license": "MIT", 2134 | "engines": { 2135 | "node": ">= 0.8" 2136 | } 2137 | }, 2138 | "node_modules/webidl-conversions": { 2139 | "version": "3.0.1", 2140 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 2141 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 2142 | "license": "BSD-2-Clause" 2143 | }, 2144 | "node_modules/whatwg-url": { 2145 | "version": "5.0.0", 2146 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 2147 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 2148 | "license": "MIT", 2149 | "dependencies": { 2150 | "tr46": "~0.0.3", 2151 | "webidl-conversions": "^3.0.0" 2152 | } 2153 | }, 2154 | "node_modules/ws": { 2155 | "version": "7.5.10", 2156 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", 2157 | "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", 2158 | "license": "MIT", 2159 | "engines": { 2160 | "node": ">=8.3.0" 2161 | }, 2162 | "peerDependencies": { 2163 | "bufferutil": "^4.0.1", 2164 | "utf-8-validate": "^5.0.2" 2165 | }, 2166 | "peerDependenciesMeta": { 2167 | "bufferutil": { 2168 | "optional": true 2169 | }, 2170 | "utf-8-validate": { 2171 | "optional": true 2172 | } 2173 | } 2174 | }, 2175 | "node_modules/yn": { 2176 | "version": "3.1.1", 2177 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 2178 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 2179 | "dev": true, 2180 | "license": "MIT", 2181 | "engines": { 2182 | "node": ">=6" 2183 | } 2184 | } 2185 | } 2186 | } 2187 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlmm-project", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "start": "node dist/dlmm-chain-pools-manager/src/index.js", 8 | "client": "npx ts-node src/dlmm-client.ts", 9 | "example": "npx ts-node src/dlmm-example.ts", 10 | "liquidity": "npx ts-node src/manage-liquidity.ts", 11 | "debug": "DEBUG=true npx ts-node --transpile-only src/dlmm-chain-pools-manager/src/index.ts", 12 | "build": "tsc", 13 | "encrypt-key": "node dist/scripts/encrypt-key.js" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "description": "", 19 | "dependencies": { 20 | "@meteora-ag/dlmm": "^1.5.0", 21 | "@solana/web3.js": "^1.91.1", 22 | "axios": "^1.9.0" 23 | }, 24 | "devDependencies": { 25 | "@types/bn.js": "^5.1.5", 26 | "@types/bs58": "^4.0.4", 27 | "@types/node": "^20.12.9", 28 | "ts-node": "^10.9.2", 29 | "typescript": "^5.4.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptocj520/meteora/4d5741bab5b31c9ecb3bb910a7eebe7755c394ab/src/.DS_Store -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptocj520/meteora/4d5741bab5b31c9ecb3bb910a7eebe7755c394ab/src/dlmm-chain-pools-manager/.DS_Store -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/README.md: -------------------------------------------------------------------------------- 1 | # DLMM串联池流动性管理系统 2 | 3 | ## 项目概述 4 | 5 | DLMM串联池流动性管理系统是一个专为Solana区块链上的DLMM(Discrete Liquidity Market Maker)池子设计的自动化管理工具。该系统能够实时监控并维护多个串联池子的流动性分布,确保流动性按照BidAsk模型正确分布,从而优化交易效率和减少滑点。 6 | 7 | 当市场价格变动,跨越不同头寸范围时,系统会自动检测并调整不符合BidAsk模型的头寸,确保市场流动性始终保持最优状态。这种自动化管理大大降低了流动性提供者的操作负担,同时提高了市场的整体效率。 8 | 9 | ## 核心功能 10 | 11 | ### 1. 动态池子管理 12 | 13 | - **自动发现池子**:系统启动时会自动扫描并识别用户的所有DLMM池子头寸,无需手动配置 14 | - **多池子串联管理**:支持同时管理多个串联的DLMM流动性池,确保它们协同工作 15 | - **头寸数据实时分析**:持续收集和分析每个头寸的bin数据和流动性分布 16 | 17 | ### 2. BidAsk模型合规性检查 18 | 19 | - **流动性分布验证**:检查每个头寸的流动性分布是否符合BidAsk模型要求 20 | - **升序/降序分布检测**:高于当前价格的区域应为升序分布,低于当前价格的区域应为降序分布 21 | - **合规性报告**:生成详细的头寸合规性报告,显示不符合要求的具体原因 22 | 23 | ### 3. 自动调整策略 24 | 25 | - **流动性移除**:对不符合BidAsk模型的头寸自动移除100%流动性 26 | - **重新添加流动性**:使用正确的BidAsk策略重新添加流动性,确保符合模型要求 27 | - **渐进式调整**:系统会一次调整一个头寸,确保调整过程平稳进行 28 | 29 | ### 4. 实时价格监控 30 | 31 | - **活跃Bin监控**:持续监控每个池子的活跃Bin变化 32 | - **价格变动检测**:实时检测X/Y代币价格变动 33 | - **池子交叉检测**:当价格跨越不同头寸范围时自动触发检查 34 | 35 | ### 5. 安全与私钥管理 36 | 37 | - **私钥加密存储**:支持使用密码加密私钥,提高系统安全性 38 | - **多级私钥加载**:支持从加密文件或配置文件加载私钥 39 | - **用户身份验证**:在操作关键功能前进行身份验证 40 | 41 | ### 6. 交易优化 42 | 43 | - **优先级费用设置**:支持设置交易优先级费用,提高交易成功率 44 | - **交易自动重试**:遇到临时错误时自动重试,最多尝试5次 45 | - **计算单元优化**:可配置计算单元限制,优化交易执行 46 | 47 | ### 7. 可视化与日志 48 | 49 | - **终端实时显示**:在终端实时显示池子状态和调整过程 50 | - **详细日志记录**:记录系统的每一步操作,便于问题排查 51 | - **状态消息更新**:直观显示当前系统状态和操作进度 52 | 53 | ## 系统架构 54 | 55 | DLMM串联池流动性管理系统采用简化的模块化架构设计,将系统分为以下几个主要部分: 56 | 57 | ``` 58 | dlmm-chain-pools-manager/ 59 | ├── src/ 60 | │ ├── models.ts # 数据模型定义 61 | │ ├── services.ts # 核心服务功能 62 | │ ├── utils.ts # 工具函数 63 | │ ├── logger.ts # 日志模块 64 | │ ├── display.ts # 显示模块 65 | │ ├── config.ts # 配置文件 66 | │ └── index.ts # 主程序入口 67 | ├── package.json # 项目依赖 68 | └── tsconfig.json # TypeScript配置 69 | ``` 70 | 71 | ### 核心模块说明 72 | 73 | 1. **数据模型 (models.ts)**: 74 | - 定义系统中的核心数据结构,包括池子、头寸和池子链等 75 | - 包含数据处理和验证逻辑 76 | - 实现BidAsk模型合规性检查的基础函数 77 | 78 | 2. **服务功能 (services.ts)**: 79 | - 整合所有核心业务逻辑到一个文件中 80 | - 包含连接管理、钱包服务、池子发现和价格监控功能 81 | - 实现流动性调整的关键算法 82 | - 处理与区块链交互的全部逻辑 83 | 84 | 3. **工具函数 (utils.ts)**: 85 | - 提供通用辅助功能和算法 86 | - 实现错误重试、数据格式化等功能 87 | - 包含通用的时间和计算工具 88 | 89 | 4. **日志模块 (logger.ts)**: 90 | - 管理全系统的日志记录 91 | - 支持多级别日志(DEBUG, INFO, WARNING, ERROR) 92 | - 提供结构化和格式化的日志输出 93 | 94 | 5. **显示模块 (display.ts)**: 95 | - 负责终端界面显示和用户交互 96 | - 实现池子状态和操作进度的可视化 97 | - 提供彩色编码的状态提示 98 | 99 | 6. **配置文件 (config.ts)**: 100 | - 集中管理所有系统参数和设置 101 | - 包含网络连接、监控和调整策略的配置项 102 | 103 | 7. **主程序 (index.ts)**: 104 | - 系统入口点 105 | - 协调各模块工作并实现主要工作流程 106 | - 处理启动、循环执行和优雅退出 107 | 108 | ## 安装指南 109 | 110 | ### 前置要求 111 | 112 | - Node.js v14.0.0 或更高版本 113 | - npm v6.0.0 或更高版本 114 | - 一个有效的Solana钱包(用于管理流动性) 115 | 116 | ### 安装步骤 117 | 118 | 1. **克隆项目** 119 | 120 | ```bash 121 | git clone https://github.com/yourusername/dlmm-chain-pools-manager.git 122 | cd dlmm-chain-pools-manager 123 | ``` 124 | 125 | 2. **安装依赖** 126 | 127 | ```bash 128 | npm install 129 | ``` 130 | 131 | 3. **配置系统** 132 | 133 | 编辑 `src/config.ts` 文件,设置关键参数: 134 | 135 | ```typescript 136 | // 基本配置 137 | export const CONFIG = { 138 | // Solana RPC端点URL 139 | RPC_ENDPOINT: "https://api.mainnet-beta.solana.com", 140 | // 刷新间隔(毫秒) 141 | REFRESH_INTERVAL_MS: 10000, 142 | // 是否在终端显示界面 143 | DISPLAY_ENABLED: true, 144 | // 日志级别: 'debug' | 'info' | 'warn' | 'error' 145 | LOG_LEVEL: "info", 146 | }; 147 | 148 | // 钱包配置 149 | export const WALLET_CONFIG = { 150 | // 私钥(Base58编码)- 生产环境建议使用环境变量或加密文件 151 | PRIVATE_KEY: "你的私钥", 152 | }; 153 | 154 | // 交易配置 155 | export const TRANSACTION_CONFIG = { 156 | // 是否启用优先级费用 157 | ENABLE_PRIORITY_FEE: true, 158 | // 优先级费用(microLamports) 159 | PRIORITY_FEE_MICROLAMPORTS: 200000, 160 | }; 161 | ``` 162 | 163 | 4. **编译项目** 164 | 165 | ```bash 166 | npm run build 167 | ``` 168 | 169 | ## 使用方法 170 | 171 | ### 开发模式 172 | 173 | 开发模式下,系统会实时编译并运行: 174 | 175 | ```bash 176 | npm run dev 177 | ``` 178 | 179 | ### 生产模式 180 | 181 | 在生产环境中,先构建项目然后运行: 182 | 183 | ```bash 184 | npm run build 185 | npm start 186 | ``` 187 | 188 | ### 退出程序 189 | 190 | 按 `Ctrl+C` 安全退出程序。 191 | 192 | ## 工作流程 193 | 194 | 系统遵循以下工作流程: 195 | 196 | 1. **初始化**: 197 | - 加载配置文件 198 | - 连接到Solana网络 199 | - 初始化钱包 200 | - 扫描并发现用户的所有DLMM头寸 201 | 202 | 2. **监控**: 203 | - 定期轮询活跃Bin和价格变动 204 | - 当检测到活跃Bin变化时,触发池子交叉检测 205 | - 检查相邻头寸是否符合BidAsk模型要求 206 | 207 | 3. **调整**: 208 | - 对不符合BidAsk模型的头寸,移除其全部流动性 209 | - 根据BidAsk模型重新添加流动性 210 | - 刷新头寸数据,确保调整生效 211 | 212 | 4. **显示**: 213 | - 实时更新终端显示,展示当前状态 214 | - 记录详细日志,以便后续分析 215 | 216 | ## 配置项说明 217 | 218 | ### 主要配置参数 219 | 220 | | 配置项 | 说明 | 默认值 | 221 | |----------------------------|----------------------------|-----------------------------------| 222 | | RPC_ENDPOINT | Solana网络RPC端点 | https://api.mainnet-beta.solana.com | 223 | | REFRESH_INTERVAL_MS | 数据刷新间隔(毫秒) | 10000 | 224 | | DISPLAY_ENABLED | 是否启用终端显示界面 | true | 225 | | LOG_LEVEL | 日志级别 | info | 226 | | PRIORITY_FEE_MICROLAMPORTS | 优先级费用(microLamports)| 200000 | 227 | | MAX_RETRIES | 交易最大重试次数 | 5 | 228 | 229 | ## BidAsk模型说明 230 | 231 | BidAsk模型是一种优化流动性分布的策略,核心理念是: 232 | 233 | 1. **高于当前价格(Ask侧)**:流动性呈**升序**分布 234 | - 例:bin 100有10个代币,bin 101有20个代币,bin 102有30个代币... 235 | 236 | 2. **低于当前价格(Bid侧)**:流动性呈**降序**分布 237 | - 例:bin 99有30个代币,bin 98有20个代币,bin 97有10个代币... 238 | 239 | 这种分布方式确保流动性集中在最可能被交易的价格区间,提高资本效率。 240 | 241 | ## 私钥加密 242 | 243 | 为了提高系统安全性,本项目支持对私钥进行加密存储,避免在配置文件中明文保存私钥。 244 | 245 | ### 加密私钥步骤 246 | 247 | 1. **运行加密命令** 248 | 249 | ```bash 250 | npm run encrypt-key 251 | ``` 252 | 253 | 2. **按提示输入私钥和密码** 254 | 255 | ``` 256 | 请输入需要加密的私钥 (Base58格式): <输入你的私钥> 257 | 请设置加密密码: <输入密码> 258 | 请再次输入密码确认: <再次输入密码> 259 | ``` 260 | 261 | 3. **确认加密文件创建成功** 262 | 263 | 成功后,系统会创建一个加密的私钥文件,默认保存在项目根目录的 `.key` 文件中。同时,系统会自动修改 `config.ts` 文件中的相关配置: 264 | 265 | ```typescript 266 | export const WALLET_CONFIG = { 267 | USE_ENCRYPTED_KEY: true, 268 | // 私钥字段将被清空 269 | PRIVATE_KEY: "", 270 | // 可选:自定义加密文件路径 271 | KEY_FILE_PATH: "./.key" 272 | }; 273 | ``` 274 | 275 | ### 使用加密私钥 276 | 277 | 启动程序时,如果配置中启用了加密私钥(`USE_ENCRYPTED_KEY: true`),系统会提示输入密码: 278 | 279 | ```bash 280 | npm start 281 | # 系统会提示:请输入密码解锁私钥: 282 | ``` 283 | 284 | 输入正确密码后,系统会解密私钥并继续启动流程。 285 | 286 | ### 安全注意事项 287 | 288 | - 密码应当足够复杂,包含大小写字母、数字和特殊符号 289 | - 定期更换密码和私钥 290 | - 加密文件(.key)包含敏感信息,请勿分享或提交到版本控制系统 291 | - 建议将加密文件添加到 `.gitignore` 中 292 | 293 | ## 故障排除 294 | 295 | ### 常见问题解决方案 296 | 297 | 1. **连接错误** 298 | - 检查RPC_ENDPOINT配置是否正确 299 | - 确认网络连接正常 300 | - 尝试使用备用RPC端点 301 | 302 | 2. **交易失败** 303 | - 增加优先级费用值 304 | - 检查钱包余额是否足够 305 | - 查看日志了解详细错误信息 306 | 307 | 3. **配置加载错误** 308 | - 确认config.ts文件存在且格式正确 309 | - 检查文件权限 310 | 311 | ### 日志分析 312 | 313 | 系统日志包含详细的操作记录和错误信息,可帮助诊断问题: 314 | 315 | - `[INFO]` - 一般信息,正常操作 316 | - `[WARN]` - 警告信息,可能需要注意 317 | - `[ERROR]` - 错误信息,需要排查 318 | - `[DEBUG]` - 调试信息,包含详细内部操作 319 | 320 | ## 安全建议 321 | 322 | 1. **私钥安全** 323 | - 不要在配置文件中存储明文私钥 324 | - 考虑使用环境变量存储私钥 325 | - 定期更换私钥 326 | 327 | 2. **权限控制** 328 | - 为流动性管理创建专用钱包 329 | - 不要在该钱包中存储大量资金 330 | 331 | 3. **网络安全** 332 | - 使用可靠的RPC端点 333 | - 避免在不安全的网络上运行程序 334 | 335 | ## 许可证 336 | 337 | 本项目采用 MIT 许可证 338 | 339 | --- 340 | 341 | 感谢使用DLMM串联池流动性管理系统! -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlmm-chain-pools-manager", 3 | "version": "1.0.0", 4 | "description": "DLMM串联池流动性管理脚本", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "ts-node src/index.ts", 9 | "dev": "ts-node-dev --respawn src/index.ts", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [ 13 | "solana", 14 | "dlmm", 15 | "defi", 16 | "liquidity" 17 | ], 18 | "author": "", 19 | "license": "ISC", 20 | "dependencies": { 21 | "@meteora-ag/dlmm": "^1.4.3", 22 | "@solana/web3.js": "^1.87.6", 23 | "bs58": "^5.0.0" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^20.10.5", 27 | "ts-node": "^10.9.1", 28 | "ts-node-dev": "^2.0.0", 29 | "typescript": "^5.3.3" 30 | } 31 | } -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/src/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DLMM串联池流动性管理脚本 - 配置文件 3 | * 4 | * 本文件集中管理所有配置参数,方便统一调整和维护 5 | */ 6 | 7 | // 应用程序配置 8 | export const APPLICATION_CONFIG = { 9 | // 应用版本 10 | VERSION: '1.0.0', 11 | // 应用名称 12 | NAME: 'DLMM链上池子管理服务', 13 | }; 14 | 15 | // RPC连接设置 16 | export const CONNECTION_CONFIG = { 17 | // Solana RPC端点 18 | RPC_ENDPOINT: 'https://solana.publicnode.com', 19 | 20 | // 备用RPC节点列表 - 尝试更多可能支持DLMM查询的节点 21 | BACKUP_RPC_ENDPOINTS: [ 22 | 'https://rpc.ankr.com/solana', 23 | 'https://api.mainnet-beta.solana.com', 24 | 'https://solana-mainnet.rpc.staratlas.cloud', 25 | ], 26 | 27 | // 连接选项 28 | CONNECTION_OPTIONS: { 29 | commitment: 'confirmed', 30 | disableRetryOnRateLimit: false, 31 | confirmTransactionInitialTimeout: 60000 32 | }, 33 | // 重连最大尝试次数 34 | MAX_CONNECTION_RETRIES: 10, 35 | // 重连间隔基础时间(毫秒) 36 | RECONNECT_BASE_DELAY_MS: 2000, 37 | // SOL余额检查区间(秒) 38 | BALANCE_CHECK_INTERVAL_SEC: 300, // 5分钟 39 | // 最小SOL余额警告阈值 40 | MIN_SOL_BALANCE: 0.01, 41 | }; 42 | 43 | // DLMM相关配置 44 | export const DLMM_CONFIG = { 45 | // 默认池地址列表(如果已知) 46 | POOL_ADDRESSES: [ 47 | '3msVd34R5KxonDzyNSV5nT19UtUeJ2RF1NaQhvVPNLxL' // 从read-position-simplified.ts中获取的示例池地址 48 | ], 49 | // 获取池子的最大重试次数 50 | MAX_POOL_FETCH_RETRIES: 3, 51 | // 获取头寸的最大重试次数 52 | MAX_POSITION_FETCH_RETRIES: 3, 53 | // 检查的最大池子数量(当自动发现池子时) 54 | MAX_POOLS_TO_CHECK: 50, 55 | }; 56 | 57 | // 钱包配置 58 | export const WALLET_CONFIG = { 59 | // 私钥,已移至加密文件存储,此处保留空字符串以保持接口兼容 60 | PRIVATE_KEY: "" 61 | }; 62 | 63 | // 监控参数 64 | export const MONITOR_CONFIG = { 65 | // 同一池子内价格监控间隔(毫秒) 66 | PRICE_CHECK_INTERVAL_MS: 3000, // 3秒 67 | }; 68 | 69 | // 调整策略参数 70 | export const ADJUSTMENT_CONFIG = { 71 | // 调整时的最大重试次数 72 | MAX_ADJUSTMENT_RETRIES: 1, 73 | // BidAsk模型最小流动性比例差异(倍数) 74 | MIN_LIQUIDITY_RATIO: 3.0, // 至少1倍差异 75 | }; 76 | 77 | // 错误处理配置 78 | export const ERROR_CONFIG = { 79 | // 临时错误的重试策略 80 | RETRY_CONFIG: { 81 | // 最大重试次数 82 | MAX_RETRIES: 3, 83 | // 初始重试延迟(毫秒) 84 | INITIAL_RETRY_DELAY_MS: 1000, 85 | // 重试延迟增长系数 86 | BACKOFF_FACTOR: 2, 87 | }, 88 | }; 89 | 90 | // 交易费用配置 91 | export const TRANSACTION_CONFIG = { 92 | // 是否启用优先级费用 93 | ENABLE_PRIORITY_FEE: true, 94 | // 优先级费用(microLamports) 95 | // 200,000 microLamports ≈ 0.000001 SOL 96 | PRIORITY_FEE_MICROLAMPORTS: 200000, 97 | // 计算单元限制(如果需要设置) 98 | COMPUTE_UNIT_LIMIT: 1000000, 99 | // 是否自动设置计算单元限制 100 | AUTO_COMPUTE_UNIT_LIMIT: false, 101 | // 交易重试策略 102 | TRANSACTION_RETRY: { 103 | // 最大重试次数 104 | MAX_RETRIES: 5, 105 | // 重试间隔(毫秒) 106 | RETRY_INTERVAL_MS: 3000, 107 | // 交易超时时间(毫秒) 108 | TRANSACTION_TIMEOUT_MS: 90000, 109 | }, 110 | }; 111 | 112 | // 日志配置 113 | export const LOGGER_CONFIG = { 114 | // 日志级别: 'debug' | 'info' | 'warn' | 'error' 115 | // 优先使用环境变量中的DEBUG设置 116 | LOG_LEVEL: process.env.DEBUG === 'true' ? 'debug' : 'info', 117 | // 是否输出时间戳 118 | TIMESTAMP: true, 119 | // 是否输出到文件 120 | LOG_TO_FILE: true, 121 | // 日志文件路径 122 | LOG_FILE_PATH: './logs/dlmm-manager.log', 123 | }; 124 | 125 | // 显示配置 126 | export const DISPLAY_CONFIG = { 127 | // 表格列宽 128 | TABLE_COLUMN_WIDTHS: { 129 | POOL_ADDRESS: 14, // 截断的池地址 130 | BIN_RANGE: 13, // bin范围 131 | PRICE_RANGE: 16, // 价格范围 132 | TOKEN_X: 15, // X代币数量 133 | TOKEN_Y: 15, // Y代币数量 134 | STATUS: 10, // 状态信息 135 | POSITION_ID: 12, // 头寸ID 136 | }, 137 | // 是否使用颜色 138 | USE_COLORS: true, 139 | // 刷新频率(毫秒) 140 | REFRESH_RATE_MS: 5000, 141 | }; -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/src/display.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DLMM串联池流动性管理脚本 - 显示模块 3 | * 4 | * 负责所有终端界面显示功能,提供实时状态更新和可视化 5 | */ 6 | 7 | import { DISPLAY_CONFIG } from './config'; 8 | import { truncateAddress } from './utils'; 9 | 10 | // 终端颜色代码 11 | const COLORS: Record = { 12 | RESET: '\x1b[0m', 13 | RED: '\x1b[31m', 14 | GREEN: '\x1b[32m', 15 | YELLOW: '\x1b[33m', 16 | BLUE: '\x1b[34m', 17 | MAGENTA: '\x1b[35m', 18 | CYAN: '\x1b[36m', 19 | WHITE: '\x1b[37m', 20 | BRIGHT: '\x1b[1m', 21 | DIM: '\x1b[2m', 22 | BG_RED: '\x1b[41m', 23 | BG_GREEN: '\x1b[42m', 24 | BG_YELLOW: '\x1b[43m', 25 | BG_BLUE: '\x1b[44m', 26 | }; 27 | 28 | /** 29 | * 池状态枚举 30 | */ 31 | export enum PoolStatus { 32 | NORMAL = 'normal', // 正常 33 | CURRENT = 'current', // 当前价格所在池 34 | ADJUSTING = 'adjusting', // 调整中 35 | WARNING = 'warning', // 警告状态 36 | ERROR = 'error', // 错误状态 37 | } 38 | 39 | /** 40 | * 池子显示数据结构 41 | */ 42 | export interface PoolDisplayData { 43 | address: string; 44 | binRange: string; 45 | priceRange: string; 46 | tokenX: string; 47 | tokenY: string; 48 | status: PoolStatus; 49 | isBidAsk?: boolean; 50 | positionId?: string; 51 | } 52 | 53 | /** 54 | * 状态颜色映射 55 | */ 56 | const STATUS_COLORS: Record = { 57 | [PoolStatus.NORMAL]: COLORS.WHITE, 58 | [PoolStatus.CURRENT]: COLORS.BG_GREEN + COLORS.WHITE, 59 | [PoolStatus.ADJUSTING]: COLORS.BG_BLUE + COLORS.WHITE, 60 | [PoolStatus.WARNING]: COLORS.BG_YELLOW + COLORS.WHITE, 61 | [PoolStatus.ERROR]: COLORS.BG_RED + COLORS.WHITE, 62 | }; 63 | 64 | /** 65 | * 状态文本映射 66 | */ 67 | const STATUS_TEXT: Record = { 68 | [PoolStatus.NORMAL]: '正常', 69 | [PoolStatus.CURRENT]: '当前', 70 | [PoolStatus.ADJUSTING]: '调整中', 71 | [PoolStatus.WARNING]: '警告', 72 | [PoolStatus.ERROR]: '错误', 73 | }; 74 | 75 | /** 76 | * 显示管理器类 77 | */ 78 | export class Display { 79 | private isFirstRender: boolean = true; 80 | private poolsData: PoolDisplayData[] = []; 81 | private currentPrice: string = 'N/A'; 82 | private lastUpdateTime: string = ''; 83 | private statusMessage: string = ''; 84 | private currentBinId: string = 'N/A'; 85 | 86 | /** 87 | * 清除屏幕 88 | */ 89 | private clearScreen(): void { 90 | if (this.isFirstRender) { 91 | console.clear(); 92 | this.isFirstRender = false; 93 | } else { 94 | // 使用ANSI转义序列移动光标到顶部 95 | process.stdout.write('\x1B[1;1H\x1B[J'); 96 | } 97 | } 98 | 99 | /** 100 | * 生成表格水平分隔符 101 | */ 102 | private generateHorizontalLine(width: number): string { 103 | return '─'.repeat(width); 104 | } 105 | 106 | /** 107 | * 生成固定宽度的单元格内容(左对齐数据) 108 | */ 109 | private formatCell(content: string, width: number): string { 110 | if (content.length > width) { 111 | return content.substring(0, width - 3) + '...'; 112 | } 113 | // 左对齐数据,前后补充空格 114 | return ' ' + content.padEnd(width - 2, ' ') + ' '; 115 | } 116 | 117 | /** 118 | * 生成固定宽度的标题单元格内容(居中对齐) 119 | */ 120 | private formatHeaderCell(content: string, width: number): string { 121 | if (content.length > width) { 122 | return content.substring(0, width - 3) + '...'; 123 | } 124 | // 居中对齐标题 125 | const padding = width - content.length; 126 | const leftPad = Math.floor(padding / 2); 127 | const rightPad = padding - leftPad; 128 | return ' '.repeat(leftPad) + content + ' '.repeat(rightPad); 129 | } 130 | 131 | /** 132 | * 渲染表头 133 | */ 134 | private renderTableHeader(): void { 135 | const { POOL_ADDRESS, BIN_RANGE, PRICE_RANGE, TOKEN_X, TOKEN_Y, STATUS, POSITION_ID } = DISPLAY_CONFIG.TABLE_COLUMN_WIDTHS; 136 | 137 | // 表头 - 使用居中对齐的标题 138 | const header = `│ ${this.formatHeaderCell('池地址', POOL_ADDRESS)} │ ${this.formatHeaderCell('Bin范围', BIN_RANGE)} │ ${this.formatHeaderCell('价格范围', PRICE_RANGE)} │ ${this.formatHeaderCell('X代币', TOKEN_X)} │ ${this.formatHeaderCell('Y代币', TOKEN_Y)} │ ${this.formatHeaderCell('状态', STATUS)} │ ${this.formatHeaderCell('头寸ID', POSITION_ID)} │`; 139 | 140 | // 分隔线 141 | const separator = `├─${this.generateHorizontalLine(POOL_ADDRESS)}─┼─${this.generateHorizontalLine(BIN_RANGE)}─┼─${this.generateHorizontalLine(PRICE_RANGE)}─┼─${this.generateHorizontalLine(TOKEN_X)}─┼─${this.generateHorizontalLine(TOKEN_Y)}─┼─${this.generateHorizontalLine(STATUS)}─┼─${this.generateHorizontalLine(POSITION_ID)}─┤`; 142 | 143 | const topLine = `┌─${this.generateHorizontalLine(POOL_ADDRESS)}─┬─${this.generateHorizontalLine(BIN_RANGE)}─┬─${this.generateHorizontalLine(PRICE_RANGE)}─┬─${this.generateHorizontalLine(TOKEN_X)}─┬─${this.generateHorizontalLine(TOKEN_Y)}─┬─${this.generateHorizontalLine(STATUS)}─┬─${this.generateHorizontalLine(POSITION_ID)}─┐`; 144 | 145 | console.log(topLine); 146 | console.log(header); 147 | console.log(separator); 148 | } 149 | 150 | /** 151 | * 渲染表格行 152 | */ 153 | private renderTableRow(pool: PoolDisplayData): void { 154 | const { POOL_ADDRESS, BIN_RANGE, PRICE_RANGE, TOKEN_X, TOKEN_Y, STATUS, POSITION_ID } = DISPLAY_CONFIG.TABLE_COLUMN_WIDTHS; 155 | 156 | const statusColor = STATUS_COLORS[pool.status]; 157 | const statusText = STATUS_TEXT[pool.status]; 158 | 159 | // 处理地址显示 160 | const displayAddress = truncateAddress(pool.address, POOL_ADDRESS); 161 | 162 | // 格式化状态显示 163 | const formattedStatus = pool.isBidAsk === false 164 | ? `${statusColor}${this.formatCell('非BidAsk', STATUS)}${COLORS.RESET}` 165 | : `${statusColor}${this.formatCell(statusText, STATUS)}${COLORS.RESET}`; 166 | 167 | // 处理头寸ID显示 168 | const displayPositionId = pool.positionId || '-'; 169 | 170 | // 构建行 171 | const row = `│ ${this.formatCell(displayAddress, POOL_ADDRESS)} │ ${this.formatCell(pool.binRange, BIN_RANGE)} │ ${this.formatCell(pool.priceRange, PRICE_RANGE)} │ ${this.formatCell(pool.tokenX, TOKEN_X)} │ ${this.formatCell(pool.tokenY, TOKEN_Y)} │ ${formattedStatus} │ ${this.formatCell(displayPositionId, POSITION_ID)} │`; 172 | 173 | console.log(row); 174 | } 175 | 176 | /** 177 | * 更新池子数据 178 | */ 179 | public updatePoolsData(poolsData: PoolDisplayData[]): void { 180 | this.poolsData = poolsData; 181 | this.lastUpdateTime = new Date().toLocaleTimeString(); 182 | } 183 | 184 | /** 185 | * 更新当前价格 186 | */ 187 | public updateCurrentPrice(price: string): void { 188 | this.currentPrice = price; 189 | } 190 | 191 | /** 192 | * 更新最后更新时间 193 | */ 194 | public updateLastUpdated(time: string): void { 195 | this.lastUpdateTime = time; 196 | } 197 | 198 | /** 199 | * 更新状态消息 200 | */ 201 | public updateStatusMessage(message: string): void { 202 | this.statusMessage = message; 203 | } 204 | 205 | /** 206 | * 显示消息(别名方法,与updateStatusMessage功能相同) 207 | */ 208 | public displayMessage(message: string): void { 209 | this.updateStatusMessage(message); 210 | this.render(); 211 | } 212 | 213 | /** 214 | * 更新当前bin ID 215 | */ 216 | public updateCurrentBinId(binId: string): void { 217 | this.currentBinId = binId; 218 | } 219 | 220 | /** 221 | * 渲染概览信息 222 | */ 223 | private renderSummary(): void { 224 | console.log(`\n当前X代币价格: ${COLORS.BRIGHT}${this.currentPrice}${COLORS.RESET}`); 225 | console.log(`当前活跃Bin ID: ${COLORS.CYAN}${this.currentBinId}${COLORS.RESET}`); 226 | console.log(`最后更新时间: ${this.lastUpdateTime}`); 227 | 228 | if (this.statusMessage) { 229 | console.log(`\n状态: ${this.statusMessage}`); 230 | } 231 | } 232 | 233 | /** 234 | * 渲染进度条 235 | */ 236 | public renderProgressBar(current: number, total: number, width: number = 30): void { 237 | const percentage = Math.min(Math.max(current / total, 0), 1); 238 | const filledWidth = Math.round(width * percentage); 239 | const emptyWidth = width - filledWidth; 240 | 241 | const filledBar = '█'.repeat(filledWidth); 242 | const emptyBar = '░'.repeat(emptyWidth); 243 | 244 | process.stdout.write(`\r[${filledBar}${emptyBar}] ${(percentage * 100).toFixed(1)}% (${current}/${total})`); 245 | 246 | if (current === total) { 247 | process.stdout.write('\n'); 248 | } 249 | } 250 | 251 | /** 252 | * 渲染完整界面 253 | */ 254 | public render(): void { 255 | this.clearScreen(); 256 | 257 | // 标题 258 | console.log(`${COLORS.BRIGHT}===== DLMM串联池流动性管理 =====${COLORS.RESET}`); 259 | 260 | // 表格 261 | if (this.poolsData.length > 0) { 262 | this.renderTableHeader(); 263 | this.poolsData.forEach(pool => this.renderTableRow(pool)); 264 | const { POOL_ADDRESS, BIN_RANGE, PRICE_RANGE, TOKEN_X, TOKEN_Y, STATUS, POSITION_ID } = DISPLAY_CONFIG.TABLE_COLUMN_WIDTHS; 265 | const bottomLine = `└─${this.generateHorizontalLine(POOL_ADDRESS)}─┴─${this.generateHorizontalLine(BIN_RANGE)}─┴─${this.generateHorizontalLine(PRICE_RANGE)}─┴─${this.generateHorizontalLine(TOKEN_X)}─┴─${this.generateHorizontalLine(TOKEN_Y)}─┴─${this.generateHorizontalLine(STATUS)}─┴─${this.generateHorizontalLine(POSITION_ID)}─┘`; 266 | console.log(bottomLine); 267 | } else { 268 | console.log("\n未发现池子数据"); 269 | } 270 | 271 | // 概览 272 | this.renderSummary(); 273 | } 274 | } 275 | 276 | // 导出单例实例 277 | export const display = new Display(); -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DLMM串联池流动性管理脚本 - 主程序入口 3 | * 4 | * 程序入口点,负责初始化各服务、实现主工作流程,以及处理错误和退出逻辑 5 | */ 6 | 7 | import { logger } from './logger'; 8 | import { display } from './display'; 9 | import { sleep } from './utils'; 10 | import { 11 | ConnectionService, 12 | WalletService, 13 | PoolDiscoveryService, 14 | PriceMonitorService, 15 | LiquidityAdjustmentService 16 | } from './services'; 17 | import { MONITOR_CONFIG, APPLICATION_CONFIG } from './config'; 18 | 19 | // 检查是否为调试模式 20 | const isDebugMode = process.env.DEBUG === 'true'; 21 | 22 | /** 23 | * 应用主类 24 | */ 25 | export class DLMMChainPoolsManager { 26 | private connectionService: ConnectionService; 27 | private walletService: WalletService; 28 | private poolDiscoveryService: PoolDiscoveryService; 29 | private priceMonitorService: PriceMonitorService | null = null; 30 | private liquidityAdjustmentService: LiquidityAdjustmentService | null = null; 31 | private running: boolean = false; 32 | 33 | /** 34 | * 构造函数 35 | */ 36 | constructor( 37 | connectionService: ConnectionService, 38 | walletService: WalletService, 39 | displayService: any // 使用any类型避免类型错误 40 | ) { 41 | // 初始化各服务 42 | this.connectionService = connectionService; 43 | this.walletService = walletService; 44 | this.poolDiscoveryService = new PoolDiscoveryService(this.connectionService, this.walletService); 45 | } 46 | 47 | /** 48 | * 初始化应用 49 | */ 50 | public async initialize(): Promise { 51 | try { 52 | logger.group('初始化应用'); 53 | 54 | if (isDebugMode) { 55 | logger.debug('调试模式已启用'); 56 | logger.debug('系统环境信息:'); 57 | logger.debug(`Node.js版本: ${process.version}`); 58 | logger.debug(`平台: ${process.platform}`); 59 | logger.debug(`系统内存: ${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)} MB`); 60 | } 61 | 62 | // 连接区块链网络 63 | logger.info('正在连接到Solana网络...'); 64 | await this.connectionService.initialize(); 65 | 66 | // 显示钱包信息 67 | const walletAddress = this.walletService.getAddress(); 68 | logger.info(`使用钱包: ${walletAddress}`); 69 | 70 | // 获取SOL余额 71 | const solBalance = await this.walletService.getSolBalance(); 72 | logger.info(`钱包SOL余额: ${solBalance.toFixed(4)} SOL`); 73 | 74 | // 发现池子 75 | logger.info('开始发现用户创建的池子...'); 76 | const poolChain = await this.poolDiscoveryService.discoverPools(); 77 | 78 | if (isDebugMode) { 79 | logger.debug(`发现的池子数量: ${poolChain.getAllPools().length}`); 80 | for (const pool of poolChain.getAllPools()) { 81 | logger.debug(`池子地址: ${pool.address}, 价格范围: ${pool.priceRange.minPrice.toFixed(8)}-${pool.priceRange.maxPrice.toFixed(8)}`); 82 | } 83 | } 84 | 85 | // 初始化价格监控服务 86 | this.priceMonitorService = new PriceMonitorService(this.connectionService, poolChain); 87 | 88 | // 初始化流动性调整服务 89 | this.liquidityAdjustmentService = new LiquidityAdjustmentService( 90 | this.connectionService, 91 | this.walletService, 92 | poolChain, 93 | this.priceMonitorService, 94 | this.poolDiscoveryService 95 | ); 96 | 97 | logger.info('应用初始化完成'); 98 | } catch (error) { 99 | logger.error(`初始化应用时出错: ${error instanceof Error ? error.message : String(error)}`); 100 | throw error; 101 | } 102 | } 103 | 104 | /** 105 | * 启动应用 106 | */ 107 | public async start(): Promise { 108 | if (this.running) { 109 | logger.warn('应用已经在运行中'); 110 | return; 111 | } 112 | 113 | try { 114 | this.running = true; 115 | logger.group('启动应用'); 116 | 117 | // 显示欢迎信息 118 | display.updateStatusMessage('正在启动...'); 119 | display.render(); 120 | 121 | // 检查并初始化(如果尚未初始化) 122 | if (!this.priceMonitorService || !this.liquidityAdjustmentService) { 123 | await this.initialize(); 124 | } 125 | 126 | // 启动价格监控 127 | logger.info('启动价格监控...'); 128 | await this.priceMonitorService!.startMonitoring(); 129 | 130 | // 进行首次邻近池子检查 131 | logger.info('进行首次邻近池子检查...'); 132 | await this.liquidityAdjustmentService!.checkAndAdjustNeighboringPools(); 133 | 134 | // 启动定时检查循环 135 | this.startPeriodicChecks(); 136 | 137 | logger.info('应用成功启动'); 138 | display.updateStatusMessage(isDebugMode ? '调试模式运行中' : '运行中'); 139 | display.render(); 140 | } catch (error) { 141 | this.running = false; 142 | logger.error(`启动应用时出错: ${error instanceof Error ? error.message : String(error)}`); 143 | display.updateStatusMessage(`启动失败: ${error instanceof Error ? error.message : String(error)}`); 144 | display.render(); 145 | } 146 | } 147 | 148 | /** 149 | * 启动定时检查循环 150 | */ 151 | private async startPeriodicChecks(): Promise { 152 | if (!this.running || !this.liquidityAdjustmentService) return; 153 | 154 | const liquidityService = this.liquidityAdjustmentService; 155 | 156 | // 启动一个异步循环,定时检查相邻池子 157 | (async () => { 158 | while (this.running) { 159 | try { 160 | await sleep(MONITOR_CONFIG.PRICE_CHECK_INTERVAL_MS); 161 | 162 | if (this.running) { 163 | logger.info('执行定时邻近池子检查...'); 164 | await liquidityService.checkAndAdjustNeighboringPools(); 165 | } 166 | } catch (error) { 167 | logger.error(`定时检查时出错: ${error instanceof Error ? error.message : String(error)}`); 168 | // 出错后继续运行,不中断循环 169 | } 170 | } 171 | })(); 172 | } 173 | 174 | /** 175 | * 停止应用 176 | */ 177 | public stop(): void { 178 | if (!this.running) { 179 | logger.warn('应用未在运行'); 180 | return; 181 | } 182 | 183 | logger.group('停止应用'); 184 | 185 | // 停止价格监控 186 | if (this.priceMonitorService) { 187 | this.priceMonitorService.stopMonitoring(); 188 | } 189 | 190 | this.running = false; 191 | logger.info('应用已停止'); 192 | display.updateStatusMessage('已停止'); 193 | display.render(); 194 | } 195 | } 196 | 197 | /** 198 | * 主函数 199 | */ 200 | async function main(): Promise { 201 | try { 202 | // 设置退出处理 203 | process.on('SIGINT', () => { 204 | logger.info('接收到终止信号,正在退出...'); 205 | display.displayMessage('正在退出,请稍候...'); 206 | process.exit(0); 207 | }); 208 | 209 | // 显示欢迎信息 210 | display.displayMessage(`DLMM链上池子管理服务 v${APPLICATION_CONFIG.VERSION}`); 211 | logger.info(`启动 DLMM链上池子管理服务 v${APPLICATION_CONFIG.VERSION}`); 212 | 213 | // 初始化基础服务 214 | const connectionService = new ConnectionService(); 215 | const walletService = new WalletService(connectionService); 216 | 217 | // 初始化钱包服务(加载私钥),这一步会提示用户输入密码 218 | logger.info('初始化钱包服务...'); 219 | await walletService.init(); 220 | logger.info('钱包初始化成功'); 221 | 222 | // 创建主管理器实例 223 | const manager = new DLMMChainPoolsManager( 224 | connectionService, 225 | walletService, 226 | display 227 | ); 228 | 229 | // 启动服务 230 | await manager.start(); 231 | } catch (error) { 232 | logger.error(`运行时错误: ${error instanceof Error ? error.message : String(error)}`); 233 | display.displayMessage(`发生错误: ${error instanceof Error ? error.message : String(error)}`); 234 | process.exit(1); 235 | } 236 | } 237 | 238 | // 运行主函数 239 | main().catch(error => { 240 | console.error('未捕获的错误:', error); 241 | process.exit(1); 242 | }); -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/src/logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DLMM串联池流动性管理脚本 - 日志模块 3 | * 4 | * 负责系统日志管理,支持多级别日志和格式化输出 5 | */ 6 | 7 | import * as fs from 'fs'; 8 | import * as path from 'path'; 9 | import { LOGGER_CONFIG } from './config'; 10 | 11 | // 日志级别枚举 12 | export enum LogLevel { 13 | DEBUG = 0, 14 | INFO = 1, 15 | WARN = 2, 16 | ERROR = 3 17 | } 18 | 19 | // 日志级别映射 20 | const LOG_LEVEL_MAP: Record = { 21 | 'debug': LogLevel.DEBUG, 22 | 'info': LogLevel.INFO, 23 | 'warn': LogLevel.WARN, 24 | 'error': LogLevel.ERROR 25 | }; 26 | 27 | // 终端颜色代码 28 | const COLORS: Record = { 29 | RESET: '\x1b[0m', 30 | DEBUG: '\x1b[36m', // 青色 31 | INFO: '\x1b[32m', // 绿色 32 | WARN: '\x1b[33m', // 黄色 33 | ERROR: '\x1b[31m', // 红色 34 | TIME: '\x1b[90m', // 灰色 35 | BG_DEBUG: '\x1b[46m\x1b[30m', // 青色背景,黑色文字 36 | }; 37 | 38 | /** 39 | * 日志管理器类 40 | */ 41 | class Logger { 42 | private level: LogLevel; 43 | private useTimestamp: boolean; 44 | private logToFile: boolean; 45 | private logFilePath: string; 46 | private isDebugMode: boolean; 47 | 48 | constructor() { 49 | this.isDebugMode = process.env.DEBUG === 'true'; 50 | this.level = this.isDebugMode ? LogLevel.DEBUG : (LOG_LEVEL_MAP[LOGGER_CONFIG.LOG_LEVEL] || LogLevel.INFO); 51 | this.useTimestamp = LOGGER_CONFIG.TIMESTAMP; 52 | this.logToFile = LOGGER_CONFIG.LOG_TO_FILE; 53 | this.logFilePath = LOGGER_CONFIG.LOG_FILE_PATH; 54 | 55 | // 如果需要记录到文件,确保日志目录存在 56 | if (this.logToFile) { 57 | const logDir = path.dirname(this.logFilePath); 58 | if (!fs.existsSync(logDir)) { 59 | fs.mkdirSync(logDir, { recursive: true }); 60 | } 61 | } 62 | 63 | // 调试模式下,打印初始化消息 64 | if (this.isDebugMode) { 65 | console.log(`${COLORS.BG_DEBUG}[DLMM调试模式]${COLORS.RESET} 日志级别: DEBUG`); 66 | } 67 | } 68 | 69 | /** 70 | * 获取当前时间戳字符串 71 | */ 72 | private getTimestamp(): string { 73 | const now = new Date(); 74 | return now.toISOString(); 75 | } 76 | 77 | /** 78 | * 格式化日志消息 79 | */ 80 | private formatMessage(level: string, message: string): string { 81 | let formattedMessage = ''; 82 | 83 | // 添加时间戳 84 | if (this.useTimestamp) { 85 | formattedMessage += `${COLORS.TIME}[${this.getTimestamp()}]${COLORS.RESET} `; 86 | } 87 | 88 | // 添加日志级别 89 | const levelColor = COLORS[level] || COLORS.RESET; 90 | 91 | // 调试模式下的DEBUG消息使用特殊格式 92 | if (level === 'DEBUG' && this.isDebugMode) { 93 | formattedMessage += `${COLORS.BG_DEBUG}[${level}]${COLORS.RESET} ${message}`; 94 | } else { 95 | formattedMessage += `${levelColor}[${level}]${COLORS.RESET} ${message}`; 96 | } 97 | 98 | return formattedMessage; 99 | } 100 | 101 | /** 102 | * 写入日志到文件 103 | */ 104 | private writeToFile(message: string): void { 105 | if (!this.logToFile) return; 106 | 107 | // 移除颜色代码的纯文本消息 108 | const plainMessage = message.replace(/\x1b\[\d+m/g, ''); 109 | 110 | try { 111 | fs.appendFileSync(this.logFilePath, plainMessage + '\n'); 112 | } catch (error) { 113 | // 如果写入文件失败,输出到控制台 114 | console.error(`无法写入日志文件: ${error}`); 115 | } 116 | } 117 | 118 | /** 119 | * DEBUG级别日志 120 | */ 121 | debug(message: string, ...args: any[]): void { 122 | if (this.level <= LogLevel.DEBUG) { 123 | const formattedMessage = this.formatMessage('DEBUG', message); 124 | console.log(formattedMessage, ...args); 125 | this.writeToFile(`${formattedMessage} ${args.join(' ')}`); 126 | } 127 | } 128 | 129 | /** 130 | * INFO级别日志 131 | */ 132 | info(message: string, ...args: any[]): void { 133 | if (this.level <= LogLevel.INFO) { 134 | const formattedMessage = this.formatMessage('INFO', message); 135 | console.log(formattedMessage, ...args); 136 | this.writeToFile(`${formattedMessage} ${args.join(' ')}`); 137 | } 138 | } 139 | 140 | /** 141 | * WARN级别日志 142 | */ 143 | warn(message: string, ...args: any[]): void { 144 | if (this.level <= LogLevel.WARN) { 145 | const formattedMessage = this.formatMessage('WARN', message); 146 | console.warn(formattedMessage, ...args); 147 | this.writeToFile(`${formattedMessage} ${args.join(' ')}`); 148 | } 149 | } 150 | 151 | /** 152 | * ERROR级别日志 153 | */ 154 | error(message: string, ...args: any[]): void { 155 | if (this.level <= LogLevel.ERROR) { 156 | const formattedMessage = this.formatMessage('ERROR', message); 157 | console.error(formattedMessage, ...args); 158 | this.writeToFile(`${formattedMessage} ${args.join(' ')}`); 159 | } 160 | } 161 | 162 | /** 163 | * 记录错误对象 164 | */ 165 | logError(error: Error, context?: string): void { 166 | const contextMsg = context ? `[${context}] ` : ''; 167 | this.error(`${contextMsg}错误: ${error.message}`); 168 | if (error.stack) { 169 | this.debug(`堆栈追踪: ${error.stack}`); 170 | } 171 | } 172 | 173 | /** 174 | * 分组标记,用于视觉上分隔日志消息 175 | */ 176 | group(title: string): void { 177 | const separator = '-'.repeat(50); 178 | this.info(`\n${separator}`); 179 | this.info(`--- ${title} ---`); 180 | this.info(`${separator}`); 181 | } 182 | } 183 | 184 | // 导出单例实例 185 | export const logger = new Logger(); -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/src/models.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DLMM串联池流动性管理脚本 - 数据模型 3 | * 4 | * 定义系统中的核心数据结构,包括Pool、PoolChain和Position类 5 | */ 6 | 7 | import { PublicKey } from '@solana/web3.js'; 8 | import * as DLMMSdk from '@meteora-ag/dlmm'; 9 | import { formatAmount, formatPrice, validateBidAskModel, calculateRealPrice } from './utils'; 10 | import { ADJUSTMENT_CONFIG, DLMM_CONFIG } from './config'; 11 | import { logger } from './logger'; 12 | import { withRetry } from './utils'; 13 | 14 | /** 15 | * BIN数据接口 16 | */ 17 | export interface BinData { 18 | binId: number; 19 | x: bigint; 20 | y: bigint; 21 | price?: string | number; 22 | } 23 | 24 | /** 25 | * 价格区间接口 26 | */ 27 | export interface PriceRange { 28 | minPrice: number; 29 | maxPrice: number; 30 | } 31 | 32 | /** 33 | * 池子类 34 | * 表示单个DLMM流动性池 35 | */ 36 | export class Pool { 37 | public address: PublicKey; 38 | public dlmmPool: any; // DLMM SDK池对象 39 | public priceRange: PriceRange; 40 | public positions: Position[] = []; 41 | public tokenXDecimals: number = 0; 42 | public tokenYDecimals: number = 0; 43 | public tokenXSymbol: string = 'UnknownX'; 44 | public tokenYSymbol: string = 'UnknownY'; 45 | 46 | /** 47 | * 构造函数 48 | */ 49 | constructor(address: PublicKey, dlmmPool: any) { 50 | this.address = address; 51 | this.dlmmPool = dlmmPool; 52 | 53 | // 提取代币精度和符号 54 | this.tokenXDecimals = dlmmPool.tokenX.decimals || 9; 55 | this.tokenYDecimals = dlmmPool.tokenY.decimals || 6; 56 | 57 | // 使用代币地址的前6位作为符号标识 58 | if (dlmmPool.tokenX?.publicKey) { 59 | this.tokenXSymbol = dlmmPool.tokenX.publicKey.toString().slice(0, 6) + '...'; 60 | } 61 | if (dlmmPool.tokenY?.publicKey) { 62 | this.tokenYSymbol = dlmmPool.tokenY.publicKey.toString().slice(0, 6) + '...'; 63 | } 64 | 65 | // 初始化价格区间 66 | this.priceRange = { minPrice: 0, maxPrice: 0 }; 67 | } 68 | 69 | /** 70 | * 获取所有bin的ID 71 | */ 72 | public getAllBinIds(): number[] { 73 | let binIds: number[] = []; 74 | for (const position of this.positions) { 75 | binIds = [...binIds, ...position.binData.map(bin => bin.binId)]; 76 | } 77 | return binIds; 78 | } 79 | 80 | /** 81 | * 获取所有bin数据 82 | */ 83 | private getAllBins(): BinData[] { 84 | let bins: BinData[] = []; 85 | for (const position of this.positions) { 86 | bins = [...bins, ...position.binData]; 87 | } 88 | return bins; 89 | } 90 | 91 | /** 92 | * 获取格式化的bin范围字符串 93 | */ 94 | public getBinRangeString(): string { 95 | const binIds = this.getAllBinIds(); 96 | if (binIds.length === 0) return '无数据'; 97 | 98 | const minBinId = Math.min(...binIds); 99 | const maxBinId = Math.max(...binIds); 100 | 101 | return `${minBinId}-${maxBinId}`; 102 | } 103 | 104 | /** 105 | * 获取格式化的价格区间字符串 106 | * 直接从bin数据中获取价格 107 | */ 108 | public getPriceRangeString(): string { 109 | try { 110 | const allBins = this.getAllBins(); 111 | if (allBins.length === 0) return '无数据'; 112 | 113 | // 计算bin范围 114 | const binIds = allBins.map(bin => bin.binId); 115 | const minBinId = Math.min(...binIds); 116 | const maxBinId = Math.max(...binIds); 117 | 118 | // 从bin数据中找出最低和最高价格 119 | let minPriceRaw = ''; 120 | let maxPriceRaw = ''; 121 | 122 | // 从price字段获取价格 123 | for (const bin of allBins) { 124 | if (bin.price && (typeof bin.price === 'string' || typeof bin.price === 'number')) { 125 | if (bin.binId === minBinId) { 126 | minPriceRaw = bin.price.toString(); 127 | } 128 | if (bin.binId === maxBinId) { 129 | maxPriceRaw = bin.price.toString(); 130 | } 131 | } 132 | } 133 | 134 | // 如果无法从bin直接获取价格,返回未知 135 | if (!minPriceRaw || !maxPriceRaw) { 136 | return '价格数据获取中'; 137 | } 138 | 139 | // 转换为真实价格 140 | const minPrice = formatPrice(minPriceRaw, this.tokenXDecimals, this.tokenYDecimals); 141 | const maxPrice = formatPrice(maxPriceRaw, this.tokenXDecimals, this.tokenYDecimals); 142 | 143 | return `${minPrice}-${maxPrice}`; 144 | } catch (error) { 145 | logger.error('格式化价格失败:', error); 146 | return '价格计算中'; 147 | } 148 | } 149 | 150 | /** 151 | * 获取完整的价格范围信息(包含bin和价格) 152 | */ 153 | public getFullPriceRangeString(): string { 154 | return `Bin:${this.getBinRangeString()} 价格:${this.getPriceRangeString()}`; 155 | } 156 | 157 | /** 158 | * 判断当前价格是否在此池范围内 159 | */ 160 | public isPriceInRange(price: number): boolean { 161 | return price >= this.priceRange.minPrice && price <= this.priceRange.maxPrice; 162 | } 163 | 164 | /** 165 | * 判断bin是否在此池范围内 166 | */ 167 | public isBinInRange(binId: number): boolean { 168 | const binIds = this.getAllBinIds(); 169 | if (binIds.length === 0) return false; 170 | 171 | const minBinId = Math.min(...binIds); 172 | const maxBinId = Math.max(...binIds); 173 | return binId >= minBinId && binId <= maxBinId; 174 | } 175 | 176 | /** 177 | * 判断该池是否只包含X代币 178 | */ 179 | public hasOnlyTokenX(): boolean { 180 | if (this.positions.length === 0) return false; 181 | 182 | // 检查是否所有bin中只有X代币 183 | for (const position of this.positions) { 184 | for (const bin of position.binData) { 185 | if (bin.y > BigInt(0)) return false; 186 | } 187 | } 188 | 189 | return true; 190 | } 191 | 192 | /** 193 | * 判断该池是否只包含Y代币 194 | */ 195 | public hasOnlyTokenY(): boolean { 196 | if (this.positions.length === 0) return false; 197 | 198 | // 检查是否所有bin中只有Y代币 199 | for (const position of this.positions) { 200 | for (const bin of position.binData) { 201 | if (bin.x > BigInt(0)) return false; 202 | } 203 | } 204 | 205 | return true; 206 | } 207 | 208 | /** 209 | * 判断池是否符合BidAsk模型 210 | * 修改判断逻辑: 211 | * - 删除"只含单种代币"的严格条件 212 | * - 低于当前价格/bin的池子只检查Y代币分布 213 | * - 高于当前价格/bin的池子只检查X代币分布 214 | */ 215 | public isBidAskCompliant(isHigherThanCurrentPrice: boolean): boolean { 216 | logger.debug(`[池${this.address.toString().slice(0, 8)}] 开始BidAsk合规性检查,期望分布: ${isHigherThanCurrentPrice ? '升序' : '降序'}`); 217 | 218 | if (this.positions.length === 0) { 219 | logger.debug(`[池${this.address.toString().slice(0, 8)}] 无头寸,自动判定为符合`); 220 | return true; 221 | } 222 | 223 | // 按binId排序的代币数量数组 224 | let sortedBins: { binId: number; value: number }[] = []; 225 | 226 | // 根据池子位置选择要检查的代币类型 227 | if (isHigherThanCurrentPrice) { 228 | // 高于当前bin的池子应检查X代币分布 229 | logger.debug(`[池${this.address.toString().slice(0, 8)}] 检查X代币分布`); 230 | 231 | for (const position of this.positions) { 232 | for (const bin of position.binData) { 233 | // 跳过X代币数量为0的bin(可能是空的或未初始化的bin) 234 | if (bin.x === BigInt(0)) continue; 235 | 236 | const xAmount = parseFloat(formatAmount(bin.x.toString(), this.tokenXDecimals)); 237 | sortedBins.push({ binId: bin.binId, value: xAmount }); 238 | } 239 | } 240 | } else { 241 | // 低于当前bin的池子应检查Y代币分布 242 | logger.debug(`[池${this.address.toString().slice(0, 8)}] 检查Y代币分布`); 243 | 244 | for (const position of this.positions) { 245 | for (const bin of position.binData) { 246 | // 跳过Y代币数量为0的bin(可能是空的或未初始化的bin) 247 | if (bin.y === BigInt(0)) continue; 248 | 249 | const yAmount = parseFloat(formatAmount(bin.y.toString(), this.tokenYDecimals)); 250 | sortedBins.push({ binId: bin.binId, value: yAmount }); 251 | } 252 | } 253 | } 254 | 255 | // 如果没有有效的流动性数据,无法判断 256 | if (sortedBins.length === 0) { 257 | logger.debug(`[池${this.address.toString().slice(0, 8)}] 无有效流动性数据,自动判定为符合`); 258 | return true; 259 | } 260 | 261 | logger.debug(`[池${this.address.toString().slice(0, 8)}] 收集到${sortedBins.length}个有效流动性数据点`); 262 | 263 | // 聚合相同binId的流动性 264 | const aggregatedBins = new Map(); 265 | for (const bin of sortedBins) { 266 | const existingValue = aggregatedBins.get(bin.binId) || 0; 267 | aggregatedBins.set(bin.binId, existingValue + bin.value); 268 | } 269 | 270 | // 转换为数组并按binId排序(基于数学大小,而非绝对值) 271 | const sortedAggregatedBins = Array.from(aggregatedBins.entries()) 272 | .sort((a, b) => a[0] - b[0]); // 按binId从小到大排序 273 | 274 | logger.debug(`[池${this.address.toString().slice(0, 8)}] 聚合后有${sortedAggregatedBins.length}个不同的bin`); 275 | 276 | // 输出详细的bin分布情况 277 | const binDistribution = sortedAggregatedBins 278 | .map(([binId, value]) => `Bin${binId}:${value.toFixed(4)}`) 279 | .join(', '); 280 | logger.debug(`[池${this.address.toString().slice(0, 8)}] Bin分布: ${binDistribution}`); 281 | 282 | // 提取排序后的流动性值数组 283 | const values = sortedAggregatedBins.map(([_, value]) => value); 284 | 285 | // 判断分布是否符合预期(升序或降序) 286 | const result = validateBidAskModel(values, isHigherThanCurrentPrice); 287 | logger.debug(`[池${this.address.toString().slice(0, 8)}] BidAsk检查结果: ${result ? '符合' : '不符合'}`); 288 | 289 | return result; 290 | } 291 | 292 | /** 293 | * 获取池子总流动性 294 | */ 295 | public getTotalLiquidity(): { totalX: bigint; totalY: bigint } { 296 | let totalX = BigInt(0); 297 | let totalY = BigInt(0); 298 | 299 | for (const position of this.positions) { 300 | const { totalX: posX, totalY: posY } = position.getTotalLiquidity(); 301 | totalX += posX; 302 | totalY += posY; 303 | } 304 | 305 | return { totalX, totalY }; 306 | } 307 | 308 | /** 309 | * 获取格式化的代币数量字符串 310 | */ 311 | public getFormattedLiquidity(): { formattedX: string; formattedY: string } { 312 | const { totalX, totalY } = this.getTotalLiquidity(); 313 | 314 | const formattedX = formatAmount(totalX.toString(), this.tokenXDecimals); 315 | const formattedY = formatAmount(totalY.toString(), this.tokenYDecimals); 316 | 317 | return { formattedX, formattedY }; 318 | } 319 | 320 | /** 321 | * 获取池子和头寸的汇总信息 322 | */ 323 | public getSummaryInfo(): any { 324 | // 获取所有bin的ID 325 | const binIds = this.getAllBinIds(); 326 | if (binIds.length === 0) { 327 | return { address: this.address.toString(), positionCount: 0 }; 328 | } 329 | 330 | // 获取总流动性 331 | const { totalX, totalY } = this.getTotalLiquidity(); 332 | 333 | // bin范围 334 | const minBinId = Math.min(...binIds); 335 | const maxBinId = Math.max(...binIds); 336 | 337 | // 价格范围 - 直接使用getPriceRangeString获取格式化价格 338 | const priceRangeStr = this.getPriceRangeString(); 339 | 340 | return { 341 | address: this.address.toString(), 342 | positionCount: this.positions.length, 343 | binRange: { 344 | min: minBinId, 345 | max: maxBinId, 346 | count: binIds.length 347 | }, 348 | priceRange: { 349 | formatted: priceRangeStr 350 | }, 351 | liquidity: { 352 | x: { 353 | amount: totalX.toString(), 354 | formatted: formatAmount(totalX.toString(), this.tokenXDecimals), 355 | symbol: this.tokenXSymbol, 356 | decimals: this.tokenXDecimals 357 | }, 358 | y: { 359 | amount: totalY.toString(), 360 | formatted: formatAmount(totalY.toString(), this.tokenYDecimals), 361 | symbol: this.tokenYSymbol, 362 | decimals: this.tokenYDecimals 363 | } 364 | }, 365 | tokens: { 366 | x: { 367 | symbol: this.tokenXSymbol, 368 | decimals: this.tokenXDecimals, 369 | address: this.dlmmPool.tokenX?.publicKey?.toString() || 'unknown' 370 | }, 371 | y: { 372 | symbol: this.tokenYSymbol, 373 | decimals: this.tokenYDecimals, 374 | address: this.dlmmPool.tokenY?.publicKey?.toString() || 'unknown' 375 | } 376 | } 377 | }; 378 | } 379 | 380 | /** 381 | * 查找池子的所有头寸 382 | */ 383 | public async fetchPositions(owner: PublicKey): Promise { 384 | try { 385 | logger.debug(`获取池子${this.address.toString()}的头寸`); 386 | 387 | // 获取用户头寸列表 388 | const { userPositions } = await this.dlmmPool.getPositionsByUserAndLbPair(owner); 389 | logger.debug(`发现${userPositions.length}个头寸记录`); 390 | 391 | // 清空原有头寸 392 | this.positions = []; 393 | 394 | // 处理每个头寸 395 | for (const positionData of userPositions) { 396 | try { 397 | // 获取完整头寸信息 398 | const fullPosition = await withRetry( 399 | async () => await this.dlmmPool.getPosition(positionData.publicKey), 400 | `获取头寸详情(${positionData.publicKey.toString()})`, 401 | DLMM_CONFIG.MAX_POSITION_FETCH_RETRIES || 3 402 | ); 403 | 404 | if (fullPosition) { 405 | // 创建新的Position对象 406 | const position = new Position( 407 | positionData.publicKey, 408 | fullPosition, 409 | this.tokenXDecimals, 410 | this.tokenYDecimals 411 | ); 412 | 413 | // 只有当position成功解析出bin数据时才添加 414 | if (position.binData.length > 0) { 415 | this.positions.push(position); 416 | 417 | // 日志输出头寸信息 418 | const { formattedX, formattedY } = position.getFormattedLiquidity(); 419 | logger.info(`加载头寸 ${position.publicKey.toString().slice(0, 8)}...: ${position.binData.length}个bin, ${formattedX} ${this.tokenXSymbol}, ${formattedY} ${this.tokenYSymbol}`); 420 | 421 | // 更新价格范围 422 | this.updatePriceRangeFromPositions(); 423 | } else { 424 | logger.warn(`头寸${positionData.publicKey.toString()}没有可用的bin数据,跳过`); 425 | } 426 | } 427 | } catch (error) { 428 | logger.error(`获取头寸详情时出错: ${error instanceof Error ? error.message : String(error)}`); 429 | } 430 | } 431 | 432 | // 成功加载后打印汇总信息 433 | if (this.positions.length > 0) { 434 | const { totalX, totalY } = this.getTotalLiquidity(); 435 | logger.info( 436 | `成功加载${this.positions.length}个头寸,总流动性: ` + 437 | `${formatAmount(totalX.toString(), this.tokenXDecimals)} ${this.tokenXSymbol}, ` + 438 | `${formatAmount(totalY.toString(), this.tokenYDecimals)} ${this.tokenYSymbol}` 439 | ); 440 | } 441 | } catch (error) { 442 | logger.error(`获取池子头寸时出错: ${error instanceof Error ? error.message : String(error)}`); 443 | throw error; 444 | } 445 | } 446 | 447 | /** 448 | * 根据头寸更新价格范围 449 | * 简化版本,只使用最简单的方法 450 | */ 451 | private updatePriceRangeFromPositions(): void { 452 | const binIds = this.getAllBinIds(); 453 | if (binIds.length === 0) return; 454 | 455 | // 找出最小和最大bin ID 456 | const minBinId = Math.min(...binIds); 457 | const maxBinId = Math.max(...binIds); 458 | 459 | // 尝试从DLMM SDK获取价格信息 460 | let minPrice = 0; 461 | let maxPrice = 0; 462 | 463 | try { 464 | // 如果SDK支持通过binId获取价格 465 | if (typeof this.dlmmPool.getPriceFromBinId === 'function') { 466 | minPrice = this.dlmmPool.getPriceFromBinId(minBinId); 467 | maxPrice = this.dlmmPool.getPriceFromBinId(maxBinId); 468 | 469 | // 应用代币精度调整 470 | minPrice = calculateRealPrice(minPrice, this.tokenXDecimals, this.tokenYDecimals); 471 | maxPrice = calculateRealPrice(maxPrice, this.tokenXDecimals, this.tokenYDecimals); 472 | } 473 | // 如果不支持,直接使用bin ID代替 474 | else { 475 | minPrice = minBinId; 476 | maxPrice = maxBinId; 477 | } 478 | } catch (error) { 479 | // 发生错误时使用bin ID 480 | logger.warn(`计算价格范围出错: ${error}, 使用bin ID代替`); 481 | minPrice = minBinId; 482 | maxPrice = maxBinId; 483 | } 484 | 485 | this.priceRange.minPrice = minPrice; 486 | this.priceRange.maxPrice = maxPrice; 487 | } 488 | 489 | /** 490 | * 获取低于当前bin的相邻头寸 491 | * @param currentBinId 当前活跃bin ID 492 | */ 493 | public getLowerPosition(currentBinId: number): Position | undefined { 494 | if (this.positions.length === 0) return undefined; 495 | 496 | // 找到所有头寸的最低bin值 497 | const positionsWithRange = this.positions.map(position => { 498 | const range = position.getBinRange(); 499 | return { position, ...range }; 500 | }); 501 | 502 | // 过滤出包含的bin全部小于当前bin的头寸,并按最大binId降序排序 503 | const lowerPositions = positionsWithRange 504 | .filter(p => p.maxBinId < currentBinId) 505 | .sort((a, b) => b.maxBinId - a.maxBinId); 506 | 507 | // 返回最接近当前bin的头寸(即最大bin值最大的头寸) 508 | return lowerPositions.length > 0 ? lowerPositions[0].position : undefined; 509 | } 510 | 511 | /** 512 | * 获取高于当前bin的相邻头寸 513 | * @param currentBinId 当前活跃bin ID 514 | */ 515 | public getHigherPosition(currentBinId: number): Position | undefined { 516 | if (this.positions.length === 0) return undefined; 517 | 518 | // 找到所有头寸的最高bin值 519 | const positionsWithRange = this.positions.map(position => { 520 | const range = position.getBinRange(); 521 | return { position, ...range }; 522 | }); 523 | 524 | // 过滤出包含的bin全部大于当前bin的头寸,并按最小binId升序排序 525 | const higherPositions = positionsWithRange 526 | .filter(p => p.minBinId > currentBinId) 527 | .sort((a, b) => a.minBinId - b.minBinId); 528 | 529 | // 返回最接近当前bin的头寸(即最小bin值最小的头寸) 530 | return higherPositions.length > 0 ? higherPositions[0].position : undefined; 531 | } 532 | 533 | /** 534 | * 获取包含当前bin的头寸 535 | * @param currentBinId 当前活跃bin ID 536 | */ 537 | public getCurrentPosition(currentBinId: number): Position | undefined { 538 | return this.positions.find(position => position.isBinInRange(currentBinId)); 539 | } 540 | } 541 | 542 | /** 543 | * 头寸类 544 | * 表示单个流动性头寸 545 | */ 546 | export class Position { 547 | public publicKey: PublicKey; 548 | public data: any; // 原始头寸数据 549 | public binData: BinData[] = []; 550 | private tokenXDecimals: number; 551 | private tokenYDecimals: number; 552 | 553 | /** 554 | * 构造函数 555 | */ 556 | constructor( 557 | publicKey: PublicKey, 558 | data: any, 559 | tokenXDecimals: number, 560 | tokenYDecimals: number 561 | ) { 562 | this.publicKey = publicKey; 563 | this.data = data; 564 | this.tokenXDecimals = tokenXDecimals; 565 | this.tokenYDecimals = tokenYDecimals; 566 | 567 | // 初始化bin数据 568 | this.binData = this.extractBinData(data); 569 | } 570 | 571 | /** 572 | * 从头寸数据中提取bin数据 573 | * 简化版本,优先检查最常见的几种数据结构 574 | */ 575 | private extractBinData(data: any): BinData[] { 576 | // 首先检查标准的positionBinData 577 | if (data.positionBinData?.length > 0) { 578 | return this.mapBinData(data.positionBinData); 579 | } 580 | 581 | // 尝试其他常见属性名 582 | if (data.bins?.length > 0) return this.mapBinData(data.bins); 583 | if (data.binPositions?.length > 0) return this.mapBinData(data.binPositions); 584 | if (data.positionData?.binPositions?.length > 0) return this.mapBinData(data.positionData.binPositions); 585 | 586 | // 深度搜索 - 只在其他方法都失败时使用 587 | const foundBinData = this.findBinArrayInObject(data); 588 | if (foundBinData) { 589 | return this.mapBinData(foundBinData); 590 | } 591 | 592 | logger.warn(`无法找到任何有效的bin数据,头寸${this.publicKey.toString()}可能无效`); 593 | return []; 594 | } 595 | 596 | /** 597 | * 递归查找对象中符合bin数据结构的数组 598 | * 简化版本,只检查关键字段 599 | */ 600 | private findBinArrayInObject(obj: any): any[] | null { 601 | if (!obj || typeof obj !== 'object') return null; 602 | 603 | // 检查当前对象是否是bin数组 604 | if (Array.isArray(obj) && obj.length > 0) { 605 | const firstItem = obj[0]; 606 | // 只检查关键字段,减少条件复杂度 607 | if (firstItem && 608 | (firstItem.binId !== undefined || 609 | firstItem.index !== undefined) && 610 | ((firstItem.x !== undefined && firstItem.y !== undefined) || 611 | (firstItem.positionXAmount !== undefined && firstItem.positionYAmount !== undefined))) { 612 | return obj; 613 | } 614 | } 615 | 616 | // 递归检查所有子对象和数组 617 | for (const key in obj) { 618 | const result = this.findBinArrayInObject(obj[key]); 619 | if (result) return result; 620 | } 621 | 622 | return null; 623 | } 624 | 625 | /** 626 | * 映射bin数据到标准格式 627 | */ 628 | private mapBinData(binArray: any[]): BinData[] { 629 | if (!binArray || binArray.length === 0) return []; 630 | 631 | // 检查第一个元素以确定字段映射 632 | const sampleBin = binArray[0]; 633 | 634 | // 确定字段名 635 | const binIdField = sampleBin.binId !== undefined ? 'binId' : 636 | sampleBin.index !== undefined ? 'index' : null; 637 | 638 | // 如果无法确定binId字段,则返回空数组 639 | if (binIdField === null) { 640 | logger.warn('无法识别bin数据的binId字段'); 641 | return []; 642 | } 643 | 644 | const xField = sampleBin.positionXAmount !== undefined ? 'positionXAmount' : 645 | sampleBin.xAmount !== undefined ? 'xAmount' : 646 | sampleBin.binXAmount !== undefined ? 'binXAmount' : 647 | sampleBin.x !== undefined ? 'x' : null; 648 | 649 | const yField = sampleBin.positionYAmount !== undefined ? 'positionYAmount' : 650 | sampleBin.yAmount !== undefined ? 'yAmount' : 651 | sampleBin.binYAmount !== undefined ? 'binYAmount' : 652 | sampleBin.y !== undefined ? 'y' : null; 653 | 654 | // 如果无法确定x或y字段,则返回空数组 655 | if (xField === null || yField === null) { 656 | logger.warn('无法识别bin数据的x或y字段'); 657 | return []; 658 | } 659 | 660 | const priceField = sampleBin.price !== undefined ? 'price' : 661 | sampleBin.rawPrice !== undefined ? 'rawPrice' : 662 | sampleBin.binPrice !== undefined ? 'binPrice' : null; 663 | 664 | // 映射数组 665 | return binArray.map(bin => { 666 | try { 667 | const binId = typeof bin[binIdField] === 'number' ? bin[binIdField] : 0; 668 | const x = bin[xField] ? BigInt(bin[xField].toString()) : BigInt(0); 669 | const y = bin[yField] ? BigInt(bin[yField].toString()) : BigInt(0); 670 | const price = priceField && bin[priceField] !== undefined ? bin[priceField] : null; 671 | 672 | return { binId, x, y, price }; 673 | } catch (error) { 674 | logger.warn(`映射bin数据时出错: ${error}`); 675 | return { binId: 0, x: BigInt(0), y: BigInt(0) }; 676 | } 677 | }); 678 | } 679 | 680 | /** 681 | * 获取头寸总流动性 682 | */ 683 | public getTotalLiquidity(): { totalX: bigint; totalY: bigint } { 684 | let totalX = BigInt(0); 685 | let totalY = BigInt(0); 686 | 687 | for (const bin of this.binData) { 688 | totalX += bin.x; 689 | totalY += bin.y; 690 | } 691 | 692 | return { totalX, totalY }; 693 | } 694 | 695 | /** 696 | * 获取格式化的代币数量字符串 697 | */ 698 | public getFormattedLiquidity(): { formattedX: string; formattedY: string } { 699 | const { totalX, totalY } = this.getTotalLiquidity(); 700 | 701 | const formattedX = formatAmount(totalX.toString(), this.tokenXDecimals); 702 | const formattedY = formatAmount(totalY.toString(), this.tokenYDecimals); 703 | 704 | return { formattedX, formattedY }; 705 | } 706 | 707 | /** 708 | * 获取头寸的价格范围字符串 709 | */ 710 | public getPriceRangeString(): string { 711 | try { 712 | if (this.binData.length === 0) return '无数据'; 713 | 714 | // 计算bin范围 715 | const binIds = this.binData.map(bin => bin.binId); 716 | const minBinId = Math.min(...binIds); 717 | const maxBinId = Math.max(...binIds); 718 | 719 | // 从bin数据中找出最低和最高价格 720 | let minPriceRaw = ''; 721 | let maxPriceRaw = ''; 722 | 723 | // 从price字段获取价格 724 | for (const bin of this.binData) { 725 | if (bin.price && (typeof bin.price === 'string' || typeof bin.price === 'number')) { 726 | if (bin.binId === minBinId) { 727 | minPriceRaw = bin.price.toString(); 728 | } 729 | if (bin.binId === maxBinId) { 730 | maxPriceRaw = bin.price.toString(); 731 | } 732 | } 733 | } 734 | 735 | // 如果无法从bin直接获取价格,返回未知 736 | if (!minPriceRaw || !maxPriceRaw) { 737 | return '价格数据获取中'; 738 | } 739 | 740 | // 转换为真实价格 741 | const minPrice = formatPrice(minPriceRaw, this.tokenXDecimals, this.tokenYDecimals); 742 | const maxPrice = formatPrice(maxPriceRaw, this.tokenXDecimals, this.tokenYDecimals); 743 | 744 | return `${minPrice}-${maxPrice}`; 745 | } catch (error) { 746 | logger.error('头寸格式化价格失败:', error); 747 | return '价格计算中'; 748 | } 749 | } 750 | 751 | /** 752 | * 获取头寸的bin范围 753 | */ 754 | public getBinRange(): { minBinId: number, maxBinId: number } { 755 | if (this.binData.length === 0) { 756 | return { minBinId: 0, maxBinId: 0 }; 757 | } 758 | 759 | const binIds = this.binData.map(bin => bin.binId); 760 | return { 761 | minBinId: Math.min(...binIds), 762 | maxBinId: Math.max(...binIds) 763 | }; 764 | } 765 | 766 | /** 767 | * 判断bin是否在该头寸范围内 768 | */ 769 | public isBinInRange(binId: number): boolean { 770 | if (this.binData.length === 0) return false; 771 | 772 | const { minBinId, maxBinId } = this.getBinRange(); 773 | return binId >= minBinId && binId <= maxBinId; 774 | } 775 | 776 | /** 777 | * 判断头寸是否符合BidAsk模型 778 | * @param isHigherThanCurrentBin 头寸是否高于当前bin 779 | */ 780 | public isBidAskCompliant(isHigherThanCurrentBin: boolean): boolean { 781 | if (this.binData.length === 0) return true; 782 | 783 | // 按binId排序的代币数量数组 784 | let sortedBins: { binId: number; value: number }[] = []; 785 | 786 | // 根据头寸位置选择要检查的代币类型 787 | if (isHigherThanCurrentBin) { 788 | // 高于当前bin的头寸应检查X代币分布 789 | for (const bin of this.binData) { 790 | // 跳过X代币数量为0的bin 791 | if (bin.x === BigInt(0)) continue; 792 | 793 | const xAmount = parseFloat(formatAmount(bin.x.toString(), this.tokenXDecimals)); 794 | sortedBins.push({ binId: bin.binId, value: xAmount }); 795 | } 796 | } else { 797 | // 低于当前bin的头寸应检查Y代币分布 798 | for (const bin of this.binData) { 799 | // 跳过Y代币数量为0的bin 800 | if (bin.y === BigInt(0)) continue; 801 | 802 | const yAmount = parseFloat(formatAmount(bin.y.toString(), this.tokenYDecimals)); 803 | sortedBins.push({ binId: bin.binId, value: yAmount }); 804 | } 805 | } 806 | 807 | // 如果没有有效的流动性数据,无法判断 808 | if (sortedBins.length === 0) return true; 809 | 810 | // 按binId排序 811 | sortedBins.sort((a, b) => a.binId - b.binId); // 按binId从小到大排序 812 | 813 | // 提取排序后的流动性值数组 814 | const values = sortedBins.map(bin => bin.value); 815 | 816 | // 判断分布是否符合预期(升序或降序) 817 | return validateBidAskModel(values, isHigherThanCurrentBin); 818 | } 819 | } 820 | 821 | /** 822 | * 串联池链类 823 | * 管理串联的池子集合 824 | */ 825 | export class PoolChain { 826 | private pools: Pool[] = []; 827 | private currentPrice: number = 0; 828 | private currentBinId: number = 0; 829 | 830 | /** 831 | * 添加池子 832 | */ 833 | public addPool(pool: Pool): void { 834 | this.pools.push(pool); 835 | this.sortPools(); 836 | } 837 | 838 | /** 839 | * 按价格范围排序池子 840 | */ 841 | private sortPools(): void { 842 | // 获取每个池子的最小bin ID用于排序 843 | this.pools.sort((a, b) => { 844 | const aBinIds = a.getAllBinIds(); 845 | const bBinIds = b.getAllBinIds(); 846 | 847 | if (aBinIds.length === 0 || bBinIds.length === 0) return 0; 848 | 849 | const aMinBin = Math.min(...aBinIds); 850 | const bMinBin = Math.min(...bBinIds); 851 | 852 | return aMinBin - bMinBin; 853 | }); 854 | } 855 | 856 | /** 857 | * 获取所有池子 858 | */ 859 | public getAllPools(): Pool[] { 860 | return this.pools; 861 | } 862 | 863 | /** 864 | * 根据地址获取池子 865 | */ 866 | public getPoolByAddress(address: string): Pool | undefined { 867 | return this.pools.find(pool => pool.address.toString() === address); 868 | } 869 | 870 | /** 871 | * 更新当前价格 872 | */ 873 | public updateCurrentPrice(price: number): void { 874 | this.currentPrice = price; 875 | } 876 | 877 | /** 878 | * 更新当前活跃bin ID 879 | */ 880 | public updateCurrentBinId(binId: number): void { 881 | this.currentBinId = binId; 882 | } 883 | 884 | /** 885 | * 获取当前活跃bin所在的池子 886 | */ 887 | public getCurrentPool(): Pool | undefined { 888 | return this.pools.find(pool => pool.isBinInRange(this.currentBinId)); 889 | } 890 | 891 | /** 892 | * 获取低于当前活跃bin的相邻池子 893 | */ 894 | public getLowerPool(): Pool | undefined { 895 | const currentPool = this.getCurrentPool(); 896 | if (!currentPool) return undefined; 897 | 898 | const currentIndex = this.pools.indexOf(currentPool); 899 | if (currentIndex <= 0) return undefined; 900 | 901 | return this.pools[currentIndex - 1]; 902 | } 903 | 904 | /** 905 | * 获取高于当前活跃bin的相邻池子 906 | */ 907 | public getHigherPool(): Pool | undefined { 908 | const currentPool = this.getCurrentPool(); 909 | if (!currentPool) return undefined; 910 | 911 | const currentIndex = this.pools.indexOf(currentPool); 912 | if (currentIndex >= this.pools.length - 1) return undefined; 913 | 914 | return this.pools[currentIndex + 1]; 915 | } 916 | 917 | /** 918 | * 检查相邻头寸是否符合BidAsk模型 919 | */ 920 | public checkNeighboringPoolsCompliance(): { 921 | lowerPosition?: { position: Position; isCompliant: boolean }; 922 | currentPosition?: { position: Position; isCompliant: boolean }; 923 | higherPosition?: { position: Position; isCompliant: boolean }; 924 | } { 925 | const result: any = {}; 926 | 927 | logger.debug(`===== 开始检查相邻头寸BidAsk合规性 =====`); 928 | logger.debug(`当前活跃Bin: ${this.currentBinId}`); 929 | 930 | const currentPool = this.getCurrentPool(); 931 | if (!currentPool) { 932 | logger.debug(`未找到包含当前bin的池子`); 933 | return result; 934 | } 935 | 936 | // 获取当前池子内的相邻头寸 937 | const lowerPosition = currentPool.getLowerPosition(this.currentBinId); 938 | const currentPosition = currentPool.getCurrentPosition(this.currentBinId); 939 | const higherPosition = currentPool.getHigherPosition(this.currentBinId); 940 | 941 | logger.debug(`找到的头寸: 当前池=${currentPool.address.toString().slice(0, 8)}, ` + 942 | `当前头寸=${currentPosition ? currentPosition.publicKey.toString().slice(0, 8) : '无'}, ` + 943 | `低头寸=${lowerPosition ? lowerPosition.publicKey.toString().slice(0, 8) : '无'}, ` + 944 | `高头寸=${higherPosition ? higherPosition.publicKey.toString().slice(0, 8) : '无'}`); 945 | 946 | if (lowerPosition) { 947 | // 获取bin范围 948 | const range = lowerPosition.getBinRange(); 949 | 950 | logger.debug(`低头寸Bin范围: ${range.minBinId} - ${range.maxBinId}`); 951 | 952 | // 低于当前bin的头寸应该是降序分布 953 | const isCompliant = lowerPosition.isBidAskCompliant(false); 954 | logger.debug(`低头寸合规性检查: 分布应为降序, 结果=${isCompliant ? '符合' : '不符合'}`); 955 | 956 | result.lowerPosition = { position: lowerPosition, isCompliant }; 957 | } else { 958 | logger.debug(`未找到低于当前bin的相邻头寸`); 959 | } 960 | 961 | if (currentPosition) { 962 | // 获取bin范围 963 | const range = currentPosition.getBinRange(); 964 | const midBinId = Math.floor((range.minBinId + range.maxBinId) / 2); 965 | 966 | logger.debug(`当前头寸Bin范围: ${range.minBinId} - ${range.maxBinId}, 中间Bin: ${midBinId}`); 967 | 968 | // 当前头寸的合规性取决于bin在头寸中的位置 969 | const isHigherHalf = this.currentBinId >= midBinId; 970 | 971 | logger.debug(`当前bin ${this.currentBinId} ${isHigherHalf ? '>=' : '<'} 中间bin ${midBinId}, ` + 972 | `应为${isHigherHalf ? '升序' : '降序'}分布`); 973 | 974 | const isCompliant = currentPosition.isBidAskCompliant(isHigherHalf); 975 | logger.debug(`当前头寸合规性检查: 分布应为${isHigherHalf ? '升序' : '降序'}, 结果=${isCompliant ? '符合' : '不符合'}`); 976 | 977 | result.currentPosition = { position: currentPosition, isCompliant }; 978 | } else { 979 | logger.debug(`未找到包含当前bin的头寸`); 980 | } 981 | 982 | if (higherPosition) { 983 | // 获取bin范围 984 | const range = higherPosition.getBinRange(); 985 | 986 | logger.debug(`高头寸Bin范围: ${range.minBinId} - ${range.maxBinId}`); 987 | 988 | // 高于当前bin的头寸应该是升序分布 989 | const isCompliant = higherPosition.isBidAskCompliant(true); 990 | logger.debug(`高头寸合规性检查: 分布应为升序, 结果=${isCompliant ? '符合' : '不符合'}`); 991 | 992 | result.higherPosition = { position: higherPosition, isCompliant }; 993 | } else { 994 | logger.debug(`未找到高于当前bin的相邻头寸`); 995 | } 996 | 997 | logger.debug(`===== 相邻头寸检查完成 =====`); 998 | 999 | return result; 1000 | } 1001 | } -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/src/services.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DLMM串联池流动性管理脚本 - 服务功能 3 | * 4 | * 整合所有核心业务逻辑,包括连接管理、钱包服务、池子发现、价格监控和流动性调整 5 | */ 6 | 7 | import { 8 | Connection, 9 | Keypair, 10 | PublicKey, 11 | Transaction, 12 | sendAndConfirmTransaction, 13 | Commitment, 14 | ComputeBudgetProgram, 15 | SystemProgram 16 | } from '@solana/web3.js'; 17 | import * as DLMMSdk from '@meteora-ag/dlmm'; // 导入DLMM SDK 18 | import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes'; 19 | import BN from 'bn.js'; // 导入BN库 20 | 21 | import { 22 | CONNECTION_CONFIG, 23 | WALLET_CONFIG, 24 | MONITOR_CONFIG, 25 | ADJUSTMENT_CONFIG, 26 | DLMM_CONFIG, 27 | TRANSACTION_CONFIG 28 | } from './config'; 29 | import { logger } from './logger'; 30 | import { sleep, withRetry, formatAmount, calculateRealPrice } from './utils'; 31 | import { Pool, PoolChain, Position } from './models'; 32 | import { display, PoolDisplayData, PoolStatus } from './display'; 33 | 34 | /** 35 | * 连接服务 36 | * 管理与Solana区块链的连接 37 | */ 38 | export class ConnectionService { 39 | private connection: Connection; 40 | private isConnected: boolean = false; 41 | 42 | /** 43 | * 构造函数 44 | */ 45 | constructor() { 46 | this.connection = new Connection( 47 | CONNECTION_CONFIG.RPC_ENDPOINT, 48 | { 49 | commitment: CONNECTION_CONFIG.CONNECTION_OPTIONS.commitment as Commitment, 50 | disableRetryOnRateLimit: CONNECTION_CONFIG.CONNECTION_OPTIONS.disableRetryOnRateLimit, 51 | confirmTransactionInitialTimeout: CONNECTION_CONFIG.CONNECTION_OPTIONS.confirmTransactionInitialTimeout 52 | } 53 | ); 54 | } 55 | 56 | /** 57 | * 初始化连接 58 | */ 59 | public async initialize(): Promise { 60 | try { 61 | logger.info(`连接到Solana网络: ${CONNECTION_CONFIG.RPC_ENDPOINT}`); 62 | 63 | // 检查连接是否正常工作 64 | await this.checkConnection(); 65 | this.isConnected = true; 66 | logger.info('成功连接到Solana网络'); 67 | } catch (error) { 68 | this.isConnected = false; 69 | logger.error(`连接到Solana网络失败: ${error instanceof Error ? error.message : String(error)}`); 70 | throw error; 71 | } 72 | } 73 | 74 | /** 75 | * 检查连接是否正常 76 | */ 77 | private async checkConnection(): Promise { 78 | try { 79 | // 获取最新区块高度验证连接 80 | const slot = await this.connection.getSlot(); 81 | if (!slot || slot <= 0) { 82 | throw new Error('获取的区块高度无效'); 83 | } 84 | 85 | logger.debug(`连接正常,当前区块高度: ${slot}`); 86 | } catch (error) { 87 | logger.error(`连接检查失败: ${error instanceof Error ? error.message : String(error)}`); 88 | this.isConnected = false; 89 | throw error; 90 | } 91 | } 92 | 93 | /** 94 | * 确保连接可用 95 | */ 96 | public async ensureConnected(): Promise { 97 | if (!this.isConnected) { 98 | await this.initialize(); 99 | return; 100 | } 101 | 102 | try { 103 | // 检查连接是否仍然可用 104 | await this.checkConnection(); 105 | } catch (error) { 106 | logger.warn(`连接已断开,尝试重新连接: ${error instanceof Error ? error.message : String(error)}`); 107 | this.isConnected = false; 108 | await this.initialize(); 109 | } 110 | } 111 | 112 | /** 113 | * 获取连接实例 114 | */ 115 | public getConnection(): Connection { 116 | if (!this.isConnected) { 117 | logger.warn('尝试获取未初始化的连接,将自动重新连接'); 118 | this.initialize().catch(error => { 119 | logger.error(`重新连接失败: ${error instanceof Error ? error.message : String(error)}`); 120 | }); 121 | } 122 | return this.connection; 123 | } 124 | } 125 | 126 | /** 127 | * 钱包服务 128 | * 管理用户钱包和交易签名 129 | */ 130 | export class WalletService { 131 | private wallet: Keypair; 132 | private connectionService: ConnectionService; 133 | 134 | /** 135 | * 构造函数 136 | */ 137 | constructor(connectionService: ConnectionService) { 138 | this.connectionService = connectionService; 139 | 140 | // 初始化钱包(实际的初始化将在init方法中完成) 141 | this.wallet = null as unknown as Keypair; 142 | } 143 | 144 | /** 145 | * 初始化钱包 146 | * 如果存在加密的私钥文件,则从加密文件加载私钥 147 | * 否则从配置文件加载私钥 148 | */ 149 | public async init(): Promise { 150 | try { 151 | logger.info('开始初始化钱包...'); 152 | 153 | // 尝试从加密文件加载私钥 154 | try { 155 | logger.debug('尝试加载加密模块...'); 156 | 157 | // 导入crypto模块 - 使用绝对路径 158 | const path = await import('path'); 159 | const projectRoot = path.resolve(process.cwd()); 160 | logger.debug(`项目根目录: ${projectRoot}`); 161 | 162 | // 使用绝对路径导入加密模块 163 | const cryptoPath = path.join(projectRoot, 'dist', 'utils', 'crypto.js'); 164 | logger.debug(`加密模块路径: ${cryptoPath}`); 165 | 166 | // 检查文件是否存在 167 | const fs = await import('fs'); 168 | if (!fs.existsSync(cryptoPath)) { 169 | throw new Error(`找不到加密模块文件: ${cryptoPath}`); 170 | } 171 | 172 | // 导入加密模块 173 | const cryptoUtils = await import(cryptoPath); 174 | 175 | // 获取加密文件路径(这将在多个位置查找) 176 | const encryptedKeyPath = cryptoUtils.getEncryptedKeyPath(); 177 | logger.debug(`加密私钥路径: ${encryptedKeyPath}`); 178 | 179 | // 检查加密私钥文件是否存在 180 | if (fs.existsSync(encryptedKeyPath)) { 181 | logger.info(`找到加密的私钥文件: ${encryptedKeyPath}`); 182 | // 从用户获取密码并解密私钥 183 | try { 184 | logger.debug('提示用户输入密码...'); 185 | const privateKeyBase58 = await cryptoUtils.loadAndDecryptPrivateKey(undefined, encryptedKeyPath); 186 | logger.debug('私钥解密成功,创建钱包...'); 187 | this.wallet = Keypair.fromSecretKey(bs58.decode(privateKeyBase58)); 188 | logger.info('成功从加密文件加载私钥'); 189 | 190 | // 成功加载私钥后,直接返回 191 | logger.info(`钱包地址: ${this.getAddress()}`); 192 | return; 193 | } catch (error) { 194 | logger.error(`解密私钥失败: ${error instanceof Error ? error.message : String(error)}`); 195 | // 如果解密失败,提示用户确认是否使用配置文件中的私钥 196 | const readline = await import('readline'); 197 | const rl = readline.createInterface({ 198 | input: process.stdin, 199 | output: process.stdout 200 | }); 201 | const answer = await new Promise((resolve) => { 202 | rl.question('解密私钥失败,是否使用配置文件中的私钥?(y/n): ', resolve); 203 | }); 204 | rl.close(); 205 | 206 | if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { 207 | // 从配置文件加载私钥 208 | this.loadPrivateKeyFromConfig(); 209 | logger.info(`钱包地址: ${this.getAddress()}`); 210 | return; 211 | } else { 212 | // 用户不想使用配置文件中的私钥,抛出错误 213 | throw new Error('无法加载私钥,程序无法继续'); 214 | } 215 | } 216 | } else { 217 | // 如果加密文件不存在,从配置文件加载私钥 218 | logger.info(`未检测到加密的私钥文件: ${encryptedKeyPath}`); 219 | this.loadPrivateKeyFromConfig(); 220 | logger.info(`钱包地址: ${this.getAddress()}`); 221 | return; 222 | } 223 | } catch (error) { 224 | // 如果尝试使用加密文件加载失败,回退到使用配置文件 225 | logger.warn(`尝试使用加密文件加载私钥失败: ${error instanceof Error ? error.message : String(error)}`); 226 | logger.info('回退到使用配置文件加载私钥'); 227 | this.loadPrivateKeyFromConfig(); 228 | logger.info(`钱包地址: ${this.getAddress()}`); 229 | } 230 | } catch (error) { 231 | const message = error instanceof Error ? error.message : String(error); 232 | logger.error(`钱包初始化失败: ${message}`); 233 | throw new Error(`钱包初始化失败: ${message}`); 234 | } 235 | } 236 | 237 | /** 238 | * 从配置文件加载私钥 239 | */ 240 | private loadPrivateKeyFromConfig(): void { 241 | const privateKeyBase58 = WALLET_CONFIG.PRIVATE_KEY; 242 | // 检查私钥是否存在且非空 243 | if (!privateKeyBase58 || privateKeyBase58.trim() === '') { 244 | throw new Error('配置文件中未设置私钥,且未找到加密的私钥文件。请使用encrypt-key工具生成加密私钥文件,或在配置中设置PRIVATE_KEY'); 245 | } 246 | 247 | try { 248 | this.wallet = Keypair.fromSecretKey(bs58.decode(privateKeyBase58)); 249 | logger.info('从配置文件加载私钥成功'); 250 | } catch (error) { 251 | const message = error instanceof Error ? error.message : String(error); 252 | throw new Error(`解析配置文件中的私钥失败: ${message}`); 253 | } 254 | } 255 | 256 | /** 257 | * 获取钱包公钥 258 | */ 259 | public getPublicKey(): PublicKey { 260 | return this.wallet.publicKey; 261 | } 262 | 263 | /** 264 | * 获取钱包密钥对 265 | */ 266 | public getKeypair(): Keypair { 267 | return this.wallet; 268 | } 269 | 270 | /** 271 | * 获取钱包地址字符串 272 | */ 273 | public getAddress(): string { 274 | return this.wallet.publicKey.toString(); 275 | } 276 | 277 | /** 278 | * 获取SOL余额 279 | */ 280 | public async getSolBalance(): Promise { 281 | const connection = this.connectionService.getConnection(); 282 | const balance = await connection.getBalance(this.wallet.publicKey); 283 | return balance / 1e9; // 转换为SOL 284 | } 285 | 286 | /** 287 | * 添加优先级费用到交易 288 | */ 289 | public addPriorityFee(transaction: Transaction): Transaction { 290 | if (!TRANSACTION_CONFIG.ENABLE_PRIORITY_FEE) { 291 | return transaction; 292 | } 293 | 294 | // 添加优先级费用 295 | const priorityFee = ComputeBudgetProgram.setComputeUnitPrice({ 296 | microLamports: TRANSACTION_CONFIG.PRIORITY_FEE_MICROLAMPORTS 297 | }); 298 | transaction.add(priorityFee); 299 | 300 | // 如果配置了计算单元限制,也添加 301 | if (TRANSACTION_CONFIG.AUTO_COMPUTE_UNIT_LIMIT) { 302 | const computeUnitLimit = ComputeBudgetProgram.setComputeUnitLimit({ 303 | units: TRANSACTION_CONFIG.COMPUTE_UNIT_LIMIT 304 | }); 305 | transaction.add(computeUnitLimit); 306 | } 307 | 308 | return transaction; 309 | } 310 | 311 | /** 312 | * 签名并发送交易 313 | */ 314 | public async signAndSendTransaction(transaction: Transaction): Promise { 315 | const connection = this.connectionService.getConnection(); 316 | try { 317 | // 添加优先级费用 318 | transaction = this.addPriorityFee(transaction); 319 | 320 | // 获取最新的blockhash 321 | const { blockhash } = await connection.getLatestBlockhash(); 322 | transaction.recentBlockhash = blockhash; 323 | transaction.feePayer = this.wallet.publicKey; 324 | 325 | // 签名并发送交易 326 | const signature = await sendAndConfirmTransaction( 327 | connection, 328 | transaction, 329 | [this.wallet], 330 | { 331 | commitment: 'confirmed', 332 | maxRetries: TRANSACTION_CONFIG.TRANSACTION_RETRY.MAX_RETRIES, 333 | skipPreflight: false 334 | } 335 | ); 336 | 337 | logger.info(`交易发送成功,签名: ${signature}`); 338 | return signature; 339 | } catch (error) { 340 | logger.error(`交易失败: ${error instanceof Error ? error.message : String(error)}`); 341 | throw error; 342 | } 343 | } 344 | } 345 | 346 | /** 347 | * 池子发现服务 348 | * 发现用户创建的所有池子 349 | */ 350 | export class PoolDiscoveryService { 351 | private connectionService: ConnectionService; 352 | private walletService: WalletService; 353 | private poolChain: PoolChain; 354 | 355 | /** 356 | * 构造函数 357 | */ 358 | constructor( 359 | connectionService: ConnectionService, 360 | walletService: WalletService 361 | ) { 362 | this.connectionService = connectionService; 363 | this.walletService = walletService; 364 | this.poolChain = new PoolChain(); 365 | } 366 | 367 | /** 368 | * 发现并加载所有池子 369 | */ 370 | public async discoverPools(): Promise { 371 | try { 372 | await this.connectionService.ensureConnected(); 373 | const connection = this.connectionService.getConnection(); 374 | const userPublicKey = this.walletService.getPublicKey(); 375 | 376 | logger.info(`开始搜索用户池子和头寸: ${userPublicKey.toString()}`); 377 | 378 | // 使用指定池地址 379 | if (DLMM_CONFIG && DLMM_CONFIG.POOL_ADDRESSES && DLMM_CONFIG.POOL_ADDRESSES.length > 0) { 380 | logger.info(`使用预设池地址搜索用户头寸,共${DLMM_CONFIG.POOL_ADDRESSES.length}个地址`); 381 | 382 | for (const poolAddressStr of DLMM_CONFIG.POOL_ADDRESSES) { 383 | try { 384 | const poolAddress = new PublicKey(poolAddressStr); 385 | logger.debug(`连接到DLMM池: ${poolAddress.toString()}`); 386 | 387 | // 创建DLMM实例 388 | const dlmmPool = await withRetry( 389 | async () => await (DLMMSdk as any).default.create(connection, poolAddress), 390 | `创建DLMM实例(${poolAddress.toString()})`, 391 | DLMM_CONFIG.MAX_POOL_FETCH_RETRIES || 3 392 | ); 393 | 394 | // 获取用户在该池子中的头寸 395 | const { userPositions } = await withRetry( 396 | async () => await dlmmPool.getPositionsByUserAndLbPair(userPublicKey), 397 | `获取用户头寸(${poolAddress.toString()})`, 398 | DLMM_CONFIG.MAX_POSITION_FETCH_RETRIES || 3 399 | ); 400 | 401 | if (userPositions.length === 0) { 402 | logger.info(`池子 ${poolAddress.toString()} 没有用户头寸,跳过`); 403 | continue; 404 | } 405 | 406 | logger.info(`在池子 ${poolAddress.toString()} 中找到 ${userPositions.length} 个用户头寸`); 407 | 408 | // 创建Pool对象并加载头寸 409 | const pool = new Pool(poolAddress, dlmmPool); 410 | await pool.fetchPositions(userPublicKey); 411 | 412 | if (pool.positions.length === 0) { 413 | logger.warn(`加载池子 ${poolAddress.toString()} 的头寸失败,无法获取头寸详情`); 414 | continue; 415 | } 416 | 417 | // 添加到池子链中 418 | this.poolChain.addPool(pool); 419 | 420 | // 打印池子和头寸汇总信息 421 | this.logPoolSummary(pool); 422 | } catch (error) { 423 | logger.error(`加载池子 ${poolAddressStr} 失败: ${error instanceof Error ? error.message : String(error)}`); 424 | } 425 | } 426 | } 427 | 428 | // 检查是否找到任何池子 429 | if (this.poolChain.getAllPools().length === 0) { 430 | logger.warn('未找到任何包含用户头寸的池子'); 431 | display.updateStatusMessage('未找到用户池子'); 432 | } else { 433 | logger.info(`发现用户池子总数: ${this.poolChain.getAllPools().length}`); 434 | display.updateStatusMessage(`找到 ${this.poolChain.getAllPools().length} 个用户池子`); 435 | 436 | // 更新显示 437 | this.updatePoolsDisplay(); 438 | } 439 | 440 | return this.poolChain; 441 | } catch (error) { 442 | logger.error(`发现池子时出错: ${error instanceof Error ? error.message : String(error)}`); 443 | display.updateStatusMessage(`发现池子失败: ${error instanceof Error ? error.message : String(error)}`); 444 | return this.poolChain; 445 | } 446 | } 447 | 448 | /** 449 | * 记录池子摘要信息 450 | */ 451 | private logPoolSummary(pool: Pool): void { 452 | const summary = pool.getSummaryInfo(); 453 | logger.info(`========== 池子和头寸汇总信息 ==========`); 454 | logger.info(`池子地址: ${summary.address}`); 455 | logger.info(`代币: ${summary.tokens.x.symbol}/${summary.tokens.y.symbol}`); 456 | logger.info(`头寸数量: ${summary.positionCount}`); 457 | logger.info(`Bin范围: ${summary.binRange.min} 至 ${summary.binRange.max} (共${summary.binRange.count}个bin)`); 458 | logger.info(`价格范围: ${summary.priceRange.formatted}`); 459 | logger.info(`总流动性: ${summary.liquidity.x.formatted} ${summary.tokens.x.symbol}, ${summary.liquidity.y.formatted} ${summary.tokens.y.symbol}`); 460 | logger.info(`=========================================`); 461 | } 462 | 463 | /** 464 | * 更新池子显示 465 | */ 466 | public updatePoolsDisplay(): void { 467 | const pools = this.poolChain.getAllPools(); 468 | 469 | const poolsData: PoolDisplayData[] = []; 470 | 471 | // 遍历每个池子 472 | for (const pool of pools) { 473 | if (pool.positions.length === 0) { 474 | // 如果没有头寸,仍显示池子 475 | const { formattedX, formattedY } = pool.getFormattedLiquidity(); 476 | 477 | poolsData.push({ 478 | address: pool.address.toString(), 479 | binRange: pool.getBinRangeString(), 480 | priceRange: pool.getPriceRangeString(), 481 | tokenX: formattedX + ' ' + pool.tokenXSymbol, 482 | tokenY: formattedY + ' ' + pool.tokenYSymbol, 483 | status: PoolStatus.NORMAL 484 | }); 485 | } else { 486 | // 如果有头寸,为每个头寸创建一行 487 | // 获取每个头寸的bin范围,并计算最大bin值用于排序 488 | const positionsWithBin = pool.positions.map(position => { 489 | const binIds = position.binData.map(bin => bin.binId); 490 | const minBinId = Math.min(...binIds); 491 | const maxBinId = Math.max(...binIds); 492 | return { 493 | position, 494 | minBinId, 495 | maxBinId 496 | }; 497 | }); 498 | 499 | // 按bin的最大值从大到小排序 500 | positionsWithBin.sort((a, b) => b.maxBinId - a.maxBinId); 501 | 502 | // 为每个排序后的头寸创建一行 503 | for (const { position, minBinId, maxBinId } of positionsWithBin) { 504 | const binRangeStr = `${minBinId}-${maxBinId}`; 505 | 506 | // 使用头寸的价格范围 507 | const priceRangeStr = position.getPriceRangeString(); 508 | 509 | // 获取该头寸的流动性 510 | const { formattedX, formattedY } = position.getFormattedLiquidity(); 511 | 512 | poolsData.push({ 513 | address: pool.address.toString(), 514 | binRange: binRangeStr, 515 | priceRange: priceRangeStr, 516 | tokenX: formattedX + ' ' + pool.tokenXSymbol, 517 | tokenY: formattedY + ' ' + pool.tokenYSymbol, 518 | status: PoolStatus.NORMAL, 519 | positionId: position.publicKey.toString().slice(0, 8) + '...' 520 | }); 521 | } 522 | } 523 | } 524 | 525 | display.updatePoolsData(poolsData); 526 | display.render(); 527 | } 528 | 529 | /** 530 | * 获取池子链 531 | */ 532 | public getPoolChain(): PoolChain { 533 | return this.poolChain; 534 | } 535 | } 536 | 537 | /** 538 | * 价格监控服务 539 | * 监控价格变化并触发相应事件 540 | */ 541 | export class PriceMonitorService { 542 | private connectionService: ConnectionService; 543 | private poolChain: PoolChain; 544 | private currentPrice: number = 0; 545 | private currentBinId: number = 0; 546 | private previousBinId: number = 0; 547 | private monitoring: boolean = false; 548 | private onPriceChangeCallbacks: ((price: number, previousPrice: number) => void)[] = []; 549 | private onPoolCrossingCallbacks: (( 550 | previousPool: Pool | undefined, 551 | currentPool: Pool | undefined 552 | ) => void)[] = []; 553 | 554 | /** 555 | * 构造函数 556 | */ 557 | constructor( 558 | connectionService: ConnectionService, 559 | poolChain: PoolChain 560 | ) { 561 | this.connectionService = connectionService; 562 | this.poolChain = poolChain; 563 | } 564 | 565 | /** 566 | * 获取当前价格 567 | */ 568 | public async getCurrentPrice(): Promise { 569 | if (this.currentPrice === 0) { 570 | await this.updatePrice(); 571 | } 572 | return this.currentPrice; 573 | } 574 | 575 | /** 576 | * 获取当前活跃bin ID 577 | */ 578 | public getCurrentBinId(): number { 579 | return this.currentBinId; 580 | } 581 | 582 | /** 583 | * 更新价格和活跃bin 584 | */ 585 | private async updatePrice(): Promise { 586 | try { 587 | // 确保连接可用 588 | await this.connectionService.ensureConnected(); 589 | 590 | // 获取所有池子 591 | const pools = this.poolChain.getAllPools(); 592 | if (pools.length === 0) { 593 | logger.warn('没有可用池子,无法获取价格'); 594 | return; 595 | } 596 | 597 | try { 598 | // 选择一个池子获取活跃bin数据 599 | // 这里默认使用第一个池子,因为活跃bin在所有池子中应该是一致的 600 | const pool = pools[0]; 601 | 602 | // 获取活跃bin 603 | const activeBin = await pool.dlmmPool.getActiveBin(); 604 | if (!activeBin) { 605 | logger.warn('获取活跃Bin失败,无法更新价格'); 606 | return; 607 | } 608 | 609 | // 获取活跃bin的ID 610 | const activeBinId = activeBin.binId || activeBin.index; 611 | if (activeBinId === undefined) { 612 | logger.warn('无法获取活跃Bin ID'); 613 | return; 614 | } 615 | 616 | // 保存之前的bin ID 617 | this.previousBinId = this.currentBinId; 618 | 619 | // 更新当前bin ID 620 | this.currentBinId = activeBinId; 621 | 622 | // 更新池链中的当前bin ID 623 | this.poolChain.updateCurrentBinId(this.currentBinId); 624 | 625 | // 获取bin对应的价格 626 | let price = 0; 627 | if (typeof pool.dlmmPool.getPriceFromBinId === 'function') { 628 | price = pool.dlmmPool.getPriceFromBinId(activeBinId); 629 | price = calculateRealPrice(price, pool.tokenXDecimals, pool.tokenYDecimals); 630 | } else { 631 | // 如果无法直接获取价格,可以使用其他方法 632 | // 例如从bin数据中获取价格信息 633 | const binIds = pool.getAllBinIds(); 634 | // 查找匹配的bin 635 | for (const position of pool.positions) { 636 | const matchingBin = position.binData.find(bin => bin.binId === activeBinId); 637 | if (matchingBin && matchingBin.price) { 638 | price = parseFloat(matchingBin.price.toString()); 639 | break; 640 | } 641 | } 642 | 643 | // 如果还是无法获取价格,使用bin ID作为近似值 644 | if (price === 0) { 645 | price = activeBinId; 646 | } 647 | } 648 | 649 | // 保存上一次价格用于对比 650 | const previousPrice = this.currentPrice; 651 | 652 | // 更新当前价格 653 | this.currentPrice = price; 654 | 655 | // 更新池链中的当前价格 (仍然需要更新价格以供其他功能使用) 656 | this.poolChain.updateCurrentPrice(this.currentPrice); 657 | 658 | // 处理bin ID变化 659 | this.processBinChange(this.currentBinId, this.previousBinId, this.currentPrice, previousPrice); 660 | } catch (error) { 661 | logger.error(`获取价格出错: ${error instanceof Error ? error.message : String(error)}`); 662 | } 663 | } catch (error) { 664 | logger.error(`更新价格出错: ${error instanceof Error ? error.message : String(error)}`); 665 | } 666 | } 667 | 668 | /** 669 | * 处理bin变化并显示 670 | */ 671 | private processBinChange(currentBinId: number, previousBinId: number, currentPrice: number, previousPrice: number): void { 672 | // 如果是首次更新,没有之前的bin记录 673 | if (previousBinId === 0) { 674 | logger.info(`首次获取活跃Bin: ${currentBinId}, 价格: ${currentPrice}`); 675 | 676 | // 更新显示 677 | display.updateCurrentPrice(String(currentPrice)); 678 | display.updateCurrentBinId(String(currentBinId)); 679 | display.updateLastUpdated(new Date().toLocaleTimeString()); 680 | return; 681 | } 682 | 683 | // 检查bin是否发生变化 684 | if (currentBinId !== previousBinId) { 685 | logger.info(`活跃Bin变化: ${previousBinId} -> ${currentBinId}, 价格: ${previousPrice.toFixed(8)} -> ${currentPrice.toFixed(8)}`); 686 | 687 | // 检查是否跨越池子边界 688 | const previousPool = this.findPoolByBin(previousBinId); 689 | const currentPool = this.findPoolByBin(currentBinId); 690 | 691 | if (previousPool !== currentPool) { 692 | logger.info(`跨越池子边界: ${previousPool?.address.toString().slice(0, 8)}... -> ${currentPool?.address.toString().slice(0, 8)}...`); 693 | 694 | // 触发池子跨越回调 695 | this.triggerPoolCrossingCallbacks(previousPool, currentPool); 696 | } 697 | 698 | // 触发价格变化回调 699 | this.triggerPriceChangeCallbacks(currentPrice, previousPrice); 700 | } else { 701 | // bin没有变化,但可能价格有微小变动 702 | if (previousPrice > 0 && currentPrice !== previousPrice) { 703 | const changePercent = Math.abs((currentPrice - previousPrice) / previousPrice) * 100; 704 | logger.debug(`价格变动${changePercent.toFixed(4)}%: ${previousPrice.toFixed(8)} -> ${currentPrice.toFixed(8)}, Bin保持在: ${currentBinId}`); 705 | } 706 | } 707 | 708 | // 更新显示 709 | display.updateCurrentPrice(String(currentPrice)); 710 | display.updateCurrentBinId(String(currentBinId)); 711 | display.updateLastUpdated(new Date().toLocaleTimeString()); 712 | } 713 | 714 | /** 715 | * 根据bin ID查找所在的池子 716 | */ 717 | private findPoolByBin(binId: number): Pool | undefined { 718 | return this.poolChain.getAllPools().find(pool => pool.isBinInRange(binId)); 719 | } 720 | 721 | /** 722 | * 触发价格变化回调 723 | */ 724 | private triggerPriceChangeCallbacks(price: number, previousPrice: number): void { 725 | for (const callback of this.onPriceChangeCallbacks) { 726 | try { 727 | callback(price, previousPrice); 728 | } catch (error) { 729 | logger.error(`执行价格变化回调时出错: ${error instanceof Error ? error.message : String(error)}`); 730 | } 731 | } 732 | } 733 | 734 | /** 735 | * 触发池子跨越回调 736 | */ 737 | private triggerPoolCrossingCallbacks(previousPool: Pool | undefined, currentPool: Pool | undefined): void { 738 | for (const callback of this.onPoolCrossingCallbacks) { 739 | try { 740 | callback(previousPool, currentPool); 741 | } catch (error) { 742 | logger.error(`执行池子跨越回调时出错: ${error instanceof Error ? error.message : String(error)}`); 743 | } 744 | } 745 | } 746 | 747 | /** 748 | * 注册价格变化回调 749 | */ 750 | public onPriceChange(callback: (price: number, previousPrice: number) => void): void { 751 | this.onPriceChangeCallbacks.push(callback); 752 | } 753 | 754 | /** 755 | * 注册池子跨越回调 756 | */ 757 | public onPoolCrossing(callback: (previousPool: Pool | undefined, currentPool: Pool | undefined) => void): void { 758 | this.onPoolCrossingCallbacks.push(callback); 759 | } 760 | 761 | /** 762 | * 开始监控 763 | */ 764 | public async startMonitoring(): Promise { 765 | if (this.monitoring) return; 766 | 767 | this.monitoring = true; 768 | logger.info('开始价格监控'); 769 | 770 | // 首次更新价格 771 | await this.updatePrice(); 772 | 773 | // 定期更新价格 774 | this.monitoringLoop(); 775 | } 776 | 777 | /** 778 | * 监控循环 779 | */ 780 | private async monitoringLoop(): Promise { 781 | while (this.monitoring) { 782 | try { 783 | await this.updatePrice(); 784 | } catch (error) { 785 | logger.error(`价格监控出错: ${error instanceof Error ? error.message : String(error)}`); 786 | } 787 | 788 | // 等待指定时间 789 | await sleep(MONITOR_CONFIG.PRICE_CHECK_INTERVAL_MS); 790 | } 791 | } 792 | 793 | /** 794 | * 停止监控 795 | */ 796 | public stopMonitoring(): void { 797 | this.monitoring = false; 798 | logger.info('停止价格监控'); 799 | } 800 | } 801 | 802 | /** 803 | * 流动性调整服务 804 | * 执行流动性移除和添加操作 805 | */ 806 | export class LiquidityAdjustmentService { 807 | private connectionService: ConnectionService; 808 | private walletService: WalletService; 809 | private poolChain: PoolChain; 810 | private priceMonitorService: PriceMonitorService; 811 | private adjusting: boolean = false; 812 | private poolDiscoveryService: PoolDiscoveryService; 813 | 814 | /** 815 | * 构造函数 816 | */ 817 | constructor( 818 | connectionService: ConnectionService, 819 | walletService: WalletService, 820 | poolChain: PoolChain, 821 | priceMonitorService: PriceMonitorService, 822 | poolDiscoveryService: PoolDiscoveryService 823 | ) { 824 | this.connectionService = connectionService; 825 | this.walletService = walletService; 826 | this.poolChain = poolChain; 827 | this.priceMonitorService = priceMonitorService; 828 | this.poolDiscoveryService = poolDiscoveryService; 829 | 830 | // 注册池子跨越回调 831 | this.priceMonitorService.onPoolCrossing((previousPool, currentPool) => { 832 | this.handlePoolCrossing(previousPool, currentPool); 833 | }); 834 | } 835 | 836 | /** 837 | * 处理池子跨越事件 838 | */ 839 | private async handlePoolCrossing(previousPool: Pool | undefined, currentPool: Pool | undefined): Promise { 840 | // 如果正在调整,则跳过 841 | if (this.adjusting) { 842 | logger.warn('正在进行流动性调整,跳过池子跨越处理'); 843 | return; 844 | } 845 | 846 | // 只有当跨越到有效池子时才进行检查 847 | if (currentPool) { 848 | await this.checkAndAdjustNeighboringPools(); 849 | } else { 850 | logger.warn('价格超出所有已知池子范围,进入监控模式'); 851 | display.updateStatusMessage('价格超出范围,仅监控'); 852 | } 853 | } 854 | 855 | /** 856 | * 检查并调整相邻池子 857 | */ 858 | public async checkAndAdjustNeighboringPools(): Promise { 859 | // 如果正在调整,则跳过 860 | if (this.adjusting) { 861 | logger.warn('正在进行流动性调整,跳过检查'); 862 | return; 863 | } 864 | 865 | try { 866 | this.adjusting = true; 867 | logger.info('开始检查相邻池子是否需要调整...'); 868 | display.updateStatusMessage('检查相邻池子中...'); 869 | 870 | // 获取当前价格 871 | const currentPrice = await this.priceMonitorService.getCurrentPrice(); 872 | // 获取当前活跃bin 873 | const currentBinId = this.priceMonitorService.getCurrentBinId(); 874 | logger.info(`当前价格: ${currentPrice.toFixed(8)}, 当前活跃Bin: ${currentBinId}`); 875 | 876 | // 检查相邻池子是否符合BidAsk模型 877 | logger.info('检查相邻头寸是否符合BidAsk模型...'); 878 | const complianceCheck = this.poolChain.checkNeighboringPoolsCompliance(); 879 | 880 | // 输出检查结果汇总 881 | logger.info('相邻头寸合规性检查结果:'); 882 | if (complianceCheck.lowerPosition) { 883 | logger.info(`- 低头寸(${complianceCheck.lowerPosition.position.publicKey.toString().slice(0, 8)}...): ${complianceCheck.lowerPosition.isCompliant ? '符合' : '不符合'}`); 884 | } else { 885 | logger.info('- 无低头寸'); 886 | } 887 | 888 | if (complianceCheck.currentPosition) { 889 | logger.info(`- 当前头寸(${complianceCheck.currentPosition.position.publicKey.toString().slice(0, 8)}...): ${complianceCheck.currentPosition.isCompliant ? '符合' : '不符合'}`); 890 | } else { 891 | logger.info('- 无当前头寸'); 892 | } 893 | 894 | if (complianceCheck.higherPosition) { 895 | logger.info(`- 高头寸(${complianceCheck.higherPosition.position.publicKey.toString().slice(0, 8)}...): ${complianceCheck.higherPosition.isCompliant ? '符合' : '不符合'}`); 896 | } else { 897 | logger.info('- 无高头寸'); 898 | } 899 | 900 | // 处理需要调整的头寸 901 | const positionsToAdjust: { position: Position; pool: Pool; isHigherThanCurrentPrice: boolean }[] = []; 902 | 903 | // 获取当前池 904 | const currentPool = this.poolChain.getCurrentPool(); 905 | if (!currentPool) { 906 | logger.info('未找到当前活跃bin所在的池子,无法进行调整'); 907 | return; 908 | } 909 | 910 | if (complianceCheck.lowerPosition && !complianceCheck.lowerPosition.isCompliant) { 911 | logger.info(`添加低头寸(${complianceCheck.lowerPosition.position.publicKey.toString().slice(0, 8)}...)到待调整列表`); 912 | positionsToAdjust.push({ 913 | position: complianceCheck.lowerPosition.position, 914 | pool: currentPool, 915 | isHigherThanCurrentPrice: false 916 | }); 917 | } 918 | 919 | if (complianceCheck.higherPosition && !complianceCheck.higherPosition.isCompliant) { 920 | logger.info(`添加高头寸(${complianceCheck.higherPosition.position.publicKey.toString().slice(0, 8)}...)到待调整列表`); 921 | positionsToAdjust.push({ 922 | position: complianceCheck.higherPosition.position, 923 | pool: currentPool, 924 | isHigherThanCurrentPrice: true 925 | }); 926 | } 927 | 928 | // 如果有需要调整的头寸,进行调整 929 | if (positionsToAdjust.length > 0) { 930 | logger.info(`发现${positionsToAdjust.length}个头寸需要调整`); 931 | display.updateStatusMessage(`准备调整${positionsToAdjust.length}个头寸`); 932 | 933 | for (const { position, pool, isHigherThanCurrentPrice } of positionsToAdjust) { 934 | logger.info(`开始调整头寸${position.publicKey.toString().slice(0, 8)}..., 期望分布: ${isHigherThanCurrentPrice ? '升序' : '降序'}`); 935 | // 调整单个头寸,而不是整个池子 936 | await this.adjustPosition(position, pool, isHigherThanCurrentPrice); 937 | } 938 | 939 | logger.info('完成所有头寸调整'); 940 | display.updateStatusMessage('所有头寸调整完成'); 941 | } else { 942 | logger.info('所有相邻头寸均符合BidAsk模型,无需调整'); 943 | display.updateStatusMessage('所有头寸正常'); 944 | } 945 | } catch (error) { 946 | logger.error(`检查和调整池子时出错: ${error instanceof Error ? error.message : String(error)}`); 947 | display.updateStatusMessage(`调整出错: ${error instanceof Error ? error.message : String(error)}`); 948 | } finally { 949 | this.adjusting = false; 950 | // 更新显示 951 | this.poolDiscoveryService.updatePoolsDisplay(); 952 | } 953 | } 954 | 955 | /** 956 | * 调整单个头寸 957 | */ 958 | private async adjustPosition(position: Position, pool: Pool, isHigherThanCurrentPrice: boolean): Promise { 959 | const positionAddress = position.publicKey.toString(); 960 | logger.info(`开始调整头寸: ${positionAddress}`); 961 | display.updateStatusMessage(`调整头寸: ${positionAddress}`); 962 | 963 | try { 964 | // 步骤1: 移除头寸的流动性 965 | const removeResult = await this.removePositionLiquidity(position, pool); 966 | if (!removeResult.success) { 967 | logger.error(`移除头寸流动性失败: ${removeResult.error || '未知错误'}`); 968 | display.updateStatusMessage(`调整失败: 移除流动性出错`); 969 | return; 970 | } 971 | 972 | if (!removeResult.liquidity) { 973 | logger.error('移除流动性成功但未返回流动性数据'); 974 | display.updateStatusMessage(`调整失败: 无流动性数据`); 975 | return; 976 | } 977 | 978 | // 步骤2: 根据BidAsk模型重新添加流动性 979 | await this.addLiquidityForPosition(pool, position, isHigherThanCurrentPrice, removeResult.liquidity); 980 | 981 | // 再次刷新池数据,确保后续检查使用最新数据 982 | await pool.fetchPositions(this.walletService.getPublicKey()); 983 | 984 | logger.info(`完成头寸调整: ${positionAddress}`); 985 | } catch (error) { 986 | logger.error(`调整头寸时出错: ${error instanceof Error ? error.message : String(error)}`); 987 | display.updateStatusMessage(`调整出错: ${error instanceof Error ? error.message : String(error)}`); 988 | } 989 | } 990 | 991 | /** 992 | * 移除单个头寸的流动性 993 | */ 994 | private async removePositionLiquidity(position: Position, pool: Pool): Promise<{ 995 | success: boolean; 996 | error?: string; 997 | liquidity?: { totalX: bigint; totalY: bigint } 998 | }> { 999 | const positionAddress = position.publicKey.toString(); 1000 | logger.info(`移除头寸${positionAddress}的流动性`); 1001 | 1002 | try { 1003 | // 获取头寸的流动性 1004 | const liquidity = position.getTotalLiquidity(); 1005 | 1006 | // 获取头寸的bin ID范围 1007 | const binIds = position.binData.map(bin => bin.binId); 1008 | 1009 | // 创建移除流动性交易 1010 | const tx = await pool.dlmmPool.removeLiquidity({ 1011 | position: position.publicKey, 1012 | user: this.walletService.getPublicKey(), 1013 | fromBinId: Math.min(...binIds), 1014 | toBinId: Math.max(...binIds), 1015 | bps: new BN(10000), // 移除100%的流动性 1016 | shouldClaimAndClose: false, // 不关闭头寸,仅移除流动性 1017 | }); 1018 | 1019 | // 签名并发送交易 1020 | await this.walletService.signAndSendTransaction(tx); 1021 | 1022 | // 等待交易确认 1023 | await sleep(2000); 1024 | 1025 | // 刷新头寸数据 1026 | await pool.fetchPositions(this.walletService.getPublicKey()); 1027 | 1028 | logger.info(`成功移除头寸${positionAddress}的流动性`); 1029 | return { success: true, liquidity }; 1030 | } catch (error) { 1031 | const errorMessage = error instanceof Error ? error.message : String(error); 1032 | logger.error(`移除头寸流动性失败: ${errorMessage}`); 1033 | return { success: false, error: errorMessage }; 1034 | } 1035 | } 1036 | 1037 | /** 1038 | * 为单个头寸按策略添加流动性 1039 | */ 1040 | private async addLiquidityForPosition( 1041 | pool: Pool, 1042 | position: Position, 1043 | isHigherThanCurrentPrice: boolean, 1044 | previousLiquidity: { totalX: bigint; totalY: bigint } 1045 | ): Promise { 1046 | const positionAddress = position.publicKey.toString(); 1047 | logger.info(`按BidAsk模型为头寸${positionAddress}添加流动性`); 1048 | 1049 | try { 1050 | // 获取头寸的bin范围 1051 | const range = position.getBinRange(); 1052 | logger.info(`头寸bin范围: ${range.minBinId} - ${range.maxBinId}`); 1053 | 1054 | // 将bigint类型转换为BN类型 1055 | const totalXAmount = new BN(previousLiquidity.totalX.toString()); 1056 | const totalYAmount = new BN(previousLiquidity.totalY.toString()); 1057 | 1058 | // 创建添加流动性的交易 1059 | const tx = await pool.dlmmPool.addLiquidityByStrategy({ 1060 | positionPubKey: position.publicKey, // 使用头寸的公钥 1061 | user: this.walletService.getPublicKey(), 1062 | totalXAmount: totalXAmount, // 使用BN类型 1063 | totalYAmount: totalYAmount, // 使用BN类型 1064 | strategy: { 1065 | maxBinId: range.maxBinId, 1066 | minBinId: range.minBinId, 1067 | strategyType: DLMMSdk.StrategyType.BidAsk, // 使用BidAsk策略 1068 | // 根据是否高于当前价格,控制策略参数 1069 | singleSidedX: isHigherThanCurrentPrice, // 高于当前价格时使用X代币(升序分布),否则使用Y代币(降序分布) 1070 | }, 1071 | }); 1072 | 1073 | // 签名并发送交易 1074 | await this.walletService.signAndSendTransaction(tx); 1075 | 1076 | // 添加流动性成功后,刷新头寸数据 1077 | await pool.fetchPositions(this.walletService.getPublicKey()); 1078 | 1079 | logger.info(`成功按BidAsk模型为头寸${positionAddress}添加流动性`); 1080 | } catch (error) { 1081 | logger.error(`添加流动性时出错: ${error instanceof Error ? error.message : String(error)}`); 1082 | throw error; 1083 | } 1084 | } 1085 | } -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DLMM串联池流动性管理脚本 - 工具函数 3 | * 4 | * 提供通用辅助功能,包括重试机制、数据处理和BidAsk模型验证 5 | */ 6 | 7 | import { ERROR_CONFIG } from './config'; 8 | import { logger } from './logger'; 9 | 10 | /** 11 | * 带重试的异步函数执行 12 | * @param fn 要执行的异步函数 13 | * @param context 上下文描述(用于日志) 14 | * @param maxRetries 最大重试次数 15 | * @param initialDelay 初始延迟(毫秒) 16 | * @param backoffFactor 退避因子 17 | * @returns 异步函数结果 18 | */ 19 | export async function withRetry( 20 | fn: () => Promise, 21 | context: string, 22 | maxRetries: number = ERROR_CONFIG.RETRY_CONFIG.MAX_RETRIES, 23 | initialDelay: number = ERROR_CONFIG.RETRY_CONFIG.INITIAL_RETRY_DELAY_MS, 24 | backoffFactor: number = ERROR_CONFIG.RETRY_CONFIG.BACKOFF_FACTOR 25 | ): Promise { 26 | let lastError: Error | null = null; 27 | let delay = initialDelay; 28 | 29 | for (let attempt = 0; attempt <= maxRetries; attempt++) { 30 | try { 31 | if (attempt > 0) { 32 | logger.debug(`[${context}] 第${attempt}次重试...`); 33 | } 34 | return await fn(); 35 | } catch (error) { 36 | lastError = error as Error; 37 | logger.warn(`[${context}] 失败(尝试 ${attempt + 1}/${maxRetries + 1}): ${lastError.message}`); 38 | 39 | if (attempt < maxRetries) { 40 | logger.debug(`[${context}] 等待${delay}ms后重试...`); 41 | await sleep(delay); 42 | delay *= backoffFactor; // 指数退避 43 | } 44 | } 45 | } 46 | 47 | throw lastError || new Error(`执行${context}失败,已重试${maxRetries}次`); 48 | } 49 | 50 | /** 51 | * 延迟指定时间 52 | * @param ms 延迟毫秒数 53 | * @returns Promise 54 | */ 55 | export function sleep(ms: number): Promise { 56 | return new Promise(resolve => setTimeout(resolve, ms)); 57 | } 58 | 59 | /** 60 | * 格式化代币数量为可读字符串 61 | * @param amount 代币数量(bigint或string) 62 | * @param decimals 小数位数 63 | * @returns 格式化后的数量字符串 64 | */ 65 | export function formatAmount(amount: bigint | string | number, decimals: number): string { 66 | const amountStr = typeof amount === 'bigint' ? amount.toString() : amount.toString(); 67 | return (Number(amountStr) / Math.pow(10, decimals)).toFixed(decimals); 68 | } 69 | 70 | /** 71 | * 将格式化的数量转换回原始值 72 | * @param formattedAmount 格式化的数量 73 | * @param decimals 小数位数 74 | * @returns 原始值 75 | */ 76 | export function parseAmount(formattedAmount: string, decimals: number): bigint { 77 | const value = parseFloat(formattedAmount); 78 | return BigInt(Math.floor(value * Math.pow(10, decimals))); 79 | } 80 | 81 | /** 82 | * 计算真实价格 83 | * 由于不同代币精度不同,需要调整价格 84 | * @param price 原始价格 85 | * @param tokenXDecimals X代币精度(如SOL的9) 86 | * @param tokenYDecimals Y代币精度(如USDC的6) 87 | * @returns 调整后的真实价格 88 | */ 89 | export function calculateRealPrice(price: number | string, tokenXDecimals: number, tokenYDecimals: number): number { 90 | const priceFactor = Math.pow(10, tokenXDecimals - tokenYDecimals); 91 | return Number(price) * priceFactor; 92 | } 93 | 94 | /** 95 | * 格式化价格显示 96 | * @param price 原始价格 97 | * @param tokenXDecimals X代币精度 98 | * @param tokenYDecimals Y代币精度 99 | * @returns 格式化后的价格字符串 100 | */ 101 | export function formatPrice(price: number | string, tokenXDecimals: number, tokenYDecimals: number): string { 102 | if (!price || price === '-') return '-'; 103 | 104 | const realPrice = calculateRealPrice(price, tokenXDecimals, tokenYDecimals); 105 | 106 | // 根据价格大小选择合适的小数位数 107 | let decimals = 2; 108 | if (realPrice < 0.1) decimals = 6; 109 | else if (realPrice < 10) decimals = 4; 110 | 111 | return realPrice.toFixed(decimals); 112 | } 113 | 114 | /** 115 | * 截断地址字符串以便显示 116 | * @param address 完整地址 117 | * @param length 截断后的长度 118 | * @returns 截断后的地址 119 | */ 120 | export function truncateAddress(address: string, length: number = 8): string { 121 | if (address.length <= length) return address; 122 | const prefixLength = Math.ceil(length / 2); 123 | const suffixLength = length - prefixLength; 124 | return `${address.substring(0, prefixLength)}...${address.substring(address.length - suffixLength)}`; 125 | } 126 | 127 | /** 128 | * 验证BidAsk模型 129 | * 检查一系列资金分配是否符合线性增长的BidAsk模型 130 | * 131 | * @param values 资金数量数组 132 | * @param isAscending 资金应该是升序还是降序 133 | * @returns 是否符合BidAsk模型 134 | */ 135 | export function validateBidAskModel( 136 | values: number[], 137 | isAscending: boolean 138 | ): boolean { 139 | if (values.length <= 1) { 140 | logger.debug(`BidAsk检查: 只有${values.length}个值,自动判定为符合`); 141 | return true; 142 | } 143 | 144 | // 检查是否为零 145 | const nonZeroValues = values.filter(v => v > 0); 146 | if (nonZeroValues.length === 0) { 147 | logger.debug(`BidAsk检查: 所有值都为0,自动判定为符合`); 148 | return true; // 全零数组视为有效 149 | } 150 | 151 | // 记录调试信息 152 | logger.debug(`BidAsk检查: 期望分布=${isAscending ? '升序' : '降序'}, 值=${nonZeroValues.join(', ')}`); 153 | 154 | // 线性增长检查 155 | for (let i = 1; i < nonZeroValues.length; i++) { 156 | const prev = nonZeroValues[i - 1]; 157 | const current = nonZeroValues[i]; 158 | 159 | if (isAscending) { 160 | // 升序: 当前值应大于前一个值 161 | if (current <= prev) { 162 | logger.debug(`BidAsk检查: 升序验证失败,位置${i}的值${current}不大于前一个值${prev}`); 163 | return false; 164 | } 165 | } else { 166 | // 降序: 当前值应小于前一个值 167 | if (current >= prev) { 168 | logger.debug(`BidAsk检查: 降序验证失败,位置${i}的值${current}不小于前一个值${prev}`); 169 | return false; 170 | } 171 | } 172 | } 173 | 174 | logger.debug(`BidAsk检查: 验证通过,符合${isAscending ? '升序' : '降序'}分布`); 175 | return true; 176 | } 177 | 178 | /** 179 | * 计算BidAsk模型的理想分布 180 | * 181 | * @param total 总资金量 182 | * @param binCount bin数量 183 | * @param isAscending 是否为升序分布 184 | * @returns 每个bin的资金分配数组 185 | */ 186 | export function calculateBidAskDistribution( 187 | total: number, 188 | binCount: number, 189 | isAscending: boolean 190 | ): number[] { 191 | if (binCount <= 0) return []; 192 | if (binCount === 1) return [total]; 193 | 194 | // 计算线性增长系数 195 | // 对于n个点的线性增长,和为total,可以用数学公式推导 196 | const sum = (binCount * (binCount + 1)) / 2; 197 | const unitValue = total / sum; 198 | 199 | // 创建线性增长的数组 200 | let distribution = Array.from({ length: binCount }, (_, i) => unitValue * (i + 1)); 201 | 202 | // 如果需要降序,反转数组 203 | if (!isAscending) { 204 | distribution.reverse(); 205 | } 206 | 207 | return distribution; 208 | } -------------------------------------------------------------------------------- /src/dlmm-chain-pools-manager/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "lib": ["es2020"], 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "dist"] 17 | } -------------------------------------------------------------------------------- /src/scripts/encrypt-key.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 私钥加密脚本 3 | * 4 | * 用途:加密私钥并存储到文件中,提高安全性 5 | * 使用方法: 6 | * 1. 运行 `npm run encrypt-key` 7 | * 2. 按提示输入私钥和加密密码 8 | */ 9 | 10 | import { 11 | encryptPrivateKey, 12 | saveEncryptedKey, 13 | getPasswordFromUser, 14 | getEncryptedKeyPath, 15 | ENCRYPTED_KEY_FILENAME 16 | } from '../utils/crypto'; 17 | import { WALLET_CONFIG } from '../dlmm-chain-pools-manager/src/config'; 18 | import path from 'path'; 19 | 20 | /** 21 | * 主函数 22 | */ 23 | async function main() { 24 | try { 25 | console.log('========================================'); 26 | console.log('私钥加密工具'); 27 | console.log('========================================'); 28 | console.log('此工具将加密您的私钥并保存到安全文件中'); 29 | console.log('只有使用正确的密码才能访问加密的私钥'); 30 | console.log('请确保记住您的密码,否则将无法恢复私钥'); 31 | console.log('========================================\n'); 32 | 33 | // 显示当前工作目录 34 | console.log(`当前工作目录: ${process.cwd()}`); 35 | 36 | // 获取要加密的私钥 37 | let privateKey: string; 38 | 39 | if (WALLET_CONFIG.PRIVATE_KEY) { 40 | console.log('检测到配置文件中存在私钥'); 41 | const useConfigKey = await getYesNoInput('是否使用配置文件中的私钥? (y/n): '); 42 | 43 | if (useConfigKey) { 44 | privateKey = WALLET_CONFIG.PRIVATE_KEY; 45 | console.log('将使用配置文件中的私钥进行加密'); 46 | } else { 47 | privateKey = await getPasswordFromUser('请输入要加密的私钥: '); 48 | } 49 | } else { 50 | privateKey = await getPasswordFromUser('请输入要加密的私钥: '); 51 | } 52 | 53 | // 获取加密密码 54 | const password = await getPasswordFromUser('请设置加密密码: '); 55 | const confirmPassword = await getPasswordFromUser('请再次输入密码确认: '); 56 | 57 | if (password !== confirmPassword) { 58 | console.error('两次输入的密码不一致,加密已取消'); 59 | process.exit(1); 60 | } 61 | 62 | // 加密私钥 63 | console.log('正在加密私钥...'); 64 | const encryptedData = encryptPrivateKey(privateKey, password); 65 | 66 | // 保存加密的私钥到文件 67 | const filePath = getEncryptedKeyPath(); 68 | saveEncryptedKey(encryptedData, filePath); 69 | 70 | console.log('\n加密完成!'); 71 | console.log(`私钥已安全加密并保存到文件: ${filePath}`); 72 | console.log('现在您可以从配置文件中删除明文私钥,使用加密的私钥文件代替'); 73 | console.log('程序将在启动时提示您输入密码来解密私钥'); 74 | 75 | // 创建一个备份文件到用户主目录 76 | const os = await import('os'); 77 | const homeDir = os.homedir(); 78 | const backupPath = path.join(homeDir, ENCRYPTED_KEY_FILENAME); 79 | saveEncryptedKey(encryptedData, backupPath); 80 | console.log(`\n已在您的主目录中创建备份文件: ${backupPath}`); 81 | } catch (error) { 82 | console.error('加密过程中出错:', error instanceof Error ? error.message : String(error)); 83 | process.exit(1); 84 | } 85 | } 86 | 87 | /** 88 | * 获取用户的是/否输入 89 | */ 90 | async function getYesNoInput(prompt: string): Promise { 91 | while (true) { 92 | const input = await getPasswordFromUser(prompt); 93 | const lowerInput = input.toLowerCase(); 94 | 95 | if (lowerInput === 'y' || lowerInput === 'yes') { 96 | return true; 97 | } else if (lowerInput === 'n' || lowerInput === 'no') { 98 | return false; 99 | } 100 | 101 | console.log('请输入 y (yes) 或 n (no)'); 102 | } 103 | } 104 | 105 | // 执行主函数 106 | if (require.main === module) { 107 | main() 108 | .then(() => process.exit(0)) 109 | .catch(error => { 110 | console.error('发生错误:', error instanceof Error ? error.message : String(error)); 111 | process.exit(1); 112 | }); 113 | } -------------------------------------------------------------------------------- /src/utils/crypto.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 私钥加密解密工具 3 | * 使用AES-256-GCM算法加密私钥 4 | */ 5 | 6 | import * as crypto from 'crypto'; 7 | import * as fs from 'fs'; 8 | import * as path from 'path'; 9 | import * as readline from 'readline'; 10 | 11 | // 加密密钥文件名 12 | export const ENCRYPTED_KEY_FILENAME = '.encrypted_wallet.dat'; 13 | // 默认加密密钥存储路径 14 | export const DEFAULT_ENCRYPTION_PATH = path.join(process.cwd()); 15 | 16 | /** 17 | * 从用户终端获取密码 18 | */ 19 | export async function getPasswordFromUser(prompt: string = '请输入解密密码: '): Promise { 20 | const rl = readline.createInterface({ 21 | input: process.stdin, 22 | output: process.stdout 23 | }); 24 | 25 | return new Promise((resolve) => { 26 | // 提示用户输入密码 27 | rl.question(prompt, (password) => { 28 | rl.close(); 29 | resolve(password); 30 | }); 31 | }); 32 | } 33 | 34 | /** 35 | * 加密私钥 36 | * @param privateKey 要加密的私钥 37 | * @param password 加密密码 38 | * @returns 加密数据对象 39 | */ 40 | export function encryptPrivateKey(privateKey: string, password: string): { 41 | encryptedData: string; 42 | iv: string; 43 | salt: string; 44 | } { 45 | // 生成随机盐值 46 | const salt = crypto.randomBytes(16).toString('hex'); 47 | 48 | // 从密码和盐值生成密钥 49 | const key = crypto.scryptSync(password, salt, 32); 50 | 51 | // 生成随机初始化向量 52 | const iv = crypto.randomBytes(16); 53 | 54 | // 创建加密器 55 | const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); 56 | 57 | // 加密私钥 58 | let encrypted = cipher.update(privateKey, 'utf8', 'hex'); 59 | encrypted += cipher.final('hex'); 60 | 61 | // 获取认证标签 62 | const authTag = cipher.getAuthTag().toString('hex'); 63 | 64 | // 组合加密数据、认证标签、初始化向量和盐值 65 | const encryptedData = encrypted + ':' + authTag; 66 | 67 | return { 68 | encryptedData, 69 | iv: iv.toString('hex'), 70 | salt 71 | }; 72 | } 73 | 74 | /** 75 | * 解密私钥 76 | * @param encryptedData 加密数据 77 | * @param iv 初始化向量 78 | * @param salt 盐值 79 | * @param password 解密密码 80 | * @returns 解密后的私钥 81 | */ 82 | export function decryptPrivateKey(encryptedData: string, iv: string, salt: string, password: string): string { 83 | try { 84 | // 分离加密数据和认证标签 85 | const [encrypted, authTag] = encryptedData.split(':'); 86 | 87 | // 从密码和盐值生成密钥 88 | const key = crypto.scryptSync(password, salt, 32); 89 | 90 | // 创建解密器 91 | const decipher = crypto.createDecipheriv( 92 | 'aes-256-gcm', 93 | key, 94 | Buffer.from(iv, 'hex') 95 | ); 96 | 97 | // 设置认证标签 98 | decipher.setAuthTag(Buffer.from(authTag, 'hex')); 99 | 100 | // 解密私钥 101 | let decrypted = decipher.update(encrypted, 'hex', 'utf8'); 102 | decrypted += decipher.final('utf8'); 103 | 104 | return decrypted; 105 | } catch (error) { 106 | throw new Error('解密失败,密码可能不正确'); 107 | } 108 | } 109 | 110 | /** 111 | * 保存加密的私钥到文件 112 | * @param encryptedData 加密数据对象 113 | * @param filePath 文件路径 114 | */ 115 | export function saveEncryptedKey( 116 | encryptedData: { encryptedData: string; iv: string; salt: string }, 117 | filePath: string = path.join(DEFAULT_ENCRYPTION_PATH, ENCRYPTED_KEY_FILENAME) 118 | ): void { 119 | // 确保目录存在 120 | const dir = path.dirname(filePath); 121 | if (!fs.existsSync(dir)) { 122 | fs.mkdirSync(dir, { recursive: true }); 123 | } 124 | 125 | // 将加密数据保存为JSON格式 126 | fs.writeFileSync( 127 | filePath, 128 | JSON.stringify(encryptedData, null, 2), 129 | { encoding: 'utf8', mode: 0o600 } // 仅所有者可读写 130 | ); 131 | 132 | console.log(`加密的私钥已保存到 ${filePath}`); 133 | } 134 | 135 | /** 136 | * 从文件加载加密的私钥 137 | * @param filePath 文件路径 138 | * @returns 加密数据对象 139 | */ 140 | export function loadEncryptedKey( 141 | filePath: string = path.join(DEFAULT_ENCRYPTION_PATH, ENCRYPTED_KEY_FILENAME) 142 | ): { encryptedData: string; iv: string; salt: string } { 143 | if (!fs.existsSync(filePath)) { 144 | throw new Error(`加密私钥文件不存在: ${filePath}`); 145 | } 146 | 147 | try { 148 | // 从文件读取加密数据 149 | const fileContent = fs.readFileSync(filePath, { encoding: 'utf8' }); 150 | return JSON.parse(fileContent); 151 | } catch (error) { 152 | throw new Error(`读取加密私钥文件失败: ${error instanceof Error ? error.message : String(error)}`); 153 | } 154 | } 155 | 156 | /** 157 | * 加载并解密私钥 158 | * @param password 解密密码 159 | * @param filePath 加密私钥文件路径 160 | * @returns 解密后的私钥 161 | */ 162 | export async function loadAndDecryptPrivateKey( 163 | password?: string, 164 | filePath: string = path.join(DEFAULT_ENCRYPTION_PATH, ENCRYPTED_KEY_FILENAME) 165 | ): Promise { 166 | // 加载加密的私钥 167 | const encryptedKeyData = loadEncryptedKey(filePath); 168 | 169 | // 如果没有提供密码,从用户终端获取 170 | if (!password) { 171 | password = await getPasswordFromUser(); 172 | } 173 | 174 | // 解密私钥 175 | return decryptPrivateKey( 176 | encryptedKeyData.encryptedData, 177 | encryptedKeyData.iv, 178 | encryptedKeyData.salt, 179 | password 180 | ); 181 | } 182 | 183 | /** 184 | * 获取加密密钥文件的完整路径 185 | * @param customPath 自定义路径 186 | * @returns 完整文件路径 187 | */ 188 | export function getEncryptedKeyPath(customPath?: string): string { 189 | // 默认路径使用绝对路径,而不是相对路径 190 | const defaultPath = customPath || DEFAULT_ENCRYPTION_PATH; 191 | const fullPath = path.resolve(defaultPath, ENCRYPTED_KEY_FILENAME); 192 | console.log(`加密文件查找路径: ${fullPath}`); 193 | console.log(`当前工作目录: ${process.cwd()}`); 194 | 195 | // 检查文件是否存在并输出日志 196 | try { 197 | if (fs.existsSync(fullPath)) { 198 | console.log(`找到加密文件: ${fullPath}`); 199 | } else { 200 | console.log(`未找到加密文件: ${fullPath}`); 201 | // 尝试查找用户主目录中的加密文件 202 | const homeDir = require('os').homedir(); 203 | const homeFilePath = path.join(homeDir, ENCRYPTED_KEY_FILENAME); 204 | if (fs.existsSync(homeFilePath)) { 205 | console.log(`在用户主目录中找到加密文件: ${homeFilePath}`); 206 | return homeFilePath; 207 | } 208 | 209 | // 尝试查找上级目录中的加密文件 210 | const parentDir = path.resolve(process.cwd(), '..'); 211 | const parentFilePath = path.join(parentDir, ENCRYPTED_KEY_FILENAME); 212 | if (fs.existsSync(parentFilePath)) { 213 | console.log(`在上级目录中找到加密文件: ${parentFilePath}`); 214 | return parentFilePath; 215 | } 216 | } 217 | } catch (error) { 218 | console.error(`检查加密文件时出错: ${error instanceof Error ? error.message : String(error)}`); 219 | } 220 | 221 | return fullPath; 222 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "lib": ["es2020"], 6 | "declaration": true, 7 | "outDir": "dist", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "rootDir": "./src" 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "dist"] 18 | } --------------------------------------------------------------------------------