polycopy 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const child_process = require("child_process");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const processLock = require("./process-lock-
|
|
6
|
+
const processLock = require("./process-lock-y_yjnMCr.js");
|
|
7
7
|
function _interopNamespace(e) {
|
|
8
8
|
if (e && e.__esModule) return e;
|
|
9
9
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const processLock = require("./process-lock-
|
|
2
|
+
const processLock = require("./process-lock-y_yjnMCr.js");
|
|
3
3
|
const grammy = require("grammy");
|
|
4
4
|
const log4js = require("log4js");
|
|
5
5
|
const path = require("path");
|
|
@@ -874,15 +874,7 @@ class RelayClientWrapper {
|
|
|
874
874
|
isInitialized() {
|
|
875
875
|
return this.initialized;
|
|
876
876
|
}
|
|
877
|
-
|
|
878
|
-
* 执行 Redeem
|
|
879
|
-
* @param conditionId 条件 ID
|
|
880
|
-
* @returns 交易哈希
|
|
881
|
-
*/
|
|
882
|
-
async redeem(conditionId) {
|
|
883
|
-
if (!this.relayClient || !this.initialized) {
|
|
884
|
-
throw new Error("RelayClient 未初始化");
|
|
885
|
-
}
|
|
877
|
+
buildRedeemTransaction(conditionId) {
|
|
886
878
|
const calldata = viem.encodeFunctionData({
|
|
887
879
|
abi: [
|
|
888
880
|
{
|
|
@@ -901,14 +893,27 @@ class RelayClientWrapper {
|
|
|
901
893
|
functionName: "redeemPositions",
|
|
902
894
|
args: [USDC_ADDRESS, viem.zeroHash, conditionId, [1n, 2n]]
|
|
903
895
|
});
|
|
904
|
-
|
|
896
|
+
return {
|
|
905
897
|
to: CTF_ADDRESS,
|
|
906
898
|
value: "0",
|
|
907
899
|
data: calldata
|
|
908
900
|
};
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* 批量执行 Redeem
|
|
904
|
+
* @param conditionIds 条件 ID 列表
|
|
905
|
+
* @returns 交易哈希
|
|
906
|
+
*/
|
|
907
|
+
async redeemBatch(conditionIds) {
|
|
908
|
+
if (!this.relayClient || !this.initialized) {
|
|
909
|
+
throw new Error("RelayClient 未初始化");
|
|
910
|
+
}
|
|
911
|
+
const transactions = conditionIds.map(
|
|
912
|
+
(conditionId) => this.buildRedeemTransaction(conditionId)
|
|
913
|
+
);
|
|
909
914
|
const response = await this.relayClient.execute(
|
|
910
|
-
|
|
911
|
-
|
|
915
|
+
transactions,
|
|
916
|
+
`Polymarket redeem positions (${transactions.length})`
|
|
912
917
|
);
|
|
913
918
|
const result = await response.wait();
|
|
914
919
|
if (!result) {
|
|
@@ -918,6 +923,14 @@ class RelayClientWrapper {
|
|
|
918
923
|
}
|
|
919
924
|
return result.transactionHash;
|
|
920
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* 执行 Redeem(兼容单个)
|
|
928
|
+
* @param conditionId 条件 ID
|
|
929
|
+
* @returns 交易哈希
|
|
930
|
+
*/
|
|
931
|
+
async redeem(conditionId) {
|
|
932
|
+
return this.redeemBatch([conditionId]);
|
|
933
|
+
}
|
|
921
934
|
}
|
|
922
935
|
class AssetFilter {
|
|
923
936
|
// endTime -> Set<assetId>
|
|
@@ -1059,6 +1072,7 @@ class OrderWatcher {
|
|
|
1059
1072
|
}
|
|
1060
1073
|
const DATA_API_HOST = "https://data-api.polymarket.com";
|
|
1061
1074
|
const BASE_INTERVAL = 5 * 60 * 1e3;
|
|
1075
|
+
const DEFAULT_BATCH_SIZE = 5;
|
|
1062
1076
|
const MAX_MATCH_TIMES = 3;
|
|
1063
1077
|
class Redeemer {
|
|
1064
1078
|
// conditionId -> RedeemRecord
|
|
@@ -1069,6 +1083,7 @@ class Redeemer {
|
|
|
1069
1083
|
state = "stopped";
|
|
1070
1084
|
scheduler;
|
|
1071
1085
|
redeemFn = null;
|
|
1086
|
+
batchSize = DEFAULT_BATCH_SIZE;
|
|
1072
1087
|
funderAddress = "";
|
|
1073
1088
|
onRedeemSuccess = null;
|
|
1074
1089
|
constructor(scheduler = {
|
|
@@ -1090,6 +1105,10 @@ class Redeemer {
|
|
|
1090
1105
|
return;
|
|
1091
1106
|
}
|
|
1092
1107
|
this.redeemFn = redeemFn;
|
|
1108
|
+
const envBatchSize = Number(process.env.POLYCOPY_REDEEM_BATCH_SIZE || "");
|
|
1109
|
+
if (!Number.isNaN(envBatchSize) && envBatchSize > 0) {
|
|
1110
|
+
this.batchSize = Math.floor(envBatchSize);
|
|
1111
|
+
}
|
|
1093
1112
|
this.funderAddress = funderAddress;
|
|
1094
1113
|
this.onRedeemSuccess = onRedeemSuccess || null;
|
|
1095
1114
|
this.state = "running";
|
|
@@ -1203,6 +1222,24 @@ class Redeemer {
|
|
|
1203
1222
|
logger.error(`获取仓位失败: ${errorMsg}`);
|
|
1204
1223
|
return;
|
|
1205
1224
|
}
|
|
1225
|
+
const currentIds = new Set(positions.map((position) => position.conditionId));
|
|
1226
|
+
this.logPendingPositions(positions);
|
|
1227
|
+
const pendingRecords = this.buildRecords(positions);
|
|
1228
|
+
this.updateMatchedRecords(currentIds);
|
|
1229
|
+
const shouldContinue = await this.executeBatches(pendingRecords);
|
|
1230
|
+
if (!shouldContinue) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
const successItems = this.collectSuccessRecords(currentIds);
|
|
1234
|
+
if (successItems.length > 0) {
|
|
1235
|
+
const successTitle = `自动 redeem: 成功 ${successItems.length} 个`;
|
|
1236
|
+
const successLines = this.logPositions(successTitle, successItems);
|
|
1237
|
+
telegramService.success(successTitle, successLines.join("\n"));
|
|
1238
|
+
this.onRedeemSuccess?.();
|
|
1239
|
+
}
|
|
1240
|
+
logger.info(`redeem 状态: ${this.records.size} 个待确认`);
|
|
1241
|
+
}
|
|
1242
|
+
logPendingPositions(positions) {
|
|
1206
1243
|
const pendingRedeemablePositions = positions.filter((position) => {
|
|
1207
1244
|
const record = this.records.get(position.conditionId);
|
|
1208
1245
|
return !record?.success;
|
|
@@ -1212,8 +1249,11 @@ class Redeemer {
|
|
|
1212
1249
|
`自动 redeem: 发现 ${pendingRedeemablePositions.length} 个可 Redeem 仓位`,
|
|
1213
1250
|
pendingRedeemablePositions.map((position) => ({ position }))
|
|
1214
1251
|
);
|
|
1252
|
+
logger.info(`Redeem 批量大小: ${this.batchSize}`);
|
|
1215
1253
|
}
|
|
1216
|
-
|
|
1254
|
+
}
|
|
1255
|
+
buildRecords(positions) {
|
|
1256
|
+
return positions.map((position) => {
|
|
1217
1257
|
let record = this.records.get(position.conditionId);
|
|
1218
1258
|
if (!record) {
|
|
1219
1259
|
record = {
|
|
@@ -1225,52 +1265,74 @@ class Redeemer {
|
|
|
1225
1265
|
this.records.set(position.conditionId, record);
|
|
1226
1266
|
}
|
|
1227
1267
|
record.position = position;
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1268
|
+
return record;
|
|
1269
|
+
}).filter((record) => !record.success && record.failedCount < MAX_MATCH_TIMES);
|
|
1270
|
+
}
|
|
1271
|
+
updateMatchedRecords(currentIds) {
|
|
1272
|
+
for (const record of this.records.values()) {
|
|
1273
|
+
if (!record.success) {
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
if (!currentIds.has(record.position.conditionId)) {
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
record.matchedCount++;
|
|
1280
|
+
if (record.matchedCount >= MAX_MATCH_TIMES) {
|
|
1281
|
+
logger.error(
|
|
1282
|
+
`Redeem 失败: 执行成功但仍存在 ${record.matchedCount} 次 (conditionId: ${record.position.conditionId.slice(0, 10)}...)`
|
|
1283
|
+
);
|
|
1284
|
+
telegramService.error(
|
|
1285
|
+
"Redeem 失败",
|
|
1286
|
+
`执行成功但仓位仍存在 ${record.matchedCount} 次
|
|
1287
|
+
conditionId: ${record.position.conditionId}
|
|
1288
|
+
价值: ${record.position.currentValue} USDC`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
async executeBatches(pendingRecords) {
|
|
1294
|
+
const batch = pendingRecords.slice(0, this.batchSize);
|
|
1295
|
+
const conditionIds = batch.map((record) => record.position.conditionId);
|
|
1296
|
+
if (conditionIds.length === 0) {
|
|
1297
|
+
return true;
|
|
1298
|
+
}
|
|
1299
|
+
try {
|
|
1300
|
+
const txHash = await this.redeemFn?.(conditionIds);
|
|
1301
|
+
batch.forEach((record) => {
|
|
1302
|
+
record.success = true;
|
|
1303
|
+
record.matchedCount = 0;
|
|
1304
|
+
});
|
|
1305
|
+
logger.info(`Redeem 已执行 (批量 ${batch.length}): ${txHash}`);
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1308
|
+
if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
|
|
1309
|
+
const resetSeconds = this.parseResetSeconds(errorMsg);
|
|
1310
|
+
logger.warning(`Redeem 限流,${resetSeconds} 秒后重试`);
|
|
1311
|
+
telegramService.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
|
|
1312
|
+
this.pauseAndRestart((resetSeconds + 60) * 1e3);
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
batch.forEach((record) => {
|
|
1316
|
+
record.failedCount++;
|
|
1317
|
+
});
|
|
1318
|
+
logger.error(`Redeem 批量异常 (批量 ${batch.length}): ${errorMsg}`);
|
|
1319
|
+
batch.forEach((record) => {
|
|
1320
|
+
if (record.failedCount >= MAX_MATCH_TIMES) {
|
|
1234
1321
|
telegramService.error(
|
|
1235
|
-
"Redeem
|
|
1236
|
-
|
|
1237
|
-
conditionId: ${position.conditionId}
|
|
1238
|
-
价值: ${position.currentValue} USDC`
|
|
1239
|
-
);
|
|
1240
|
-
}
|
|
1241
|
-
} else if (record.failedCount < MAX_MATCH_TIMES) {
|
|
1242
|
-
try {
|
|
1243
|
-
const txHash = await this.redeemFn?.(position.conditionId);
|
|
1244
|
-
record.success = true;
|
|
1245
|
-
logger.info(`Redeem 已执行: ${txHash}`);
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1248
|
-
if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
|
|
1249
|
-
const resetSeconds = this.parseResetSeconds(errorMsg);
|
|
1250
|
-
logger.warning(`Redeem 限流,${resetSeconds} 秒后重试`);
|
|
1251
|
-
telegramService.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
|
|
1252
|
-
this.pauseAndRestart((resetSeconds + 60) * 1e3);
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1255
|
-
record.failedCount++;
|
|
1256
|
-
logger.error(
|
|
1257
|
-
`Redeem 异常 (第 ${record.failedCount} 次): ${errorMsg}`
|
|
1258
|
-
);
|
|
1259
|
-
if (record.failedCount >= MAX_MATCH_TIMES) {
|
|
1260
|
-
telegramService.error(
|
|
1261
|
-
"Redeem 异常",
|
|
1262
|
-
`重试 ${record.failedCount} 次仍失败
|
|
1263
|
-
conditionId: ${position.conditionId}
|
|
1322
|
+
"Redeem 异常",
|
|
1323
|
+
`重试 ${record.failedCount} 次仍失败
|
|
1324
|
+
conditionId: ${record.position.conditionId}
|
|
1264
1325
|
错误: ${errorMsg}`
|
|
1265
|
-
|
|
1266
|
-
}
|
|
1326
|
+
);
|
|
1267
1327
|
}
|
|
1268
|
-
}
|
|
1328
|
+
});
|
|
1269
1329
|
}
|
|
1330
|
+
return true;
|
|
1331
|
+
}
|
|
1332
|
+
collectSuccessRecords(currentIds) {
|
|
1270
1333
|
const successItems = [];
|
|
1271
1334
|
for (const [conditionId, record] of this.records.entries()) {
|
|
1272
|
-
|
|
1273
|
-
if (!stillExists) {
|
|
1335
|
+
if (!currentIds.has(conditionId)) {
|
|
1274
1336
|
successItems.push({
|
|
1275
1337
|
position: record.position,
|
|
1276
1338
|
matchedCount: record.matchedCount
|
|
@@ -1278,13 +1340,7 @@ conditionId: ${position.conditionId}
|
|
|
1278
1340
|
this.records.delete(conditionId);
|
|
1279
1341
|
}
|
|
1280
1342
|
}
|
|
1281
|
-
|
|
1282
|
-
const successTitle = `自动 redeem: 成功 ${successItems.length} 个`;
|
|
1283
|
-
const successLines = this.logPositions(successTitle, successItems);
|
|
1284
|
-
telegramService.success(successTitle, successLines.join("\n"));
|
|
1285
|
-
this.onRedeemSuccess?.();
|
|
1286
|
-
}
|
|
1287
|
-
logger.info(`redeem 状态: ${this.records.size} 个待确认`);
|
|
1343
|
+
return successItems;
|
|
1288
1344
|
}
|
|
1289
1345
|
/**
|
|
1290
1346
|
* 统一格式化仓位信息
|
|
@@ -1371,7 +1427,7 @@ class Trader {
|
|
|
1371
1427
|
return;
|
|
1372
1428
|
}
|
|
1373
1429
|
this.redeemer.start(
|
|
1374
|
-
this.
|
|
1430
|
+
this.executeRedeemBatch.bind(this),
|
|
1375
1431
|
this.config.polymarket.funderAddress || "",
|
|
1376
1432
|
() => this.balanceCache.invalidate()
|
|
1377
1433
|
);
|
|
@@ -1577,13 +1633,13 @@ class Trader {
|
|
|
1577
1633
|
}
|
|
1578
1634
|
}
|
|
1579
1635
|
/**
|
|
1580
|
-
*
|
|
1636
|
+
* 批量执行 Redeem(供 Redeemer 调用)
|
|
1581
1637
|
*/
|
|
1582
|
-
async
|
|
1638
|
+
async executeRedeemBatch(conditionIds) {
|
|
1583
1639
|
if (!this.relayClient.isInitialized()) {
|
|
1584
1640
|
throw new Error("RelayClient 未初始化");
|
|
1585
1641
|
}
|
|
1586
|
-
return this.relayClient.
|
|
1642
|
+
return this.relayClient.redeemBatch(conditionIds);
|
|
1587
1643
|
}
|
|
1588
1644
|
/**
|
|
1589
1645
|
* 停止交易模块
|
|
@@ -1654,8 +1710,12 @@ ${message}`);
|
|
|
1654
1710
|
process.on("beforeExit", async () => {
|
|
1655
1711
|
await logger.flush();
|
|
1656
1712
|
});
|
|
1657
|
-
logger.section("PolyMarket 跟单机器人");
|
|
1658
1713
|
const config = await processLock.ensureConfig();
|
|
1714
|
+
if (process.env.POLYCOPY_DAEMON === "1") {
|
|
1715
|
+
enterDaemonMode();
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
logger.section("PolyMarket 跟单机器人");
|
|
1659
1719
|
telegramService.init(
|
|
1660
1720
|
config.telegram.botToken,
|
|
1661
1721
|
config.telegram.adminChatId,
|
|
@@ -1721,10 +1781,6 @@ ${message}`);
|
|
|
1721
1781
|
}
|
|
1722
1782
|
}
|
|
1723
1783
|
});
|
|
1724
|
-
if (process.env.POLYCOPY_DAEMON === "1") {
|
|
1725
|
-
enterDaemonMode();
|
|
1726
|
-
return;
|
|
1727
|
-
}
|
|
1728
1784
|
let isShuttingDown = false;
|
|
1729
1785
|
const shutdown = async () => {
|
|
1730
1786
|
if (isShuttingDown) return;
|
|
@@ -467,12 +467,13 @@ async function ensureConfig() {
|
|
|
467
467
|
}
|
|
468
468
|
return config;
|
|
469
469
|
}
|
|
470
|
+
const normalizePath = (value) => value.replace(/\\/g, "/").toLowerCase();
|
|
471
|
+
const distIndexPath = normalizePath(DIST_INDEX);
|
|
472
|
+
const distCliPath = normalizePath(DIST_CLI);
|
|
470
473
|
const normalizeCommand = (command) => command.replace(/\\/g, "/");
|
|
471
474
|
const isPolycopyCommand = (command) => {
|
|
472
475
|
const normalized = normalizeCommand(command).toLowerCase();
|
|
473
|
-
|
|
474
|
-
const distCliMarker = "dist/cli.js";
|
|
475
|
-
return normalized.includes(DIST_INDEX.toLowerCase()) || normalized.includes(DIST_CLI.toLowerCase()) || normalized.includes(distIndexMarker) || normalized.includes(distCliMarker);
|
|
476
|
+
return normalized.includes(distIndexPath) || normalized.includes(distCliPath) || normalized.includes("dist/cli.js");
|
|
476
477
|
};
|
|
477
478
|
const findPolycopyPids = () => {
|
|
478
479
|
if (process.platform === "win32") {
|