@wopr-network/platform-core 1.66.1 → 1.67.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/billing/crypto/btc/checkout.d.ts +4 -0
- package/dist/billing/crypto/btc/checkout.js +1 -2
- package/dist/billing/crypto/btc/index.d.ts +0 -4
- package/dist/billing/crypto/btc/index.js +0 -2
- package/dist/billing/crypto/evm/__tests__/checkout.test.js +8 -11
- package/dist/billing/crypto/evm/__tests__/eth-checkout.test.js +15 -1
- package/dist/billing/crypto/evm/checkout.d.ts +2 -0
- package/dist/billing/crypto/evm/checkout.js +1 -2
- package/dist/billing/crypto/evm/eth-checkout.d.ts +13 -2
- package/dist/billing/crypto/evm/eth-checkout.js +2 -4
- package/dist/billing/crypto/evm/eth-settler.d.ts +1 -1
- package/dist/billing/crypto/evm/index.d.ts +2 -8
- package/dist/billing/crypto/evm/index.js +0 -3
- package/dist/billing/crypto/evm/types.d.ts +16 -0
- package/dist/billing/crypto/index.d.ts +1 -6
- package/dist/billing/crypto/index.js +2 -3
- package/dist/billing/crypto/types.d.ts +0 -43
- package/dist/billing/crypto/types.js +1 -24
- package/package.json +1 -5
- package/src/billing/crypto/btc/checkout.ts +3 -2
- package/src/billing/crypto/btc/index.ts +0 -4
- package/src/billing/crypto/evm/__tests__/checkout.test.ts +10 -12
- package/src/billing/crypto/evm/__tests__/eth-checkout.test.ts +17 -1
- package/src/billing/crypto/evm/__tests__/eth-settler.test.ts +1 -1
- package/src/billing/crypto/evm/checkout.ts +3 -2
- package/src/billing/crypto/evm/eth-checkout.ts +15 -6
- package/src/billing/crypto/evm/eth-settler.ts +1 -1
- package/src/billing/crypto/evm/index.ts +8 -7
- package/src/billing/crypto/evm/types.ts +17 -0
- package/src/billing/crypto/index.ts +14 -12
- package/src/billing/crypto/types.ts +0 -63
- package/dist/billing/crypto/__tests__/address-gen.test.d.ts +0 -1
- package/dist/billing/crypto/__tests__/address-gen.test.js +0 -219
- package/dist/billing/crypto/__tests__/key-server.test.d.ts +0 -1
- package/dist/billing/crypto/__tests__/key-server.test.js +0 -363
- package/dist/billing/crypto/__tests__/watcher-service.test.d.ts +0 -1
- package/dist/billing/crypto/__tests__/watcher-service.test.js +0 -174
- package/dist/billing/crypto/address-gen.d.ts +0 -24
- package/dist/billing/crypto/address-gen.js +0 -176
- package/dist/billing/crypto/btc/__tests__/watcher.test.d.ts +0 -1
- package/dist/billing/crypto/btc/__tests__/watcher.test.js +0 -170
- package/dist/billing/crypto/btc/watcher.d.ts +0 -44
- package/dist/billing/crypto/btc/watcher.js +0 -118
- package/dist/billing/crypto/evm/__tests__/eth-watcher.test.d.ts +0 -1
- package/dist/billing/crypto/evm/__tests__/eth-watcher.test.js +0 -167
- package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.d.ts +0 -1
- package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.js +0 -159
- package/dist/billing/crypto/evm/__tests__/watcher.test.d.ts +0 -1
- package/dist/billing/crypto/evm/__tests__/watcher.test.js +0 -145
- package/dist/billing/crypto/evm/eth-watcher.d.ts +0 -66
- package/dist/billing/crypto/evm/eth-watcher.js +0 -121
- package/dist/billing/crypto/evm/watcher.d.ts +0 -51
- package/dist/billing/crypto/evm/watcher.js +0 -156
- package/dist/billing/crypto/key-server-entry.d.ts +0 -1
- package/dist/billing/crypto/key-server-entry.js +0 -122
- package/dist/billing/crypto/key-server.d.ts +0 -32
- package/dist/billing/crypto/key-server.js +0 -348
- package/dist/billing/crypto/oracle/__tests__/chainlink.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/chainlink.test.js +0 -83
- package/dist/billing/crypto/oracle/__tests__/coingecko.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/coingecko.test.js +0 -65
- package/dist/billing/crypto/oracle/__tests__/composite.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/composite.test.js +0 -48
- package/dist/billing/crypto/oracle/__tests__/convert.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/convert.test.js +0 -61
- package/dist/billing/crypto/oracle/__tests__/fixed.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/fixed.test.js +0 -20
- package/dist/billing/crypto/oracle/chainlink.d.ts +0 -26
- package/dist/billing/crypto/oracle/chainlink.js +0 -62
- package/dist/billing/crypto/oracle/coingecko.d.ts +0 -22
- package/dist/billing/crypto/oracle/coingecko.js +0 -71
- package/dist/billing/crypto/oracle/composite.d.ts +0 -14
- package/dist/billing/crypto/oracle/composite.js +0 -34
- package/dist/billing/crypto/oracle/convert.d.ts +0 -30
- package/dist/billing/crypto/oracle/convert.js +0 -51
- package/dist/billing/crypto/oracle/fixed.d.ts +0 -10
- package/dist/billing/crypto/oracle/fixed.js +0 -22
- package/dist/billing/crypto/oracle/index.d.ts +0 -9
- package/dist/billing/crypto/oracle/index.js +0 -6
- package/dist/billing/crypto/oracle/types.d.ts +0 -22
- package/dist/billing/crypto/oracle/types.js +0 -7
- package/dist/billing/crypto/plugin/__tests__/integration.test.d.ts +0 -1
- package/dist/billing/crypto/plugin/__tests__/integration.test.js +0 -58
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.d.ts +0 -1
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.js +0 -46
- package/dist/billing/crypto/plugin/__tests__/registry.test.d.ts +0 -1
- package/dist/billing/crypto/plugin/__tests__/registry.test.js +0 -49
- package/dist/billing/crypto/plugin/index.d.ts +0 -2
- package/dist/billing/crypto/plugin/index.js +0 -1
- package/dist/billing/crypto/plugin/interfaces.d.ts +0 -97
- package/dist/billing/crypto/plugin/interfaces.js +0 -2
- package/dist/billing/crypto/plugin/registry.d.ts +0 -8
- package/dist/billing/crypto/plugin/registry.js +0 -21
- package/dist/billing/crypto/plugin-watcher-service.d.ts +0 -32
- package/dist/billing/crypto/plugin-watcher-service.js +0 -113
- package/dist/billing/crypto/tron/__tests__/address-convert.test.d.ts +0 -1
- package/dist/billing/crypto/tron/__tests__/address-convert.test.js +0 -55
- package/dist/billing/crypto/tron/address-convert.d.ts +0 -14
- package/dist/billing/crypto/tron/address-convert.js +0 -93
- package/dist/billing/crypto/watcher-service.d.ts +0 -55
- package/dist/billing/crypto/watcher-service.js +0 -438
- package/src/billing/crypto/__tests__/address-gen.test.ts +0 -264
- package/src/billing/crypto/__tests__/key-server.test.ts +0 -395
- package/src/billing/crypto/__tests__/watcher-service.test.ts +0 -242
- package/src/billing/crypto/address-gen.ts +0 -185
- package/src/billing/crypto/btc/__tests__/watcher.test.ts +0 -201
- package/src/billing/crypto/btc/watcher.ts +0 -161
- package/src/billing/crypto/evm/__tests__/eth-watcher.test.ts +0 -190
- package/src/billing/crypto/evm/__tests__/watcher-confirmations.test.ts +0 -191
- package/src/billing/crypto/evm/__tests__/watcher.test.ts +0 -167
- package/src/billing/crypto/evm/eth-watcher.ts +0 -182
- package/src/billing/crypto/evm/watcher.ts +0 -204
- package/src/billing/crypto/key-server-entry.ts +0 -144
- package/src/billing/crypto/key-server.ts +0 -444
- package/src/billing/crypto/oracle/__tests__/chainlink.test.ts +0 -107
- package/src/billing/crypto/oracle/__tests__/coingecko.test.ts +0 -75
- package/src/billing/crypto/oracle/__tests__/composite.test.ts +0 -61
- package/src/billing/crypto/oracle/__tests__/convert.test.ts +0 -74
- package/src/billing/crypto/oracle/__tests__/fixed.test.ts +0 -23
- package/src/billing/crypto/oracle/chainlink.ts +0 -86
- package/src/billing/crypto/oracle/coingecko.ts +0 -96
- package/src/billing/crypto/oracle/composite.ts +0 -35
- package/src/billing/crypto/oracle/convert.ts +0 -53
- package/src/billing/crypto/oracle/fixed.ts +0 -25
- package/src/billing/crypto/oracle/index.ts +0 -9
- package/src/billing/crypto/oracle/types.ts +0 -28
- package/src/billing/crypto/plugin/__tests__/integration.test.ts +0 -64
- package/src/billing/crypto/plugin/__tests__/interfaces.test.ts +0 -51
- package/src/billing/crypto/plugin/__tests__/registry.test.ts +0 -58
- package/src/billing/crypto/plugin/index.ts +0 -17
- package/src/billing/crypto/plugin/interfaces.ts +0 -106
- package/src/billing/crypto/plugin/registry.ts +0 -26
- package/src/billing/crypto/plugin-watcher-service.ts +0 -148
- package/src/billing/crypto/tron/__tests__/address-convert.test.ts +0 -67
- package/src/billing/crypto/tron/address-convert.ts +0 -89
- package/src/billing/crypto/watcher-service.ts +0 -549
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { EvmWatcher } from "../watcher.js";
|
|
3
|
-
const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
4
|
-
function mockTransferLog(to, amount, blockNumber) {
|
|
5
|
-
return {
|
|
6
|
-
address: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
7
|
-
topics: [
|
|
8
|
-
TRANSFER_TOPIC,
|
|
9
|
-
`0x${"00".repeat(12)}${"ab".repeat(20)}`,
|
|
10
|
-
`0x${"00".repeat(12)}${to.slice(2).toLowerCase()}`,
|
|
11
|
-
],
|
|
12
|
-
data: `0x${amount.toString(16).padStart(64, "0")}`,
|
|
13
|
-
blockNumber: `0x${blockNumber.toString(16)}`,
|
|
14
|
-
transactionHash: `0x${"ff".repeat(32)}`,
|
|
15
|
-
logIndex: "0x0",
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function makeCursorStore() {
|
|
19
|
-
const cursors = new Map();
|
|
20
|
-
return {
|
|
21
|
-
get: vi.fn().mockImplementation(async (id) => cursors.get(id) ?? null),
|
|
22
|
-
save: vi.fn().mockImplementation(async (id, val) => {
|
|
23
|
-
cursors.set(id, val);
|
|
24
|
-
}),
|
|
25
|
-
hasProcessedTx: vi.fn().mockResolvedValue(false),
|
|
26
|
-
markProcessedTx: vi.fn().mockResolvedValue(undefined),
|
|
27
|
-
getConfirmationCount: vi.fn().mockResolvedValue(null),
|
|
28
|
-
saveConfirmationCount: vi.fn().mockResolvedValue(undefined),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
describe("EvmWatcher — intermediate confirmations", () => {
|
|
32
|
-
it("emits events with confirmation count", async () => {
|
|
33
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
34
|
-
const events = [];
|
|
35
|
-
// Base has confirmations: 1. Latest block is 105. Log at block 103 -> 2 confirmations.
|
|
36
|
-
const mockRpc = vi
|
|
37
|
-
.fn()
|
|
38
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`) // eth_blockNumber
|
|
39
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10000000n, 103)]); // eth_getLogs
|
|
40
|
-
const watcher = new EvmWatcher({
|
|
41
|
-
chain: "base",
|
|
42
|
-
token: "USDC",
|
|
43
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
44
|
-
decimals: 6,
|
|
45
|
-
confirmations: 1,
|
|
46
|
-
rpcCall: mockRpc,
|
|
47
|
-
fromBlock: 100,
|
|
48
|
-
watchedAddresses: [toAddr],
|
|
49
|
-
cursorStore: makeCursorStore(),
|
|
50
|
-
onPayment: (evt) => {
|
|
51
|
-
events.push(evt);
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
await watcher.poll();
|
|
55
|
-
expect(events).toHaveLength(1);
|
|
56
|
-
expect(events[0].confirmations).toBe(2); // 105 - 103
|
|
57
|
-
expect(events[0].confirmationsRequired).toBe(1); // Base chain config
|
|
58
|
-
});
|
|
59
|
-
it("skips event when confirmation count unchanged", async () => {
|
|
60
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
61
|
-
const events = [];
|
|
62
|
-
const cursorStore = makeCursorStore();
|
|
63
|
-
cursorStore.getConfirmationCount.mockResolvedValue(2);
|
|
64
|
-
const mockRpc = vi
|
|
65
|
-
.fn()
|
|
66
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`)
|
|
67
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10000000n, 103)]);
|
|
68
|
-
const watcher = new EvmWatcher({
|
|
69
|
-
chain: "base",
|
|
70
|
-
token: "USDC",
|
|
71
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
72
|
-
decimals: 6,
|
|
73
|
-
confirmations: 1,
|
|
74
|
-
rpcCall: mockRpc,
|
|
75
|
-
fromBlock: 100,
|
|
76
|
-
watchedAddresses: [toAddr],
|
|
77
|
-
cursorStore,
|
|
78
|
-
onPayment: (evt) => {
|
|
79
|
-
events.push(evt);
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
await watcher.poll();
|
|
83
|
-
expect(events).toHaveLength(0);
|
|
84
|
-
});
|
|
85
|
-
it("re-emits when confirmations increase", async () => {
|
|
86
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
87
|
-
const events = [];
|
|
88
|
-
const cursorStore = makeCursorStore();
|
|
89
|
-
cursorStore.getConfirmationCount.mockResolvedValue(1);
|
|
90
|
-
const mockRpc = vi
|
|
91
|
-
.fn()
|
|
92
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`)
|
|
93
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10000000n, 103)]);
|
|
94
|
-
const watcher = new EvmWatcher({
|
|
95
|
-
chain: "base",
|
|
96
|
-
token: "USDC",
|
|
97
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
98
|
-
decimals: 6,
|
|
99
|
-
confirmations: 1,
|
|
100
|
-
rpcCall: mockRpc,
|
|
101
|
-
fromBlock: 100,
|
|
102
|
-
watchedAddresses: [toAddr],
|
|
103
|
-
cursorStore,
|
|
104
|
-
onPayment: (evt) => {
|
|
105
|
-
events.push(evt);
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
await watcher.poll();
|
|
109
|
-
expect(events).toHaveLength(1);
|
|
110
|
-
expect(events[0].confirmations).toBe(2);
|
|
111
|
-
});
|
|
112
|
-
it("includes confirmationsRequired from chain config", async () => {
|
|
113
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
114
|
-
const events = [];
|
|
115
|
-
const mockRpc = vi
|
|
116
|
-
.fn()
|
|
117
|
-
.mockResolvedValueOnce(`0x${(110).toString(16)}`)
|
|
118
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10000000n, 105)]);
|
|
119
|
-
const watcher = new EvmWatcher({
|
|
120
|
-
chain: "base",
|
|
121
|
-
token: "USDC",
|
|
122
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
123
|
-
decimals: 6,
|
|
124
|
-
confirmations: 1,
|
|
125
|
-
rpcCall: mockRpc,
|
|
126
|
-
fromBlock: 100,
|
|
127
|
-
watchedAddresses: [toAddr],
|
|
128
|
-
cursorStore: makeCursorStore(),
|
|
129
|
-
onPayment: (evt) => {
|
|
130
|
-
events.push(evt);
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
await watcher.poll();
|
|
134
|
-
expect(events).toHaveLength(1);
|
|
135
|
-
expect(events[0].confirmationsRequired).toBe(1); // Base chain config
|
|
136
|
-
});
|
|
137
|
-
it("saves confirmation count after emitting", async () => {
|
|
138
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
139
|
-
const cursorStore = makeCursorStore();
|
|
140
|
-
const mockRpc = vi
|
|
141
|
-
.fn()
|
|
142
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`)
|
|
143
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10000000n, 103)]);
|
|
144
|
-
const watcher = new EvmWatcher({
|
|
145
|
-
chain: "base",
|
|
146
|
-
token: "USDC",
|
|
147
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
148
|
-
decimals: 6,
|
|
149
|
-
confirmations: 1,
|
|
150
|
-
rpcCall: mockRpc,
|
|
151
|
-
fromBlock: 100,
|
|
152
|
-
watchedAddresses: [toAddr],
|
|
153
|
-
cursorStore,
|
|
154
|
-
onPayment: () => { },
|
|
155
|
-
});
|
|
156
|
-
await watcher.poll();
|
|
157
|
-
expect(cursorStore.saveConfirmationCount).toHaveBeenCalledWith(expect.any(String), expect.stringContaining("0x"), 2);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { EvmWatcher } from "../watcher.js";
|
|
3
|
-
const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
4
|
-
function mockTransferLog(to, amount, blockNumber) {
|
|
5
|
-
return {
|
|
6
|
-
address: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
7
|
-
topics: [
|
|
8
|
-
TRANSFER_TOPIC,
|
|
9
|
-
`0x${"00".repeat(12)}${"ab".repeat(20)}`, // from (padded)
|
|
10
|
-
`0x${"00".repeat(12)}${to.slice(2).toLowerCase()}`, // to (padded)
|
|
11
|
-
],
|
|
12
|
-
data: `0x${amount.toString(16).padStart(64, "0")}`,
|
|
13
|
-
blockNumber: `0x${blockNumber.toString(16)}`,
|
|
14
|
-
transactionHash: `0x${"ff".repeat(32)}`,
|
|
15
|
-
logIndex: "0x0",
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
describe("EvmWatcher", () => {
|
|
19
|
-
it("parses Transfer log into EvmPaymentEvent", async () => {
|
|
20
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
21
|
-
const events = [];
|
|
22
|
-
const mockRpc = vi
|
|
23
|
-
.fn()
|
|
24
|
-
.mockResolvedValueOnce(`0x${(102).toString(16)}`)
|
|
25
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10000000n, 99)]);
|
|
26
|
-
const watcher = new EvmWatcher({
|
|
27
|
-
chain: "base",
|
|
28
|
-
token: "USDC",
|
|
29
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
30
|
-
decimals: 6,
|
|
31
|
-
confirmations: 1,
|
|
32
|
-
rpcCall: mockRpc,
|
|
33
|
-
fromBlock: 99,
|
|
34
|
-
watchedAddresses: [toAddr],
|
|
35
|
-
onPayment: (evt) => {
|
|
36
|
-
events.push(evt);
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
await watcher.poll();
|
|
40
|
-
expect(events).toHaveLength(1);
|
|
41
|
-
expect(events[0].amountUsdCents).toBe(1000);
|
|
42
|
-
expect(events[0].to).toMatch(/^0x/);
|
|
43
|
-
});
|
|
44
|
-
it("advances cursor after processing", async () => {
|
|
45
|
-
const mockRpc = vi
|
|
46
|
-
.fn()
|
|
47
|
-
.mockResolvedValueOnce(`0x${(200).toString(16)}`)
|
|
48
|
-
.mockResolvedValueOnce([]);
|
|
49
|
-
const watcher = new EvmWatcher({
|
|
50
|
-
chain: "base",
|
|
51
|
-
token: "USDC",
|
|
52
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
53
|
-
decimals: 6,
|
|
54
|
-
confirmations: 1,
|
|
55
|
-
rpcCall: mockRpc,
|
|
56
|
-
fromBlock: 100,
|
|
57
|
-
watchedAddresses: ["0xdeadbeef"],
|
|
58
|
-
onPayment: vi.fn(),
|
|
59
|
-
});
|
|
60
|
-
await watcher.poll();
|
|
61
|
-
expect(watcher.cursor).toBeGreaterThan(100);
|
|
62
|
-
});
|
|
63
|
-
it("skips blocks not yet confirmed", async () => {
|
|
64
|
-
const events = [];
|
|
65
|
-
// latest = 50, cursor = 50 → latest < cursor is false, but range is empty (50..50)
|
|
66
|
-
// With intermediate confirmations, we still scan the range but find no logs
|
|
67
|
-
const mockRpc = vi
|
|
68
|
-
.fn()
|
|
69
|
-
.mockResolvedValueOnce(`0x${(50).toString(16)}`) // eth_blockNumber
|
|
70
|
-
.mockResolvedValueOnce([]); // eth_getLogs (empty)
|
|
71
|
-
const watcher = new EvmWatcher({
|
|
72
|
-
chain: "base",
|
|
73
|
-
token: "USDC",
|
|
74
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
75
|
-
decimals: 6,
|
|
76
|
-
confirmations: 1,
|
|
77
|
-
rpcCall: mockRpc,
|
|
78
|
-
fromBlock: 50,
|
|
79
|
-
watchedAddresses: ["0xdeadbeef"],
|
|
80
|
-
onPayment: (evt) => {
|
|
81
|
-
events.push(evt);
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
await watcher.poll();
|
|
85
|
-
expect(events).toHaveLength(0);
|
|
86
|
-
});
|
|
87
|
-
it("processes multiple logs in one poll", async () => {
|
|
88
|
-
const addr1 = `0x${"aa".repeat(20)}`;
|
|
89
|
-
const addr2 = `0x${"bb".repeat(20)}`;
|
|
90
|
-
const events = [];
|
|
91
|
-
const mockRpc = vi
|
|
92
|
-
.fn()
|
|
93
|
-
.mockResolvedValueOnce(`0x${(110).toString(16)}`)
|
|
94
|
-
.mockResolvedValueOnce([mockTransferLog(addr1, 5000000n, 105), mockTransferLog(addr2, 20000000n, 107)]);
|
|
95
|
-
const watcher = new EvmWatcher({
|
|
96
|
-
chain: "base",
|
|
97
|
-
token: "USDC",
|
|
98
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
99
|
-
decimals: 6,
|
|
100
|
-
confirmations: 1,
|
|
101
|
-
rpcCall: mockRpc,
|
|
102
|
-
fromBlock: 100,
|
|
103
|
-
watchedAddresses: [addr1, addr2],
|
|
104
|
-
onPayment: (evt) => {
|
|
105
|
-
events.push(evt);
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
await watcher.poll();
|
|
109
|
-
expect(events).toHaveLength(2);
|
|
110
|
-
expect(events[0].amountUsdCents).toBe(500);
|
|
111
|
-
expect(events[1].amountUsdCents).toBe(2000);
|
|
112
|
-
});
|
|
113
|
-
it("does nothing when no new blocks", async () => {
|
|
114
|
-
const mockRpc = vi.fn().mockResolvedValueOnce(`0x${(99).toString(16)}`);
|
|
115
|
-
const watcher = new EvmWatcher({
|
|
116
|
-
chain: "base",
|
|
117
|
-
token: "USDC",
|
|
118
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
119
|
-
decimals: 6,
|
|
120
|
-
confirmations: 1,
|
|
121
|
-
rpcCall: mockRpc,
|
|
122
|
-
fromBlock: 100,
|
|
123
|
-
watchedAddresses: ["0xdeadbeef"],
|
|
124
|
-
onPayment: vi.fn(),
|
|
125
|
-
});
|
|
126
|
-
await watcher.poll();
|
|
127
|
-
expect(watcher.cursor).toBe(100);
|
|
128
|
-
expect(mockRpc).toHaveBeenCalledTimes(1);
|
|
129
|
-
});
|
|
130
|
-
it("early-returns when no watched addresses are set", async () => {
|
|
131
|
-
const mockRpc = vi.fn();
|
|
132
|
-
const watcher = new EvmWatcher({
|
|
133
|
-
chain: "base",
|
|
134
|
-
token: "USDC",
|
|
135
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
136
|
-
decimals: 6,
|
|
137
|
-
confirmations: 1,
|
|
138
|
-
rpcCall: mockRpc,
|
|
139
|
-
fromBlock: 0,
|
|
140
|
-
onPayment: vi.fn(),
|
|
141
|
-
});
|
|
142
|
-
await watcher.poll();
|
|
143
|
-
expect(mockRpc).not.toHaveBeenCalled(); // no RPC calls at all
|
|
144
|
-
});
|
|
145
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { IWatcherCursorStore } from "../cursor-store.js";
|
|
2
|
-
import type { IPriceOracle } from "../oracle/types.js";
|
|
3
|
-
import type { EvmChain } from "./types.js";
|
|
4
|
-
type RpcCall = (method: string, params: unknown[]) => Promise<unknown>;
|
|
5
|
-
/** Event emitted on each confirmation increment for a native ETH deposit. */
|
|
6
|
-
export interface EthPaymentEvent {
|
|
7
|
-
readonly chain: EvmChain;
|
|
8
|
-
readonly from: string;
|
|
9
|
-
readonly to: string;
|
|
10
|
-
/** Raw value in wei (BigInt as string for serialization). */
|
|
11
|
-
readonly valueWei: string;
|
|
12
|
-
/** USD cents equivalent at detection time (integer). */
|
|
13
|
-
readonly amountUsdCents: number;
|
|
14
|
-
readonly txHash: string;
|
|
15
|
-
readonly blockNumber: number;
|
|
16
|
-
/** Current confirmation count (latest block - tx block). */
|
|
17
|
-
readonly confirmations: number;
|
|
18
|
-
/** Required confirmations for this chain. */
|
|
19
|
-
readonly confirmationsRequired: number;
|
|
20
|
-
}
|
|
21
|
-
export interface EthWatcherOpts {
|
|
22
|
-
chain: EvmChain;
|
|
23
|
-
rpcCall: RpcCall;
|
|
24
|
-
oracle: IPriceOracle;
|
|
25
|
-
fromBlock: number;
|
|
26
|
-
onPayment: (event: EthPaymentEvent) => void | Promise<void>;
|
|
27
|
-
watchedAddresses?: string[];
|
|
28
|
-
cursorStore?: IWatcherCursorStore;
|
|
29
|
-
/** Required confirmations (from DB). */
|
|
30
|
-
confirmations: number;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Native ETH transfer watcher.
|
|
34
|
-
*
|
|
35
|
-
* Unlike the ERC-20 EvmWatcher which uses eth_getLogs for Transfer events,
|
|
36
|
-
* this scans blocks for transactions where `to` matches a watched deposit
|
|
37
|
-
* address and `value > 0`.
|
|
38
|
-
*
|
|
39
|
-
* Scans up to latest block (not just confirmed) to detect pending txs.
|
|
40
|
-
* Emits events on each confirmation increment. Only advances cursor
|
|
41
|
-
* past fully-confirmed blocks.
|
|
42
|
-
*/
|
|
43
|
-
export declare class EthWatcher {
|
|
44
|
-
private _cursor;
|
|
45
|
-
private readonly chain;
|
|
46
|
-
private readonly rpc;
|
|
47
|
-
private readonly oracle;
|
|
48
|
-
private readonly onPayment;
|
|
49
|
-
private readonly confirmations;
|
|
50
|
-
private readonly cursorStore?;
|
|
51
|
-
private readonly watcherId;
|
|
52
|
-
private _watchedAddresses;
|
|
53
|
-
constructor(opts: EthWatcherOpts);
|
|
54
|
-
/** Load cursor from DB. Call once at startup before first poll. */
|
|
55
|
-
init(): Promise<void>;
|
|
56
|
-
setWatchedAddresses(addresses: string[]): void;
|
|
57
|
-
get cursor(): number;
|
|
58
|
-
/**
|
|
59
|
-
* Poll for native ETH transfers to watched addresses, including unconfirmed blocks.
|
|
60
|
-
*
|
|
61
|
-
* Scans from cursor to latest block. Emits events with current confirmation count.
|
|
62
|
-
* Re-emits on each confirmation increment. Only advances cursor past fully-confirmed blocks.
|
|
63
|
-
*/
|
|
64
|
-
poll(): Promise<void>;
|
|
65
|
-
}
|
|
66
|
-
export {};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { nativeToCents } from "../oracle/convert.js";
|
|
2
|
-
/**
|
|
3
|
-
* Native ETH transfer watcher.
|
|
4
|
-
*
|
|
5
|
-
* Unlike the ERC-20 EvmWatcher which uses eth_getLogs for Transfer events,
|
|
6
|
-
* this scans blocks for transactions where `to` matches a watched deposit
|
|
7
|
-
* address and `value > 0`.
|
|
8
|
-
*
|
|
9
|
-
* Scans up to latest block (not just confirmed) to detect pending txs.
|
|
10
|
-
* Emits events on each confirmation increment. Only advances cursor
|
|
11
|
-
* past fully-confirmed blocks.
|
|
12
|
-
*/
|
|
13
|
-
export class EthWatcher {
|
|
14
|
-
_cursor;
|
|
15
|
-
chain;
|
|
16
|
-
rpc;
|
|
17
|
-
oracle;
|
|
18
|
-
onPayment;
|
|
19
|
-
confirmations;
|
|
20
|
-
cursorStore;
|
|
21
|
-
watcherId;
|
|
22
|
-
_watchedAddresses;
|
|
23
|
-
constructor(opts) {
|
|
24
|
-
this.chain = opts.chain;
|
|
25
|
-
this.rpc = opts.rpcCall;
|
|
26
|
-
this.oracle = opts.oracle;
|
|
27
|
-
this._cursor = opts.fromBlock;
|
|
28
|
-
this.onPayment = opts.onPayment;
|
|
29
|
-
this.confirmations = opts.confirmations;
|
|
30
|
-
this.cursorStore = opts.cursorStore;
|
|
31
|
-
this.watcherId = `eth:${opts.chain}`;
|
|
32
|
-
this._watchedAddresses = new Set((opts.watchedAddresses ?? []).map((a) => a.toLowerCase()));
|
|
33
|
-
}
|
|
34
|
-
/** Load cursor from DB. Call once at startup before first poll. */
|
|
35
|
-
async init() {
|
|
36
|
-
if (!this.cursorStore)
|
|
37
|
-
return;
|
|
38
|
-
const saved = await this.cursorStore.get(this.watcherId);
|
|
39
|
-
if (saved !== null)
|
|
40
|
-
this._cursor = saved;
|
|
41
|
-
}
|
|
42
|
-
setWatchedAddresses(addresses) {
|
|
43
|
-
this._watchedAddresses = new Set(addresses.map((a) => a.toLowerCase()));
|
|
44
|
-
}
|
|
45
|
-
get cursor() {
|
|
46
|
-
return this._cursor;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Poll for native ETH transfers to watched addresses, including unconfirmed blocks.
|
|
50
|
-
*
|
|
51
|
-
* Scans from cursor to latest block. Emits events with current confirmation count.
|
|
52
|
-
* Re-emits on each confirmation increment. Only advances cursor past fully-confirmed blocks.
|
|
53
|
-
*/
|
|
54
|
-
async poll() {
|
|
55
|
-
if (this._watchedAddresses.size === 0)
|
|
56
|
-
return;
|
|
57
|
-
const latestHex = (await this.rpc("eth_blockNumber", []));
|
|
58
|
-
const latest = Number.parseInt(latestHex, 16);
|
|
59
|
-
const confirmed = latest - this.confirmations;
|
|
60
|
-
if (latest < this._cursor)
|
|
61
|
-
return;
|
|
62
|
-
const { priceMicros } = await this.oracle.getPrice("ETH");
|
|
63
|
-
// Scan up to latest (not just confirmed) to detect pending txs.
|
|
64
|
-
// Fetch blocks in batches to avoid bursting RPC rate limits on fast chains (e.g. Tron 3s blocks).
|
|
65
|
-
const BATCH_SIZE = 5;
|
|
66
|
-
for (let batchStart = this._cursor; batchStart <= latest; batchStart += BATCH_SIZE) {
|
|
67
|
-
const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, latest);
|
|
68
|
-
const blockNums = Array.from({ length: batchEnd - batchStart + 1 }, (_, i) => batchStart + i);
|
|
69
|
-
const blocks = await Promise.all(blockNums.map((bn) => this.rpc("eth_getBlockByNumber", [`0x${bn.toString(16)}`, true]).then((b) => ({ blockNum: bn, block: b, error: null }), (err) => ({ blockNum: bn, block: null, error: err }))));
|
|
70
|
-
// Stop processing at the first failed block so the cursor doesn't advance past it.
|
|
71
|
-
const firstFailIdx = blocks.findIndex((b) => b.error !== null || !b.block);
|
|
72
|
-
const safeBlocks = firstFailIdx === -1 ? blocks : blocks.slice(0, firstFailIdx);
|
|
73
|
-
for (const { blockNum, block } of safeBlocks) {
|
|
74
|
-
if (!block)
|
|
75
|
-
break;
|
|
76
|
-
const confs = latest - blockNum;
|
|
77
|
-
for (const tx of block.transactions) {
|
|
78
|
-
if (!tx.to)
|
|
79
|
-
continue;
|
|
80
|
-
const to = tx.to.toLowerCase();
|
|
81
|
-
if (!this._watchedAddresses.has(to))
|
|
82
|
-
continue;
|
|
83
|
-
const valueWei = BigInt(tx.value);
|
|
84
|
-
if (valueWei === 0n)
|
|
85
|
-
continue;
|
|
86
|
-
// Skip if we already emitted at this confirmation count
|
|
87
|
-
if (this.cursorStore) {
|
|
88
|
-
const lastConf = await this.cursorStore.getConfirmationCount(this.watcherId, tx.hash);
|
|
89
|
-
if (lastConf !== null && confs <= lastConf)
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const amountUsdCents = nativeToCents(valueWei, priceMicros, 18);
|
|
93
|
-
const event = {
|
|
94
|
-
chain: this.chain,
|
|
95
|
-
from: tx.from.toLowerCase(),
|
|
96
|
-
to,
|
|
97
|
-
valueWei: valueWei.toString(),
|
|
98
|
-
amountUsdCents,
|
|
99
|
-
txHash: tx.hash,
|
|
100
|
-
blockNumber: blockNum,
|
|
101
|
-
confirmations: confs,
|
|
102
|
-
confirmationsRequired: this.confirmations,
|
|
103
|
-
};
|
|
104
|
-
await this.onPayment(event);
|
|
105
|
-
if (this.cursorStore) {
|
|
106
|
-
await this.cursorStore.saveConfirmationCount(this.watcherId, tx.hash, confs);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// Only advance cursor past fully-confirmed blocks
|
|
110
|
-
if (blockNum <= confirmed) {
|
|
111
|
-
this._cursor = blockNum + 1;
|
|
112
|
-
if (this.cursorStore) {
|
|
113
|
-
await this.cursorStore.save(this.watcherId, this._cursor);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (firstFailIdx !== -1)
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { IWatcherCursorStore } from "../cursor-store.js";
|
|
2
|
-
import type { EvmChain, EvmPaymentEvent, StablecoinToken } from "./types.js";
|
|
3
|
-
type RpcCall = (method: string, params: unknown[]) => Promise<unknown>;
|
|
4
|
-
export interface EvmWatcherOpts {
|
|
5
|
-
chain: EvmChain;
|
|
6
|
-
token: StablecoinToken;
|
|
7
|
-
rpcCall: RpcCall;
|
|
8
|
-
fromBlock: number;
|
|
9
|
-
onPayment: (event: EvmPaymentEvent) => void | Promise<void>;
|
|
10
|
-
/** Active deposit addresses to watch. Filters eth_getLogs by topic[2] (to address). */
|
|
11
|
-
watchedAddresses?: string[];
|
|
12
|
-
cursorStore?: IWatcherCursorStore;
|
|
13
|
-
/** Contract address for the ERC20 token (from DB). */
|
|
14
|
-
contractAddress: string;
|
|
15
|
-
/** Token decimals (from DB). */
|
|
16
|
-
decimals: number;
|
|
17
|
-
/** Required confirmations (from DB). */
|
|
18
|
-
confirmations: number;
|
|
19
|
-
}
|
|
20
|
-
export declare class EvmWatcher {
|
|
21
|
-
private _cursor;
|
|
22
|
-
private readonly chain;
|
|
23
|
-
private readonly token;
|
|
24
|
-
private readonly rpc;
|
|
25
|
-
private readonly onPayment;
|
|
26
|
-
private readonly confirmations;
|
|
27
|
-
private readonly contractAddress;
|
|
28
|
-
private readonly decimals;
|
|
29
|
-
private readonly cursorStore?;
|
|
30
|
-
private readonly watcherId;
|
|
31
|
-
private _watchedAddresses;
|
|
32
|
-
constructor(opts: EvmWatcherOpts);
|
|
33
|
-
/** Load cursor from DB. Call once at startup before first poll. */
|
|
34
|
-
init(): Promise<void>;
|
|
35
|
-
/** Update the set of watched deposit addresses (e.g. after a new checkout). */
|
|
36
|
-
setWatchedAddresses(addresses: string[]): void;
|
|
37
|
-
get cursor(): number;
|
|
38
|
-
/**
|
|
39
|
-
* Poll for Transfer events, including pending (unconfirmed) blocks.
|
|
40
|
-
*
|
|
41
|
-
* Two-phase scan:
|
|
42
|
-
* 1. Scan cursor..latest for new/updated txs, emit with current confirmation count
|
|
43
|
-
* 2. Re-check pending txs automatically since cursor doesn't advance past unconfirmed blocks
|
|
44
|
-
*
|
|
45
|
-
* Cursor only advances past fully-confirmed blocks.
|
|
46
|
-
*/
|
|
47
|
-
poll(): Promise<void>;
|
|
48
|
-
}
|
|
49
|
-
/** Create an RPC caller for a given URL (plain JSON-RPC over fetch). */
|
|
50
|
-
export declare function createRpcCaller(rpcUrl: string, extraHeaders?: Record<string, string>): RpcCall;
|
|
51
|
-
export {};
|