diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/PositionCheckService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/PositionCheckService.kt index f170dbe..63cb6eb 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/PositionCheckService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/PositionCheckService.kt @@ -51,6 +51,9 @@ class PositionCheckService( // 记录已发送通知的仓位(避免重复推送) private val notifiedRedeemablePositions = ConcurrentHashMap() // "accountId_marketId_outcomeIndex" -> lastNotificationTime + // 记录已处理的赎回仓位(避免重复赎回) + private val processedRedeemablePositions = ConcurrentHashMap() // "accountId_marketId_outcomeIndex" -> lastProcessTime + // 记录已发送提示的配置(避免重复推送) private val notifiedConfigs = ConcurrentHashMap() // accountId/copyTradingId -> lastNotificationTime @@ -139,6 +142,14 @@ class PositionCheckService( notifiedRedeemablePositions.remove(key) } + // 清理过期的已处理赎回仓位记录 + val expiredProcessed = processedRedeemablePositions.entries.filter { (_, timestamp) -> + (now - timestamp) > expireTime + } + expiredProcessed.forEach { (key, _) -> + processedRedeemablePositions.remove(key) + } + // 清理过期的配置通知记录 val expiredConfigs = notifiedConfigs.entries.filter { (_, timestamp) -> (now - timestamp) > expireTime @@ -147,8 +158,8 @@ class PositionCheckService( notifiedConfigs.remove(key) } - if (expiredPositions.isNotEmpty() || expiredConfigs.isNotEmpty()) { - logger.debug("清理过期缓存: positions=${expiredPositions.size}, configs=${expiredConfigs.size}") + if (expiredPositions.isNotEmpty() || expiredProcessed.isNotEmpty() || expiredConfigs.isNotEmpty()) { + logger.debug("清理过期缓存: positions=${expiredPositions.size}, processed=${expiredProcessed.size}, configs=${expiredConfigs.size}") } } @@ -235,9 +246,28 @@ class PositionCheckService( continue } + // 过滤掉已经处理过的仓位(去重,避免重复赎回) + val now = System.currentTimeMillis() + val positionsToRedeem = positions.filter { position -> + val positionKey = "${accountId}_${position.marketId}_${position.outcomeIndex ?: 0}" + val lastProcessed = processedRedeemablePositions[positionKey] + // 如果最近30分钟内已经处理过,跳过(避免重复赎回) + if (lastProcessed != null && (now - lastProcessed) < 1800000) { // 30分钟 + logger.debug("跳过已处理的赎回仓位: $positionKey (上次处理时间: ${lastProcessed})") + false + } else { + true + } + } + + if (positionsToRedeem.isEmpty()) { + logger.debug("所有仓位都已处理过,跳过赎回: accountId=$accountId") + continue + } + // 先执行赎回(不查找订单) val redeemRequest = com.wrbug.polymarketbot.dto.PositionRedeemRequest( - positions = positions.map { position -> + positions = positionsToRedeem.map { position -> com.wrbug.polymarketbot.dto.AccountRedeemPositionItem( accountId = accountId, marketId = position.marketId, @@ -250,10 +280,16 @@ class PositionCheckService( val redeemResult = accountService.redeemPositions(redeemRequest) redeemResult.fold( onSuccess = { response -> - logger.info("自动赎回成功: accountId=$accountId, redeemedCount=${positions.size}, totalValue=${response.totalRedeemedValue}") + logger.info("自动赎回成功: accountId=$accountId, redeemedCount=${positionsToRedeem.size}, totalValue=${response.totalRedeemedValue}") + + // 记录已处理的仓位(避免重复赎回) + for (position in positionsToRedeem) { + val positionKey = "${accountId}_${position.marketId}_${position.outcomeIndex ?: 0}" + processedRedeemablePositions[positionKey] = now + } // 赎回成功后,再查找订单并更新订单状态 - for (position in positions) { + for (position in positionsToRedeem) { // 查找相同仓位的未卖出订单(remaining_quantity > 0) val unmatchedOrders = mutableListOf() for (copyTrading in copyTradings) { diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..784ef0a Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png new file mode 100644 index 0000000..18a1002 Binary files /dev/null and b/frontend/public/favicon-16x16.png differ diff --git a/frontend/public/favicon-32x32.png b/frontend/public/favicon-32x32.png new file mode 100644 index 0000000..2399589 Binary files /dev/null and b/frontend/public/favicon-32x32.png differ diff --git a/frontend/public/icon-512x512.png b/frontend/public/icon-512x512.png new file mode 100644 index 0000000..a2b4a9e Binary files /dev/null and b/frontend/public/icon-512x512.png differ diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 17db5b3..c3af30f 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -17,7 +17,8 @@ import { SettingOutlined, GithubOutlined, TwitterOutlined, - CheckCircleOutlined + CheckCircleOutlined, + SendOutlined } from '@ant-design/icons' import type { MenuProps } from 'antd' import type { ReactNode } from 'react' @@ -197,12 +198,13 @@ const Layout: React.FC = ({ children }) => { size="normal" darkMode={true} /> -
+
@@ -210,14 +212,24 @@ const Layout: React.FC = ({ children }) => { href="https://x.com/quant_tr" target="_blank" rel="noopener noreferrer" - style={{ color: '#fff', fontSize: '18px' }} + style={{ color: '#fff', fontSize: '16px', display: 'flex', alignItems: 'center' }} + title="Twitter" > + + +
@@ -265,23 +277,30 @@ const Layout: React.FC = ({ children }) => { }} >
- PolyHermes - = ({ children }) => { items={menuItems} onClick={handleMenuClick} style={{ - height: 'calc(100vh - 64px)', + height: 'calc(100vh - 100px)', borderRight: 0, overflowY: 'auto' }} diff --git a/frontend/src/pages/AccountDetail.tsx b/frontend/src/pages/AccountDetail.tsx index e3b3273..69f72ca 100644 --- a/frontend/src/pages/AccountDetail.tsx +++ b/frontend/src/pages/AccountDetail.tsx @@ -72,23 +72,12 @@ const AccountDetail: React.FC = () => { setEditLoading(true) try { - // 构建更新请求,空字符串转换为 undefined(不修改) + // 构建更新请求,只支持编辑账户名称 const updateData: any = { accountId: account.id, accountName: values.accountName || undefined, } - // 只有非空字符串才更新 API 凭证 - if (values.apiKey && values.apiKey.trim()) { - updateData.apiKey = values.apiKey.trim() - } - if (values.apiSecret && values.apiSecret.trim()) { - updateData.apiSecret = values.apiSecret.trim() - } - if (values.apiPassphrase && values.apiPassphrase.trim()) { - updateData.apiPassphrase = values.apiPassphrase.trim() - } - await updateAccount(updateData) message.success(t('account.updateSuccess')) @@ -335,42 +324,18 @@ const AccountDetail: React.FC = () => { size={isMobile ? 'middle' : 'large'} > - - - - - - - - - - - - - + diff --git a/frontend/src/pages/AccountList.tsx b/frontend/src/pages/AccountList.tsx index 2e08c6a..c7b9bbd 100644 --- a/frontend/src/pages/AccountList.tsx +++ b/frontend/src/pages/AccountList.tsx @@ -171,23 +171,12 @@ const AccountList: React.FC = () => { setEditLoading(true) try { - // 构建更新请求,空字符串转换为 undefined(不修改) + // 构建更新请求,只支持编辑账户名称 const updateData: any = { accountId: editAccount.id, accountName: values.accountName || undefined } - // 只有非空字符串才更新 API 凭证 - if (values.apiKey && values.apiKey.trim()) { - updateData.apiKey = values.apiKey.trim() - } - if (values.apiSecret && values.apiSecret.trim()) { - updateData.apiSecret = values.apiSecret.trim() - } - if (values.apiPassphrase && values.apiPassphrase.trim()) { - updateData.apiPassphrase = values.apiPassphrase.trim() - } - await updateAccount(updateData) message.success(t('accountList.updateSuccess')) @@ -778,42 +767,18 @@ const AccountList: React.FC = () => { size={isMobile ? 'middle' : 'large'} > - - - - - - - - - - - - - + diff --git a/frontend/src/pages/CopyTradingList.tsx b/frontend/src/pages/CopyTradingList.tsx index a36917a..458aab7 100644 --- a/frontend/src/pages/CopyTradingList.tsx +++ b/frontend/src/pages/CopyTradingList.tsx @@ -265,15 +265,6 @@ const CopyTradingList: React.FC = () => { fixed: 'right' as const, render: (_: any, record: CopyTrading) => { const menuItems: MenuProps['items'] = [ - { - key: 'edit', - label: t('common.edit') || '编辑', - icon: , - onClick: () => navigate(`/copy-trading/edit/${record.id}`) - }, - { - type: 'divider' - }, { key: 'buyOrders', label: t('copyTradingList.buyOrders') || '买入订单', diff --git a/polymarket-demo/icon-512x512.png b/polymarket-demo/icon-512x512.png new file mode 100644 index 0000000..32ddf7d Binary files /dev/null and b/polymarket-demo/icon-512x512.png differ diff --git a/polymarket-demo/package-lock.json b/polymarket-demo/package-lock.json index 73d7938..5cfba50 100644 --- a/polymarket-demo/package-lock.json +++ b/polymarket-demo/package-lock.json @@ -14,6 +14,7 @@ }, "devDependencies": { "@types/node": "^18.7.18", + "canvas": "^3.2.0", "ts-node": "^10.9.1", "typescript": "^4.8.3" } @@ -957,11 +958,42 @@ "form-data": "^4.0.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bech32": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", @@ -977,6 +1009,30 @@ "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==" }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -989,6 +1045,26 @@ "node": ">= 0.4" } }, + "node_modules/canvas": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.0.tgz", + "integrity": "sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1006,6 +1082,30 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1014,6 +1114,15 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1050,6 +1159,15 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1162,6 +1280,15 @@ "npm": ">=3" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1196,6 +1323,12 @@ "node": ">= 6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1239,6 +1372,12 @@ "node": ">= 0.4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1305,11 +1444,37 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/is-hex-prefixed": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", @@ -1362,6 +1527,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -1372,16 +1549,215 @@ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-hex-prefix": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", @@ -1394,6 +1770,43 @@ "npm": ">=3" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -1442,6 +1855,18 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -1470,12 +1895,24 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/polymarket-demo/package.json b/polymarket-demo/package.json index b064a0a..37a6114 100644 --- a/polymarket-demo/package.json +++ b/polymarket-demo/package.json @@ -6,7 +6,8 @@ "scripts": { "test": "ts-node src/createOrder.ts", "build": "tsc", - "start": "node dist/createOrder.js" + "start": "node dist/createOrder.js", + "generate-icon": "node scripts/generate-icon.js" }, "keywords": [ "polymarket", @@ -21,8 +22,8 @@ }, "devDependencies": { "@types/node": "^18.7.18", + "canvas": "^3.2.0", "ts-node": "^10.9.1", "typescript": "^4.8.3" } } - diff --git a/polymarket-demo/scripts/generate-icon.js b/polymarket-demo/scripts/generate-icon.js new file mode 100644 index 0000000..17fc66b --- /dev/null +++ b/polymarket-demo/scripts/generate-icon.js @@ -0,0 +1,107 @@ +const { createCanvas } = require('canvas'); +const fs = require('fs'); +const path = require('path'); + +// 创建 512x512 的画布 +const canvas = createCanvas(512, 512); +const ctx = canvas.getContext('2d'); + +// 设置背景色(标题背景色 #001529) +ctx.fillStyle = '#001529'; +ctx.fillRect(0, 0, 512, 512); + +// 设置图标颜色(深色背景使用较亮的颜色) +const gradientColors = { + start: '#69c0ff', + end: '#b37feb' +}; + +// 创建渐变 +const gradient = ctx.createLinearGradient(0, 0, 512, 512); +gradient.addColorStop(0, gradientColors.start); +gradient.addColorStop(1, gradientColors.end); + +// 计算缩放比例(原始 viewBox 是 64x64,需要缩放到 512x512) +const scale = 512 / 64; +const centerX = 256; +const centerY = 256; + +// 保存当前状态 +ctx.save(); + +// 移动到中心并缩放 +ctx.translate(centerX, centerY); +ctx.scale(scale, scale); +ctx.translate(-32, -32); // 偏移到原点 + +// 绘制左侧箭头(指向中心) +ctx.beginPath(); +ctx.moveTo(16, 32); +ctx.lineTo(8, 24); +ctx.lineTo(8, 40); +ctx.closePath(); +ctx.fillStyle = gradient; +ctx.fill(); + +// 绘制中心连接线 +ctx.beginPath(); +ctx.moveTo(20, 32); +ctx.lineTo(44, 32); +ctx.strokeStyle = gradient; +ctx.lineWidth = 3 / scale; // 调整线宽 +ctx.lineCap = 'round'; +ctx.stroke(); + +// 绘制右侧箭头(指向中心) +ctx.beginPath(); +ctx.moveTo(48, 32); +ctx.lineTo(56, 24); +ctx.lineTo(56, 40); +ctx.closePath(); +ctx.fillStyle = gradient; +ctx.fill(); + +// 绘制中心圆点 +ctx.beginPath(); +ctx.arc(32, 32, 5, 0, Math.PI * 2); +ctx.fillStyle = gradient; +ctx.fill(); + +// 绘制装饰性数据流弧线(上方) +ctx.beginPath(); +ctx.moveTo(20, 20); +ctx.quadraticCurveTo(32, 14, 44, 20); +ctx.strokeStyle = gradient; +ctx.lineWidth = 2 / scale; +ctx.globalAlpha = 0.5; +ctx.lineCap = 'round'; +ctx.stroke(); + +// 绘制装饰性数据流弧线(下方) +ctx.beginPath(); +ctx.moveTo(20, 44); +ctx.quadraticCurveTo(32, 50, 44, 44); +ctx.strokeStyle = gradient; +ctx.lineWidth = 2 / scale; +ctx.globalAlpha = 0.5; +ctx.lineCap = 'round'; +ctx.stroke(); + +// 恢复状态 +ctx.restore(); + +// 保存为 PNG(输出到当前目录) +const outputPath = path.join(__dirname, '../icon-512x512.png'); +// 确保输出目录存在 +const outputDir = path.dirname(outputPath); +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +const buffer = canvas.toBuffer('image/png'); +fs.writeFileSync(outputPath, buffer); + +console.log(`✅ 图标已生成: ${outputPath}`); +console.log(` 尺寸: 512x512 像素`); +console.log(` 背景色: #001529`); +