polycopy 0.1.8 → 0.1.9
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,76 @@ class Redeemer {
|
|
|
1225
1265
|
this.records.set(position.conditionId, record);
|
|
1226
1266
|
}
|
|
1227
1267
|
record.position = position;
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
+
for (let i = 0; i < pendingRecords.length; i += this.batchSize) {
|
|
1295
|
+
const batch = pendingRecords.slice(i, i + this.batchSize);
|
|
1296
|
+
const conditionIds = batch.map((record) => record.position.conditionId);
|
|
1297
|
+
if (conditionIds.length === 0) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
const txHash = await this.redeemFn?.(conditionIds);
|
|
1302
|
+
batch.forEach((record) => {
|
|
1244
1303
|
record.success = true;
|
|
1245
|
-
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1304
|
+
record.matchedCount = 0;
|
|
1305
|
+
});
|
|
1306
|
+
logger.info(`Redeem 已执行 (批量 ${batch.length}): ${txHash}`);
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1309
|
+
if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
|
|
1310
|
+
const resetSeconds = this.parseResetSeconds(errorMsg);
|
|
1311
|
+
logger.warning(`Redeem 限流,${resetSeconds} 秒后重试`);
|
|
1312
|
+
telegramService.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
|
|
1313
|
+
this.pauseAndRestart((resetSeconds + 60) * 1e3);
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
batch.forEach((record) => {
|
|
1255
1317
|
record.failedCount++;
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1318
|
+
});
|
|
1319
|
+
logger.error(`Redeem 批量异常 (批量 ${batch.length}): ${errorMsg}`);
|
|
1320
|
+
batch.forEach((record) => {
|
|
1259
1321
|
if (record.failedCount >= MAX_MATCH_TIMES) {
|
|
1260
1322
|
telegramService.error(
|
|
1261
1323
|
"Redeem 异常",
|
|
1262
1324
|
`重试 ${record.failedCount} 次仍失败
|
|
1263
|
-
conditionId: ${position.conditionId}
|
|
1325
|
+
conditionId: ${record.position.conditionId}
|
|
1264
1326
|
错误: ${errorMsg}`
|
|
1265
1327
|
);
|
|
1266
1328
|
}
|
|
1267
|
-
}
|
|
1329
|
+
});
|
|
1268
1330
|
}
|
|
1269
1331
|
}
|
|
1332
|
+
return true;
|
|
1333
|
+
}
|
|
1334
|
+
collectSuccessRecords(currentIds) {
|
|
1270
1335
|
const successItems = [];
|
|
1271
1336
|
for (const [conditionId, record] of this.records.entries()) {
|
|
1272
|
-
|
|
1273
|
-
if (!stillExists) {
|
|
1337
|
+
if (!currentIds.has(conditionId)) {
|
|
1274
1338
|
successItems.push({
|
|
1275
1339
|
position: record.position,
|
|
1276
1340
|
matchedCount: record.matchedCount
|
|
@@ -1278,13 +1342,7 @@ conditionId: ${position.conditionId}
|
|
|
1278
1342
|
this.records.delete(conditionId);
|
|
1279
1343
|
}
|
|
1280
1344
|
}
|
|
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} 个待确认`);
|
|
1345
|
+
return successItems;
|
|
1288
1346
|
}
|
|
1289
1347
|
/**
|
|
1290
1348
|
* 统一格式化仓位信息
|
|
@@ -1371,7 +1429,7 @@ class Trader {
|
|
|
1371
1429
|
return;
|
|
1372
1430
|
}
|
|
1373
1431
|
this.redeemer.start(
|
|
1374
|
-
this.
|
|
1432
|
+
this.executeRedeemBatch.bind(this),
|
|
1375
1433
|
this.config.polymarket.funderAddress || "",
|
|
1376
1434
|
() => this.balanceCache.invalidate()
|
|
1377
1435
|
);
|
|
@@ -1577,13 +1635,13 @@ class Trader {
|
|
|
1577
1635
|
}
|
|
1578
1636
|
}
|
|
1579
1637
|
/**
|
|
1580
|
-
*
|
|
1638
|
+
* 批量执行 Redeem(供 Redeemer 调用)
|
|
1581
1639
|
*/
|
|
1582
|
-
async
|
|
1640
|
+
async executeRedeemBatch(conditionIds) {
|
|
1583
1641
|
if (!this.relayClient.isInitialized()) {
|
|
1584
1642
|
throw new Error("RelayClient 未初始化");
|
|
1585
1643
|
}
|
|
1586
|
-
return this.relayClient.
|
|
1644
|
+
return this.relayClient.redeemBatch(conditionIds);
|
|
1587
1645
|
}
|
|
1588
1646
|
/**
|
|
1589
1647
|
* 停止交易模块
|
|
@@ -1654,8 +1712,12 @@ ${message}`);
|
|
|
1654
1712
|
process.on("beforeExit", async () => {
|
|
1655
1713
|
await logger.flush();
|
|
1656
1714
|
});
|
|
1657
|
-
logger.section("PolyMarket 跟单机器人");
|
|
1658
1715
|
const config = await processLock.ensureConfig();
|
|
1716
|
+
if (process.env.POLYCOPY_DAEMON === "1") {
|
|
1717
|
+
enterDaemonMode();
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
logger.section("PolyMarket 跟单机器人");
|
|
1659
1721
|
telegramService.init(
|
|
1660
1722
|
config.telegram.botToken,
|
|
1661
1723
|
config.telegram.adminChatId,
|
|
@@ -1721,10 +1783,6 @@ ${message}`);
|
|
|
1721
1783
|
}
|
|
1722
1784
|
}
|
|
1723
1785
|
});
|
|
1724
|
-
if (process.env.POLYCOPY_DAEMON === "1") {
|
|
1725
|
-
enterDaemonMode();
|
|
1726
|
-
return;
|
|
1727
|
-
}
|
|
1728
1786
|
let isShuttingDown = false;
|
|
1729
1787
|
const shutdown = async () => {
|
|
1730
1788
|
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") {
|