@wopr-network/platform-core 1.67.0 → 1.68.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/auth/better-auth.js +7 -0
- 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/dist/email/client.js +16 -0
- package/package.json +4 -7
- package/src/auth/better-auth.ts +8 -0
- 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/src/email/client.ts +18 -0
- 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 -742
- 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 -472
- 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 -823
- 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 -617
- 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,161 +0,0 @@
|
|
|
1
|
-
import type { IWatcherCursorStore } from "../cursor-store.js";
|
|
2
|
-
import { nativeToCents } from "../oracle/convert.js";
|
|
3
|
-
import type { IPriceOracle } from "../oracle/types.js";
|
|
4
|
-
import type { BitcoindConfig, BtcPaymentEvent } from "./types.js";
|
|
5
|
-
|
|
6
|
-
type RpcCall = (method: string, params: unknown[]) => Promise<unknown>;
|
|
7
|
-
|
|
8
|
-
export interface BtcWatcherOpts {
|
|
9
|
-
config: BitcoindConfig;
|
|
10
|
-
rpcCall: RpcCall;
|
|
11
|
-
/** Addresses to watch (must be imported into bitcoind wallet first). */
|
|
12
|
-
watchedAddresses: string[];
|
|
13
|
-
onPayment: (event: BtcPaymentEvent) => void | Promise<void>;
|
|
14
|
-
/** Price oracle for BTC/USD conversion. */
|
|
15
|
-
oracle: IPriceOracle;
|
|
16
|
-
/** Required — BTC has no block cursor, so txid dedup must be persisted. */
|
|
17
|
-
cursorStore: IWatcherCursorStore;
|
|
18
|
-
/** Override chain identity for cursor namespace (default: config.network). Prevents txid collisions across BTC/LTC/DOGE. */
|
|
19
|
-
chainId?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface ReceivedByAddress {
|
|
23
|
-
address: string;
|
|
24
|
-
amount: number;
|
|
25
|
-
confirmations: number;
|
|
26
|
-
txids: string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class BtcWatcher {
|
|
30
|
-
private readonly rpc: RpcCall;
|
|
31
|
-
private readonly addresses: Set<string>;
|
|
32
|
-
private readonly onPayment: BtcWatcherOpts["onPayment"];
|
|
33
|
-
private readonly minConfirmations: number;
|
|
34
|
-
private readonly oracle: IPriceOracle;
|
|
35
|
-
private readonly cursorStore: IWatcherCursorStore;
|
|
36
|
-
private readonly watcherId: string;
|
|
37
|
-
|
|
38
|
-
constructor(opts: BtcWatcherOpts) {
|
|
39
|
-
this.rpc = opts.rpcCall;
|
|
40
|
-
this.addresses = new Set(opts.watchedAddresses);
|
|
41
|
-
this.onPayment = opts.onPayment;
|
|
42
|
-
this.minConfirmations = opts.config.confirmations;
|
|
43
|
-
this.oracle = opts.oracle;
|
|
44
|
-
this.cursorStore = opts.cursorStore;
|
|
45
|
-
this.watcherId = `btc:${opts.chainId ?? opts.config.network}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Update the set of watched addresses. */
|
|
49
|
-
setWatchedAddresses(addresses: string[]): void {
|
|
50
|
-
this.addresses.clear();
|
|
51
|
-
for (const a of addresses) this.addresses.add(a);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Import an address into bitcoind's wallet (watch-only).
|
|
56
|
-
* Uses `importdescriptors` (modern bitcoind v24+) with fallback to legacy `importaddress`.
|
|
57
|
-
*/
|
|
58
|
-
async importAddress(address: string): Promise<void> {
|
|
59
|
-
try {
|
|
60
|
-
// Modern bitcoind: get descriptor checksum, then import
|
|
61
|
-
const info = (await this.rpc("getdescriptorinfo", [`addr(${address})`])) as {
|
|
62
|
-
descriptor: string;
|
|
63
|
-
};
|
|
64
|
-
const result = (await this.rpc("importdescriptors", [[{ desc: info.descriptor, timestamp: 0 }]])) as Array<{
|
|
65
|
-
success: boolean;
|
|
66
|
-
error?: { message: string };
|
|
67
|
-
}>;
|
|
68
|
-
if (result[0] && !result[0].success) {
|
|
69
|
-
throw new Error(result[0].error?.message ?? "importdescriptors failed");
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// Fallback: legacy importaddress (bitcoind <v24)
|
|
73
|
-
await this.rpc("importaddress", [address, "", false]);
|
|
74
|
-
}
|
|
75
|
-
this.addresses.add(address);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Poll for payments to watched addresses, including unconfirmed txs.
|
|
80
|
-
*
|
|
81
|
-
* Fires onPayment on every confirmation increment (0, 1, 2, ... threshold).
|
|
82
|
-
* Only marks a tx as fully processed once it reaches the confirmation threshold.
|
|
83
|
-
*/
|
|
84
|
-
async poll(): Promise<void> {
|
|
85
|
-
if (this.addresses.size === 0) return;
|
|
86
|
-
|
|
87
|
-
// Poll with minconf=0 to see unconfirmed txs
|
|
88
|
-
const received = (await this.rpc("listreceivedbyaddress", [
|
|
89
|
-
0, // minconf=0: see ALL txs including unconfirmed
|
|
90
|
-
false, // include_empty
|
|
91
|
-
true, // include_watchonly
|
|
92
|
-
])) as ReceivedByAddress[];
|
|
93
|
-
|
|
94
|
-
const { priceMicros } = await this.oracle.getPrice("BTC");
|
|
95
|
-
|
|
96
|
-
for (const entry of received) {
|
|
97
|
-
if (!this.addresses.has(entry.address)) continue;
|
|
98
|
-
|
|
99
|
-
for (const txid of entry.txids) {
|
|
100
|
-
// Skip fully-processed txids (already reached threshold, persisted to DB)
|
|
101
|
-
if (await this.cursorStore.hasProcessedTx(this.watcherId, txid)) continue;
|
|
102
|
-
|
|
103
|
-
// Get transaction details for the exact amount sent to this address
|
|
104
|
-
const tx = (await this.rpc("gettransaction", [txid, true])) as {
|
|
105
|
-
details: Array<{ address: string; amount: number; category: string }>;
|
|
106
|
-
confirmations: number;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const detail = tx.details.find((d) => d.address === entry.address && d.category === "receive");
|
|
110
|
-
if (!detail) continue;
|
|
111
|
-
|
|
112
|
-
// Check if confirmations have increased since last seen
|
|
113
|
-
const lastSeen = await this.cursorStore.getConfirmationCount(this.watcherId, txid);
|
|
114
|
-
if (lastSeen !== null && tx.confirmations <= lastSeen) continue; // No change
|
|
115
|
-
|
|
116
|
-
const amountSats = Math.round(detail.amount * 100_000_000);
|
|
117
|
-
// priceMicros is microdollars per 1 BTC. Convert sats→USD cents via nativeToCents.
|
|
118
|
-
const amountUsdCents = nativeToCents(BigInt(amountSats), priceMicros, 8);
|
|
119
|
-
|
|
120
|
-
const event: BtcPaymentEvent = {
|
|
121
|
-
address: entry.address,
|
|
122
|
-
txid,
|
|
123
|
-
amountSats,
|
|
124
|
-
amountUsdCents,
|
|
125
|
-
confirmations: tx.confirmations,
|
|
126
|
-
confirmationsRequired: this.minConfirmations,
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
await this.onPayment(event);
|
|
130
|
-
|
|
131
|
-
// Persist confirmation count
|
|
132
|
-
await this.cursorStore.saveConfirmationCount(this.watcherId, txid, tx.confirmations);
|
|
133
|
-
|
|
134
|
-
// Mark as fully processed once we reach the threshold
|
|
135
|
-
if (tx.confirmations >= this.minConfirmations) {
|
|
136
|
-
await this.cursorStore.markProcessedTx(this.watcherId, txid);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** Create a bitcoind JSON-RPC caller with basic auth. */
|
|
144
|
-
export function createBitcoindRpc(config: BitcoindConfig): RpcCall {
|
|
145
|
-
let id = 0;
|
|
146
|
-
const auth = btoa(`${config.rpcUser}:${config.rpcPassword}`);
|
|
147
|
-
return async (method: string, params: unknown[]): Promise<unknown> => {
|
|
148
|
-
const res = await fetch(config.rpcUrl, {
|
|
149
|
-
method: "POST",
|
|
150
|
-
headers: {
|
|
151
|
-
"Content-Type": "application/json",
|
|
152
|
-
Authorization: `Basic ${auth}`,
|
|
153
|
-
},
|
|
154
|
-
body: JSON.stringify({ jsonrpc: "1.0", id: ++id, method, params }),
|
|
155
|
-
});
|
|
156
|
-
if (!res.ok) throw new Error(`bitcoind ${method} failed: ${res.status}`);
|
|
157
|
-
const data = (await res.json()) as { result?: unknown; error?: { message: string } };
|
|
158
|
-
if (data.error) throw new Error(`bitcoind ${method}: ${data.error.message}`);
|
|
159
|
-
return data.result;
|
|
160
|
-
};
|
|
161
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { EthWatcher } from "../eth-watcher.js";
|
|
3
|
-
|
|
4
|
-
function makeRpc(responses: Record<string, unknown>) {
|
|
5
|
-
return vi.fn(async (method: string) => responses[method]);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const mockOracle = { getPrice: vi.fn().mockResolvedValue({ priceMicros: 3_500_000_000, updatedAt: new Date() }) };
|
|
9
|
-
|
|
10
|
-
describe("EthWatcher", () => {
|
|
11
|
-
it("detects native ETH transfer to watched address", async () => {
|
|
12
|
-
const onPayment = vi.fn();
|
|
13
|
-
// latest = 0xa (10), fromBlock = 10 → scans exactly block 10
|
|
14
|
-
const rpc = makeRpc({
|
|
15
|
-
eth_blockNumber: "0xa",
|
|
16
|
-
eth_getBlockByNumber: {
|
|
17
|
-
transactions: [
|
|
18
|
-
{
|
|
19
|
-
hash: "0xabc",
|
|
20
|
-
from: "0xsender",
|
|
21
|
-
to: "0xdeposit",
|
|
22
|
-
value: "0xDE0B6B3A7640000", // 1 ETH = 10^18 wei
|
|
23
|
-
blockNumber: "0xa",
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const watcher = new EthWatcher({
|
|
30
|
-
chain: "base",
|
|
31
|
-
rpcCall: rpc,
|
|
32
|
-
oracle: mockOracle,
|
|
33
|
-
fromBlock: 10,
|
|
34
|
-
confirmations: 1,
|
|
35
|
-
onPayment,
|
|
36
|
-
watchedAddresses: ["0xDeposit"],
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
await watcher.poll();
|
|
40
|
-
|
|
41
|
-
expect(onPayment).toHaveBeenCalledOnce();
|
|
42
|
-
const event = onPayment.mock.calls[0][0];
|
|
43
|
-
expect(event.to).toBe("0xdeposit");
|
|
44
|
-
expect(event.valueWei).toBe("1000000000000000000");
|
|
45
|
-
expect(event.amountUsdCents).toBe(350_000); // 1 ETH × $3,500 = $3,500 = 350,000 cents
|
|
46
|
-
expect(event.txHash).toBe("0xabc");
|
|
47
|
-
expect(event.confirmations).toBe(0);
|
|
48
|
-
expect(event.confirmationsRequired).toBe(1);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("skips transactions not to watched addresses", async () => {
|
|
52
|
-
const onPayment = vi.fn();
|
|
53
|
-
const rpc = makeRpc({
|
|
54
|
-
eth_blockNumber: "0xb",
|
|
55
|
-
eth_getBlockByNumber: {
|
|
56
|
-
transactions: [{ hash: "0xabc", from: "0xa", to: "0xother", value: "0xDE0B6B3A7640000", blockNumber: "0xa" }],
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const watcher = new EthWatcher({
|
|
61
|
-
chain: "base",
|
|
62
|
-
rpcCall: rpc,
|
|
63
|
-
oracle: mockOracle,
|
|
64
|
-
fromBlock: 10,
|
|
65
|
-
confirmations: 1,
|
|
66
|
-
onPayment,
|
|
67
|
-
watchedAddresses: ["0xDeposit"],
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await watcher.poll();
|
|
71
|
-
expect(onPayment).not.toHaveBeenCalled();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("skips zero-value transactions", async () => {
|
|
75
|
-
const onPayment = vi.fn();
|
|
76
|
-
const rpc = makeRpc({
|
|
77
|
-
eth_blockNumber: "0xb",
|
|
78
|
-
eth_getBlockByNumber: {
|
|
79
|
-
transactions: [{ hash: "0xabc", from: "0xa", to: "0xdeposit", value: "0x0", blockNumber: "0xa" }],
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const watcher = new EthWatcher({
|
|
84
|
-
chain: "base",
|
|
85
|
-
rpcCall: rpc,
|
|
86
|
-
oracle: mockOracle,
|
|
87
|
-
fromBlock: 10,
|
|
88
|
-
confirmations: 1,
|
|
89
|
-
onPayment,
|
|
90
|
-
watchedAddresses: ["0xDeposit"],
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
await watcher.poll();
|
|
94
|
-
expect(onPayment).not.toHaveBeenCalled();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("does not double-process same txid", async () => {
|
|
98
|
-
const onPayment = vi.fn();
|
|
99
|
-
const confirmations = new Map<string, number>();
|
|
100
|
-
const cursorStore = {
|
|
101
|
-
get: vi.fn().mockResolvedValue(null),
|
|
102
|
-
save: vi.fn().mockResolvedValue(undefined),
|
|
103
|
-
hasProcessedTx: vi.fn().mockResolvedValue(false),
|
|
104
|
-
markProcessedTx: vi.fn().mockResolvedValue(undefined),
|
|
105
|
-
getConfirmationCount: vi
|
|
106
|
-
.fn()
|
|
107
|
-
.mockImplementation(async (_: string, txId: string) => confirmations.get(txId) ?? null),
|
|
108
|
-
saveConfirmationCount: vi.fn().mockImplementation(async (_: string, txId: string, count: number) => {
|
|
109
|
-
confirmations.set(txId, count);
|
|
110
|
-
}),
|
|
111
|
-
};
|
|
112
|
-
const rpc = makeRpc({
|
|
113
|
-
eth_blockNumber: "0xb",
|
|
114
|
-
eth_getBlockByNumber: {
|
|
115
|
-
transactions: [{ hash: "0xabc", from: "0xa", to: "0xdeposit", value: "0xDE0B6B3A7640000", blockNumber: "0xa" }],
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const watcher = new EthWatcher({
|
|
120
|
-
chain: "base",
|
|
121
|
-
rpcCall: rpc,
|
|
122
|
-
oracle: mockOracle,
|
|
123
|
-
fromBlock: 10,
|
|
124
|
-
confirmations: 1,
|
|
125
|
-
onPayment,
|
|
126
|
-
watchedAddresses: ["0xDeposit"],
|
|
127
|
-
cursorStore,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
await watcher.poll();
|
|
131
|
-
// Second poll — same block, same confirmations → no duplicate emission
|
|
132
|
-
await watcher.poll();
|
|
133
|
-
|
|
134
|
-
expect(onPayment).toHaveBeenCalledOnce();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("skips poll when no watched addresses", async () => {
|
|
138
|
-
const onPayment = vi.fn();
|
|
139
|
-
const rpc = vi.fn();
|
|
140
|
-
|
|
141
|
-
const watcher = new EthWatcher({
|
|
142
|
-
chain: "base",
|
|
143
|
-
rpcCall: rpc,
|
|
144
|
-
oracle: mockOracle,
|
|
145
|
-
fromBlock: 10,
|
|
146
|
-
confirmations: 1,
|
|
147
|
-
onPayment,
|
|
148
|
-
watchedAddresses: [],
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
await watcher.poll();
|
|
152
|
-
expect(rpc).not.toHaveBeenCalled();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("does not mark txid as processed if onPayment throws", async () => {
|
|
156
|
-
const onPayment = vi.fn().mockRejectedValueOnce(new Error("db fail")).mockResolvedValueOnce(undefined);
|
|
157
|
-
const cursorStore = {
|
|
158
|
-
get: vi.fn().mockResolvedValue(null),
|
|
159
|
-
save: vi.fn().mockResolvedValue(undefined),
|
|
160
|
-
hasProcessedTx: vi.fn().mockResolvedValue(false),
|
|
161
|
-
markProcessedTx: vi.fn().mockResolvedValue(undefined),
|
|
162
|
-
getConfirmationCount: vi.fn().mockResolvedValue(null),
|
|
163
|
-
saveConfirmationCount: vi.fn().mockResolvedValue(undefined),
|
|
164
|
-
};
|
|
165
|
-
// latest = 0xa (10) = fromBlock → exactly one block to scan
|
|
166
|
-
const rpc = makeRpc({
|
|
167
|
-
eth_blockNumber: "0xa",
|
|
168
|
-
eth_getBlockByNumber: {
|
|
169
|
-
transactions: [{ hash: "0xabc", from: "0xa", to: "0xdeposit", value: "0xDE0B6B3A7640000", blockNumber: "0xa" }],
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const watcher = new EthWatcher({
|
|
174
|
-
chain: "base",
|
|
175
|
-
rpcCall: rpc,
|
|
176
|
-
oracle: mockOracle,
|
|
177
|
-
fromBlock: 10,
|
|
178
|
-
confirmations: 1,
|
|
179
|
-
onPayment,
|
|
180
|
-
watchedAddresses: ["0xDeposit"],
|
|
181
|
-
cursorStore,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
await expect(watcher.poll()).rejects.toThrow("db fail");
|
|
185
|
-
|
|
186
|
-
// Retry — should process the same tx again since confirmationCount wasn't saved (error before save)
|
|
187
|
-
await watcher.poll();
|
|
188
|
-
expect(onPayment).toHaveBeenCalledTimes(2);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { EvmWatcher } from "../watcher.js";
|
|
3
|
-
|
|
4
|
-
const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
5
|
-
|
|
6
|
-
function mockTransferLog(to: string, amount: bigint, blockNumber: number) {
|
|
7
|
-
return {
|
|
8
|
-
address: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
9
|
-
topics: [
|
|
10
|
-
TRANSFER_TOPIC,
|
|
11
|
-
`0x${"00".repeat(12)}${"ab".repeat(20)}`,
|
|
12
|
-
`0x${"00".repeat(12)}${to.slice(2).toLowerCase()}`,
|
|
13
|
-
],
|
|
14
|
-
data: `0x${amount.toString(16).padStart(64, "0")}`,
|
|
15
|
-
blockNumber: `0x${blockNumber.toString(16)}`,
|
|
16
|
-
transactionHash: `0x${"ff".repeat(32)}`,
|
|
17
|
-
logIndex: "0x0",
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function makeCursorStore() {
|
|
22
|
-
const cursors = new Map<string, number>();
|
|
23
|
-
return {
|
|
24
|
-
get: vi.fn().mockImplementation(async (id: string) => cursors.get(id) ?? null),
|
|
25
|
-
save: vi.fn().mockImplementation(async (id: string, val: number) => {
|
|
26
|
-
cursors.set(id, val);
|
|
27
|
-
}),
|
|
28
|
-
hasProcessedTx: vi.fn().mockResolvedValue(false),
|
|
29
|
-
markProcessedTx: vi.fn().mockResolvedValue(undefined),
|
|
30
|
-
getConfirmationCount: vi.fn().mockResolvedValue(null),
|
|
31
|
-
saveConfirmationCount: vi.fn().mockResolvedValue(undefined),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe("EvmWatcher — intermediate confirmations", () => {
|
|
36
|
-
it("emits events with confirmation count", async () => {
|
|
37
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
38
|
-
const events: Array<{ confirmations: number; confirmationsRequired: number }> = [];
|
|
39
|
-
|
|
40
|
-
// Base has confirmations: 1. Latest block is 105. Log at block 103 -> 2 confirmations.
|
|
41
|
-
const mockRpc = vi
|
|
42
|
-
.fn()
|
|
43
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`) // eth_blockNumber
|
|
44
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10_000_000n, 103)]); // eth_getLogs
|
|
45
|
-
|
|
46
|
-
const watcher = new EvmWatcher({
|
|
47
|
-
chain: "base",
|
|
48
|
-
token: "USDC",
|
|
49
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
50
|
-
decimals: 6,
|
|
51
|
-
confirmations: 1,
|
|
52
|
-
rpcCall: mockRpc,
|
|
53
|
-
fromBlock: 100,
|
|
54
|
-
watchedAddresses: [toAddr],
|
|
55
|
-
cursorStore: makeCursorStore(),
|
|
56
|
-
onPayment: (evt) => {
|
|
57
|
-
events.push(evt);
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
await watcher.poll();
|
|
62
|
-
|
|
63
|
-
expect(events).toHaveLength(1);
|
|
64
|
-
expect(events[0].confirmations).toBe(2); // 105 - 103
|
|
65
|
-
expect(events[0].confirmationsRequired).toBe(1); // Base chain config
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("skips event when confirmation count unchanged", async () => {
|
|
69
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
70
|
-
const events: Array<{ confirmations: number }> = [];
|
|
71
|
-
const cursorStore = makeCursorStore();
|
|
72
|
-
cursorStore.getConfirmationCount.mockResolvedValue(2);
|
|
73
|
-
|
|
74
|
-
const mockRpc = vi
|
|
75
|
-
.fn()
|
|
76
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`)
|
|
77
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10_000_000n, 103)]);
|
|
78
|
-
|
|
79
|
-
const watcher = new EvmWatcher({
|
|
80
|
-
chain: "base",
|
|
81
|
-
token: "USDC",
|
|
82
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
83
|
-
decimals: 6,
|
|
84
|
-
confirmations: 1,
|
|
85
|
-
rpcCall: mockRpc,
|
|
86
|
-
fromBlock: 100,
|
|
87
|
-
watchedAddresses: [toAddr],
|
|
88
|
-
cursorStore,
|
|
89
|
-
onPayment: (evt) => {
|
|
90
|
-
events.push(evt);
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
await watcher.poll();
|
|
95
|
-
|
|
96
|
-
expect(events).toHaveLength(0);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("re-emits when confirmations increase", async () => {
|
|
100
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
101
|
-
const events: Array<{ confirmations: number }> = [];
|
|
102
|
-
const cursorStore = makeCursorStore();
|
|
103
|
-
cursorStore.getConfirmationCount.mockResolvedValue(1);
|
|
104
|
-
|
|
105
|
-
const mockRpc = vi
|
|
106
|
-
.fn()
|
|
107
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`)
|
|
108
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10_000_000n, 103)]);
|
|
109
|
-
|
|
110
|
-
const watcher = new EvmWatcher({
|
|
111
|
-
chain: "base",
|
|
112
|
-
token: "USDC",
|
|
113
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
114
|
-
decimals: 6,
|
|
115
|
-
confirmations: 1,
|
|
116
|
-
rpcCall: mockRpc,
|
|
117
|
-
fromBlock: 100,
|
|
118
|
-
watchedAddresses: [toAddr],
|
|
119
|
-
cursorStore,
|
|
120
|
-
onPayment: (evt) => {
|
|
121
|
-
events.push(evt);
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
await watcher.poll();
|
|
126
|
-
|
|
127
|
-
expect(events).toHaveLength(1);
|
|
128
|
-
expect(events[0].confirmations).toBe(2);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("includes confirmationsRequired from chain config", async () => {
|
|
132
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
133
|
-
const events: Array<{ confirmationsRequired: number }> = [];
|
|
134
|
-
|
|
135
|
-
const mockRpc = vi
|
|
136
|
-
.fn()
|
|
137
|
-
.mockResolvedValueOnce(`0x${(110).toString(16)}`)
|
|
138
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10_000_000n, 105)]);
|
|
139
|
-
|
|
140
|
-
const watcher = new EvmWatcher({
|
|
141
|
-
chain: "base",
|
|
142
|
-
token: "USDC",
|
|
143
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
144
|
-
decimals: 6,
|
|
145
|
-
confirmations: 1,
|
|
146
|
-
rpcCall: mockRpc,
|
|
147
|
-
fromBlock: 100,
|
|
148
|
-
watchedAddresses: [toAddr],
|
|
149
|
-
cursorStore: makeCursorStore(),
|
|
150
|
-
onPayment: (evt) => {
|
|
151
|
-
events.push(evt);
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
await watcher.poll();
|
|
156
|
-
|
|
157
|
-
expect(events).toHaveLength(1);
|
|
158
|
-
expect(events[0].confirmationsRequired).toBe(1); // Base chain config
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("saves confirmation count after emitting", async () => {
|
|
162
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
163
|
-
const cursorStore = makeCursorStore();
|
|
164
|
-
|
|
165
|
-
const mockRpc = vi
|
|
166
|
-
.fn()
|
|
167
|
-
.mockResolvedValueOnce(`0x${(105).toString(16)}`)
|
|
168
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10_000_000n, 103)]);
|
|
169
|
-
|
|
170
|
-
const watcher = new EvmWatcher({
|
|
171
|
-
chain: "base",
|
|
172
|
-
token: "USDC",
|
|
173
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
174
|
-
decimals: 6,
|
|
175
|
-
confirmations: 1,
|
|
176
|
-
rpcCall: mockRpc,
|
|
177
|
-
fromBlock: 100,
|
|
178
|
-
watchedAddresses: [toAddr],
|
|
179
|
-
cursorStore,
|
|
180
|
-
onPayment: () => {},
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
await watcher.poll();
|
|
184
|
-
|
|
185
|
-
expect(cursorStore.saveConfirmationCount).toHaveBeenCalledWith(
|
|
186
|
-
expect.any(String),
|
|
187
|
-
expect.stringContaining("0x"),
|
|
188
|
-
2,
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { EvmWatcher } from "../watcher.js";
|
|
3
|
-
|
|
4
|
-
const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
5
|
-
|
|
6
|
-
function mockTransferLog(to: string, amount: bigint, blockNumber: number) {
|
|
7
|
-
return {
|
|
8
|
-
address: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
9
|
-
topics: [
|
|
10
|
-
TRANSFER_TOPIC,
|
|
11
|
-
`0x${"00".repeat(12)}${"ab".repeat(20)}`, // from (padded)
|
|
12
|
-
`0x${"00".repeat(12)}${to.slice(2).toLowerCase()}`, // to (padded)
|
|
13
|
-
],
|
|
14
|
-
data: `0x${amount.toString(16).padStart(64, "0")}`,
|
|
15
|
-
blockNumber: `0x${blockNumber.toString(16)}`,
|
|
16
|
-
transactionHash: `0x${"ff".repeat(32)}`,
|
|
17
|
-
logIndex: "0x0",
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe("EvmWatcher", () => {
|
|
22
|
-
it("parses Transfer log into EvmPaymentEvent", async () => {
|
|
23
|
-
const toAddr = `0x${"cc".repeat(20)}`;
|
|
24
|
-
const events: { amountUsdCents: number; to: string }[] = [];
|
|
25
|
-
const mockRpc = vi
|
|
26
|
-
.fn()
|
|
27
|
-
.mockResolvedValueOnce(`0x${(102).toString(16)}`)
|
|
28
|
-
.mockResolvedValueOnce([mockTransferLog(toAddr, 10_000_000n, 99)]);
|
|
29
|
-
|
|
30
|
-
const watcher = new EvmWatcher({
|
|
31
|
-
chain: "base",
|
|
32
|
-
token: "USDC",
|
|
33
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
34
|
-
decimals: 6,
|
|
35
|
-
confirmations: 1,
|
|
36
|
-
rpcCall: mockRpc,
|
|
37
|
-
fromBlock: 99,
|
|
38
|
-
watchedAddresses: [toAddr],
|
|
39
|
-
onPayment: (evt) => {
|
|
40
|
-
events.push(evt);
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
await watcher.poll();
|
|
45
|
-
|
|
46
|
-
expect(events).toHaveLength(1);
|
|
47
|
-
expect(events[0].amountUsdCents).toBe(1000);
|
|
48
|
-
expect(events[0].to).toMatch(/^0x/);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("advances cursor after processing", async () => {
|
|
52
|
-
const mockRpc = vi
|
|
53
|
-
.fn()
|
|
54
|
-
.mockResolvedValueOnce(`0x${(200).toString(16)}`)
|
|
55
|
-
.mockResolvedValueOnce([]);
|
|
56
|
-
|
|
57
|
-
const watcher = new EvmWatcher({
|
|
58
|
-
chain: "base",
|
|
59
|
-
token: "USDC",
|
|
60
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
61
|
-
decimals: 6,
|
|
62
|
-
confirmations: 1,
|
|
63
|
-
rpcCall: mockRpc,
|
|
64
|
-
fromBlock: 100,
|
|
65
|
-
watchedAddresses: ["0xdeadbeef"],
|
|
66
|
-
onPayment: vi.fn(),
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
await watcher.poll();
|
|
70
|
-
expect(watcher.cursor).toBeGreaterThan(100);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("skips blocks not yet confirmed", async () => {
|
|
74
|
-
const events: unknown[] = [];
|
|
75
|
-
// latest = 50, cursor = 50 → latest < cursor is false, but range is empty (50..50)
|
|
76
|
-
// With intermediate confirmations, we still scan the range but find no logs
|
|
77
|
-
const mockRpc = vi
|
|
78
|
-
.fn()
|
|
79
|
-
.mockResolvedValueOnce(`0x${(50).toString(16)}`) // eth_blockNumber
|
|
80
|
-
.mockResolvedValueOnce([]); // eth_getLogs (empty)
|
|
81
|
-
|
|
82
|
-
const watcher = new EvmWatcher({
|
|
83
|
-
chain: "base",
|
|
84
|
-
token: "USDC",
|
|
85
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
86
|
-
decimals: 6,
|
|
87
|
-
confirmations: 1,
|
|
88
|
-
rpcCall: mockRpc,
|
|
89
|
-
fromBlock: 50,
|
|
90
|
-
watchedAddresses: ["0xdeadbeef"],
|
|
91
|
-
onPayment: (evt) => {
|
|
92
|
-
events.push(evt);
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
await watcher.poll();
|
|
97
|
-
expect(events).toHaveLength(0);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("processes multiple logs in one poll", async () => {
|
|
101
|
-
const addr1 = `0x${"aa".repeat(20)}`;
|
|
102
|
-
const addr2 = `0x${"bb".repeat(20)}`;
|
|
103
|
-
const events: { amountUsdCents: number }[] = [];
|
|
104
|
-
const mockRpc = vi
|
|
105
|
-
.fn()
|
|
106
|
-
.mockResolvedValueOnce(`0x${(110).toString(16)}`)
|
|
107
|
-
.mockResolvedValueOnce([mockTransferLog(addr1, 5_000_000n, 105), mockTransferLog(addr2, 20_000_000n, 107)]);
|
|
108
|
-
|
|
109
|
-
const watcher = new EvmWatcher({
|
|
110
|
-
chain: "base",
|
|
111
|
-
token: "USDC",
|
|
112
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
113
|
-
decimals: 6,
|
|
114
|
-
confirmations: 1,
|
|
115
|
-
rpcCall: mockRpc,
|
|
116
|
-
fromBlock: 100,
|
|
117
|
-
watchedAddresses: [addr1, addr2],
|
|
118
|
-
onPayment: (evt) => {
|
|
119
|
-
events.push(evt);
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await watcher.poll();
|
|
124
|
-
|
|
125
|
-
expect(events).toHaveLength(2);
|
|
126
|
-
expect(events[0].amountUsdCents).toBe(500);
|
|
127
|
-
expect(events[1].amountUsdCents).toBe(2000);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("does nothing when no new blocks", async () => {
|
|
131
|
-
const mockRpc = vi.fn().mockResolvedValueOnce(`0x${(99).toString(16)}`);
|
|
132
|
-
|
|
133
|
-
const watcher = new EvmWatcher({
|
|
134
|
-
chain: "base",
|
|
135
|
-
token: "USDC",
|
|
136
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
137
|
-
decimals: 6,
|
|
138
|
-
confirmations: 1,
|
|
139
|
-
rpcCall: mockRpc,
|
|
140
|
-
fromBlock: 100,
|
|
141
|
-
watchedAddresses: ["0xdeadbeef"],
|
|
142
|
-
onPayment: vi.fn(),
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
await watcher.poll();
|
|
146
|
-
expect(watcher.cursor).toBe(100);
|
|
147
|
-
expect(mockRpc).toHaveBeenCalledTimes(1);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("early-returns when no watched addresses are set", async () => {
|
|
151
|
-
const mockRpc = vi.fn();
|
|
152
|
-
|
|
153
|
-
const watcher = new EvmWatcher({
|
|
154
|
-
chain: "base",
|
|
155
|
-
token: "USDC",
|
|
156
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
157
|
-
decimals: 6,
|
|
158
|
-
confirmations: 1,
|
|
159
|
-
rpcCall: mockRpc,
|
|
160
|
-
fromBlock: 0,
|
|
161
|
-
onPayment: vi.fn(),
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
await watcher.poll();
|
|
165
|
-
expect(mockRpc).not.toHaveBeenCalled(); // no RPC calls at all
|
|
166
|
-
});
|
|
167
|
-
});
|