├── README.md ├── main.js ├── package.json ├── pk.txt └── proxies.txt /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Pharos Testnet 自动交互脚本 3 | 4 | 由 [雪糕战神@Xuegaogx](https://twitter.com/Xuegaogx) 编写 5 | 自动执行 Pharos 测试网的水龙头领取、每日签到、转账、Swap 操作 6 | 7 | --- 8 | 9 | ## 🧾 功能说明 10 | 11 | 本脚本每轮自动完成以下步骤: 12 | 13 | 1. **加载私钥 & 代理池** 14 | 2. 针对每个地址依次执行: 15 | - ✅ 登录并领取水龙头(若可领取) 16 | - ✅ 登录并执行每日签到(若未签到) 17 | - ✅ 向随机地址发起 10 次 PHRS 转账(防 Bot) 18 | - ✅ 在 WPHRS / USDC 之间进行 10 次 Swap(模拟交互) 19 | 3. 完成所有钱包后自动倒计时 30 分钟,循环执行 20 | 21 | --- 22 | 23 | ## ✅ 使用前准备 24 | 25 | ### 环境要求 26 | 27 | - Node.js >= 18 28 | - 包管理器:npm / yarn / pnpm 均可 29 | - 网络支持:直连 / 支持 http(s) 代理 30 | - 支持 Linux / Mac / Windows (WSL) 31 | 32 | ### 克隆仓库 33 | 34 | ```bash 35 | git clone https://github.com/GzGod/pharos.git 36 | cd pharos 37 | ``` 38 | 39 | ### 安装依赖 40 | 41 | ```bash 42 | npm install 43 | ``` 44 | 45 | --- 46 | 47 | ## 🛠 文件配置说明 48 | 49 | ### 1. 私钥文件 `pk.txt` 50 | 51 | 一行一个私钥(不带 0x): 52 | 53 | ``` 54 | abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 55 | fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321 56 | ``` 57 | 58 | > ⚠️ 请勿上传该文件到公开仓库! 59 | 60 | --- 61 | 62 | ### 2. 代理池文件 `proxies.txt`(可选) 63 | 64 | 一行一个代理,格式示例: 65 | 66 | ``` 67 | http://username:password@ip:port 68 | http://127.0.0.1:7890 69 | ``` 70 | 71 | 如未提供该文件,则自动采用直连模式。 72 | 73 | --- 74 | 75 | ## ▶️ 运行脚本 76 | 77 | ```bash 78 | node main.js 79 | ``` 80 | 81 | 脚本将持续循环执行,如需后台运行可搭配 `screen` 或 `pm2` 使用。 82 | 83 | --- 84 | 85 | ## 📄 免责声明 86 | 87 | - 本脚本仅用于 Pharos 测试网参与用途,**不提供任何投资建议** 88 | - 请自行评估风险,使用本脚本代表您已同意自行承担后果 89 | 90 | --- 91 | 92 | ## 🙋 联系作者 93 | 94 | - TG频道:https://t.me/xuegaoz 95 | - GitHub:https://github.com/Gzgod 96 | - 推特:[@Xuegaogx](https://twitter.com/Xuegaogx) 97 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { ethers } = require('ethers'); 3 | const fs = require('fs'); 4 | const { HttpsProxyAgent } = require('https-proxy-agent'); 5 | const randomUseragent = require('random-useragent'); 6 | const axios = require('axios'); 7 | 8 | // ===== 配置区 ===== 9 | const network = { 10 | name: 'Pharos Testnet', 11 | chainId: 688688, 12 | rpcUrl: 'https://testnet.dplabs-internal.com', 13 | nativeCurrency: 'PHRS', 14 | }; 15 | 16 | const tokens = { 17 | USDC: { address: '0xad902cf99c2de2f1ba5ec4d642fd7e49cae9ee37', decimals: 6 }, 18 | WPHRS: { address: '0x76aaada469d23216be5f7c596fa25f282ff9b364', decimals: 18 }, 19 | }; 20 | 21 | const contractAddress = '0x1a4de519154ae51200b0ad7c90f7fac75547888a'; 22 | const multicallABI = ['function multicall(uint256 collectionAndSelfcalls, bytes[] data) public']; 23 | const erc20ABI = [ 24 | 'function balanceOf(address) view returns (uint256)', 25 | 'function allowance(address owner, address spender) view returns (uint256)', 26 | 'function approve(address spender, uint256 amount) public returns (bool)', 27 | ]; 28 | 29 | // ===== 日志输出 ===== 30 | const colors = { 31 | reset: '\x1b[0m', cyan: '\x1b[36m', green: '\x1b[32m', 32 | yellow: '\x1b[33m', red: '\x1b[31m', white: '\x1b[37m', bold: '\x1b[1m', 33 | }; 34 | 35 | const logger = { 36 | info: (msg) => console.log(`${colors.green}[✓] ${msg}${colors.reset}`), 37 | wallet: (msg) => console.log(`${colors.yellow}[➤] ${msg}${colors.reset}`), 38 | warn: (msg) => console.log(`${colors.yellow}[!] ${msg}${colors.reset}`), 39 | error: (msg) => console.log(`${colors.red}[✗] ${msg}${colors.reset}`), 40 | success: (msg) => console.log(`${colors.green}[+] ${msg}${colors.reset}`), 41 | loading: (msg) => console.log(`${colors.cyan}[⟳] ${msg}${colors.reset}`), 42 | step: (msg) => console.log(`${colors.white}[➤] ${msg}${colors.reset}`), 43 | }; 44 | 45 | const printBanner = () => { 46 | const asciiArt = [ 47 | " ╔═╗╔═╦╗─╔╦═══╦═══╦═══╦═══╗", 48 | " ╚╗╚╝╔╣║─║║╔══╣╔═╗║╔═╗║╔═╗║", 49 | " ─╚╗╔╝║║─║║╚══╣║─╚╣║─║║║─║║", 50 | " ─╔╝╚╗║║─║║╔══╣║╔═╣╚═╝║║─║║", 51 | " ╔╝╔╗╚╣╚═╝║╚══╣╚╩═║╔═╗║╚═╝║", 52 | " ╚═╝╚═╩═══╩═══╩═══╩╝─╚╩═══╝" 53 | ]; 54 | const infoLines = [ 55 | " 关注tg频道:t.me/xuegaoz", 56 | " 我的gihub:github.com/Gzgod", 57 | " 我的推特:推特雪糕战神@Xuegaogx" 58 | ]; 59 | 60 | console.log(`${colors.cyan}${colors.bold}`); 61 | asciiArt.forEach(line => console.log(line)); 62 | console.log(); 63 | infoLines.forEach(line => console.log(line)); 64 | console.log(`${colors.reset}\n`); 65 | }; 66 | 67 | // ===== 工具函数 ===== 68 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 69 | 70 | const retry = async (fn, maxAttempts = 3, delayMs = 2000) => { 71 | for (let attempt = 1; attempt <= maxAttempts; attempt++) { 72 | try { 73 | return await fn(); 74 | } catch (error) { 75 | if (attempt === maxAttempts) throw error; 76 | logger.warn(`Attempt ${attempt} failed, retrying in ${delayMs}ms...`); 77 | await delay(delayMs); 78 | } 79 | } 80 | }; 81 | 82 | const claimFaucet = async (wallet, proxy = null) => { 83 | try { 84 | logger.step(`开始领取水龙头 - ${wallet.address}`); 85 | const message = "pharos"; 86 | const signature = await wallet.signMessage(message); 87 | 88 | const loginUrl = `https://api.pharosnetwork.xyz/user/login?address=${wallet.address}&signature=${signature}&invite_code=S6NGMzXSCDBxhnwo`; 89 | const headers = { 90 | accept: "application/json, text/plain, */*", 91 | "accept-language": "en-US,en;q=0.8", 92 | "sec-ch-ua": '"Chromium";v="136", "Brave";v="136", "Not.A/Brand";v="99"', 93 | "sec-ch-ua-mobile": "?0", 94 | "sec-ch-ua-platform": '"Windows"', 95 | "sec-fetch-dest": "empty", 96 | "sec-fetch-mode": "cors", 97 | "sec-fetch-site": "same-site", 98 | "sec-gpc": "1", 99 | Referer: "https://testnet.pharosnetwork.xyz/", 100 | "Referrer-Policy": "strict-origin-when-cross-origin", 101 | "User-Agent": randomUseragent.getRandom(), 102 | }; 103 | 104 | const loginResponse = await retry(async () => { 105 | const res = await axios.post(loginUrl, {}, { 106 | headers, 107 | httpsAgent: proxy ? new HttpsProxyAgent(proxy) : undefined, 108 | }); 109 | if (res.status === 403) throw new Error('403 Forbidden: Check API access or proxy'); 110 | return res; 111 | }); 112 | 113 | const jwt = loginResponse.data?.data?.jwt; 114 | if (!jwt) { 115 | logger.warn('水龙头登录失败'); 116 | return; 117 | } 118 | 119 | const statusResponse = await retry(async () => { 120 | const res = await axios.get(`https://api.pharosnetwork.xyz/faucet/status?address=${wallet.address}`, { 121 | headers: { ...headers, authorization: `Bearer ${jwt}` }, 122 | httpsAgent: proxy ? new HttpsProxyAgent(proxy) : undefined, 123 | }); 124 | if (res.status === 403) throw new Error('403 Forbidden: Check JWT or API restrictions'); 125 | return res; 126 | }); 127 | 128 | const available = statusResponse.data?.data?.is_able_to_faucet; 129 | if (!available) { 130 | const nextAvailable = new Date(statusResponse.data?.data?.avaliable_timestamp * 1000).toLocaleString('en-US', { timeZone: 'Asia/Makassar' }); 131 | logger.warn(`今日水龙头已领取,下一可用时间:${nextAvailable}`); 132 | return; 133 | } 134 | 135 | const claimResponse = await retry(async () => { 136 | const res = await axios.post(`https://api.pharosnetwork.xyz/faucet/daily?address=${wallet.address}`, {}, { 137 | headers: { ...headers, authorization: `Bearer ${jwt}` }, 138 | httpsAgent: proxy ? new HttpsProxyAgent(proxy) : undefined, 139 | }); 140 | if (res.status === 403) throw new Error('403 Forbidden: Check API access or rate limits'); 141 | return res; 142 | }); 143 | 144 | if (claimResponse.data?.code === 0) { 145 | logger.success('水龙头领取成功'); 146 | } else { 147 | logger.warn(`水龙头领取失败:${claimResponse.data?.msg || '未知错误'}`); 148 | } 149 | } catch (e) { 150 | logger.error(`领取水龙头异常:${e.message}`); 151 | if (e.response) { 152 | logger.error(`响应详情:${JSON.stringify(e.response.data, null, 2)}`); 153 | } 154 | } 155 | }; 156 | 157 | const performCheckIn = async (wallet, proxy = null) => { 158 | try { 159 | logger.step(`开始每日签到 - ${wallet.address}`); 160 | const message = "pharos"; 161 | const signature = await wallet.signMessage(message); 162 | const loginUrl = `https://api.pharosnetwork.xyz/user/login?address=${wallet.address}&signature=${signature}&invite_code=S6NGMzXSCDBxhnwo`; 163 | const headers = { 164 | accept: "application/json, text/plain, */*", 165 | "accept-language": "en-US,en;q=0.8", 166 | "sec-ch-ua": '"Chromium";v="136", "Brave";v="136", "Not.A/Brand";v="99"', 167 | "sec-ch-ua-mobile": "?0", 168 | "sec-ch-ua-platform": '"Windows"', 169 | "sec-fetch-dest": "empty", 170 | "sec-fetch-mode": "cors", 171 | "sec-fetch-site": "same-site", 172 | "sec-gpc": "1", 173 | Referer: "https://testnet.pharosnetwork.xyz/", 174 | "Referrer-Policy": "strict-origin-when-cross-origin", 175 | "User-Agent": randomUseragent.getRandom(), 176 | }; 177 | 178 | const loginRes = await retry(async () => { 179 | const res = await axios.post(loginUrl, {}, { 180 | headers, 181 | httpsAgent: proxy ? new HttpsProxyAgent(proxy) : undefined, 182 | }); 183 | if (res.status === 403) throw new Error('403 Forbidden: Check API access or proxy'); 184 | return res; 185 | }); 186 | 187 | const jwt = loginRes.data?.data?.jwt; 188 | if (!jwt) { 189 | logger.warn('签到登录失败'); 190 | return; 191 | } 192 | 193 | const signRes = await retry(async () => { 194 | const res = await axios.post(`https://api.pharosnetwork.xyz/sign/in?address=${wallet.address}`, {}, { 195 | headers: { ...headers, authorization: `Bearer ${jwt}` }, 196 | httpsAgent: proxy ? new HttpsProxyAgent(proxy) : undefined, 197 | }); 198 | if (res.status === 403) throw new Error('403 Forbidden: Check JWT or API restrictions'); 199 | return res; 200 | }); 201 | 202 | if (signRes.data?.code === 0) { 203 | logger.success('签到成功'); 204 | } else { 205 | logger.warn(`签到失败或已签过:${signRes.data?.msg || '未知错误'}`); 206 | } 207 | } catch (e) { 208 | logger.error(`签到异常:${e.message}`); 209 | if (e.response) { 210 | logger.error(`响应详情:${JSON.stringify(e.response.data, null, 2)}`); 211 | } 212 | } 213 | }; 214 | 215 | const transferPHRS = async (wallet, provider) => { 216 | try { 217 | for (let i = 0; i < 10; i++) { 218 | const amount = 0.000001; 219 | const to = ethers.Wallet.createRandom().address; 220 | const balance = await provider.getBalance(wallet.address); 221 | const required = ethers.parseEther(amount.toString()); 222 | if (balance < required) { 223 | logger.warn(`PHRS 余额不足,跳过转账 ${i + 1}`); 224 | return; 225 | } 226 | const tx = await wallet.sendTransaction({ 227 | to, 228 | value: required, 229 | gasLimit: 21000, 230 | gasPrice: 0, 231 | }); 232 | logger.loading(`转账 ${i + 1} 发出,等待确认...`); 233 | await tx.wait(); 234 | logger.success(`转账 ${i + 1} 成功: ${tx.hash}`); 235 | await delay(1000 + Math.random() * 2000); 236 | } 237 | } catch (e) { 238 | logger.error(`转账异常:${e.message}`); 239 | if (e.transaction) { 240 | logger.error(`交易详情:${JSON.stringify(e.transaction, null, 2)}`); 241 | } 242 | if (e.receipt) { 243 | logger.error(`收据:${JSON.stringify(e.receipt, null, 2)}`); 244 | } 245 | } 246 | }; 247 | 248 | const performSwap = async (wallet, provider) => { 249 | try { 250 | const pairs = [ 251 | { from: 'WPHRS', to: 'USDC', amount: 0.001 }, 252 | { from: 'USDC', to: 'WPHRS', amount: 0.1 }, 253 | ]; 254 | const contract = new ethers.Contract(contractAddress, multicallABI, wallet); 255 | 256 | for (let i = 0; i < 10; i++) { 257 | const pair = pairs[Math.floor(Math.random() * pairs.length)]; 258 | const token = tokens[pair.from]; 259 | const decimals = token.decimals; 260 | const amount = ethers.parseUnits(pair.amount.toString(), decimals); 261 | const tokenContract = new ethers.Contract(token.address, erc20ABI, wallet); 262 | const balance = await tokenContract.balanceOf(wallet.address); 263 | if (balance < amount) { 264 | logger.warn(`${pair.from} 余额不足,跳过 swap ${i + 1}`); 265 | return; 266 | } 267 | const allowance = await tokenContract.allowance(wallet.address, contractAddress); 268 | if (allowance < amount) { 269 | const approveTx = await tokenContract.approve(contractAddress, ethers.MaxUint256); 270 | await approveTx.wait(); 271 | logger.success('授权成功'); 272 | } 273 | const data = ethers.AbiCoder.defaultAbiCoder().encode( 274 | ['address', 'address', 'uint256', 'address', 'uint256', 'uint256', 'uint256'], 275 | [ 276 | tokens[pair.from].address, 277 | tokens[pair.to].address, 278 | 500, 279 | wallet.address, 280 | pair.from === 'WPHRS' ? '0x0000002386f26fc10000' : '0x016345785d8a0000', 281 | 0, 282 | 0, 283 | ] 284 | ); 285 | const tx = await contract.multicall( 286 | Math.floor(Date.now() / 1000), 287 | [ethers.concat(['0x04e45aaf', data])], 288 | { gasLimit: 219249, gasPrice: 0 } 289 | ); 290 | logger.loading(`Swap ${i + 1} 发出,等待确认...`); 291 | await tx.wait(); 292 | logger.success(`Swap ${i + 1} 成功: ${tx.hash}`); 293 | await delay(1000 + Math.random() * 2000); 294 | } 295 | } catch (e) { 296 | logger.error(`Swap 执行异常:${e.message}`); 297 | if (e.transaction) { 298 | logger.error(`交易详情:${JSON.stringify(e.transaction, null, 2)}`); 299 | } 300 | if (e.receipt) { 301 | logger.error(`收据:${JSON.stringify(e.receipt, null, 2)}`); 302 | } 303 | } 304 | }; 305 | 306 | const loadProxies = () => { 307 | try { 308 | return fs.readFileSync('proxies.txt', 'utf8') 309 | .split('\n') 310 | .map(line => line.trim()) 311 | .filter(Boolean); 312 | } catch { 313 | logger.warn('未找到 proxies.txt,使用直连模式'); 314 | return []; 315 | } 316 | }; 317 | 318 | const getRandomProxy = (proxies) => proxies[Math.floor(Math.random() * proxies.length)]; 319 | 320 | const initProvider = (proxy = null) => { 321 | const options = { chainId: network.chainId, name: network.name }; 322 | if (proxy) { 323 | logger.info(`使用代理:${proxy}`); 324 | const agent = new HttpsProxyAgent(proxy); 325 | return new ethers.JsonRpcProvider(network.rpcUrl, options, { 326 | fetchOptions: { agent }, 327 | headers: { 'User-Agent': randomUseragent.getRandom() }, 328 | }); 329 | } else { 330 | logger.info('使用直连模式'); 331 | return new ethers.JsonRpcProvider(network.rpcUrl, options); 332 | } 333 | }; 334 | 335 | const waitCountdown = async (minutes = 30) => { 336 | const totalSeconds = minutes * 60; 337 | logger.info(`开始等待 ${minutes} 分钟...`); 338 | 339 | for (let s = totalSeconds; s >= 0; s--) { 340 | const m = Math.floor(s / 60); 341 | const sec = s % 60; 342 | process.stdout.write(`\r${colors.cyan}剩余时间:${m}分 ${sec}秒${colors.reset} `); 343 | await delay(1000); 344 | } 345 | 346 | process.stdout.write('\r计时结束,重新开始流程...\n'); 347 | }; 348 | 349 | const main = async () => { 350 | printBanner(); 351 | 352 | const proxyList = loadProxies(); 353 | const privateKeys = fs.readFileSync('pk.txt', 'utf8') 354 | .split('\n') 355 | .map(line => line.trim()) 356 | .filter(Boolean); 357 | 358 | if (!privateKeys.length) { 359 | logger.error('未读取到任何私钥,请确认 pk.txt 中至少包含一个私钥'); 360 | return; 361 | } 362 | 363 | while (true) { 364 | for (const pk of privateKeys) { 365 | const proxy = proxyList.length ? getRandomProxy(proxyList) : null; 366 | const provider = initProvider(proxy); 367 | const wallet = new ethers.Wallet(pk, provider); 368 | 369 | logger.wallet(`当前钱包地址:${wallet.address}`); 370 | 371 | await claimFaucet(wallet, proxy); 372 | await delay(2000); // 防止请求过快 373 | await performCheckIn(wallet, proxy); 374 | await delay(2000); 375 | await transferPHRS(wallet, provider); 376 | await delay(2000); 377 | await performSwap(wallet, provider); 378 | } 379 | 380 | logger.success('所有钱包执行完毕,等待下一轮...'); 381 | await waitCountdown(); 382 | } 383 | }; 384 | 385 | main().catch(err => { 386 | logger.error(`脚本运行失败:${err.message}`); 387 | process.exit(1); 388 | }); 389 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pharos-bot", 3 | "version": "1.0.0", 4 | "description": "Pharos 测试网自动交互脚本", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "雪糕战神@Xuegaogx", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^1.6.8", 13 | "dotenv": "^16.3.1", 14 | "ethers": "^6.10.0", 15 | "https-proxy-agent": "^7.0.4", 16 | "random-useragent": "^0.5.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pk.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /proxies.txt: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------