Initial commit: Polymarket copy trading bot

- Backend: Spring Boot + Kotlin implementation
  - Account management with private key import
  - Leader management
  - Copy trading configuration
  - Order synchronization
  - Balance and position queries via Polymarket API
  - Ethereum RPC integration for USDC balance
  - Proxy address calculation

- Frontend: React + TypeScript
  - Account management UI
  - Mobile responsive design
  - Account import with private key/mnemonic support
  - Balance display and account details modal

- Database: MySQL with Flyway migrations
- API Integration: Polymarket CLOB API, Data API, Ethereum RPC
This commit is contained in:
WrBug
2025-11-21 04:32:08 +08:00
commit 4f7fef145f
70 changed files with 14618 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
import { ethers } from 'ethers'
/**
* 从私钥推导钱包地址
*/
export function getAddressFromPrivateKey(privateKey: string): string {
try {
// 移除 0x 前缀(如果有)
const cleanKey = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey
// 创建钱包
const wallet = new ethers.Wallet(`0x${cleanKey}`)
return wallet.address
} catch (error) {
throw new Error(`无效的私钥: ${error}`)
}
}
/**
* 从助记词推导钱包地址
* @param mnemonic 助记词(12或24个单词,用空格分隔)
* @param index 派生路径索引(默认0,使用第一个地址)
*/
export function getAddressFromMnemonic(mnemonic: string, index: number = 0): string {
try {
// 验证助记词格式
if (!isValidMnemonic(mnemonic)) {
throw new Error('助记词格式不正确')
}
// ethers.js v6: 如果 index 为 0,可以直接使用 Wallet.fromPhrase
// 它默认使用路径 m/44'/60'/0'/0/0
if (index === 0) {
const wallet = ethers.Wallet.fromPhrase(mnemonic.trim())
return wallet.address
}
// 对于其他索引,使用 HDNodeWallet
// 从助记词创建 Mnemonic 对象
const mnemonicObj = ethers.Mnemonic.fromPhrase(mnemonic.trim())
// 使用标准 BIP44 路径直接创建钱包:m/44'/60'/0'/0/index
// ethers.js v6: HDNodeWallet.fromMnemonic 可以直接指定路径
const derivationPath = `m/44'/60'/0'/0/${index}`
const wallet = ethers.HDNodeWallet.fromMnemonic(mnemonicObj, derivationPath)
return wallet.address
} catch (error: any) {
// 如果直接指定路径失败,尝试分步派生
try {
const mnemonicObj = ethers.Mnemonic.fromPhrase(mnemonic.trim())
// 先创建根节点(不指定路径)
const rootNode = ethers.HDNodeWallet.fromMnemonic(mnemonicObj)
// 使用相对路径(不以 m/ 开头)
const relativePath = `44'/60'/0'/0/${index}`
const wallet = rootNode.derivePath(relativePath)
return wallet.address
} catch (fallbackError: any) {
throw new Error(`无效的助记词: ${error.message || error}`)
}
}
}
/**
* 从助记词导出私钥
* @param mnemonic 助记词(12或24个单词,用空格分隔)
* @param index 派生路径索引(默认0,使用第一个地址)
*/
export function getPrivateKeyFromMnemonic(mnemonic: string, index: number = 0): string {
try {
// 验证助记词格式
if (!isValidMnemonic(mnemonic)) {
throw new Error('助记词格式不正确')
}
// ethers.js v6: 如果 index 为 0,可以直接使用 Wallet.fromPhrase
// 它默认使用路径 m/44'/60'/0'/0/0
if (index === 0) {
const wallet = ethers.Wallet.fromPhrase(mnemonic.trim())
return wallet.privateKey
}
// 对于其他索引,使用 HDNodeWallet
// 从助记词创建 Mnemonic 对象
const mnemonicObj = ethers.Mnemonic.fromPhrase(mnemonic.trim())
// 使用标准 BIP44 路径直接创建钱包:m/44'/60'/0'/0/index
// ethers.js v6: HDNodeWallet.fromMnemonic 可以直接指定路径
const derivationPath = `m/44'/60'/0'/0/${index}`
const wallet = ethers.HDNodeWallet.fromMnemonic(mnemonicObj, derivationPath)
return wallet.privateKey
} catch (error: any) {
// 如果直接指定路径失败,尝试分步派生
try {
const mnemonicObj = ethers.Mnemonic.fromPhrase(mnemonic.trim())
// 先创建根节点(不指定路径)
const rootNode = ethers.HDNodeWallet.fromMnemonic(mnemonicObj)
// 使用相对路径(不以 m/ 开头)
const relativePath = `44'/60'/0'/0/${index}`
const wallet = rootNode.derivePath(relativePath)
return wallet.privateKey
} catch (fallbackError: any) {
throw new Error(`无法从助记词导出私钥: ${error.message || error}`)
}
}
}
/**
* 验证助记词格式
*/
export function isValidMnemonic(mnemonic: string): boolean {
try {
if (!mnemonic || !mnemonic.trim()) {
return false
}
const words = mnemonic.trim().split(/\s+/)
// 助记词应该是 12 或 24 个单词
if (words.length !== 12 && words.length !== 24) {
return false
}
// 使用 ethers 验证助记词
ethers.Mnemonic.fromPhrase(mnemonic.trim())
return true
} catch {
return false
}
}
/**
* 验证钱包地址格式
*/
export function isValidWalletAddress(address: string): boolean {
return /^0x[a-fA-F0-9]{40}$/.test(address)
}
/**
* 验证私钥格式
*/
export function isValidPrivateKey(privateKey: string): boolean {
try {
const cleanKey = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey
return /^[a-fA-F0-9]{64}$/.test(cleanKey)
} catch {
return false
}
}