@wopr-network/crypto-plugins 1.0.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/.github/workflows/ci.yml +33 -0
- package/.github/workflows/publish.yml +12 -0
- package/biome.json +23 -0
- package/dist/__tests__/bitcoin-encoder.test.d.ts +2 -0
- package/dist/__tests__/bitcoin-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/bitcoin-encoder.test.js +97 -0
- package/dist/__tests__/bitcoin-encoder.test.js.map +1 -0
- package/dist/__tests__/dogecoin-encoder.test.d.ts +2 -0
- package/dist/__tests__/dogecoin-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/dogecoin-encoder.test.js +57 -0
- package/dist/__tests__/dogecoin-encoder.test.js.map +1 -0
- package/dist/__tests__/litecoin-encoder.test.d.ts +2 -0
- package/dist/__tests__/litecoin-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/litecoin-encoder.test.js +44 -0
- package/dist/__tests__/litecoin-encoder.test.js.map +1 -0
- package/dist/__tests__/registry.test.d.ts +2 -0
- package/dist/__tests__/registry.test.d.ts.map +1 -0
- package/dist/__tests__/registry.test.js +75 -0
- package/dist/__tests__/registry.test.js.map +1 -0
- package/dist/__tests__/rpc.test.d.ts +2 -0
- package/dist/__tests__/rpc.test.d.ts.map +1 -0
- package/dist/__tests__/rpc.test.js +31 -0
- package/dist/__tests__/rpc.test.js.map +1 -0
- package/dist/__tests__/solana-encoder.test.d.ts +2 -0
- package/dist/__tests__/solana-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/solana-encoder.test.js +85 -0
- package/dist/__tests__/solana-encoder.test.js.map +1 -0
- package/dist/__tests__/solana-watcher.test.d.ts +2 -0
- package/dist/__tests__/solana-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/solana-watcher.test.js +281 -0
- package/dist/__tests__/solana-watcher.test.js.map +1 -0
- package/dist/__tests__/sweep-key-parity.test.d.ts +2 -0
- package/dist/__tests__/sweep-key-parity.test.d.ts.map +1 -0
- package/dist/__tests__/sweep-key-parity.test.js +236 -0
- package/dist/__tests__/sweep-key-parity.test.js.map +1 -0
- package/dist/__tests__/tron-encoder.test.d.ts +2 -0
- package/dist/__tests__/tron-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/tron-encoder.test.js +93 -0
- package/dist/__tests__/tron-encoder.test.js.map +1 -0
- package/dist/__tests__/utxo-watcher.test.d.ts +2 -0
- package/dist/__tests__/utxo-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/utxo-watcher.test.js +218 -0
- package/dist/__tests__/utxo-watcher.test.js.map +1 -0
- package/dist/bitcoin/encoder.d.ts +15 -0
- package/dist/bitcoin/encoder.d.ts.map +1 -0
- package/dist/bitcoin/encoder.js +286 -0
- package/dist/bitcoin/encoder.js.map +1 -0
- package/dist/bitcoin/index.d.ts +4 -0
- package/dist/bitcoin/index.d.ts.map +1 -0
- package/dist/bitcoin/index.js +20 -0
- package/dist/bitcoin/index.js.map +1 -0
- package/dist/dogecoin/encoder.d.ts +19 -0
- package/dist/dogecoin/encoder.d.ts.map +1 -0
- package/dist/dogecoin/encoder.js +145 -0
- package/dist/dogecoin/encoder.js.map +1 -0
- package/dist/dogecoin/index.d.ts +4 -0
- package/dist/dogecoin/index.d.ts.map +1 -0
- package/dist/dogecoin/index.js +20 -0
- package/dist/dogecoin/index.js.map +1 -0
- package/dist/evm/encoder.d.ts +7 -0
- package/dist/evm/encoder.d.ts.map +1 -0
- package/dist/evm/encoder.js +43 -0
- package/dist/evm/encoder.js.map +1 -0
- package/dist/evm/eth-watcher.d.ts +38 -0
- package/dist/evm/eth-watcher.d.ts.map +1 -0
- package/dist/evm/eth-watcher.js +138 -0
- package/dist/evm/eth-watcher.js.map +1 -0
- package/dist/evm/index.d.ts +16 -0
- package/dist/evm/index.d.ts.map +1 -0
- package/dist/evm/index.js +34 -0
- package/dist/evm/index.js.map +1 -0
- package/dist/evm/types.d.ts +43 -0
- package/dist/evm/types.d.ts.map +1 -0
- package/dist/evm/types.js +101 -0
- package/dist/evm/types.js.map +1 -0
- package/dist/evm/watcher.d.ts +42 -0
- package/dist/evm/watcher.d.ts.map +1 -0
- package/dist/evm/watcher.js +162 -0
- package/dist/evm/watcher.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/litecoin/encoder.d.ts +8 -0
- package/dist/litecoin/encoder.d.ts.map +1 -0
- package/dist/litecoin/encoder.js +16 -0
- package/dist/litecoin/encoder.js.map +1 -0
- package/dist/litecoin/index.d.ts +4 -0
- package/dist/litecoin/index.d.ts.map +1 -0
- package/dist/litecoin/index.js +20 -0
- package/dist/litecoin/index.js.map +1 -0
- package/dist/shared/test-helpers/index.d.ts +9 -0
- package/dist/shared/test-helpers/index.d.ts.map +1 -0
- package/dist/shared/test-helpers/index.js +30 -0
- package/dist/shared/test-helpers/index.js.map +1 -0
- package/dist/shared/utxo/index.d.ts +5 -0
- package/dist/shared/utxo/index.d.ts.map +1 -0
- package/dist/shared/utxo/index.js +3 -0
- package/dist/shared/utxo/index.js.map +1 -0
- package/dist/shared/utxo/rpc.d.ts +24 -0
- package/dist/shared/utxo/rpc.d.ts.map +1 -0
- package/dist/shared/utxo/rpc.js +75 -0
- package/dist/shared/utxo/rpc.js.map +1 -0
- package/dist/shared/utxo/types.d.ts +40 -0
- package/dist/shared/utxo/types.d.ts.map +1 -0
- package/dist/shared/utxo/types.js +2 -0
- package/dist/shared/utxo/types.js.map +1 -0
- package/dist/shared/utxo/watcher.d.ts +55 -0
- package/dist/shared/utxo/watcher.d.ts.map +1 -0
- package/dist/shared/utxo/watcher.js +150 -0
- package/dist/shared/utxo/watcher.js.map +1 -0
- package/dist/solana/encoder.d.ts +13 -0
- package/dist/solana/encoder.d.ts.map +1 -0
- package/dist/solana/encoder.js +62 -0
- package/dist/solana/encoder.js.map +1 -0
- package/dist/solana/index.d.ts +17 -0
- package/dist/solana/index.d.ts.map +1 -0
- package/dist/solana/index.js +32 -0
- package/dist/solana/index.js.map +1 -0
- package/dist/solana/sweeper.d.ts +47 -0
- package/dist/solana/sweeper.d.ts.map +1 -0
- package/dist/solana/sweeper.js +151 -0
- package/dist/solana/sweeper.js.map +1 -0
- package/dist/solana/types.d.ts +49 -0
- package/dist/solana/types.d.ts.map +1 -0
- package/dist/solana/types.js +2 -0
- package/dist/solana/types.js.map +1 -0
- package/dist/solana/watcher.d.ts +59 -0
- package/dist/solana/watcher.d.ts.map +1 -0
- package/dist/solana/watcher.js +251 -0
- package/dist/solana/watcher.js.map +1 -0
- package/dist/sweep/evm-sweeper.d.ts +31 -0
- package/dist/sweep/evm-sweeper.d.ts.map +1 -0
- package/dist/sweep/evm-sweeper.js +229 -0
- package/dist/sweep/evm-sweeper.js.map +1 -0
- package/dist/sweep/index.d.ts +22 -0
- package/dist/sweep/index.d.ts.map +1 -0
- package/dist/sweep/index.js +290 -0
- package/dist/sweep/index.js.map +1 -0
- package/dist/sweep/tron-sweeper.d.ts +40 -0
- package/dist/sweep/tron-sweeper.d.ts.map +1 -0
- package/dist/sweep/tron-sweeper.js +363 -0
- package/dist/sweep/tron-sweeper.js.map +1 -0
- package/dist/sweep/utxo-sweeper.d.ts +14 -0
- package/dist/sweep/utxo-sweeper.d.ts.map +1 -0
- package/dist/sweep/utxo-sweeper.js +13 -0
- package/dist/sweep/utxo-sweeper.js.map +1 -0
- package/dist/tron/address-convert.d.ts +15 -0
- package/dist/tron/address-convert.d.ts.map +1 -0
- package/dist/tron/address-convert.js +95 -0
- package/dist/tron/address-convert.js.map +1 -0
- package/dist/tron/encoder.d.ts +20 -0
- package/dist/tron/encoder.d.ts.map +1 -0
- package/dist/tron/encoder.js +67 -0
- package/dist/tron/encoder.js.map +1 -0
- package/dist/tron/index.d.ts +6 -0
- package/dist/tron/index.d.ts.map +1 -0
- package/dist/tron/index.js +20 -0
- package/dist/tron/index.js.map +1 -0
- package/dist/tron/sha256.d.ts +6 -0
- package/dist/tron/sha256.d.ts.map +1 -0
- package/dist/tron/sha256.js +90 -0
- package/dist/tron/sha256.js.map +1 -0
- package/dist/tron/watcher.d.ts +42 -0
- package/dist/tron/watcher.d.ts.map +1 -0
- package/dist/tron/watcher.js +168 -0
- package/dist/tron/watcher.js.map +1 -0
- package/package.json +47 -0
- package/src/__tests__/bitcoin-encoder.test.ts +115 -0
- package/src/__tests__/dogecoin-encoder.test.ts +66 -0
- package/src/__tests__/litecoin-encoder.test.ts +51 -0
- package/src/__tests__/registry.test.ts +91 -0
- package/src/__tests__/rpc.test.ts +36 -0
- package/src/__tests__/solana-encoder.test.ts +103 -0
- package/src/__tests__/solana-watcher.test.ts +316 -0
- package/src/__tests__/sweep-key-parity.test.ts +302 -0
- package/src/__tests__/tron-encoder.test.ts +108 -0
- package/src/__tests__/utxo-watcher.test.ts +252 -0
- package/src/bitcoin/encoder.ts +320 -0
- package/src/bitcoin/index.ts +23 -0
- package/src/dogecoin/encoder.ts +161 -0
- package/src/dogecoin/index.ts +23 -0
- package/src/evm/encoder.ts +49 -0
- package/src/evm/eth-watcher.ts +168 -0
- package/src/evm/index.ts +46 -0
- package/src/evm/types.ts +146 -0
- package/src/evm/watcher.ts +189 -0
- package/src/index.ts +21 -0
- package/src/litecoin/encoder.ts +18 -0
- package/src/litecoin/index.ts +23 -0
- package/src/shared/test-helpers/index.ts +36 -0
- package/src/shared/utxo/index.ts +12 -0
- package/src/shared/utxo/rpc.ts +80 -0
- package/src/shared/utxo/types.ts +43 -0
- package/src/shared/utxo/watcher.ts +195 -0
- package/src/solana/encoder.ts +72 -0
- package/src/solana/index.ts +36 -0
- package/src/solana/sweeper.ts +196 -0
- package/src/solana/types.ts +52 -0
- package/src/solana/watcher.ts +282 -0
- package/src/sweep/evm-sweeper.ts +296 -0
- package/src/sweep/index.ts +353 -0
- package/src/sweep/tron-sweeper.ts +467 -0
- package/src/sweep/utxo-sweeper.ts +23 -0
- package/src/tron/address-convert.ts +91 -0
- package/src/tron/encoder.ts +74 -0
- package/src/tron/index.ts +23 -0
- package/src/tron/sha256.ts +100 -0
- package/src/tron/watcher.ts +208 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IChainWatcher,
|
|
3
|
+
IPriceOracle,
|
|
4
|
+
IWatcherCursorStore,
|
|
5
|
+
PaymentEvent,
|
|
6
|
+
WatcherOpts,
|
|
7
|
+
} from "@wopr-network/platform-core/crypto-plugin";
|
|
8
|
+
import type { RpcCall, RpcTransaction } from "./types.js";
|
|
9
|
+
import { createRpcCaller } from "./watcher.js";
|
|
10
|
+
|
|
11
|
+
/** Microdollars per cent. Used for oracle price conversion. */
|
|
12
|
+
const MICROS_PER_CENT = 10_000n;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert native token amount to USD cents using oracle price in microdollars.
|
|
16
|
+
*
|
|
17
|
+
* @param rawAmount - Raw value in smallest unit (e.g. wei)
|
|
18
|
+
* @param priceMicros - Price per whole token in microdollars (10^-6 USD)
|
|
19
|
+
* @param decimals - Token decimals (18 for ETH)
|
|
20
|
+
*/
|
|
21
|
+
function nativeToCents(rawAmount: bigint, priceMicros: number, decimals: number): number {
|
|
22
|
+
if (rawAmount < 0n) throw new Error("rawAmount must be non-negative");
|
|
23
|
+
if (!Number.isInteger(priceMicros) || priceMicros <= 0) {
|
|
24
|
+
throw new Error(`priceMicros must be a positive integer, got ${priceMicros}`);
|
|
25
|
+
}
|
|
26
|
+
return Number((rawAmount * BigInt(priceMicros)) / (MICROS_PER_CENT * 10n ** BigInt(decimals)));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Native ETH transfer watcher.
|
|
31
|
+
*
|
|
32
|
+
* Unlike the ERC-20 EvmWatcher which uses eth_getLogs for Transfer events,
|
|
33
|
+
* this scans blocks for transactions where `to` matches a watched deposit
|
|
34
|
+
* address and `value > 0`.
|
|
35
|
+
*
|
|
36
|
+
* Scans up to latest block (not just confirmed) to detect pending txs.
|
|
37
|
+
* Emits events on each confirmation increment. Only advances cursor
|
|
38
|
+
* past fully-confirmed blocks.
|
|
39
|
+
*/
|
|
40
|
+
export class EthWatcher implements IChainWatcher {
|
|
41
|
+
private _cursor = 0;
|
|
42
|
+
private _stopped = false;
|
|
43
|
+
private readonly chain: string;
|
|
44
|
+
private readonly token: string;
|
|
45
|
+
private readonly rpc: RpcCall;
|
|
46
|
+
private readonly oracle: IPriceOracle;
|
|
47
|
+
private readonly confirmations: number;
|
|
48
|
+
private readonly cursorStore: IWatcherCursorStore;
|
|
49
|
+
private readonly watcherId: string;
|
|
50
|
+
private _watchedAddresses: Set<string>;
|
|
51
|
+
|
|
52
|
+
constructor(opts: WatcherOpts) {
|
|
53
|
+
this.chain = opts.chain;
|
|
54
|
+
this.token = opts.token;
|
|
55
|
+
this.rpc = createRpcCaller(opts.rpcUrl, opts.rpcHeaders);
|
|
56
|
+
this.oracle = opts.oracle;
|
|
57
|
+
this._cursor = 0;
|
|
58
|
+
this.confirmations = opts.confirmations;
|
|
59
|
+
this.cursorStore = opts.cursorStore;
|
|
60
|
+
this.watcherId = `eth:${opts.chain}`;
|
|
61
|
+
this._watchedAddresses = new Set<string>();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async init(): Promise<void> {
|
|
65
|
+
const saved = await this.cursorStore.get(this.watcherId);
|
|
66
|
+
if (saved !== null) this._cursor = saved;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setWatchedAddresses(addresses: string[]): void {
|
|
70
|
+
this._watchedAddresses = new Set(addresses.map((a) => a.toLowerCase()));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getCursor(): number {
|
|
74
|
+
return this._cursor;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
stop(): void {
|
|
78
|
+
this._stopped = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Poll for native ETH transfers to watched addresses, including unconfirmed blocks.
|
|
83
|
+
*
|
|
84
|
+
* Scans from cursor to latest block in batches of 5. Emits events with current
|
|
85
|
+
* confirmation count. Re-emits on each confirmation increment. Only advances
|
|
86
|
+
* cursor past fully-confirmed blocks.
|
|
87
|
+
*/
|
|
88
|
+
async poll(): Promise<PaymentEvent[]> {
|
|
89
|
+
if (this._stopped || this._watchedAddresses.size === 0) return [];
|
|
90
|
+
|
|
91
|
+
const latestHex = (await this.rpc("eth_blockNumber", [])) as string;
|
|
92
|
+
const latest = Number.parseInt(latestHex, 16);
|
|
93
|
+
const confirmed = latest - this.confirmations;
|
|
94
|
+
|
|
95
|
+
if (latest < this._cursor) return [];
|
|
96
|
+
|
|
97
|
+
const { priceMicros } = await this.oracle.getPrice("ETH");
|
|
98
|
+
|
|
99
|
+
const events: PaymentEvent[] = [];
|
|
100
|
+
|
|
101
|
+
// Fetch blocks in batches to avoid bursting RPC rate limits on fast chains.
|
|
102
|
+
const BATCH_SIZE = 5;
|
|
103
|
+
for (let batchStart = this._cursor; batchStart <= latest; batchStart += BATCH_SIZE) {
|
|
104
|
+
if (this._stopped) break;
|
|
105
|
+
|
|
106
|
+
const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, latest);
|
|
107
|
+
const blockNums = Array.from({ length: batchEnd - batchStart + 1 }, (_, i) => batchStart + i);
|
|
108
|
+
|
|
109
|
+
const blocks = await Promise.all(
|
|
110
|
+
blockNums.map((bn) =>
|
|
111
|
+
this.rpc("eth_getBlockByNumber", [`0x${bn.toString(16)}`, true]).then(
|
|
112
|
+
(b) => ({ blockNum: bn, block: b as { transactions: RpcTransaction[] } | null, error: null }),
|
|
113
|
+
(err: unknown) => ({ blockNum: bn, block: null, error: err }),
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Stop processing at the first failed block so the cursor doesn't advance past it.
|
|
119
|
+
const firstFailIdx = blocks.findIndex((b) => b.error !== null || !b.block);
|
|
120
|
+
const safeBlocks = firstFailIdx === -1 ? blocks : blocks.slice(0, firstFailIdx);
|
|
121
|
+
for (const { blockNum, block } of safeBlocks) {
|
|
122
|
+
if (!block) break;
|
|
123
|
+
|
|
124
|
+
const confs = latest - blockNum;
|
|
125
|
+
|
|
126
|
+
for (const tx of block.transactions) {
|
|
127
|
+
if (!tx.to) continue;
|
|
128
|
+
const to = tx.to.toLowerCase();
|
|
129
|
+
if (!this._watchedAddresses.has(to)) continue;
|
|
130
|
+
|
|
131
|
+
const valueWei = BigInt(tx.value);
|
|
132
|
+
if (valueWei === 0n) continue;
|
|
133
|
+
|
|
134
|
+
// Skip if we already emitted at this confirmation count
|
|
135
|
+
const lastConf = await this.cursorStore.getConfirmationCount(this.watcherId, tx.hash);
|
|
136
|
+
if (lastConf !== null && confs <= lastConf) continue;
|
|
137
|
+
|
|
138
|
+
const amountUsdCents = nativeToCents(valueWei, priceMicros, 18);
|
|
139
|
+
|
|
140
|
+
events.push({
|
|
141
|
+
chain: this.chain,
|
|
142
|
+
token: this.token,
|
|
143
|
+
from: tx.from.toLowerCase(),
|
|
144
|
+
to,
|
|
145
|
+
rawAmount: valueWei.toString(),
|
|
146
|
+
amountUsdCents,
|
|
147
|
+
txHash: tx.hash,
|
|
148
|
+
blockNumber: blockNum,
|
|
149
|
+
confirmations: confs,
|
|
150
|
+
confirmationsRequired: this.confirmations,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await this.cursorStore.saveConfirmationCount(this.watcherId, tx.hash, confs);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Only advance cursor past fully-confirmed blocks
|
|
157
|
+
if (blockNum <= confirmed) {
|
|
158
|
+
this._cursor = blockNum + 1;
|
|
159
|
+
await this.cursorStore.save(this.watcherId, this._cursor);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (firstFailIdx !== -1) break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return events;
|
|
167
|
+
}
|
|
168
|
+
}
|
package/src/evm/index.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { IChainPlugin, SweeperOpts, WatcherOpts } from "@wopr-network/platform-core/crypto-plugin";
|
|
2
|
+
import { EvmAddressEncoder } from "./encoder.js";
|
|
3
|
+
import { EthWatcher } from "./eth-watcher.js";
|
|
4
|
+
import { EvmWatcher } from "./watcher.js";
|
|
5
|
+
|
|
6
|
+
export { EvmAddressEncoder } from "./encoder.js";
|
|
7
|
+
export { EthWatcher } from "./eth-watcher.js";
|
|
8
|
+
export type {
|
|
9
|
+
ChainConfig,
|
|
10
|
+
EvmChain,
|
|
11
|
+
RpcCall,
|
|
12
|
+
RpcLog,
|
|
13
|
+
RpcTransaction,
|
|
14
|
+
StablecoinToken,
|
|
15
|
+
TokenConfig,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
export { DEFAULT_CHAINS, DEFAULT_TOKENS } from "./types.js";
|
|
18
|
+
export { createRpcCaller, EvmWatcher } from "./watcher.js";
|
|
19
|
+
|
|
20
|
+
const encoder = new EvmAddressEncoder();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* EVM chain plugin.
|
|
24
|
+
*
|
|
25
|
+
* Supports ERC-20 stablecoin watching (EvmWatcher) and native ETH watching (EthWatcher).
|
|
26
|
+
* The watcher created depends on whether a contractAddress is provided in opts:
|
|
27
|
+
* - With contractAddress: EvmWatcher (ERC-20 Transfer log scanner)
|
|
28
|
+
* - Without contractAddress: EthWatcher (native ETH block scanner)
|
|
29
|
+
*/
|
|
30
|
+
export const evmPlugin: IChainPlugin = {
|
|
31
|
+
pluginId: "evm",
|
|
32
|
+
supportedCurve: "secp256k1",
|
|
33
|
+
encoders: {
|
|
34
|
+
evm: encoder,
|
|
35
|
+
},
|
|
36
|
+
createWatcher(opts: WatcherOpts) {
|
|
37
|
+
if (opts.contractAddress) {
|
|
38
|
+
return new EvmWatcher(opts);
|
|
39
|
+
}
|
|
40
|
+
return new EthWatcher(opts);
|
|
41
|
+
},
|
|
42
|
+
createSweeper(_opts: SweeperOpts) {
|
|
43
|
+
throw new Error("Not implemented — EVM sweeper is planned for Phase 3");
|
|
44
|
+
},
|
|
45
|
+
version: 1,
|
|
46
|
+
};
|
package/src/evm/types.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/** Supported EVM chains. */
|
|
2
|
+
export type EvmChain = "base" | "ethereum" | "arbitrum" | "polygon" | (string & {});
|
|
3
|
+
|
|
4
|
+
/** Supported stablecoin tokens. */
|
|
5
|
+
export type StablecoinToken = "USDC" | "USDT" | "DAI";
|
|
6
|
+
|
|
7
|
+
/** Chain configuration. */
|
|
8
|
+
export interface ChainConfig {
|
|
9
|
+
readonly chain: EvmChain;
|
|
10
|
+
readonly rpcUrl: string;
|
|
11
|
+
readonly confirmations: number;
|
|
12
|
+
readonly blockTimeMs: number;
|
|
13
|
+
readonly chainId: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Token configuration on a specific chain. */
|
|
17
|
+
export interface TokenConfig {
|
|
18
|
+
readonly token: StablecoinToken;
|
|
19
|
+
readonly chain: EvmChain;
|
|
20
|
+
readonly contractAddress: `0x${string}`;
|
|
21
|
+
readonly decimals: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** JSON-RPC call function signature. */
|
|
25
|
+
export type RpcCall = (method: string, params: unknown[]) => Promise<unknown>;
|
|
26
|
+
|
|
27
|
+
/** Raw JSON-RPC log entry. */
|
|
28
|
+
export interface RpcLog {
|
|
29
|
+
address: string;
|
|
30
|
+
topics: string[];
|
|
31
|
+
data: string;
|
|
32
|
+
blockNumber: string;
|
|
33
|
+
transactionHash: string;
|
|
34
|
+
logIndex: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Raw JSON-RPC transaction entry. */
|
|
38
|
+
export interface RpcTransaction {
|
|
39
|
+
hash: string;
|
|
40
|
+
from: string;
|
|
41
|
+
to: string | null;
|
|
42
|
+
value: string;
|
|
43
|
+
blockNumber: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Default chain configurations. */
|
|
47
|
+
export const DEFAULT_CHAINS: Record<string, ChainConfig> = {
|
|
48
|
+
base: {
|
|
49
|
+
chain: "base",
|
|
50
|
+
rpcUrl: "http://op-geth:8545",
|
|
51
|
+
confirmations: 1,
|
|
52
|
+
blockTimeMs: 2000,
|
|
53
|
+
chainId: 8453,
|
|
54
|
+
},
|
|
55
|
+
ethereum: {
|
|
56
|
+
chain: "ethereum",
|
|
57
|
+
rpcUrl: "http://geth:8545",
|
|
58
|
+
confirmations: 12,
|
|
59
|
+
blockTimeMs: 12000,
|
|
60
|
+
chainId: 1,
|
|
61
|
+
},
|
|
62
|
+
arbitrum: {
|
|
63
|
+
chain: "arbitrum",
|
|
64
|
+
rpcUrl: "http://nitro:8547",
|
|
65
|
+
confirmations: 1,
|
|
66
|
+
blockTimeMs: 250,
|
|
67
|
+
chainId: 42161,
|
|
68
|
+
},
|
|
69
|
+
polygon: {
|
|
70
|
+
chain: "polygon",
|
|
71
|
+
rpcUrl: "http://bor:8545",
|
|
72
|
+
confirmations: 32,
|
|
73
|
+
blockTimeMs: 2000,
|
|
74
|
+
chainId: 137,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Default stablecoin token configs. */
|
|
79
|
+
export const DEFAULT_TOKENS: Record<string, TokenConfig> = {
|
|
80
|
+
"USDC:base": {
|
|
81
|
+
token: "USDC",
|
|
82
|
+
chain: "base",
|
|
83
|
+
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
84
|
+
decimals: 6,
|
|
85
|
+
},
|
|
86
|
+
"USDT:base": {
|
|
87
|
+
token: "USDT",
|
|
88
|
+
chain: "base",
|
|
89
|
+
contractAddress: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
|
|
90
|
+
decimals: 6,
|
|
91
|
+
},
|
|
92
|
+
"DAI:base": {
|
|
93
|
+
token: "DAI",
|
|
94
|
+
chain: "base",
|
|
95
|
+
contractAddress: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
|
96
|
+
decimals: 18,
|
|
97
|
+
},
|
|
98
|
+
"USDC:ethereum": {
|
|
99
|
+
token: "USDC",
|
|
100
|
+
chain: "ethereum",
|
|
101
|
+
contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
102
|
+
decimals: 6,
|
|
103
|
+
},
|
|
104
|
+
"USDT:ethereum": {
|
|
105
|
+
token: "USDT",
|
|
106
|
+
chain: "ethereum",
|
|
107
|
+
contractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
108
|
+
decimals: 6,
|
|
109
|
+
},
|
|
110
|
+
"DAI:ethereum": {
|
|
111
|
+
token: "DAI",
|
|
112
|
+
chain: "ethereum",
|
|
113
|
+
contractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
114
|
+
decimals: 18,
|
|
115
|
+
},
|
|
116
|
+
"USDC:arbitrum": {
|
|
117
|
+
token: "USDC",
|
|
118
|
+
chain: "arbitrum",
|
|
119
|
+
contractAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
120
|
+
decimals: 6,
|
|
121
|
+
},
|
|
122
|
+
"USDT:arbitrum": {
|
|
123
|
+
token: "USDT",
|
|
124
|
+
chain: "arbitrum",
|
|
125
|
+
contractAddress: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
126
|
+
decimals: 6,
|
|
127
|
+
},
|
|
128
|
+
"DAI:arbitrum": {
|
|
129
|
+
token: "DAI",
|
|
130
|
+
chain: "arbitrum",
|
|
131
|
+
contractAddress: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
|
|
132
|
+
decimals: 18,
|
|
133
|
+
},
|
|
134
|
+
"USDC:polygon": {
|
|
135
|
+
token: "USDC",
|
|
136
|
+
chain: "polygon",
|
|
137
|
+
contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
138
|
+
decimals: 6,
|
|
139
|
+
},
|
|
140
|
+
"USDT:polygon": {
|
|
141
|
+
token: "USDT",
|
|
142
|
+
chain: "polygon",
|
|
143
|
+
contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
|
144
|
+
decimals: 6,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IChainWatcher,
|
|
3
|
+
IWatcherCursorStore,
|
|
4
|
+
PaymentEvent,
|
|
5
|
+
WatcherOpts,
|
|
6
|
+
} from "@wopr-network/platform-core/crypto-plugin";
|
|
7
|
+
import type { RpcCall, RpcLog } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
10
|
+
|
|
11
|
+
/** Create an RPC caller for a given URL (plain JSON-RPC over fetch). */
|
|
12
|
+
export function createRpcCaller(rpcUrl: string, extraHeaders?: Record<string, string>): RpcCall {
|
|
13
|
+
let id = 0;
|
|
14
|
+
const headers: Record<string, string> = { "Content-Type": "application/json", ...extraHeaders };
|
|
15
|
+
return async (method: string, params: unknown[]): Promise<unknown> => {
|
|
16
|
+
const res = await fetch(rpcUrl, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers,
|
|
19
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method, params }),
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const body = await res.text().catch(() => "");
|
|
23
|
+
const hasApiKey = "TRON-PRO-API-KEY" in headers;
|
|
24
|
+
console.error(
|
|
25
|
+
`[rpc] ${method} ${res.status} auth=${hasApiKey} url=${rpcUrl.replace(/apikey=[^&]+/, "apikey=***")} body=${body.slice(0, 200)}`,
|
|
26
|
+
);
|
|
27
|
+
throw new Error(`RPC ${method} failed: ${res.status}`);
|
|
28
|
+
}
|
|
29
|
+
const data = (await res.json()) as { result?: unknown; error?: { message: string } };
|
|
30
|
+
if (data.error) throw new Error(`RPC ${method} error: ${data.error.message}`);
|
|
31
|
+
return data.result;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert token raw amount (BigInt) to USD cents (integer).
|
|
37
|
+
* Stablecoins are 1:1 USD. Truncates fractional cents.
|
|
38
|
+
*/
|
|
39
|
+
function centsFromTokenAmount(rawAmount: bigint, decimals: number): number {
|
|
40
|
+
return Number((rawAmount * 100n) / 10n ** BigInt(decimals));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* ERC-20 Transfer log scanner.
|
|
45
|
+
*
|
|
46
|
+
* Scans from cursor to latest block for Transfer events matching watched
|
|
47
|
+
* deposit addresses. Emits events with current confirmation count. Re-emits
|
|
48
|
+
* on each confirmation increment. Only advances cursor past fully-confirmed blocks.
|
|
49
|
+
*/
|
|
50
|
+
export class EvmWatcher implements IChainWatcher {
|
|
51
|
+
private _cursor = 0;
|
|
52
|
+
private _stopped = false;
|
|
53
|
+
private readonly chain: string;
|
|
54
|
+
private readonly token: string;
|
|
55
|
+
private readonly rpc: RpcCall;
|
|
56
|
+
private readonly confirmations: number;
|
|
57
|
+
private readonly contractAddress: string;
|
|
58
|
+
private readonly decimals: number;
|
|
59
|
+
private readonly cursorStore: IWatcherCursorStore;
|
|
60
|
+
private readonly watcherId: string;
|
|
61
|
+
private _watchedAddresses: string[];
|
|
62
|
+
|
|
63
|
+
constructor(opts: WatcherOpts) {
|
|
64
|
+
this.chain = opts.chain;
|
|
65
|
+
this.token = opts.token;
|
|
66
|
+
this.rpc = createRpcCaller(opts.rpcUrl, opts.rpcHeaders);
|
|
67
|
+
this._cursor = 0;
|
|
68
|
+
this.confirmations = opts.confirmations;
|
|
69
|
+
this.contractAddress = (opts.contractAddress ?? "").toLowerCase();
|
|
70
|
+
this.decimals = opts.decimals;
|
|
71
|
+
this.cursorStore = opts.cursorStore;
|
|
72
|
+
this.watcherId = `evm:${opts.chain}:${opts.token}`;
|
|
73
|
+
this._watchedAddresses = [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async init(): Promise<void> {
|
|
77
|
+
const saved = await this.cursorStore.get(this.watcherId);
|
|
78
|
+
if (saved !== null) this._cursor = saved;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setWatchedAddresses(addresses: string[]): void {
|
|
82
|
+
this._watchedAddresses = addresses.map((a) => a.toLowerCase());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getCursor(): number {
|
|
86
|
+
return this._cursor;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
stop(): void {
|
|
90
|
+
this._stopped = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Poll for ERC-20 Transfer events, including pending (unconfirmed) blocks.
|
|
95
|
+
*
|
|
96
|
+
* Two-phase scan:
|
|
97
|
+
* 1. Scan cursor..latest for new/updated txs, emit with current confirmation count
|
|
98
|
+
* 2. Re-check pending txs automatically since cursor doesn't advance past unconfirmed blocks
|
|
99
|
+
*
|
|
100
|
+
* Cursor only advances past fully-confirmed blocks.
|
|
101
|
+
*
|
|
102
|
+
* Returns PaymentEvent[] instead of using callbacks.
|
|
103
|
+
*/
|
|
104
|
+
async poll(): Promise<PaymentEvent[]> {
|
|
105
|
+
if (this._stopped || this._watchedAddresses.length === 0) return [];
|
|
106
|
+
|
|
107
|
+
const latestHex = (await this.rpc("eth_blockNumber", [])) as string;
|
|
108
|
+
const latest = Number.parseInt(latestHex, 16);
|
|
109
|
+
const confirmed = latest - this.confirmations;
|
|
110
|
+
|
|
111
|
+
if (latest < this._cursor) return [];
|
|
112
|
+
|
|
113
|
+
// Filter by topic[2] (to address) when watched addresses are set.
|
|
114
|
+
const toFilter =
|
|
115
|
+
this._watchedAddresses.length > 0
|
|
116
|
+
? this._watchedAddresses.map((a) => `0x000000000000000000000000${a.slice(2)}`)
|
|
117
|
+
: null;
|
|
118
|
+
|
|
119
|
+
// Scan from cursor to latest (not just confirmed) to detect pending txs
|
|
120
|
+
const logs = (await this.rpc("eth_getLogs", [
|
|
121
|
+
{
|
|
122
|
+
address: this.contractAddress,
|
|
123
|
+
topics: [TRANSFER_TOPIC, null, toFilter],
|
|
124
|
+
fromBlock: `0x${this._cursor.toString(16)}`,
|
|
125
|
+
toBlock: `0x${latest.toString(16)}`,
|
|
126
|
+
},
|
|
127
|
+
])) as RpcLog[];
|
|
128
|
+
|
|
129
|
+
// Group logs by block
|
|
130
|
+
const logsByBlock = new Map<number, RpcLog[]>();
|
|
131
|
+
for (const log of logs) {
|
|
132
|
+
const bn = Number.parseInt(log.blockNumber, 16);
|
|
133
|
+
const arr = logsByBlock.get(bn);
|
|
134
|
+
if (arr) arr.push(log);
|
|
135
|
+
else logsByBlock.set(bn, [log]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const events: PaymentEvent[] = [];
|
|
139
|
+
|
|
140
|
+
// Process all blocks (including unconfirmed), emit with confirmation count
|
|
141
|
+
const blockNums = [...logsByBlock.keys()].sort((a, b) => a - b);
|
|
142
|
+
for (const blockNum of blockNums) {
|
|
143
|
+
const confs = latest - blockNum;
|
|
144
|
+
|
|
145
|
+
for (const log of logsByBlock.get(blockNum) ?? []) {
|
|
146
|
+
const txKey = `${log.transactionHash}:${log.logIndex}`;
|
|
147
|
+
|
|
148
|
+
// Skip if we already emitted at this confirmation count
|
|
149
|
+
const lastConf = await this.cursorStore.getConfirmationCount(this.watcherId, txKey);
|
|
150
|
+
if (lastConf !== null && confs <= lastConf) continue;
|
|
151
|
+
|
|
152
|
+
const to = `0x${log.topics[2].slice(26)}`.toLowerCase();
|
|
153
|
+
const from = `0x${log.topics[1].slice(26)}`.toLowerCase();
|
|
154
|
+
const rawAmount = BigInt(log.data);
|
|
155
|
+
const amountUsdCents = centsFromTokenAmount(rawAmount, this.decimals);
|
|
156
|
+
|
|
157
|
+
events.push({
|
|
158
|
+
chain: this.chain,
|
|
159
|
+
token: this.token,
|
|
160
|
+
from,
|
|
161
|
+
to,
|
|
162
|
+
rawAmount: rawAmount.toString(),
|
|
163
|
+
amountUsdCents,
|
|
164
|
+
txHash: log.transactionHash,
|
|
165
|
+
blockNumber: blockNum,
|
|
166
|
+
confirmations: confs,
|
|
167
|
+
confirmationsRequired: this.confirmations,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Track confirmation count
|
|
171
|
+
await this.cursorStore.saveConfirmationCount(this.watcherId, txKey, confs);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Only advance cursor past fully-confirmed blocks
|
|
175
|
+
if (blockNum <= confirmed) {
|
|
176
|
+
this._cursor = blockNum + 1;
|
|
177
|
+
await this.cursorStore.save(this.watcherId, this._cursor);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Advance cursor if no logs found but confirmed blocks exist
|
|
182
|
+
if (blockNums.length === 0 && confirmed >= this._cursor) {
|
|
183
|
+
this._cursor = confirmed + 1;
|
|
184
|
+
await this.cursorStore.save(this.watcherId, this._cursor);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return events;
|
|
188
|
+
}
|
|
189
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { bitcoinPlugin } from "./bitcoin/index.js";
|
|
2
|
+
export { dogecoinPlugin, encodeP2pkhAddress, p2pkhEncoder } from "./dogecoin/index.js";
|
|
3
|
+
export { evmPlugin } from "./evm/index.js";
|
|
4
|
+
export { bech32Encoder as ltcBech32Encoder, litecoinPlugin } from "./litecoin/index.js";
|
|
5
|
+
export {
|
|
6
|
+
base58Encode,
|
|
7
|
+
createSolanaRpcCaller,
|
|
8
|
+
SolanaAddressEncoder,
|
|
9
|
+
SolanaSweeper,
|
|
10
|
+
SolanaWatcher,
|
|
11
|
+
solanaPlugin,
|
|
12
|
+
} from "./solana/index.js";
|
|
13
|
+
export {
|
|
14
|
+
encodeKeccakB58Address,
|
|
15
|
+
hexToTron,
|
|
16
|
+
isTronAddress,
|
|
17
|
+
keccakB58Encoder,
|
|
18
|
+
TronEvmWatcher,
|
|
19
|
+
tronPlugin,
|
|
20
|
+
tronToHex,
|
|
21
|
+
} from "./tron/index.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EncodingParams, IAddressEncoder } from "@wopr-network/platform-core/crypto-plugin";
|
|
2
|
+
|
|
3
|
+
import { encodeBech32Address } from "../bitcoin/encoder.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Litecoin bech32 address encoder.
|
|
7
|
+
* Implements IAddressEncoder for the litecoin plugin.
|
|
8
|
+
* Default HRP is "ltc" (mainnet). Override with params.hrp for testnet ("tltc").
|
|
9
|
+
*/
|
|
10
|
+
export const bech32Encoder: IAddressEncoder = {
|
|
11
|
+
encode(publicKey: Uint8Array, params: EncodingParams): string {
|
|
12
|
+
const hrp = params.hrp ?? "ltc";
|
|
13
|
+
return encodeBech32Address(publicKey, hrp);
|
|
14
|
+
},
|
|
15
|
+
encodingType(): string {
|
|
16
|
+
return "bech32";
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IChainPlugin, WatcherOpts } from "@wopr-network/platform-core/crypto-plugin";
|
|
2
|
+
|
|
3
|
+
import { createRpcFromOpts } from "../shared/utxo/index.js";
|
|
4
|
+
import { createUtxoWatcher } from "../shared/utxo/watcher.js";
|
|
5
|
+
import { bech32Encoder } from "./encoder.js";
|
|
6
|
+
|
|
7
|
+
export { bech32Encoder } from "./encoder.js";
|
|
8
|
+
|
|
9
|
+
export const litecoinPlugin: IChainPlugin = {
|
|
10
|
+
pluginId: "litecoin",
|
|
11
|
+
supportedCurve: "secp256k1",
|
|
12
|
+
encoders: {
|
|
13
|
+
bech32: bech32Encoder,
|
|
14
|
+
},
|
|
15
|
+
createWatcher(opts: WatcherOpts) {
|
|
16
|
+
const rpc = createRpcFromOpts(opts.rpcUrl, opts.rpcHeaders);
|
|
17
|
+
return createUtxoWatcher(opts, rpc);
|
|
18
|
+
},
|
|
19
|
+
createSweeper() {
|
|
20
|
+
throw new Error("Not implemented");
|
|
21
|
+
},
|
|
22
|
+
version: 1,
|
|
23
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IPriceOracle, IWatcherCursorStore } from "@wopr-network/platform-core/crypto-plugin";
|
|
2
|
+
|
|
3
|
+
/** In-memory mock cursor store for testing UTXO watchers. */
|
|
4
|
+
export function createMockCursorStore(): IWatcherCursorStore & {
|
|
5
|
+
_cursors: Map<string, number>;
|
|
6
|
+
_confirmations: Map<string, number>;
|
|
7
|
+
} {
|
|
8
|
+
const cursors = new Map<string, number>();
|
|
9
|
+
const confirmations = new Map<string, number>();
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
_cursors: cursors,
|
|
13
|
+
_confirmations: confirmations,
|
|
14
|
+
async get(watcherId: string): Promise<number | null> {
|
|
15
|
+
return cursors.get(watcherId) ?? null;
|
|
16
|
+
},
|
|
17
|
+
async save(watcherId: string, cursor: number): Promise<void> {
|
|
18
|
+
cursors.set(watcherId, cursor);
|
|
19
|
+
},
|
|
20
|
+
async getConfirmationCount(watcherId: string, txKey: string): Promise<number | null> {
|
|
21
|
+
return confirmations.get(`${watcherId}:${txKey}`) ?? null;
|
|
22
|
+
},
|
|
23
|
+
async saveConfirmationCount(watcherId: string, txKey: string, count: number): Promise<void> {
|
|
24
|
+
confirmations.set(`${watcherId}:${txKey}`, count);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Mock price oracle that returns a fixed price. */
|
|
30
|
+
export function createMockOracle(priceMicros = 100_000_000_000): IPriceOracle {
|
|
31
|
+
return {
|
|
32
|
+
async getPrice(_token: string) {
|
|
33
|
+
return { priceMicros };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { createBitcoindRpc, createRpcFromOpts, parseRpcUrl } from "./rpc.js";
|
|
2
|
+
export type {
|
|
3
|
+
DescriptorInfo,
|
|
4
|
+
GetTransactionResponse,
|
|
5
|
+
ImportDescriptorResult,
|
|
6
|
+
ReceivedByAddress,
|
|
7
|
+
RpcCall,
|
|
8
|
+
TxDetail,
|
|
9
|
+
UtxoNodeConfig,
|
|
10
|
+
} from "./types.js";
|
|
11
|
+
export type { UtxoWatcherConfig } from "./watcher.js";
|
|
12
|
+
export { createUtxoWatcher, UtxoWatcher } from "./watcher.js";
|