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-BbFF27h8.js");
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-BbFF27h8.js");
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
- const tx = {
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
- [tx],
911
- "Polymarket redeem positions"
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
- for (const position of positions) {
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
- if (record.success) {
1229
- record.matchedCount++;
1230
- if (record.matchedCount >= MAX_MATCH_TIMES) {
1231
- logger.error(
1232
- `Redeem 失败: 执行成功但仍存在 ${record.matchedCount} (conditionId: ${position.conditionId.slice(0, 10)}...)`
1233
- );
1234
- telegramService.error(
1235
- "Redeem 失败",
1236
- `执行成功但仓位仍存在 ${record.matchedCount}
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);
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
- 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
- }
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
- logger.error(
1257
- `Redeem 异常 ( ${record.failedCount}): ${errorMsg}`
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
- const stillExists = positions.some((p) => p.conditionId === conditionId);
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
- if (successItems.length > 0) {
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.executeRedeem.bind(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
- * 执行 Redeem(供 Redeemer 调用)
1638
+ * 批量执行 Redeem(供 Redeemer 调用)
1581
1639
  */
1582
- async executeRedeem(conditionId) {
1640
+ async executeRedeemBatch(conditionIds) {
1583
1641
  if (!this.relayClient.isInitialized()) {
1584
1642
  throw new Error("RelayClient 未初始化");
1585
1643
  }
1586
- return this.relayClient.redeem(conditionId);
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
- const distIndexMarker = "dist/index.js";
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") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polycopy",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "polycopy test",
5
5
  "main": "dist/index.js",
6
6
  "bin": {