@antseed/cli 0.1.149 → 0.1.151
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/README.md +38 -19
- package/dist/cli/commands/buyer/activity.d.ts +3 -0
- package/dist/cli/commands/buyer/activity.d.ts.map +1 -0
- package/dist/cli/commands/buyer/activity.js +555 -0
- package/dist/cli/commands/buyer/activity.js.map +1 -0
- package/dist/cli/commands/buyer/daemon.d.ts +13 -0
- package/dist/cli/commands/buyer/daemon.d.ts.map +1 -0
- package/dist/cli/commands/buyer/daemon.js +39 -0
- package/dist/cli/commands/buyer/daemon.js.map +1 -0
- package/dist/cli/commands/buyer/deposit.d.ts +8 -0
- package/dist/cli/commands/buyer/deposit.d.ts.map +1 -1
- package/dist/cli/commands/buyer/deposit.js +338 -25
- package/dist/cli/commands/buyer/deposit.js.map +1 -1
- package/dist/cli/commands/buyer/index.d.ts.map +1 -1
- package/dist/cli/commands/buyer/index.js +2 -0
- package/dist/cli/commands/buyer/index.js.map +1 -1
- package/dist/cli/commands/buyer/start.d.ts.map +1 -1
- package/dist/cli/commands/buyer/start.js +44 -2
- package/dist/cli/commands/buyer/start.js.map +1 -1
- package/dist/cli/commands/buyer/sweep.d.ts.map +1 -1
- package/dist/cli/commands/buyer/sweep.js +17 -17
- package/dist/cli/commands/buyer/sweep.js.map +1 -1
- package/dist/cli/commands/payments.d.ts.map +1 -1
- package/dist/cli/commands/payments.js +29 -34
- package/dist/cli/commands/payments.js.map +1 -1
- package/dist/cli/commands/seller/start.d.ts.map +1 -1
- package/dist/cli/commands/seller/start.js +4 -1
- package/dist/cli/commands/seller/start.js.map +1 -1
- package/dist/cli/commands/seller/start.test.js +15 -0
- package/dist/cli/commands/seller/start.test.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +2 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/loader.test.js +18 -0
- package/dist/config/loader.test.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/generated/baked-defaults.d.ts +15 -0
- package/dist/generated/baked-defaults.d.ts.map +1 -0
- package/dist/generated/baked-defaults.js +15 -0
- package/dist/generated/baked-defaults.js.map +1 -0
- package/dist/plugins/manager.d.ts +1 -0
- package/dist/plugins/manager.d.ts.map +1 -1
- package/dist/plugins/manager.js +15 -4
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/manager.test.d.ts +2 -0
- package/dist/plugins/manager.test.d.ts.map +1 -0
- package/dist/plugins/manager.test.js +15 -0
- package/dist/plugins/manager.test.js.map +1 -0
- package/dist/proxy/buyer-proxy.d.ts +12 -1
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +75 -2
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +96 -7
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/deposit-watcher.d.ts +125 -0
- package/dist/proxy/deposit-watcher.d.ts.map +1 -0
- package/dist/proxy/deposit-watcher.js +306 -0
- package/dist/proxy/deposit-watcher.js.map +1 -0
- package/dist/proxy/deposit-watcher.test.d.ts +2 -0
- package/dist/proxy/deposit-watcher.test.d.ts.map +1 -0
- package/dist/proxy/deposit-watcher.test.js +173 -0
- package/dist/proxy/deposit-watcher.test.js.map +1 -0
- package/dist/proxy/request-utils.js +4 -3
- package/dist/proxy/request-utils.js.map +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watches the buyer's hot wallet for incoming USDC and sweeps it into the
|
|
3
|
+
* deposits contract via a relayer — the daemon-side engine behind the
|
|
4
|
+
* desktop's automatic deposits and `antseed deposit`.
|
|
5
|
+
*
|
|
6
|
+
* The buyer never holds funds and never pays gas: the sweep is authorized
|
|
7
|
+
* off-chain (EIP-3009 receive authorization) and broadcast by whichever peer
|
|
8
|
+
* relays it — this module signs and waits, it never sends a transaction.
|
|
9
|
+
*
|
|
10
|
+
* All I/O is injected so the same engine runs inside the buyer daemon
|
|
11
|
+
* (dispatch through the live node) and inside one-shot CLI commands
|
|
12
|
+
* (dispatch through an ephemeral node).
|
|
13
|
+
*/
|
|
14
|
+
import { makeUsdcDomain } from '@antseed/node';
|
|
15
|
+
import type { Identity, SweepReceiptPayload, SweepRequestPayload } from '@antseed/node';
|
|
16
|
+
export type DepositWatchPhase = 'deferred' | 'received' | 'sweeping' | 'credited' | 'error';
|
|
17
|
+
export interface DepositWatchEvent {
|
|
18
|
+
/** Monotonic per-watcher counter so pollers can forward each event exactly once. */
|
|
19
|
+
seq: number;
|
|
20
|
+
phase: DepositWatchPhase;
|
|
21
|
+
amountBaseUnits?: string;
|
|
22
|
+
txHash?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
at: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* active — a deposit flow is on screen (desktop view or `antseed deposit`).
|
|
28
|
+
* background — the flow closed; card/Fun deliveries can land minutes later,
|
|
29
|
+
* so keep a slow poll until the linger window passes with an empty wallet.
|
|
30
|
+
* idle — the daemon's resting cadence (auto-sweep on): watch forever, cheaply.
|
|
31
|
+
* off — not polling.
|
|
32
|
+
*/
|
|
33
|
+
export type DepositWatchMode = 'active' | 'background' | 'idle' | 'off';
|
|
34
|
+
export interface DepositWatcherStatus {
|
|
35
|
+
mode: DepositWatchMode;
|
|
36
|
+
address: string;
|
|
37
|
+
/** Last observed hot-wallet USDC balance in base units. */
|
|
38
|
+
usdcBalance: string;
|
|
39
|
+
sweepInFlight: boolean;
|
|
40
|
+
lastEvent: DepositWatchEvent | null;
|
|
41
|
+
}
|
|
42
|
+
/** The read surface the watcher needs; DepositsClient satisfies it. */
|
|
43
|
+
export interface DepositWatcherDepositsReader {
|
|
44
|
+
getUSDCBalance(address: string): Promise<bigint>;
|
|
45
|
+
getBuyerBalance(address: string): Promise<{
|
|
46
|
+
available: bigint;
|
|
47
|
+
reserved: bigint;
|
|
48
|
+
}>;
|
|
49
|
+
getBuyerCreditLimit(address: string): Promise<bigint>;
|
|
50
|
+
}
|
|
51
|
+
/** The relay surface the watcher needs; DepositRelayClient satisfies it. */
|
|
52
|
+
export interface DepositWatcherRelayReader {
|
|
53
|
+
fee(): Promise<bigint>;
|
|
54
|
+
verifyUsdcDomain(usdcAddress: string, domain: ReturnType<typeof makeUsdcDomain>): Promise<boolean>;
|
|
55
|
+
getSweepConfirmation(txHash: string, expected: {
|
|
56
|
+
buyer: string;
|
|
57
|
+
deposited: bigint;
|
|
58
|
+
fee: bigint;
|
|
59
|
+
authNonce: string;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
deposited: bigint;
|
|
62
|
+
txHash: string;
|
|
63
|
+
} | null>;
|
|
64
|
+
isAuthorizationUsed(usdcAddress: string, authorizer: string, nonce: string): Promise<boolean>;
|
|
65
|
+
}
|
|
66
|
+
export interface DepositWatcherDeps {
|
|
67
|
+
wallet: Identity['wallet'];
|
|
68
|
+
address: string;
|
|
69
|
+
depositsClient: DepositWatcherDepositsReader;
|
|
70
|
+
relayClient: DepositWatcherRelayReader;
|
|
71
|
+
usdcAddress: string;
|
|
72
|
+
evmChainId: number;
|
|
73
|
+
depositRelayAddress: string;
|
|
74
|
+
/** Offer the signed payload to relayers (one at a time, via a node). */
|
|
75
|
+
dispatch: (payload: SweepRequestPayload) => Promise<{
|
|
76
|
+
offered: number;
|
|
77
|
+
accepted: boolean;
|
|
78
|
+
}>;
|
|
79
|
+
/** Refresh discovery and connect to a few relay-capable peers. */
|
|
80
|
+
connectRelayers: () => Promise<void>;
|
|
81
|
+
/** Latest relayer receipt for a sweep authNonce, if one arrived. */
|
|
82
|
+
getReceipt: (authNonce: string) => Promise<SweepReceiptPayload | null>;
|
|
83
|
+
onEvent?: (event: DepositWatchEvent) => void;
|
|
84
|
+
/** Resting cadence once the background linger expires; null stops instead. */
|
|
85
|
+
idleIntervalMs?: number | null;
|
|
86
|
+
}
|
|
87
|
+
export declare const DEPOSIT_WATCH_ACTIVE_INTERVAL_MS = 2000;
|
|
88
|
+
export declare const DEPOSIT_WATCH_BACKGROUND_INTERVAL_MS = 15000;
|
|
89
|
+
export declare const DEPOSIT_WATCH_IDLE_INTERVAL_MS = 30000;
|
|
90
|
+
export declare class DepositWatcher {
|
|
91
|
+
private readonly _deps;
|
|
92
|
+
private _timer;
|
|
93
|
+
private _mode;
|
|
94
|
+
private _lingerDeadline;
|
|
95
|
+
private _lastBalance;
|
|
96
|
+
private _sweepInFlight;
|
|
97
|
+
private _lastSweepAttemptAt;
|
|
98
|
+
private _lastEvent;
|
|
99
|
+
private _seq;
|
|
100
|
+
private _stopped;
|
|
101
|
+
constructor(deps: DepositWatcherDeps);
|
|
102
|
+
status(): DepositWatcherStatus;
|
|
103
|
+
/** A deposit flow is on screen: poll fast, sweep existing funds promptly. */
|
|
104
|
+
promote(): void;
|
|
105
|
+
/**
|
|
106
|
+
* The deposit flow closed: don't stop — deliveries (card/Fun purchases) can
|
|
107
|
+
* land minutes later. Keep polling at the background cadence; once the
|
|
108
|
+
* linger window passes with an empty wallet, fall back to the idle cadence
|
|
109
|
+
* (or stop when idle polling is disabled).
|
|
110
|
+
*/
|
|
111
|
+
demote(): void;
|
|
112
|
+
/** Start the daemon's resting watch (auto-sweep). */
|
|
113
|
+
startIdle(): void;
|
|
114
|
+
stop(): void;
|
|
115
|
+
private _setMode;
|
|
116
|
+
private _clearTimer;
|
|
117
|
+
private _emit;
|
|
118
|
+
private _poll;
|
|
119
|
+
/** Sign and dispatch one sweep of the current hot-wallet balance. */
|
|
120
|
+
sweepNow(): Promise<void>;
|
|
121
|
+
private _sweepOnce;
|
|
122
|
+
private _waitForConfirmation;
|
|
123
|
+
private _pollReceiptTxHash;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=deposit-watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposit-watcher.d.ts","sourceRoot":"","sources":["../../src/proxy/deposit-watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watches the buyer's hot wallet for incoming USDC and sweeps it into the
|
|
3
|
+
* deposits contract via a relayer — the daemon-side engine behind the
|
|
4
|
+
* desktop's automatic deposits and `antseed deposit`.
|
|
5
|
+
*
|
|
6
|
+
* The buyer never holds funds and never pays gas: the sweep is authorized
|
|
7
|
+
* off-chain (EIP-3009 receive authorization) and broadcast by whichever peer
|
|
8
|
+
* relays it — this module signs and waits, it never sends a transaction.
|
|
9
|
+
*
|
|
10
|
+
* All I/O is injected so the same engine runs inside the buyer daemon
|
|
11
|
+
* (dispatch through the live node) and inside one-shot CLI commands
|
|
12
|
+
* (dispatch through an ephemeral node).
|
|
13
|
+
*/
|
|
14
|
+
import { buildReceiveAuthorization, makeUsdcDomain } from '@antseed/node';
|
|
15
|
+
export const DEPOSIT_WATCH_ACTIVE_INTERVAL_MS = 2_000;
|
|
16
|
+
export const DEPOSIT_WATCH_BACKGROUND_INTERVAL_MS = 15_000;
|
|
17
|
+
export const DEPOSIT_WATCH_IDLE_INTERVAL_MS = 30_000;
|
|
18
|
+
// How long the background watch keeps polling an empty wallet before falling
|
|
19
|
+
// back to idle (or stopping). Any activity re-arms it.
|
|
20
|
+
const DEPOSIT_WATCH_LINGER_MS = 30 * 60_000;
|
|
21
|
+
const SWEEP_AUTH_VALIDITY_SECS = 3_600;
|
|
22
|
+
const SWEEP_CONFIRM_TIMEOUT_MS = 120_000;
|
|
23
|
+
const SWEEP_POLL_INTERVAL_MS = 1_000;
|
|
24
|
+
// After a failed/incomplete sweep the funds stay in the wallet; retry on the
|
|
25
|
+
// watcher tick once this cooldown passes instead of hammering the network.
|
|
26
|
+
const SWEEP_RETRY_COOLDOWN_MS = 60_000;
|
|
27
|
+
// AntseedDeposits enforces a 1 USDC minimum first deposit (net of the fee).
|
|
28
|
+
const MIN_FIRST_DEPOSIT_BASE_UNITS = 1000000n;
|
|
29
|
+
// Minimum top-up when the sweep is clamped by the credit-limit headroom.
|
|
30
|
+
// Without a floor, a wallet holding more than the remaining headroom would
|
|
31
|
+
// grind it into the limit gap in dust increments — paying the fixed relay
|
|
32
|
+
// fee on each sliver every time spending frees a few cents of headroom.
|
|
33
|
+
const MIN_TOPUP_BASE_UNITS = 1000000n;
|
|
34
|
+
function sleep(ms) {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
export class DepositWatcher {
|
|
38
|
+
_deps;
|
|
39
|
+
_timer = null;
|
|
40
|
+
_mode = 'off';
|
|
41
|
+
_lingerDeadline = 0;
|
|
42
|
+
_lastBalance = 0n;
|
|
43
|
+
_sweepInFlight = false;
|
|
44
|
+
_lastSweepAttemptAt = 0;
|
|
45
|
+
_lastEvent = null;
|
|
46
|
+
_seq = 0;
|
|
47
|
+
_stopped = false;
|
|
48
|
+
constructor(deps) {
|
|
49
|
+
this._deps = deps;
|
|
50
|
+
}
|
|
51
|
+
status() {
|
|
52
|
+
return {
|
|
53
|
+
mode: this._mode,
|
|
54
|
+
address: this._deps.address,
|
|
55
|
+
usdcBalance: this._lastBalance.toString(),
|
|
56
|
+
sweepInFlight: this._sweepInFlight,
|
|
57
|
+
lastEvent: this._lastEvent,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** A deposit flow is on screen: poll fast, sweep existing funds promptly. */
|
|
61
|
+
promote() {
|
|
62
|
+
this._setMode('active', DEPOSIT_WATCH_ACTIVE_INTERVAL_MS);
|
|
63
|
+
void this._poll();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The deposit flow closed: don't stop — deliveries (card/Fun purchases) can
|
|
67
|
+
* land minutes later. Keep polling at the background cadence; once the
|
|
68
|
+
* linger window passes with an empty wallet, fall back to the idle cadence
|
|
69
|
+
* (or stop when idle polling is disabled).
|
|
70
|
+
*/
|
|
71
|
+
demote() {
|
|
72
|
+
this._lingerDeadline = Date.now() + DEPOSIT_WATCH_LINGER_MS;
|
|
73
|
+
this._setMode('background', DEPOSIT_WATCH_BACKGROUND_INTERVAL_MS);
|
|
74
|
+
}
|
|
75
|
+
/** Start the daemon's resting watch (auto-sweep). */
|
|
76
|
+
startIdle() {
|
|
77
|
+
this._setMode('idle', this._deps.idleIntervalMs ?? DEPOSIT_WATCH_IDLE_INTERVAL_MS);
|
|
78
|
+
void this._poll();
|
|
79
|
+
}
|
|
80
|
+
stop() {
|
|
81
|
+
this._stopped = true;
|
|
82
|
+
this._clearTimer();
|
|
83
|
+
this._mode = 'off';
|
|
84
|
+
}
|
|
85
|
+
_setMode(mode, intervalMs) {
|
|
86
|
+
if (this._stopped)
|
|
87
|
+
return;
|
|
88
|
+
this._clearTimer();
|
|
89
|
+
this._mode = mode;
|
|
90
|
+
this._timer = setInterval(() => { void this._poll(); }, intervalMs);
|
|
91
|
+
// A busy interval must never pile up sweeps; the in-flight guard makes
|
|
92
|
+
// overlapping polls harmless, so unref keeps the timer from holding the
|
|
93
|
+
// process open in one-shot commands.
|
|
94
|
+
this._timer.unref?.();
|
|
95
|
+
}
|
|
96
|
+
_clearTimer() {
|
|
97
|
+
if (this._timer) {
|
|
98
|
+
clearInterval(this._timer);
|
|
99
|
+
this._timer = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
_emit(event) {
|
|
103
|
+
this._lastEvent = { ...event, seq: ++this._seq, at: Date.now() };
|
|
104
|
+
this._deps.onEvent?.(this._lastEvent);
|
|
105
|
+
}
|
|
106
|
+
async _poll() {
|
|
107
|
+
if (this._stopped)
|
|
108
|
+
return;
|
|
109
|
+
let balance;
|
|
110
|
+
try {
|
|
111
|
+
balance = await this._deps.depositsClient.getUSDCBalance(this._deps.address);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return; // transient RPC failure — try again next tick
|
|
115
|
+
}
|
|
116
|
+
if (this._mode === 'background') {
|
|
117
|
+
if (balance > 0n || this._sweepInFlight) {
|
|
118
|
+
this._lingerDeadline = Date.now() + DEPOSIT_WATCH_LINGER_MS;
|
|
119
|
+
}
|
|
120
|
+
else if (Date.now() > this._lingerDeadline) {
|
|
121
|
+
if (this._deps.idleIntervalMs === null)
|
|
122
|
+
this.stop();
|
|
123
|
+
else
|
|
124
|
+
this._setMode('idle', this._deps.idleIntervalMs ?? DEPOSIT_WATCH_IDLE_INTERVAL_MS);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (balance > this._lastBalance) {
|
|
129
|
+
const delta = balance - this._lastBalance;
|
|
130
|
+
this._lastBalance = balance;
|
|
131
|
+
this._emit({ phase: 'received', amountBaseUnits: delta.toString() });
|
|
132
|
+
void this.sweepNow();
|
|
133
|
+
}
|
|
134
|
+
else if (balance < this._lastBalance) {
|
|
135
|
+
this._lastBalance = balance;
|
|
136
|
+
}
|
|
137
|
+
else if (balance > 0n && !this._sweepInFlight && Date.now() - this._lastSweepAttemptAt > SWEEP_RETRY_COOLDOWN_MS) {
|
|
138
|
+
// Funds from an earlier failed/partial sweep are still sitting in the
|
|
139
|
+
// wallet — retry once the cooldown passes.
|
|
140
|
+
void this.sweepNow();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Sign and dispatch one sweep of the current hot-wallet balance. */
|
|
144
|
+
async sweepNow() {
|
|
145
|
+
if (this._sweepInFlight)
|
|
146
|
+
return;
|
|
147
|
+
this._sweepInFlight = true;
|
|
148
|
+
this._lastSweepAttemptAt = Date.now();
|
|
149
|
+
try {
|
|
150
|
+
await this._sweepOnce();
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
this._emit({ phase: 'error', error: err instanceof Error ? err.message : String(err) });
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
this._sweepInFlight = false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async _sweepOnce() {
|
|
160
|
+
const { depositsClient, relayClient, address, wallet } = this._deps;
|
|
161
|
+
const [usdcBalance, deposits, creditLimit, fee] = await Promise.all([
|
|
162
|
+
depositsClient.getUSDCBalance(address),
|
|
163
|
+
depositsClient.getBuyerBalance(address),
|
|
164
|
+
depositsClient.getBuyerCreditLimit(address),
|
|
165
|
+
relayClient.fee(),
|
|
166
|
+
]);
|
|
167
|
+
const depositsBalance = deposits.available + deposits.reserved;
|
|
168
|
+
// Below the sweepable minimum (fee, plus the contract's 1 USDC first-
|
|
169
|
+
// deposit floor) — keep waiting; more USDC may still be on the way.
|
|
170
|
+
const minRequired = depositsBalance === 0n ? MIN_FIRST_DEPOSIT_BASE_UNITS + fee : fee + 1n;
|
|
171
|
+
if (usdcBalance < minRequired)
|
|
172
|
+
return;
|
|
173
|
+
// Deposits caps the credited balance at the buyer's credit limit; a net
|
|
174
|
+
// amount past it would revert the whole sweep, so clamp and leave the
|
|
175
|
+
// rest in the wallet for a later sweep.
|
|
176
|
+
const headroom = creditLimit > depositsBalance ? creditLimit - depositsBalance : 0n;
|
|
177
|
+
if (headroom === 0n) {
|
|
178
|
+
this._emit({ phase: 'deferred' });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Headroom-clamped top-up: wait until at least MIN_TOPUP_BASE_UNITS of
|
|
182
|
+
// headroom is free instead of sweeping fee-heavy slivers repeatedly.
|
|
183
|
+
if (usdcBalance - fee > headroom && headroom < MIN_TOPUP_BASE_UNITS) {
|
|
184
|
+
this._emit({ phase: 'deferred' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
let amount = usdcBalance;
|
|
188
|
+
if (amount - fee > headroom)
|
|
189
|
+
amount = headroom + fee;
|
|
190
|
+
this._emit({ phase: 'sweeping', amountBaseUnits: amount.toString() });
|
|
191
|
+
// A wrong USDC domain (name/version differ per deployment) would produce
|
|
192
|
+
// signatures the token silently rejects — refuse to sign.
|
|
193
|
+
const usdcDomain = makeUsdcDomain(this._deps.evmChainId, this._deps.usdcAddress);
|
|
194
|
+
const domainOk = await relayClient.verifyUsdcDomain(this._deps.usdcAddress, usdcDomain);
|
|
195
|
+
if (!domainOk) {
|
|
196
|
+
throw new Error('USDC EIP-712 domain mismatch — refusing to sign the sweep authorization.');
|
|
197
|
+
}
|
|
198
|
+
// The single EIP-3009 signature, addressed to the relay contract, is the
|
|
199
|
+
// consent to its immutable fixed FEE — no second signature.
|
|
200
|
+
const nowSecs = Math.floor(Date.now() / 1000);
|
|
201
|
+
const validAfter = nowSecs - 60;
|
|
202
|
+
const validBefore = nowSecs + SWEEP_AUTH_VALIDITY_SECS;
|
|
203
|
+
const { message, signature: sig3009 } = await buildReceiveAuthorization(wallet, usdcDomain, {
|
|
204
|
+
to: this._deps.depositRelayAddress,
|
|
205
|
+
value: amount,
|
|
206
|
+
validAfter: BigInt(validAfter),
|
|
207
|
+
validBefore: BigInt(validBefore),
|
|
208
|
+
});
|
|
209
|
+
const payload = {
|
|
210
|
+
version: 1,
|
|
211
|
+
evmChainId: this._deps.evmChainId,
|
|
212
|
+
relayAddress: this._deps.depositRelayAddress,
|
|
213
|
+
from: address,
|
|
214
|
+
amount: amount.toString(),
|
|
215
|
+
validAfter,
|
|
216
|
+
validBefore,
|
|
217
|
+
nonce: message.nonce,
|
|
218
|
+
sig3009,
|
|
219
|
+
};
|
|
220
|
+
let dispatch = await this._deps.dispatch(payload);
|
|
221
|
+
if (dispatch.offered === 0) {
|
|
222
|
+
await this._deps.connectRelayers();
|
|
223
|
+
dispatch = await this._deps.dispatch(payload);
|
|
224
|
+
}
|
|
225
|
+
if (dispatch.offered === 0) {
|
|
226
|
+
throw new Error('No deposit relayers are reachable right now. Your USDC is safe in the wallet — retrying automatically.');
|
|
227
|
+
}
|
|
228
|
+
// accepted === false means every relayer in the round declined (or stayed
|
|
229
|
+
// silent) — fail fast into the watcher's retry instead of waiting out the
|
|
230
|
+
// full confirmation window.
|
|
231
|
+
if (!dispatch.accepted) {
|
|
232
|
+
throw new Error('No deposit relayer accepted the request right now. Your USDC is safe in the wallet — retrying automatically.');
|
|
233
|
+
}
|
|
234
|
+
const result = await this._waitForConfirmation({
|
|
235
|
+
initialTotal: depositsBalance,
|
|
236
|
+
expectedNet: amount - fee,
|
|
237
|
+
fee,
|
|
238
|
+
authNonce: message.nonce,
|
|
239
|
+
});
|
|
240
|
+
if (!result) {
|
|
241
|
+
throw new Error('The deposit was not confirmed in time. Your USDC is safe in the wallet — retrying automatically.');
|
|
242
|
+
}
|
|
243
|
+
this._lastBalance = await depositsClient.getUSDCBalance(address).catch(() => 0n);
|
|
244
|
+
this._emit({
|
|
245
|
+
phase: 'credited',
|
|
246
|
+
amountBaseUnits: result.credited.toString(),
|
|
247
|
+
...(result.txHash ? { txHash: result.txHash } : {}),
|
|
248
|
+
});
|
|
249
|
+
// The on-chain fallbacks often confirm before the relayer's 'confirmed'
|
|
250
|
+
// receipt (the only carrier of the tx hash) arrives. Never delay the
|
|
251
|
+
// credited status for it — backfill the hash when the receipt lands.
|
|
252
|
+
if (!result.txHash) {
|
|
253
|
+
void this._pollReceiptTxHash(message.nonce, 15_000).then((txHash) => {
|
|
254
|
+
if (!txHash || this._stopped)
|
|
255
|
+
return;
|
|
256
|
+
this._emit({ phase: 'credited', amountBaseUnits: result.credited.toString(), txHash });
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Source of truth is on-chain: a matching SweepExecuted relay event, the
|
|
261
|
+
// consumed EIP-3009 authorization, or the deposits-balance increase. Relayer
|
|
262
|
+
// receipts only surface the txHash faster; zero receipts must be tolerated.
|
|
263
|
+
async _waitForConfirmation(params) {
|
|
264
|
+
const { initialTotal, expectedNet, fee, authNonce } = params;
|
|
265
|
+
const { depositsClient, relayClient, address } = this._deps;
|
|
266
|
+
const deadline = Date.now() + SWEEP_CONFIRM_TIMEOUT_MS;
|
|
267
|
+
let txHash;
|
|
268
|
+
while (Date.now() < deadline) {
|
|
269
|
+
const receipt = await this._deps.getReceipt(authNonce).catch(() => null);
|
|
270
|
+
if (receipt?.txHash)
|
|
271
|
+
txHash = receipt.txHash;
|
|
272
|
+
if (txHash) {
|
|
273
|
+
const confirmation = await relayClient.getSweepConfirmation(txHash, {
|
|
274
|
+
buyer: address,
|
|
275
|
+
deposited: expectedNet,
|
|
276
|
+
fee,
|
|
277
|
+
authNonce,
|
|
278
|
+
}).catch(() => null);
|
|
279
|
+
if (confirmation) {
|
|
280
|
+
return { credited: confirmation.deposited, txHash: confirmation.txHash };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const authorizationUsed = await relayClient.isAuthorizationUsed(this._deps.usdcAddress, address, authNonce).catch(() => false);
|
|
284
|
+
if (authorizationUsed) {
|
|
285
|
+
return { credited: expectedNet, ...(txHash ? { txHash } : {}) };
|
|
286
|
+
}
|
|
287
|
+
const current = await depositsClient.getBuyerBalance(address).catch(() => null);
|
|
288
|
+
if (current && current.available + current.reserved >= initialTotal + expectedNet) {
|
|
289
|
+
return { credited: current.available + current.reserved - initialTotal, ...(txHash ? { txHash } : {}) };
|
|
290
|
+
}
|
|
291
|
+
await sleep(SWEEP_POLL_INTERVAL_MS);
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
async _pollReceiptTxHash(authNonce, graceMs) {
|
|
296
|
+
const deadline = Date.now() + graceMs;
|
|
297
|
+
while (Date.now() < deadline) {
|
|
298
|
+
const receipt = await this._deps.getReceipt(authNonce).catch(() => null);
|
|
299
|
+
if (receipt?.txHash)
|
|
300
|
+
return receipt.txHash;
|
|
301
|
+
await sleep(1_000);
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=deposit-watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposit-watcher.js","sourceRoot":"","sources":["../../src/proxy/deposit-watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposit-watcher.test.d.ts","sourceRoot":"","sources":["../../src/proxy/deposit-watcher.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { loadOrCreateIdentity } from '@antseed/node';
|
|
7
|
+
import { DepositWatcher, } from './deposit-watcher.js';
|
|
8
|
+
const RELAY_ADDRESS = '0x' + '11'.repeat(20);
|
|
9
|
+
const USDC_ADDRESS = '0x' + '22'.repeat(20);
|
|
10
|
+
const FEE = 50000n; // $0.05
|
|
11
|
+
function makeDepositsReader(chain) {
|
|
12
|
+
return {
|
|
13
|
+
getUSDCBalance: async () => chain.walletBalance,
|
|
14
|
+
getBuyerBalance: async () => ({ available: chain.depositsAvailable, reserved: chain.depositsReserved }),
|
|
15
|
+
getBuyerCreditLimit: async () => chain.creditLimit,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function makeRelayReader(chain) {
|
|
19
|
+
return {
|
|
20
|
+
fee: async () => FEE,
|
|
21
|
+
verifyUsdcDomain: async () => true,
|
|
22
|
+
getSweepConfirmation: async () => null,
|
|
23
|
+
isAuthorizationUsed: async () => chain.authorizationUsed,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function makeWatcher(chain, overrides = {}) {
|
|
27
|
+
const dir = await mkdtemp(join(tmpdir(), 'antseed-deposit-watcher-'));
|
|
28
|
+
const identity = await loadOrCreateIdentity(dir);
|
|
29
|
+
const events = [];
|
|
30
|
+
const dispatched = [];
|
|
31
|
+
const watcher = new DepositWatcher({
|
|
32
|
+
wallet: identity.wallet,
|
|
33
|
+
address: identity.wallet.address,
|
|
34
|
+
depositsClient: makeDepositsReader(chain),
|
|
35
|
+
relayClient: makeRelayReader(chain),
|
|
36
|
+
usdcAddress: USDC_ADDRESS,
|
|
37
|
+
evmChainId: 8453,
|
|
38
|
+
depositRelayAddress: RELAY_ADDRESS,
|
|
39
|
+
dispatch: async (payload) => {
|
|
40
|
+
dispatched.push(payload);
|
|
41
|
+
// The relayer submits on-chain: the wallet drains and the deposit lands.
|
|
42
|
+
chain.authorizationUsed = true;
|
|
43
|
+
chain.walletBalance = 0n;
|
|
44
|
+
return { offered: 1, accepted: true };
|
|
45
|
+
},
|
|
46
|
+
connectRelayers: async () => { },
|
|
47
|
+
getReceipt: async () => null,
|
|
48
|
+
onEvent: (event) => events.push(event),
|
|
49
|
+
idleIntervalMs: null,
|
|
50
|
+
...overrides,
|
|
51
|
+
});
|
|
52
|
+
return { watcher, events, dispatched, cleanup: () => rm(dir, { recursive: true, force: true }) };
|
|
53
|
+
}
|
|
54
|
+
async function waitFor(predicate, timeoutMs = 5_000) {
|
|
55
|
+
const deadline = Date.now() + timeoutMs;
|
|
56
|
+
while (!predicate()) {
|
|
57
|
+
if (Date.now() > deadline)
|
|
58
|
+
throw new Error('Timed out waiting for condition');
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
test('deposit watcher sweeps incoming USDC and reports received → sweeping → credited', async (t) => {
|
|
63
|
+
const chain = {
|
|
64
|
+
walletBalance: 5000000n, // 5 USDC waiting in the hot wallet
|
|
65
|
+
depositsAvailable: 0n,
|
|
66
|
+
depositsReserved: 0n,
|
|
67
|
+
creditLimit: 100000000n,
|
|
68
|
+
authorizationUsed: false,
|
|
69
|
+
};
|
|
70
|
+
const { watcher, events, dispatched, cleanup } = await makeWatcher(chain);
|
|
71
|
+
t.after(async () => { watcher.stop(); await cleanup(); });
|
|
72
|
+
watcher.promote();
|
|
73
|
+
await waitFor(() => events.some((e) => e.phase === 'credited'));
|
|
74
|
+
assert.deepEqual(events.map((e) => e.phase), ['received', 'sweeping', 'credited']);
|
|
75
|
+
assert.equal(dispatched.length, 1);
|
|
76
|
+
const payload = dispatched[0];
|
|
77
|
+
assert.equal(payload.amount, '5000000');
|
|
78
|
+
assert.equal(payload.relayAddress, RELAY_ADDRESS);
|
|
79
|
+
assert.equal(payload.evmChainId, 8453);
|
|
80
|
+
assert.match(payload.sig3009, /^0x[0-9a-fA-F]+$/);
|
|
81
|
+
// Credited amount is net of the fixed relay fee.
|
|
82
|
+
const credited = events.find((e) => e.phase === 'credited');
|
|
83
|
+
assert.equal(credited.amountBaseUnits, (5000000n - FEE).toString());
|
|
84
|
+
// Sequence numbers are monotonic so pollers can dedupe.
|
|
85
|
+
assert.deepEqual(events.map((e) => e.seq), [1, 2, 3]);
|
|
86
|
+
});
|
|
87
|
+
test('deposit watcher clamps the sweep to credit-limit headroom', async (t) => {
|
|
88
|
+
const chain = {
|
|
89
|
+
walletBalance: 10000000n, // 10 USDC in the wallet…
|
|
90
|
+
depositsAvailable: 6000000n,
|
|
91
|
+
depositsReserved: 0n,
|
|
92
|
+
creditLimit: 8000000n, // …but only 2 USDC of headroom left
|
|
93
|
+
authorizationUsed: false,
|
|
94
|
+
};
|
|
95
|
+
const { watcher, events, dispatched, cleanup } = await makeWatcher(chain);
|
|
96
|
+
t.after(async () => { watcher.stop(); await cleanup(); });
|
|
97
|
+
watcher.promote();
|
|
98
|
+
await waitFor(() => events.some((e) => e.phase === 'credited'));
|
|
99
|
+
assert.equal(dispatched.length, 1);
|
|
100
|
+
// headroom (2 USDC) + fee, leaving the rest in the wallet for later.
|
|
101
|
+
assert.equal(dispatched[0].amount, (2000000n + FEE).toString());
|
|
102
|
+
});
|
|
103
|
+
test('deposit watcher defers when the deposits balance is at its credit limit', async (t) => {
|
|
104
|
+
const chain = {
|
|
105
|
+
walletBalance: 5000000n,
|
|
106
|
+
depositsAvailable: 8000000n,
|
|
107
|
+
depositsReserved: 0n,
|
|
108
|
+
creditLimit: 8000000n,
|
|
109
|
+
authorizationUsed: false,
|
|
110
|
+
};
|
|
111
|
+
const { watcher, events, dispatched, cleanup } = await makeWatcher(chain);
|
|
112
|
+
t.after(async () => { watcher.stop(); await cleanup(); });
|
|
113
|
+
watcher.promote();
|
|
114
|
+
await waitFor(() => events.some((e) => e.phase === 'deferred'));
|
|
115
|
+
assert.equal(dispatched.length, 0);
|
|
116
|
+
assert.deepEqual(events.map((e) => e.phase), ['received', 'deferred']);
|
|
117
|
+
});
|
|
118
|
+
test('deposit watcher waits below the 1 USDC first-deposit floor', async (t) => {
|
|
119
|
+
const chain = {
|
|
120
|
+
walletBalance: 500000n, // $0.50 — below the first-deposit minimum + fee
|
|
121
|
+
depositsAvailable: 0n,
|
|
122
|
+
depositsReserved: 0n,
|
|
123
|
+
creditLimit: 100000000n,
|
|
124
|
+
authorizationUsed: false,
|
|
125
|
+
};
|
|
126
|
+
const { watcher, events, dispatched, cleanup } = await makeWatcher(chain);
|
|
127
|
+
t.after(async () => { watcher.stop(); await cleanup(); });
|
|
128
|
+
watcher.promote();
|
|
129
|
+
await waitFor(() => events.some((e) => e.phase === 'received'));
|
|
130
|
+
// Give the sweep attempt a moment to (not) dispatch.
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
132
|
+
assert.equal(dispatched.length, 0);
|
|
133
|
+
assert.deepEqual(events.map((e) => e.phase), ['received']);
|
|
134
|
+
});
|
|
135
|
+
test('deposit watcher fails fast when no relayer accepts, and reports the error', async (t) => {
|
|
136
|
+
const chain = {
|
|
137
|
+
walletBalance: 5000000n,
|
|
138
|
+
depositsAvailable: 0n,
|
|
139
|
+
depositsReserved: 0n,
|
|
140
|
+
creditLimit: 100000000n,
|
|
141
|
+
authorizationUsed: false,
|
|
142
|
+
};
|
|
143
|
+
const { watcher, events, cleanup } = await makeWatcher(chain, {
|
|
144
|
+
dispatch: async () => ({ offered: 2, accepted: false }),
|
|
145
|
+
});
|
|
146
|
+
t.after(async () => { watcher.stop(); await cleanup(); });
|
|
147
|
+
watcher.promote();
|
|
148
|
+
await waitFor(() => events.some((e) => e.phase === 'error'));
|
|
149
|
+
const error = events.find((e) => e.phase === 'error');
|
|
150
|
+
assert.match(error.error ?? '', /No deposit relayer accepted/);
|
|
151
|
+
});
|
|
152
|
+
test('deposit watcher status exposes mode transitions and the last event', async (t) => {
|
|
153
|
+
const chain = {
|
|
154
|
+
walletBalance: 0n,
|
|
155
|
+
depositsAvailable: 0n,
|
|
156
|
+
depositsReserved: 0n,
|
|
157
|
+
creditLimit: 100000000n,
|
|
158
|
+
authorizationUsed: false,
|
|
159
|
+
};
|
|
160
|
+
const { watcher, cleanup } = await makeWatcher(chain);
|
|
161
|
+
t.after(async () => { watcher.stop(); await cleanup(); });
|
|
162
|
+
assert.equal(watcher.status().mode, 'off');
|
|
163
|
+
watcher.promote();
|
|
164
|
+
assert.equal(watcher.status().mode, 'active');
|
|
165
|
+
watcher.demote();
|
|
166
|
+
assert.equal(watcher.status().mode, 'background');
|
|
167
|
+
watcher.startIdle();
|
|
168
|
+
assert.equal(watcher.status().mode, 'idle');
|
|
169
|
+
watcher.stop();
|
|
170
|
+
assert.equal(watcher.status().mode, 'off');
|
|
171
|
+
assert.equal(watcher.status().address.length, 42);
|
|
172
|
+
});
|
|
173
|
+
//# sourceMappingURL=deposit-watcher.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposit-watcher.test.js","sourceRoot":"","sources":["../../src/proxy/deposit-watcher.test.ts"],"names":[],"mappings":"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"}
|