polycopy 0.1.0 → 0.1.1

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/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const init = require("./init-OIFlNHFx.js");
2
+ const init = require("./init-C5cwUXyD.js");
3
3
  async function main() {
4
4
  try {
5
5
  await init.runConfigWizard();
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const init = require("./init-OIFlNHFx.js");
2
+ const init = require("./init-C5cwUXyD.js");
3
3
  const grammy = require("grammy");
4
4
  const wallet = require("@ethersproject/wallet");
5
5
  const clobClient = require("@polymarket/clob-client");
@@ -204,13 +204,13 @@ class ClobClientWrapper {
204
204
  clobClient.OrderType.GTC
205
205
  );
206
206
  if (response.success) {
207
- init.logger.success(`买单创建成功: ${response.orderID}`);
207
+ init.logger.success(`买单已提交: ${response.orderID}`);
208
208
  return {
209
209
  success: true,
210
210
  orderId: response.orderID
211
211
  };
212
212
  } else {
213
- init.logger.error(`买单创建失败: ${response.errorMsg}`);
213
+ init.logger.error(`买单提交失败: ${response.errorMsg}`);
214
214
  return {
215
215
  success: false,
216
216
  orderId: "",
@@ -219,7 +219,7 @@ class ClobClientWrapper {
219
219
  }
220
220
  } catch (error) {
221
221
  const errorMsg = error instanceof Error ? error.message : String(error);
222
- init.logger.error(`买单创建异常: ${errorMsg}`);
222
+ init.logger.error(`买单提交失败: ${errorMsg}`);
223
223
  return {
224
224
  success: false,
225
225
  orderId: "",
@@ -410,7 +410,7 @@ class AssetFilter {
410
410
  this.traded.set(endTime, /* @__PURE__ */ new Set());
411
411
  }
412
412
  this.traded.get(endTime).add(assetId);
413
- init.logger.info(`已标记 assetId 交易完成: ${assetId.slice(0, 10)}...`);
413
+ init.logger.info(`已标记 assetId: ${assetId.slice(0, 10)}...`);
414
414
  }
415
415
  /**
416
416
  * 清理过期记录
@@ -530,10 +530,20 @@ class Redeemer {
530
530
  records = /* @__PURE__ */ new Map();
531
531
  intervalId;
532
532
  delayTimeoutId;
533
- running = false;
533
+ // stopped: 未启动; running: 正常轮询; paused: 限流等待恢复
534
+ state = "stopped";
535
+ scheduler;
534
536
  redeemFn = null;
535
537
  funderAddress = "";
536
538
  onRedeemSuccess = null;
539
+ constructor(scheduler = {
540
+ setInterval: setPromiseInterval__default.default,
541
+ clearInterval: setPromiseInterval.clearPromiseInterval,
542
+ setTimeout,
543
+ clearTimeout
544
+ }) {
545
+ this.scheduler = scheduler;
546
+ }
537
547
  /**
538
548
  * 启动 Redeemer
539
549
  * @param redeemFn 执行 Redeem 的函数(返回交易哈希)
@@ -541,26 +551,24 @@ class Redeemer {
541
551
  * @param onRedeemSuccess Redeem 成功后的回调(用于更新余额缓存)
542
552
  */
543
553
  start(redeemFn, funderAddress, onRedeemSuccess) {
544
- if (this.running) {
554
+ if (this.state !== "stopped") {
545
555
  return;
546
556
  }
547
557
  this.redeemFn = redeemFn;
548
558
  this.funderAddress = funderAddress;
549
559
  this.onRedeemSuccess = onRedeemSuccess || null;
550
- this.running = true;
551
- init.logger.info("自动 redeem 已启动");
552
- this.intervalId = setPromiseInterval__default.default(async () => {
553
- await this.runOnce();
554
- }, BASE_INTERVAL);
560
+ this.state = "running";
561
+ init.logger.success("自动 redeem 已启动");
562
+ this.startInterval();
555
563
  }
556
564
  /**
557
- * 启动定时任务
565
+ * 启动定时任务(仅在 running 状态生效)
558
566
  */
559
567
  startInterval() {
560
- if (!this.running || this.intervalId !== void 0) {
568
+ if (this.state !== "running" || this.intervalId !== void 0) {
561
569
  return;
562
570
  }
563
- this.intervalId = setPromiseInterval__default.default(async () => {
571
+ this.intervalId = this.scheduler.setInterval(async () => {
564
572
  await this.runOnce();
565
573
  }, BASE_INTERVAL);
566
574
  }
@@ -569,15 +577,19 @@ class Redeemer {
569
577
  */
570
578
  pauseAndRestart(delayMs) {
571
579
  if (this.intervalId !== void 0) {
572
- setPromiseInterval.clearPromiseInterval(this.intervalId);
580
+ this.scheduler.clearInterval(this.intervalId);
573
581
  this.intervalId = void 0;
574
582
  }
575
- this.delayTimeoutId = setTimeout(() => {
583
+ if (this.delayTimeoutId) {
584
+ this.scheduler.clearTimeout(this.delayTimeoutId);
576
585
  this.delayTimeoutId = void 0;
577
- if (this.running) {
578
- this.runOnce().finally(() => {
579
- this.startInterval();
580
- });
586
+ }
587
+ this.state = "paused";
588
+ this.delayTimeoutId = this.scheduler.setTimeout(() => {
589
+ this.delayTimeoutId = void 0;
590
+ if (this.state === "paused") {
591
+ this.state = "running";
592
+ this.startInterval();
581
593
  }
582
594
  }, delayMs);
583
595
  }
@@ -586,14 +598,14 @@ class Redeemer {
586
598
  */
587
599
  stop() {
588
600
  if (this.intervalId !== void 0) {
589
- setPromiseInterval.clearPromiseInterval(this.intervalId);
601
+ this.scheduler.clearInterval(this.intervalId);
590
602
  this.intervalId = void 0;
591
603
  }
592
604
  if (this.delayTimeoutId) {
593
- clearTimeout(this.delayTimeoutId);
605
+ this.scheduler.clearTimeout(this.delayTimeoutId);
594
606
  this.delayTimeoutId = void 0;
595
607
  }
596
- this.running = false;
608
+ this.state = "stopped";
597
609
  init.logger.info("Redeemer 已停止");
598
610
  }
599
611
  /**
@@ -621,7 +633,9 @@ class Redeemer {
621
633
  positions.push({
622
634
  conditionId: item.conditionId,
623
635
  assetId: item.asset,
624
- currentValue
636
+ currentValue,
637
+ slug: item.slug,
638
+ outcome: item.outcome
625
639
  });
626
640
  } else {
627
641
  break;
@@ -633,7 +647,7 @@ class Redeemer {
633
647
  * 执行一轮 Redeem
634
648
  */
635
649
  async runOnce() {
636
- if (!this.running || !this.redeemFn) {
650
+ if (this.state !== "running" || !this.redeemFn) {
637
651
  return;
638
652
  }
639
653
  let positions;
@@ -651,7 +665,16 @@ class Redeemer {
651
665
  init.logger.error(`获取仓位失败: ${errorMsg}`);
652
666
  return;
653
667
  }
654
- init.logger.info(`自动 redeem: ${positions.length} 个可 Redeem 仓位`);
668
+ const pendingRedeemablePositions = positions.filter((position) => {
669
+ const record = this.records.get(position.conditionId);
670
+ return !record?.success;
671
+ });
672
+ if (pendingRedeemablePositions.length > 0) {
673
+ this.logPositions(
674
+ `自动 redeem: 发现 ${pendingRedeemablePositions.length} 个可 Redeem 仓位`,
675
+ pendingRedeemablePositions.map((position) => ({ position }))
676
+ );
677
+ }
655
678
  for (const position of positions) {
656
679
  let record = this.records.get(position.conditionId);
657
680
  if (!record) {
@@ -679,11 +702,10 @@ conditionId: ${position.conditionId}
679
702
  }
680
703
  } else if (record.failedCount < MAX_MATCH_TIMES) {
681
704
  try {
682
- const txHash = await this.redeemFn(position.conditionId);
705
+ const txHash = await this.redeemFn?.(position.conditionId);
683
706
  record.success = true;
684
- init.logger.info(`Redeem 交易已提交: ${txHash}`);
707
+ init.logger.info(`Redeem 已执行: ${txHash}`);
685
708
  } catch (error) {
686
- record.failedCount++;
687
709
  const errorMsg = error instanceof Error ? error.message : String(error);
688
710
  if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
689
711
  const resetSeconds = this.parseResetSeconds(errorMsg);
@@ -692,6 +714,7 @@ conditionId: ${position.conditionId}
692
714
  this.pauseAndRestart((resetSeconds + 60) * 1e3);
693
715
  return;
694
716
  }
717
+ record.failedCount++;
695
718
  init.logger.error(
696
719
  `Redeem 异常 (第 ${record.failedCount} 次): ${errorMsg}`
697
720
  );
@@ -706,24 +729,49 @@ conditionId: ${position.conditionId}
706
729
  }
707
730
  }
708
731
  }
709
- const successList = [];
732
+ const successItems = [];
710
733
  for (const [conditionId, record] of this.records.entries()) {
711
734
  const stillExists = positions.some((p) => p.conditionId === conditionId);
712
735
  if (!stillExists) {
713
- successList.push(
714
- `${record.position.assetId.slice(0, 10)}... (${record.position.currentValue} USDC, matched: ${record.matchedCount})`
715
- );
736
+ successItems.push({
737
+ position: record.position,
738
+ matchedCount: record.matchedCount
739
+ });
716
740
  this.records.delete(conditionId);
717
741
  }
718
742
  }
719
- if (successList.length > 0) {
720
- init.logger.success(`Redeem 成功: ${successList.length} 个`);
721
- for (const item of successList) {
722
- init.logger.line("", `- ${item}`);
723
- }
743
+ if (successItems.length > 0) {
744
+ const successTitle = `自动 redeem: 成功 ${successItems.length} 个`;
745
+ const successLines = this.logPositions(successTitle, successItems);
746
+ notifier.success(successTitle, successLines.join("\n"));
724
747
  this.onRedeemSuccess?.();
725
748
  }
726
- init.logger.info(`redeem 记录: ${this.records.size} 个待确认`);
749
+ init.logger.info(`redeem 状态: ${this.records.size} 个待确认`);
750
+ }
751
+ /**
752
+ * 统一格式化仓位信息
753
+ */
754
+ formatPositionLine(position, matchedCount) {
755
+ const label = position.slug || `${position.assetId.slice(0, 10)}...`;
756
+ const outcomeLabel = position.outcome ? ` ${position.outcome}` : "";
757
+ const matchedLabel = matchedCount !== void 0 ? ` (matched: ${matchedCount})` : "";
758
+ return `${label}${outcomeLabel}: ${position.currentValue} USDC${matchedLabel}`;
759
+ }
760
+ /**
761
+ * 统一输出 Redeem 仓位列表
762
+ */
763
+ logPositions(title, items) {
764
+ init.logger.info(title);
765
+ const lines = [];
766
+ for (const item of items) {
767
+ const line = this.formatPositionLine(
768
+ item.position,
769
+ item.matchedCount
770
+ );
771
+ lines.push(line);
772
+ init.logger.line("", `- ${line}`);
773
+ }
774
+ return lines;
727
775
  }
728
776
  /**
729
777
  * 解析 429 错误中的 resets in 时间
@@ -862,8 +910,7 @@ class Trader {
862
910
  }
863
911
  this.assetFilter.markTraded(assetId, endTime);
864
912
  if (sellPrice === 1) {
865
- init.logger.success(`买单已提交: ${buyResult.orderId}`);
866
- init.logger.line("", "sellPrice = 1,成交后将自动 redeem");
913
+ init.logger.info("sellPrice = 1,成交后将自动 redeem");
867
914
  return;
868
915
  }
869
916
  this.watchAndHandle(
@@ -990,10 +1037,10 @@ class Trader {
990
1037
  init.logger.info(`创建卖单: ${size.toFixed(4)} 份 @ ${price}`);
991
1038
  const sellResult = await this.client.createSellOrder(assetId, price, size);
992
1039
  if (sellResult.success) {
993
- init.logger.success(`卖单已创建: ${sellResult.orderId}`);
1040
+ init.logger.success(`卖单已提交: ${sellResult.orderId}`);
994
1041
  } else {
995
- init.logger.error(`卖单创建失败: ${sellResult.errorMsg}`);
996
- notifier.error("卖单创建失败", `错误: ${sellResult.errorMsg}`);
1042
+ init.logger.error(`卖单提交失败: ${sellResult.errorMsg}`);
1043
+ notifier.error("卖单提交失败", `错误: ${sellResult.errorMsg}`);
997
1044
  }
998
1045
  }
999
1046
  /**
@@ -462,7 +462,7 @@ class TelegramListener {
462
462
  logger.info("正在启动 Bot...");
463
463
  const botInfo = await this.bot.api.getMe();
464
464
  const botId = botInfo.id;
465
- logger.success(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
465
+ logger.info(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
466
466
  await this.checkAndDisplayChannels(botId);
467
467
  if (onReady) {
468
468
  await onReady();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polycopy",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "polycopy test",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  "cli": "node dist/cli.js",
16
16
  "preuninstall": "node dist/cli.js stop 2>/dev/null || true",
17
17
  "prepublishOnly": "npm run build",
18
- "test": "echo \"Error: no test specified\" && exit 1"
18
+ "test": "HOME=$PWD node --import tsx tests/redeemer.test.ts",
19
+ "test:redeemer": "HOME=$PWD node --import tsx tests/redeemer.test.ts"
19
20
  },
20
21
  "dependencies": {
21
22
  "@polymarket/builder-relayer-client": "^0.0.8",
@@ -33,6 +34,7 @@
33
34
  "@types/lodash": "^4.17.23",
34
35
  "@types/node": "^20.0.0",
35
36
  "prettier": "^3.7.4",
37
+ "tsx": "^4.19.2",
36
38
  "typescript": "^5.9.3",
37
39
  "vite": "^7.3.1",
38
40
  "vite-tsconfig-paths": "^6.0.4"