@antseed/node 0.2.27 → 0.2.28
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 +14 -13
- package/dist/buyer-request-handler.d.ts +41 -0
- package/dist/buyer-request-handler.d.ts.map +1 -0
- package/dist/buyer-request-handler.js +254 -0
- package/dist/buyer-request-handler.js.map +1 -0
- package/dist/discovery/announcer.d.ts +5 -4
- package/dist/discovery/announcer.d.ts.map +1 -1
- package/dist/discovery/announcer.js +11 -18
- package/dist/discovery/announcer.js.map +1 -1
- package/dist/discovery/index.d.ts +0 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +0 -1
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/metadata-codec.d.ts +2 -2
- package/dist/discovery/metadata-codec.d.ts.map +1 -1
- package/dist/discovery/metadata-codec.js +47 -72
- package/dist/discovery/metadata-codec.js.map +1 -1
- package/dist/discovery/metadata-validator.js +6 -6
- package/dist/discovery/metadata-validator.js.map +1 -1
- package/dist/discovery/peer-lookup.d.ts.map +1 -1
- package/dist/discovery/peer-lookup.js +1 -2
- package/dist/discovery/peer-lookup.js.map +1 -1
- package/dist/discovery/peer-metadata.d.ts +3 -5
- package/dist/discovery/peer-metadata.d.ts.map +1 -1
- package/dist/discovery/peer-metadata.js +1 -1
- package/dist/discovery/reputation-verifier.d.ts +2 -25
- package/dist/discovery/reputation-verifier.d.ts.map +1 -1
- package/dist/discovery/reputation-verifier.js +2 -48
- package/dist/discovery/reputation-verifier.js.map +1 -1
- package/dist/discovery/stats-verifier.d.ts +27 -0
- package/dist/discovery/stats-verifier.d.ts.map +1 -0
- package/dist/discovery/stats-verifier.js +38 -0
- package/dist/discovery/stats-verifier.js.map +1 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/metering/index.d.ts +1 -0
- package/dist/metering/index.d.ts.map +1 -1
- package/dist/metering/index.js +1 -0
- package/dist/metering/index.js.map +1 -1
- package/dist/metering/receipt-generator.d.ts +4 -4
- package/dist/metering/receipt-generator.d.ts.map +1 -1
- package/dist/metering/receipt-verifier.d.ts +6 -6
- package/dist/metering/receipt-verifier.d.ts.map +1 -1
- package/dist/metering/receipt-verifier.js +1 -1
- package/dist/metering/seller-session-tracker.d.ts +91 -0
- package/dist/metering/seller-session-tracker.d.ts.map +1 -0
- package/dist/metering/seller-session-tracker.js +261 -0
- package/dist/metering/seller-session-tracker.js.map +1 -0
- package/dist/metering/storage.d.ts +11 -5
- package/dist/metering/storage.d.ts.map +1 -1
- package/dist/metering/storage.js +28 -80
- package/dist/metering/storage.js.map +1 -1
- package/dist/node.d.ts +69 -117
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +240 -1269
- package/dist/node.js.map +1 -1
- package/dist/p2p/connection-auth.d.ts +2 -1
- package/dist/p2p/connection-auth.d.ts.map +1 -1
- package/dist/p2p/connection-auth.js +6 -6
- package/dist/p2p/connection-auth.js.map +1 -1
- package/dist/p2p/connection-manager.d.ts +3 -2
- package/dist/p2p/connection-manager.d.ts.map +1 -1
- package/dist/p2p/connection-manager.js +6 -6
- package/dist/p2p/connection-manager.js.map +1 -1
- package/dist/p2p/identity.d.ts +22 -15
- package/dist/p2p/identity.d.ts.map +1 -1
- package/dist/p2p/identity.js +66 -51
- package/dist/p2p/identity.js.map +1 -1
- package/dist/p2p/index.d.ts +1 -1
- package/dist/p2p/index.d.ts.map +1 -1
- package/dist/p2p/index.js +1 -1
- package/dist/p2p/index.js.map +1 -1
- package/dist/p2p/payment-codec.d.ts +4 -8
- package/dist/p2p/payment-codec.d.ts.map +1 -1
- package/dist/p2p/payment-codec.js +27 -57
- package/dist/p2p/payment-codec.js.map +1 -1
- package/dist/p2p/payment-mux.d.ts +4 -10
- package/dist/p2p/payment-mux.d.ts.map +1 -1
- package/dist/p2p/payment-mux.js +11 -33
- package/dist/p2p/payment-mux.js.map +1 -1
- package/dist/payments/balance-manager.d.ts +2 -2
- package/dist/payments/balance-manager.d.ts.map +1 -1
- package/dist/payments/balance-manager.js +5 -5
- package/dist/payments/balance-manager.js.map +1 -1
- package/dist/payments/buyer-payment-manager.d.ts +154 -21
- package/dist/payments/buyer-payment-manager.d.ts.map +1 -1
- package/dist/payments/buyer-payment-manager.js +540 -166
- package/dist/payments/buyer-payment-manager.js.map +1 -1
- package/dist/payments/buyer-payment-negotiator.d.ts +84 -0
- package/dist/payments/buyer-payment-negotiator.d.ts.map +1 -0
- package/dist/payments/buyer-payment-negotiator.js +624 -0
- package/dist/payments/buyer-payment-negotiator.js.map +1 -0
- package/dist/payments/chain-config.d.ts +10 -4
- package/dist/payments/chain-config.d.ts.map +1 -1
- package/dist/payments/chain-config.js +19 -9
- package/dist/payments/chain-config.js.map +1 -1
- package/dist/payments/channel-session-state.d.ts +13 -0
- package/dist/payments/channel-session-state.d.ts.map +1 -0
- package/dist/payments/channel-session-state.js +25 -0
- package/dist/payments/channel-session-state.js.map +1 -0
- package/dist/payments/channel-store.d.ts +87 -0
- package/dist/payments/channel-store.d.ts.map +1 -0
- package/dist/payments/channel-store.js +276 -0
- package/dist/payments/channel-store.js.map +1 -0
- package/dist/payments/evm/ants-token-client.d.ts +1 -1
- package/dist/payments/evm/ants-token-client.d.ts.map +1 -1
- package/dist/payments/evm/ants-token-client.js +3 -4
- package/dist/payments/evm/ants-token-client.js.map +1 -1
- package/dist/payments/evm/base-evm-client.d.ts +10 -1
- package/dist/payments/evm/base-evm-client.d.ts.map +1 -1
- package/dist/payments/evm/base-evm-client.js +34 -1
- package/dist/payments/evm/base-evm-client.js.map +1 -1
- package/dist/payments/evm/channels-client.d.ts +51 -0
- package/dist/payments/evm/channels-client.d.ts.map +1 -0
- package/dist/payments/evm/channels-client.js +101 -0
- package/dist/payments/evm/channels-client.js.map +1 -0
- package/dist/payments/evm/deposits-client.d.ts +30 -0
- package/dist/payments/evm/deposits-client.d.ts.map +1 -0
- package/dist/payments/evm/deposits-client.js +78 -0
- package/dist/payments/evm/deposits-client.js.map +1 -0
- package/dist/payments/evm/emissions-client.d.ts +3 -4
- package/dist/payments/evm/emissions-client.d.ts.map +1 -1
- package/dist/payments/evm/emissions-client.js +11 -30
- package/dist/payments/evm/emissions-client.js.map +1 -1
- package/dist/payments/evm/identity-client.d.ts +10 -23
- package/dist/payments/evm/identity-client.d.ts.map +1 -1
- package/dist/payments/evm/identity-client.js +43 -100
- package/dist/payments/evm/identity-client.js.map +1 -1
- package/dist/payments/evm/keypair.d.ts +3 -14
- package/dist/payments/evm/keypair.d.ts.map +1 -1
- package/dist/payments/evm/keypair.js +4 -20
- package/dist/payments/evm/keypair.js.map +1 -1
- package/dist/payments/evm/sessions-client.d.ts +30 -0
- package/dist/payments/evm/sessions-client.d.ts.map +1 -0
- package/dist/payments/evm/sessions-client.js +61 -0
- package/dist/payments/evm/sessions-client.js.map +1 -0
- package/dist/payments/evm/signatures.d.ts +43 -12
- package/dist/payments/evm/signatures.d.ts.map +1 -1
- package/dist/payments/evm/signatures.js +62 -45
- package/dist/payments/evm/signatures.js.map +1 -1
- package/dist/payments/evm/staking-client.d.ts +24 -0
- package/dist/payments/evm/staking-client.d.ts.map +1 -0
- package/dist/payments/evm/staking-client.js +54 -0
- package/dist/payments/evm/staking-client.js.map +1 -0
- package/dist/payments/evm/stats-client.d.ts +20 -0
- package/dist/payments/evm/stats-client.d.ts.map +1 -0
- package/dist/payments/evm/stats-client.js +25 -0
- package/dist/payments/evm/stats-client.js.map +1 -0
- package/dist/payments/index.d.ts +17 -10
- package/dist/payments/index.d.ts.map +1 -1
- package/dist/payments/index.js +15 -8
- package/dist/payments/index.js.map +1 -1
- package/dist/payments/pricing.d.ts +25 -0
- package/dist/payments/pricing.d.ts.map +1 -0
- package/dist/payments/pricing.js +33 -0
- package/dist/payments/pricing.js.map +1 -0
- package/dist/payments/readiness.d.ts +4 -3
- package/dist/payments/readiness.d.ts.map +1 -1
- package/dist/payments/readiness.js +11 -18
- package/dist/payments/readiness.js.map +1 -1
- package/dist/payments/seller-payment-manager.d.ts +72 -47
- package/dist/payments/seller-payment-manager.d.ts.map +1 -1
- package/dist/payments/seller-payment-manager.js +558 -275
- package/dist/payments/seller-payment-manager.js.map +1 -1
- package/dist/payments/session-store.d.ts +3 -0
- package/dist/payments/session-store.d.ts.map +1 -1
- package/dist/payments/session-store.js +31 -2
- package/dist/payments/session-store.js.map +1 -1
- package/dist/payments/types.d.ts +5 -3
- package/dist/payments/types.d.ts.map +1 -1
- package/dist/proxy/proxy-mux.d.ts.map +1 -1
- package/dist/proxy/proxy-mux.js +3 -2
- package/dist/proxy/proxy-mux.js.map +1 -1
- package/dist/proxy/request-codec.d.ts.map +1 -1
- package/dist/proxy/request-codec.js +3 -0
- package/dist/proxy/request-codec.js.map +1 -1
- package/dist/reputation/rating-manager.d.ts.map +1 -1
- package/dist/reputation/rating-manager.js +2 -4
- package/dist/reputation/rating-manager.js.map +1 -1
- package/dist/reputation/report-manager.d.ts.map +1 -1
- package/dist/reputation/report-manager.js +2 -4
- package/dist/reputation/report-manager.js.map +1 -1
- package/dist/routing/default-router.d.ts.map +1 -1
- package/dist/routing/default-router.js +4 -9
- package/dist/routing/default-router.js.map +1 -1
- package/dist/seller-request-handler.d.ts +54 -0
- package/dist/seller-request-handler.d.ts.map +1 -0
- package/dist/seller-request-handler.js +359 -0
- package/dist/seller-request-handler.js.map +1 -0
- package/dist/storage/migrate.d.ts +13 -0
- package/dist/storage/migrate.d.ts.map +1 -0
- package/dist/storage/migrate.js +28 -0
- package/dist/storage/migrate.js.map +1 -0
- package/dist/storage/migrations/channels/001_create_tables.d.ts +3 -0
- package/dist/storage/migrations/channels/001_create_tables.d.ts.map +1 -0
- package/dist/storage/migrations/channels/001_create_tables.js +45 -0
- package/dist/storage/migrations/channels/001_create_tables.js.map +1 -0
- package/dist/storage/migrations/channels/002_add_auth_sig_columns.d.ts +3 -0
- package/dist/storage/migrations/channels/002_add_auth_sig_columns.d.ts.map +1 -0
- package/dist/storage/migrations/channels/002_add_auth_sig_columns.js +19 -0
- package/dist/storage/migrations/channels/002_add_auth_sig_columns.js.map +1 -0
- package/dist/storage/migrations/channels/index.d.ts +3 -0
- package/dist/storage/migrations/channels/index.d.ts.map +1 -0
- package/dist/storage/migrations/channels/index.js +4 -0
- package/dist/storage/migrations/channels/index.js.map +1 -0
- package/dist/storage/migrations/metering/001_create_tables.d.ts +3 -0
- package/dist/storage/migrations/metering/001_create_tables.d.ts.map +1 -0
- package/dist/storage/migrations/metering/001_create_tables.js +80 -0
- package/dist/storage/migrations/metering/001_create_tables.js.map +1 -0
- package/dist/storage/migrations/metering/index.d.ts +3 -0
- package/dist/storage/migrations/metering/index.d.ts.map +1 -0
- package/dist/storage/migrations/metering/index.js +3 -0
- package/dist/storage/migrations/metering/index.js.map +1 -0
- package/dist/types/capability.d.ts +1 -1
- package/dist/types/metering.d.ts +1 -1
- package/dist/types/peer.d.ts +10 -11
- package/dist/types/peer.d.ts.map +1 -1
- package/dist/types/peer.js +7 -3
- package/dist/types/peer.js.map +1 -1
- package/dist/types/protocol.d.ts +22 -70
- package/dist/types/protocol.d.ts.map +1 -1
- package/dist/types/protocol.js +1 -3
- package/dist/types/protocol.js.map +1 -1
- package/dist/types/rating.d.ts +1 -1
- package/dist/types/rating.d.ts.map +1 -1
- package/dist/types/report.d.ts +1 -1
- package/dist/types/report.d.ts.map +1 -1
- package/dist/utils/response-usage.d.ts +10 -0
- package/dist/utils/response-usage.d.ts.map +1 -0
- package/dist/utils/response-usage.js +34 -0
- package/dist/utils/response-usage.js.map +1 -0
- package/package.json +3 -3
package/dist/node.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
2
|
import { homedir } from "node:os";
|
|
5
3
|
import { join } from "node:path";
|
|
6
4
|
import { loadOrCreateIdentity } from "./p2p/identity.js";
|
|
7
|
-
import {
|
|
5
|
+
import { peerIdToAddress } from "./types/peer.js";
|
|
8
6
|
import { MeteringStorage } from "./metering/storage.js";
|
|
9
7
|
import { ReceiptGenerator } from "./metering/receipt-generator.js";
|
|
8
|
+
import { SellerSessionTracker, } from "./metering/seller-session-tracker.js";
|
|
10
9
|
import { ConnectionState } from "./types/connection.js";
|
|
11
10
|
import { DHTNode, DEFAULT_DHT_CONFIG, } from "./discovery/dht-node.js";
|
|
12
11
|
import { toBootstrapConfig, OFFICIAL_BOOTSTRAP_NODES, mergeBootstrapNodes } from "./discovery/bootstrap.js";
|
|
@@ -20,61 +19,17 @@ import { FrameDecoder, encodeFrame } from "./p2p/message-protocol.js";
|
|
|
20
19
|
import { KeepaliveManager, buildPongPayload } from "./p2p/keepalive.js";
|
|
21
20
|
import { MessageType } from "./types/protocol.js";
|
|
22
21
|
import { NatTraversal } from "./p2p/nat-traversal.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
import { BalanceManager, BaseEscrowClient, SessionStore, } from "./payments/index.js";
|
|
26
|
-
import { parseJsonObject, extractUsage } from "@antseed/api-adapter";
|
|
22
|
+
import { signUtf8 } from "./p2p/identity.js";
|
|
23
|
+
import { BalanceManager, DepositsClient, ChannelsClient, StakingClient, ChannelStore, } from "./payments/index.js";
|
|
27
24
|
import { debugLog, debugWarn } from "./utils/debug.js";
|
|
28
25
|
import { parsePublicAddress } from "./discovery/public-address.js";
|
|
29
26
|
import { BuyerPaymentManager } from "./payments/buyer-payment-manager.js";
|
|
27
|
+
import { BuyerPaymentNegotiator } from "./payments/buyer-payment-negotiator.js";
|
|
30
28
|
import { SellerPaymentManager } from "./payments/seller-payment-manager.js";
|
|
31
|
-
import { identityToEvmAddress } from "./payments/evm/keypair.js";
|
|
32
29
|
import { IdentityClient } from "./payments/evm/identity-client.js";
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
* Extract actual token usage from an LLM provider response body.
|
|
36
|
-
* Handles both JSON and SSE (streaming) responses. Returns zeros
|
|
37
|
-
* if usage data is not found (caller should fall back to estimation).
|
|
38
|
-
*/
|
|
39
|
-
function parseResponseUsage(body) {
|
|
40
|
-
const parsed = parseJsonObject(body);
|
|
41
|
-
if (parsed) {
|
|
42
|
-
return extractUsage(parsed);
|
|
43
|
-
}
|
|
44
|
-
// SSE streaming: scan data lines for a usage object
|
|
45
|
-
const text = new TextDecoder().decode(body);
|
|
46
|
-
let inputTokens = 0;
|
|
47
|
-
let outputTokens = 0;
|
|
48
|
-
for (const line of text.split('\n')) {
|
|
49
|
-
const trimmed = line.trim();
|
|
50
|
-
if (!trimmed.startsWith('data:'))
|
|
51
|
-
continue;
|
|
52
|
-
const payload = trimmed.slice(5).trim();
|
|
53
|
-
if (!payload || payload === '[DONE]')
|
|
54
|
-
continue;
|
|
55
|
-
try {
|
|
56
|
-
const event = JSON.parse(payload);
|
|
57
|
-
const usage = extractUsage(event);
|
|
58
|
-
if (usage.inputTokens > 0)
|
|
59
|
-
inputTokens = Math.max(inputTokens, usage.inputTokens);
|
|
60
|
-
if (usage.outputTokens > 0)
|
|
61
|
-
outputTokens = Math.max(outputTokens, usage.outputTokens);
|
|
62
|
-
}
|
|
63
|
-
catch { /* skip non-JSON lines */ }
|
|
64
|
-
}
|
|
65
|
-
return { inputTokens, outputTokens };
|
|
66
|
-
}
|
|
67
|
-
function parsePaymentRequiredBody(body) {
|
|
68
|
-
try {
|
|
69
|
-
const parsed = JSON.parse(new TextDecoder().decode(body));
|
|
70
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
30
|
+
import { SellerRequestHandler } from "./seller-request-handler.js";
|
|
31
|
+
import { BuyerRequestHandler, } from "./buyer-request-handler.js";
|
|
76
32
|
export class AntseedNode extends EventEmitter {
|
|
77
|
-
static _METADATA_REFRESH_DEBOUNCE_MS = 200;
|
|
78
33
|
_config;
|
|
79
34
|
_identity = null;
|
|
80
35
|
_dht = null;
|
|
@@ -91,33 +46,29 @@ export class AntseedNode extends EventEmitter {
|
|
|
91
46
|
_metering = null;
|
|
92
47
|
_receiptGenerator = null;
|
|
93
48
|
_balanceManager = null;
|
|
94
|
-
|
|
49
|
+
_depositsClient = null;
|
|
50
|
+
_channelsClient = null;
|
|
51
|
+
_stakingClient = null;
|
|
95
52
|
_identityClient = null;
|
|
96
53
|
_paymentMuxes = new Map();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
/** Per-buyer session tracking: buyerPeerId → seller session state */
|
|
100
|
-
_sessions = new Map();
|
|
101
|
-
_settlementTimers = new Map();
|
|
54
|
+
/** Seller-side request handler (provider matching, execution, load tracking). */
|
|
55
|
+
_sellerHandler = null;
|
|
102
56
|
/** Buyer-side payment manager (initialized when buyer has payment config). */
|
|
103
57
|
_buyerPaymentManager = null;
|
|
58
|
+
/** Buyer-side payment negotiation (402 handling, SpendingAuth, cost tracking). */
|
|
59
|
+
_buyerNegotiator = null;
|
|
60
|
+
/** Buyer-side request execution (streaming, timeouts, 402 retry). */
|
|
61
|
+
_buyerHandler = null;
|
|
104
62
|
/** Seller-side payment manager (initialized when seller has payment config). */
|
|
105
63
|
_sellerPaymentManager = null;
|
|
106
|
-
/** Shared
|
|
107
|
-
|
|
64
|
+
/** Shared channel store for payment persistence. */
|
|
65
|
+
_channelStore = null;
|
|
108
66
|
/** Periodic timeout checker interval. */
|
|
109
67
|
_timeoutCheckerInterval = null;
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
_manualApprovalCache = null;
|
|
115
|
-
/** Buffered PaymentRequired that arrived before _doNegotiatePayment registered its listener.
|
|
116
|
-
* This handles the race where 402 + PaymentRequired arrive in the same I/O tick. */
|
|
117
|
-
_bufferedPaymentRequired = new Map();
|
|
118
|
-
/** Per-peer mutex to prevent concurrent payment negotiations. */
|
|
119
|
-
_paymentNegotiationLocks = new Map();
|
|
120
|
-
/** Peers the caller has manually approved by retrying after a 402. */
|
|
68
|
+
/** Block cursor for CloseRequested event polling. */
|
|
69
|
+
_closeRequestedFromBlock = 0;
|
|
70
|
+
/** Seller session lifecycle tracking (metering, settlement). */
|
|
71
|
+
_sessionTracker = null;
|
|
121
72
|
constructor(config) {
|
|
122
73
|
super();
|
|
123
74
|
this._config = config;
|
|
@@ -141,6 +92,10 @@ export class AntseedNode extends EventEmitter {
|
|
|
141
92
|
get buyerPaymentManager() {
|
|
142
93
|
return this._buyerPaymentManager;
|
|
143
94
|
}
|
|
95
|
+
/** Buyer-side payment negotiator (null if payments not configured for buyer). */
|
|
96
|
+
get buyerNegotiator() {
|
|
97
|
+
return this._buyerNegotiator;
|
|
98
|
+
}
|
|
144
99
|
/** Actual DHT port after binding (0 means not started). */
|
|
145
100
|
get dhtPort() {
|
|
146
101
|
return this._dht?.getPort() ?? 0;
|
|
@@ -149,6 +104,10 @@ export class AntseedNode extends EventEmitter {
|
|
|
149
104
|
get signalingPort() {
|
|
150
105
|
return this._connectionManager?.getListeningPort() ?? 0;
|
|
151
106
|
}
|
|
107
|
+
/** ERC-8004 IdentityRegistry client (null if not configured). */
|
|
108
|
+
get identityClient() {
|
|
109
|
+
return this._identityClient;
|
|
110
|
+
}
|
|
152
111
|
/** Current connection state for a peer if a connection exists, otherwise null. */
|
|
153
112
|
getPeerConnectionState(peerId) {
|
|
154
113
|
return this._connectionManager?.getConnection(peerId)?.state ?? null;
|
|
@@ -158,31 +117,11 @@ export class AntseedNode extends EventEmitter {
|
|
|
158
117
|
* Includes open sessions before they are finalized/settled.
|
|
159
118
|
*/
|
|
160
119
|
getActiveSellerSessions() {
|
|
161
|
-
|
|
162
|
-
for (const [buyerPeerId, session] of this._sessions.entries()) {
|
|
163
|
-
snapshots.push({
|
|
164
|
-
sessionId: session.sessionId,
|
|
165
|
-
buyerPeerId,
|
|
166
|
-
provider: session.provider,
|
|
167
|
-
startedAt: session.startedAt,
|
|
168
|
-
lastActivityAt: session.lastActivityAt,
|
|
169
|
-
totalRequests: session.totalRequests,
|
|
170
|
-
totalTokens: session.totalTokens,
|
|
171
|
-
avgLatencyMs: session.totalRequests > 0 ? session.totalLatencyMs / session.totalRequests : 0,
|
|
172
|
-
settling: Boolean(session.settling),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
return snapshots;
|
|
120
|
+
return this._sessionTracker?.getActiveSessions() ?? [];
|
|
176
121
|
}
|
|
177
|
-
/** Number of active in-memory seller
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
for (const session of this._sessions.values()) {
|
|
181
|
-
if (!session.settling) {
|
|
182
|
-
count += 1;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return count;
|
|
122
|
+
/** Number of active in-memory seller channels that are not currently settling. */
|
|
123
|
+
getActiveSellerChannelCount() {
|
|
124
|
+
return this._sessionTracker?.getActiveChannelCount() ?? 0;
|
|
186
125
|
}
|
|
187
126
|
async start() {
|
|
188
127
|
if (this._started) {
|
|
@@ -213,17 +152,17 @@ export class AntseedNode extends EventEmitter {
|
|
|
213
152
|
return;
|
|
214
153
|
}
|
|
215
154
|
// End all active buyer payment sessions before shutdown
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
155
|
+
if (this._buyerNegotiator) {
|
|
156
|
+
this._buyerNegotiator.cleanup();
|
|
157
|
+
}
|
|
158
|
+
if (this._sessionTracker) {
|
|
159
|
+
await this._sessionTracker.finalizeAllSessions("node-stop");
|
|
160
|
+
this._sessionTracker.clearTimers();
|
|
220
161
|
}
|
|
221
|
-
this.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
this._metadataRefreshTimer = null;
|
|
162
|
+
if (this._sellerHandler) {
|
|
163
|
+
this._sellerHandler.clearMetadataRefreshTimer();
|
|
164
|
+
this._sellerHandler = null;
|
|
225
165
|
}
|
|
226
|
-
this._providerLoadCounts.clear();
|
|
227
166
|
// Remove NAT port mappings
|
|
228
167
|
if (this._nat) {
|
|
229
168
|
await this._nat.cleanup();
|
|
@@ -275,31 +214,27 @@ export class AntseedNode extends EventEmitter {
|
|
|
275
214
|
clearInterval(this._timeoutCheckerInterval);
|
|
276
215
|
this._timeoutCheckerInterval = null;
|
|
277
216
|
}
|
|
278
|
-
if (this.
|
|
217
|
+
if (this._channelStore) {
|
|
279
218
|
try {
|
|
280
|
-
this.
|
|
219
|
+
this._channelStore.close();
|
|
281
220
|
}
|
|
282
221
|
catch {
|
|
283
222
|
// ignore close errors
|
|
284
223
|
}
|
|
285
|
-
this.
|
|
224
|
+
this._channelStore = null;
|
|
286
225
|
}
|
|
287
226
|
this._peerLookup = null;
|
|
288
227
|
this._receiptGenerator = null;
|
|
289
228
|
this._balanceManager = null;
|
|
290
|
-
this.
|
|
229
|
+
this._depositsClient = null;
|
|
230
|
+
this._channelsClient = null;
|
|
231
|
+
this._stakingClient = null;
|
|
291
232
|
this._identityClient = null;
|
|
292
233
|
this._buyerPaymentManager = null;
|
|
234
|
+
this._buyerNegotiator = null;
|
|
235
|
+
this._buyerHandler = null;
|
|
293
236
|
this._sellerPaymentManager = null;
|
|
294
|
-
this.
|
|
295
|
-
// Clean up payment negotiation state
|
|
296
|
-
for (const [, pending] of this._pendingPaymentRequired) {
|
|
297
|
-
clearTimeout(pending.timer);
|
|
298
|
-
pending.reject(new Error('Node stopped'));
|
|
299
|
-
}
|
|
300
|
-
this._pendingPaymentRequired.clear();
|
|
301
|
-
this._bufferedPaymentRequired.clear();
|
|
302
|
-
this._paymentNegotiationLocks.clear();
|
|
237
|
+
this._sessionTracker = null;
|
|
303
238
|
this._started = false;
|
|
304
239
|
this.emit("stopped");
|
|
305
240
|
}
|
|
@@ -328,31 +263,18 @@ export class AntseedNode extends EventEmitter {
|
|
|
328
263
|
peers.push(p);
|
|
329
264
|
}
|
|
330
265
|
}
|
|
331
|
-
//
|
|
332
|
-
if (this.
|
|
266
|
+
// Verify claimed on-chain stats against actual contract data
|
|
267
|
+
if (this._channelsClient && this._stakingClient) {
|
|
333
268
|
for (const p of peers) {
|
|
334
|
-
if (!p.evmAddress)
|
|
335
|
-
continue;
|
|
336
269
|
try {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
timestamp: 0,
|
|
343
|
-
signature: "",
|
|
344
|
-
evmAddress: p.evmAddress,
|
|
345
|
-
onChainReputation: p.onChainReputation,
|
|
346
|
-
onChainSessionCount: p.onChainSessionCount,
|
|
347
|
-
onChainDisputeCount: p.onChainDisputeCount,
|
|
348
|
-
};
|
|
349
|
-
const result = await verifyReputation(this._identityClient, metadata);
|
|
350
|
-
p.onChainReputation = result.actualReputation;
|
|
351
|
-
p.onChainSessionCount = result.actualSessionCount;
|
|
352
|
-
p.onChainDisputeCount = result.actualDisputeCount;
|
|
270
|
+
const evmAddress = peerIdToAddress(p.peerId);
|
|
271
|
+
const agentId = await this._stakingClient.getAgentId(evmAddress);
|
|
272
|
+
const stats = await this._channelsClient.getAgentStats(agentId);
|
|
273
|
+
p.onChainChannelCount = stats.channelCount;
|
|
274
|
+
p.onChainGhostCount = stats.ghostCount;
|
|
353
275
|
}
|
|
354
276
|
catch {
|
|
355
|
-
//
|
|
277
|
+
// Contract lookup failed for this peer — keep claimed data
|
|
356
278
|
}
|
|
357
279
|
}
|
|
358
280
|
}
|
|
@@ -368,285 +290,64 @@ export class AntseedNode extends EventEmitter {
|
|
|
368
290
|
async connectToPeer(peer) {
|
|
369
291
|
const conn = await this._getOrCreateConnection(peer);
|
|
370
292
|
this._getOrCreateMux(peer.peerId, conn);
|
|
293
|
+
const negotiator = this._buyerNegotiator;
|
|
294
|
+
if (negotiator) {
|
|
295
|
+
this._paymentMuxes.set(peer.peerId, negotiator.getOrCreatePaymentMux(peer.peerId, conn));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Query session stats for a specific seller peer.
|
|
300
|
+
* Combines channel store data (authoritative payment/session info) with
|
|
301
|
+
* metering events when available.
|
|
302
|
+
*/
|
|
303
|
+
getMeteringStatsByPeer(sellerPeerId) {
|
|
304
|
+
const buyerAddress = this._identity?.wallet.address ?? null;
|
|
305
|
+
const channel = (buyerAddress != null)
|
|
306
|
+
? (this._channelStore?.getActiveChannelByPeerAndBuyer(sellerPeerId, 'buyer', buyerAddress)
|
|
307
|
+
?? this._channelStore?.getLatestChannelByPeerAndBuyer(sellerPeerId, 'buyer', buyerAddress))
|
|
308
|
+
: (this._channelStore?.getActiveChannelByPeer(sellerPeerId, 'buyer')
|
|
309
|
+
?? this._channelStore?.getLatestChannel(sellerPeerId, 'buyer'))
|
|
310
|
+
?? null;
|
|
311
|
+
const lifetime = (buyerAddress != null)
|
|
312
|
+
? this._channelStore?.getTotalsByPeerAndBuyer(sellerPeerId, 'buyer', buyerAddress)
|
|
313
|
+
: this._channelStore?.getTotalsByPeer(sellerPeerId, 'buyer')
|
|
314
|
+
?? null;
|
|
315
|
+
if (!channel && !lifetime)
|
|
316
|
+
return null;
|
|
317
|
+
const liveTotals = this._buyerPaymentManager?.getResponseTokenTotals(sellerPeerId);
|
|
318
|
+
const inputTokens = (liveTotals != null) ? liveTotals.input
|
|
319
|
+
: (channel != null) ? Number(channel.tokensDelivered || '0')
|
|
320
|
+
: 0;
|
|
321
|
+
const outputTokens = (liveTotals != null) ? liveTotals.output
|
|
322
|
+
: (channel != null) ? Number(channel.previousConsumption || '0')
|
|
323
|
+
: 0;
|
|
324
|
+
return {
|
|
325
|
+
totalRequests: channel?.requestCount ?? 0,
|
|
326
|
+
inputTokens,
|
|
327
|
+
outputTokens,
|
|
328
|
+
totalTokens: inputTokens + outputTokens,
|
|
329
|
+
reservedUsdc: this._buyerPaymentManager?.getReserveCeiling(sellerPeerId)?.toString() ?? null,
|
|
330
|
+
consumedUsdc: channel?.authMax ?? null,
|
|
331
|
+
channelStatus: channel?.status ?? null,
|
|
332
|
+
reservedAt: channel?.reservedAt ?? null,
|
|
333
|
+
lifetimeSessions: lifetime?.totalSessions ?? 0,
|
|
334
|
+
lifetimeRequests: lifetime?.totalRequests ?? 0,
|
|
335
|
+
lifetimeInputTokens: lifetime?.totalInputTokens ?? 0,
|
|
336
|
+
lifetimeOutputTokens: lifetime?.totalOutputTokens ?? 0,
|
|
337
|
+
lifetimeTotalTokens: (lifetime?.totalInputTokens ?? 0) + (lifetime?.totalOutputTokens ?? 0),
|
|
338
|
+
lifetimeAuthorizedUsdc: (lifetime?.totalAuthorizedUsdc ?? 0n).toString(),
|
|
339
|
+
lifetimeFirstSessionAt: lifetime?.firstSessionAt ?? null,
|
|
340
|
+
};
|
|
371
341
|
}
|
|
372
342
|
async sendRequest(peer, req, options) {
|
|
373
|
-
|
|
343
|
+
if (!this._buyerHandler)
|
|
344
|
+
throw new Error("Node not started or not in buyer mode");
|
|
345
|
+
return this._buyerHandler.sendRequest(peer, req, undefined, options);
|
|
374
346
|
}
|
|
375
347
|
async sendRequestStream(peer, req, callbacks, options) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (!req.requestId || typeof req.requestId !== "string") {
|
|
380
|
-
throw new Error("requestId must be a non-empty string");
|
|
381
|
-
}
|
|
382
|
-
if (!this._connectionManager || !this._identity) {
|
|
383
|
-
throw new Error("Node not started");
|
|
384
|
-
}
|
|
385
|
-
const opName = callbacks ? "sendRequestStream" : "sendRequest";
|
|
386
|
-
debugLog(`[Node] ${opName} ${req.method} ${req.path} → peer ${peer.peerId.slice(0, 12)}... (reqId=${req.requestId.slice(0, 8)})`);
|
|
387
|
-
const conn = await this._getOrCreateConnection(peer);
|
|
388
|
-
debugLog(`[Node] Connection to ${peer.peerId.slice(0, 12)}... state=${conn.state}`);
|
|
389
|
-
const mux = this._getOrCreateMux(peer.peerId, conn);
|
|
390
|
-
// Extract and strip x-antseed-spending-auth header if present (manual approval flow)
|
|
391
|
-
const externalSpendingAuth = req.headers[ANTSEED_SPENDING_AUTH_HEADER] ?? null;
|
|
392
|
-
if (externalSpendingAuth) {
|
|
393
|
-
const { [ANTSEED_SPENDING_AUTH_HEADER]: _, ...cleanHeaders } = req.headers;
|
|
394
|
-
req = { ...req, headers: cleanHeaders };
|
|
395
|
-
}
|
|
396
|
-
// If we already have a payment session with this peer, skip negotiation.
|
|
397
|
-
const needsPaymentNegotiation = this._buyerPaymentManager
|
|
398
|
-
&& !this._buyerLockedPeers.has(peer.peerId);
|
|
399
|
-
// If an external spending auth was provided, apply it before sending the request.
|
|
400
|
-
if (externalSpendingAuth && needsPaymentNegotiation) {
|
|
401
|
-
debugLog(`[Node] Applying external spending auth for ${peer.peerId.slice(0, 12)}...`);
|
|
402
|
-
await this._applyExternalSpendingAuth(peer, conn, externalSpendingAuth);
|
|
403
|
-
}
|
|
404
|
-
const startTime = Date.now();
|
|
405
|
-
const executeRequest = () => new Promise((resolve, reject) => {
|
|
406
|
-
const timeoutMs = this._config.requestTimeoutMs ?? 30_000;
|
|
407
|
-
const maxStreamBufferBytes = Math.max(1, this._config.maxStreamBufferBytes ?? 16 * 1024 * 1024);
|
|
408
|
-
const maxStreamDurationMs = Math.max(1, this._config.maxStreamDurationMs ?? 5 * 60_000);
|
|
409
|
-
const streamInitialResponseTimeoutMs = callbacks ? Math.max(timeoutMs, 90_000) : timeoutMs;
|
|
410
|
-
// Idle timeout for streaming: resets on each chunk so long-running
|
|
411
|
-
// streams (thinking models, large outputs) stay alive as long as
|
|
412
|
-
// data keeps flowing.
|
|
413
|
-
const streamIdleTimeoutMs = Math.max(timeoutMs, 60_000);
|
|
414
|
-
let settled = false;
|
|
415
|
-
let streamStarted = false;
|
|
416
|
-
let streamStartedAtMs = 0;
|
|
417
|
-
let streamBufferedBytes = 0;
|
|
418
|
-
let streamStartResponse = null;
|
|
419
|
-
const streamChunks = [];
|
|
420
|
-
let activeTimeout = null;
|
|
421
|
-
let activeTimeoutMs = streamInitialResponseTimeoutMs;
|
|
422
|
-
const abortSignal = options?.signal;
|
|
423
|
-
let abortListenerAttached = false;
|
|
424
|
-
let connectionStateListenerAttached = false;
|
|
425
|
-
const hasConnectionStateEvents = typeof conn.on === "function"
|
|
426
|
-
&& typeof conn.off === "function";
|
|
427
|
-
const cleanupAbortListener = () => {
|
|
428
|
-
if (abortSignal && abortListenerAttached) {
|
|
429
|
-
abortSignal.removeEventListener("abort", onAbort);
|
|
430
|
-
abortListenerAttached = false;
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
const cleanupConnectionListener = () => {
|
|
434
|
-
if (!connectionStateListenerAttached)
|
|
435
|
-
return;
|
|
436
|
-
conn.off("stateChange", onConnectionStateChange);
|
|
437
|
-
connectionStateListenerAttached = false;
|
|
438
|
-
};
|
|
439
|
-
const onConnectionStateChange = (state) => {
|
|
440
|
-
if (settled)
|
|
441
|
-
return;
|
|
442
|
-
if (state !== ConnectionState.Closed && state !== ConnectionState.Failed) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
settled = true;
|
|
446
|
-
if (activeTimeout)
|
|
447
|
-
clearTimeout(activeTimeout);
|
|
448
|
-
cleanupAbortListener();
|
|
449
|
-
cleanupConnectionListener();
|
|
450
|
-
mux.cancelProxyRequest(req.requestId);
|
|
451
|
-
reject(new Error(`Connection to ${peer.peerId} ${state.toLowerCase()} during request ${req.requestId}`));
|
|
452
|
-
};
|
|
453
|
-
const onAbort = () => {
|
|
454
|
-
if (settled)
|
|
455
|
-
return;
|
|
456
|
-
settled = true;
|
|
457
|
-
if (activeTimeout)
|
|
458
|
-
clearTimeout(activeTimeout);
|
|
459
|
-
cleanupAbortListener();
|
|
460
|
-
cleanupConnectionListener();
|
|
461
|
-
debugWarn(`[Node] Request ${req.requestId.slice(0, 8)} aborted by caller`);
|
|
462
|
-
mux.cancelProxyRequest(req.requestId);
|
|
463
|
-
reject(new Error(`Request ${req.requestId} aborted`));
|
|
464
|
-
};
|
|
465
|
-
if (abortSignal) {
|
|
466
|
-
if (abortSignal.aborted) {
|
|
467
|
-
onAbort();
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
471
|
-
abortListenerAttached = true;
|
|
472
|
-
}
|
|
473
|
-
if (hasConnectionStateEvents) {
|
|
474
|
-
conn.on("stateChange", onConnectionStateChange);
|
|
475
|
-
connectionStateListenerAttached = true;
|
|
476
|
-
}
|
|
477
|
-
const resetTimeout = (ms) => {
|
|
478
|
-
if (activeTimeout)
|
|
479
|
-
clearTimeout(activeTimeout);
|
|
480
|
-
activeTimeoutMs = ms;
|
|
481
|
-
activeTimeout = setTimeout(() => {
|
|
482
|
-
if (settled)
|
|
483
|
-
return;
|
|
484
|
-
settled = true;
|
|
485
|
-
cleanupAbortListener();
|
|
486
|
-
cleanupConnectionListener();
|
|
487
|
-
debugWarn(`[Node] Request ${req.requestId.slice(0, 8)} timed out after ${Date.now() - startTime}ms `
|
|
488
|
-
+ `(timeout=${activeTimeoutMs}ms, stream=${callbacks ? "true" : "false"}, streamStarted=${streamStarted ? "true" : "false"}, buffered=${streamBufferedBytes}b)`);
|
|
489
|
-
mux.cancelProxyRequest(req.requestId);
|
|
490
|
-
reject(new Error(`Request ${req.requestId} timed out`));
|
|
491
|
-
}, ms);
|
|
492
|
-
};
|
|
493
|
-
// Initial timeout: wait for the first response frame.
|
|
494
|
-
resetTimeout(streamInitialResponseTimeoutMs);
|
|
495
|
-
const finish = (response) => {
|
|
496
|
-
if (settled)
|
|
497
|
-
return;
|
|
498
|
-
settled = true;
|
|
499
|
-
if (activeTimeout)
|
|
500
|
-
clearTimeout(activeTimeout);
|
|
501
|
-
cleanupAbortListener();
|
|
502
|
-
cleanupConnectionListener();
|
|
503
|
-
const cleaned = this._stripStreamingHeader(response);
|
|
504
|
-
debugLog(`[Node] Response for ${req.requestId.slice(0, 8)}: status=${cleaned.statusCode} (${Date.now() - startTime}ms, ${cleaned.body.length}b)`);
|
|
505
|
-
resolve(cleaned);
|
|
506
|
-
};
|
|
507
|
-
const fail = (error) => {
|
|
508
|
-
if (settled)
|
|
509
|
-
return;
|
|
510
|
-
settled = true;
|
|
511
|
-
if (activeTimeout)
|
|
512
|
-
clearTimeout(activeTimeout);
|
|
513
|
-
cleanupAbortListener();
|
|
514
|
-
cleanupConnectionListener();
|
|
515
|
-
reject(error);
|
|
516
|
-
};
|
|
517
|
-
mux.sendProxyRequest(req, (response, metadata) => {
|
|
518
|
-
if (settled)
|
|
519
|
-
return;
|
|
520
|
-
if (metadata.streamingStart) {
|
|
521
|
-
streamStarted = true;
|
|
522
|
-
streamStartedAtMs = Date.now();
|
|
523
|
-
streamBufferedBytes = 0;
|
|
524
|
-
streamStartResponse = this._stripStreamingHeader(response);
|
|
525
|
-
debugLog(`[Node] Stream started for ${req.requestId.slice(0, 8)}; idle-timeout=${streamIdleTimeoutMs}ms`);
|
|
526
|
-
// Switch to streaming idle timeout: resets on each chunk.
|
|
527
|
-
resetTimeout(streamIdleTimeoutMs);
|
|
528
|
-
callbacks?.onResponseStart?.(streamStartResponse, { streaming: true });
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
callbacks?.onResponseStart?.(this._stripStreamingHeader(response), { streaming: false });
|
|
532
|
-
finish(response);
|
|
533
|
-
}, (chunk) => {
|
|
534
|
-
if (settled)
|
|
535
|
-
return;
|
|
536
|
-
if (!streamStarted)
|
|
537
|
-
return;
|
|
538
|
-
// Reset idle timeout on each chunk so streaming stays alive.
|
|
539
|
-
resetTimeout(streamIdleTimeoutMs);
|
|
540
|
-
if (Date.now() - streamStartedAtMs > maxStreamDurationMs) {
|
|
541
|
-
mux.cancelProxyRequest(req.requestId);
|
|
542
|
-
fail(new Error(`Stream ${req.requestId} exceeded max duration (${maxStreamDurationMs}ms)`));
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
callbacks?.onResponseChunk?.(chunk);
|
|
546
|
-
if (chunk.data.length > 0) {
|
|
547
|
-
if (callbacks?.onResponseChunk) {
|
|
548
|
-
// Streaming mode: chunks already delivered to caller via callback.
|
|
549
|
-
// Track byte count for the debug timeout log only — do not
|
|
550
|
-
// enforce maxStreamBufferBytes so large streams aren't rejected.
|
|
551
|
-
streamBufferedBytes += chunk.data.length;
|
|
552
|
-
streamChunks.push(chunk.data);
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
// Non-streaming: accumulate chunks for the final response body.
|
|
556
|
-
const nextBufferedBytes = streamBufferedBytes + chunk.data.length;
|
|
557
|
-
if (nextBufferedBytes > maxStreamBufferBytes) {
|
|
558
|
-
mux.cancelProxyRequest(req.requestId);
|
|
559
|
-
fail(new Error(`Stream ${req.requestId} exceeded max buffered size (${maxStreamBufferBytes} bytes)`));
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
streamBufferedBytes = nextBufferedBytes;
|
|
563
|
-
streamChunks.push(chunk.data);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (!chunk.done)
|
|
567
|
-
return;
|
|
568
|
-
if (!streamStartResponse) {
|
|
569
|
-
fail(new Error(`Stream ${req.requestId} ended before response start`));
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
finish({
|
|
573
|
-
...streamStartResponse,
|
|
574
|
-
body: concatChunks(streamChunks),
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
});
|
|
578
|
-
// Execute the request. If we get a 402 and payment negotiation is needed,
|
|
579
|
-
// wait for the seller's PaymentRequired message, negotiate, and retry.
|
|
580
|
-
const response = await executeRequest();
|
|
581
|
-
if (response.statusCode === 402 && needsPaymentNegotiation && !externalSpendingAuth) {
|
|
582
|
-
const manualApproval = await this._isManualApprovalEnabled();
|
|
583
|
-
const directPaymentBody = parsePaymentRequiredBody(response.body);
|
|
584
|
-
const responseAlreadyHasRequirements = Boolean(directPaymentBody?.sellerEvmAddr);
|
|
585
|
-
const waitMs = manualApproval ? 10_000 : 2_000;
|
|
586
|
-
const buffered = responseAlreadyHasRequirements
|
|
587
|
-
? null
|
|
588
|
-
: await this._awaitPaymentRequired(peer.peerId, conn, waitMs);
|
|
589
|
-
if (buffered)
|
|
590
|
-
this._bufferedPaymentRequired.delete(peer.peerId);
|
|
591
|
-
// Helper: return enriched 402 so the caller can show an approval / add-credits card
|
|
592
|
-
const returnPaymentRequired = (reason) => {
|
|
593
|
-
debugLog(`[Node] Got 402 from ${peer.peerId.slice(0, 12)}... — returning to caller (${reason})`);
|
|
594
|
-
if (responseAlreadyHasRequirements) {
|
|
595
|
-
return response;
|
|
596
|
-
}
|
|
597
|
-
if (buffered) {
|
|
598
|
-
const enrichedBody = JSON.stringify({
|
|
599
|
-
error: 'payment_required',
|
|
600
|
-
peerId: peer.peerId,
|
|
601
|
-
sellerEvmAddr: buffered.sellerEvmAddr,
|
|
602
|
-
tokenRate: buffered.tokenRate,
|
|
603
|
-
firstSignCap: buffered.firstSignCap,
|
|
604
|
-
suggestedAmount: buffered.suggestedAmount,
|
|
605
|
-
});
|
|
606
|
-
return {
|
|
607
|
-
...response,
|
|
608
|
-
headers: { ...response.headers, 'content-type': 'application/json' },
|
|
609
|
-
body: new TextEncoder().encode(enrichedBody),
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
return response;
|
|
613
|
-
};
|
|
614
|
-
// If manual approval is on, always return the 402 to the caller
|
|
615
|
-
if (manualApproval) {
|
|
616
|
-
return returnPaymentRequired(responseAlreadyHasRequirements ? 'manual approval (direct body)' : 'manual approval');
|
|
617
|
-
}
|
|
618
|
-
// Auto mode: check if we can actually pay before attempting negotiation
|
|
619
|
-
if (!this._escrowClient || !this._identity || !this._buyerPaymentManager) {
|
|
620
|
-
return returnPaymentRequired('no escrow configured');
|
|
621
|
-
}
|
|
622
|
-
// Check on-chain balance — if insufficient, return 402 instead of failing mid-negotiate
|
|
623
|
-
try {
|
|
624
|
-
const buyerAddr = identityToEvmAddress(this._identity);
|
|
625
|
-
const balance = await this._escrowClient.getBuyerBalance(buyerAddr);
|
|
626
|
-
if (balance.available <= 0n) {
|
|
627
|
-
return returnPaymentRequired('insufficient credits');
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
catch (err) {
|
|
631
|
-
debugWarn(`[Node] Failed to check buyer balance: ${err instanceof Error ? err.message : err}`);
|
|
632
|
-
// Fall through to negotiate — let it fail naturally if balance is truly insufficient
|
|
633
|
-
}
|
|
634
|
-
// Auto-negotiate: sign SpendingAuth internally and retry
|
|
635
|
-
// Re-buffer the PaymentRequired so _doNegotiatePayment can consume it
|
|
636
|
-
if (buffered)
|
|
637
|
-
this._bufferedPaymentRequired.set(peer.peerId, buffered);
|
|
638
|
-
debugLog(`[Node] Got 402 from ${peer.peerId.slice(0, 12)}... — auto-negotiating payment`);
|
|
639
|
-
try {
|
|
640
|
-
await this._negotiatePayment(peer, conn);
|
|
641
|
-
debugLog(`[Node] Payment negotiated with ${peer.peerId.slice(0, 12)}... — retrying request`);
|
|
642
|
-
return executeRequest();
|
|
643
|
-
}
|
|
644
|
-
catch (err) {
|
|
645
|
-
this._buyerLockedPeers.delete(peer.peerId);
|
|
646
|
-
throw err;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
return response;
|
|
348
|
+
if (!this._buyerHandler)
|
|
349
|
+
throw new Error("Node not started or not in buyer mode");
|
|
350
|
+
return this._buyerHandler.sendRequest(peer, req, callbacks, options);
|
|
650
351
|
}
|
|
651
352
|
_createDHTConfig(port, bootstrapNodes) {
|
|
652
353
|
return {
|
|
@@ -719,24 +420,16 @@ export class AntseedNode extends EventEmitter {
|
|
|
719
420
|
this._muxes.get(peerId)?.abortPendingUploads();
|
|
720
421
|
this._muxes.delete(peerId);
|
|
721
422
|
this._paymentMuxes.delete(peerId);
|
|
722
|
-
this._bufferedPaymentRequired.delete(peerId);
|
|
723
|
-
// Cancel any in-flight PaymentRequired wait so _doNegotiatePayment
|
|
724
|
-
// fails immediately instead of blocking for 10s on a dead connection.
|
|
725
|
-
const pendingPR = this._pendingPaymentRequired.get(peerId);
|
|
726
|
-
if (pendingPR) {
|
|
727
|
-
clearTimeout(pendingPR.timer);
|
|
728
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
729
|
-
pendingPR.reject(new Error(`Peer ${peerId.slice(0, 12)}... disconnected during payment negotiation`));
|
|
730
|
-
}
|
|
731
|
-
// Don't delete _paymentNegotiationLocks here — the pending rejection
|
|
732
|
-
// causes _doNegotiatePayment to throw, and its finally block owns cleanup.
|
|
733
|
-
// Deleting here would race with a new negotiation started on reconnect.
|
|
734
423
|
this._decoders.delete(peerId);
|
|
735
|
-
//
|
|
424
|
+
// Clean up buyer-side payment state on disconnect
|
|
425
|
+
this._buyerNegotiator?.onPeerDisconnect(peerId);
|
|
426
|
+
// Handle buyer disconnect (seller side)
|
|
736
427
|
if (this._sellerPaymentManager) {
|
|
737
428
|
this._sellerPaymentManager.onBuyerDisconnect(peerId);
|
|
738
429
|
}
|
|
739
|
-
|
|
430
|
+
if (this._sessionTracker) {
|
|
431
|
+
void this._sessionTracker.finalizeSession(peerId, "disconnect");
|
|
432
|
+
}
|
|
740
433
|
}
|
|
741
434
|
});
|
|
742
435
|
// Start keepalive pings on outbound (buyer-initiated) connections to
|
|
@@ -769,7 +462,6 @@ export class AntseedNode extends EventEmitter {
|
|
|
769
462
|
const dhtPort = this._config.dhtPort ?? 6881;
|
|
770
463
|
const signalingPort = this._config.signalingPort ?? 6882;
|
|
771
464
|
debugLog(`[Node] Starting seller — DHT port=${dhtPort}, signaling port=${signalingPort}`);
|
|
772
|
-
// Initialize metering storage
|
|
773
465
|
const dataDir = this._config.dataDir ?? join(homedir(), ".antseed");
|
|
774
466
|
try {
|
|
775
467
|
this._metering = new MeteringStorage(join(dataDir, "metering.db"));
|
|
@@ -781,10 +473,25 @@ export class AntseedNode extends EventEmitter {
|
|
|
781
473
|
if (this._metering) {
|
|
782
474
|
this._receiptGenerator = new ReceiptGenerator({
|
|
783
475
|
peerId: identity.peerId,
|
|
784
|
-
sign: (message) =>
|
|
476
|
+
sign: (message) => signUtf8(identity.wallet, message),
|
|
785
477
|
});
|
|
786
478
|
}
|
|
479
|
+
// Initialize seller session tracker
|
|
480
|
+
this._sessionTracker = new SellerSessionTracker(identity, this._metering, this._receiptGenerator, { settlementIdleMs: this._config.payments?.settlementIdleMs }, {
|
|
481
|
+
onSessionUpdated: (snapshot) => this.emit("session:updated", snapshot),
|
|
482
|
+
onSessionFinalized: (info) => this.emit("session:finalized", info),
|
|
483
|
+
});
|
|
787
484
|
await this._initializePayments(dataDir);
|
|
485
|
+
// Wire idle-timeout session finalization to on-chain settlement
|
|
486
|
+
if (this._sellerPaymentManager) {
|
|
487
|
+
const spm = this._sellerPaymentManager;
|
|
488
|
+
this.on("session:finalized", (info) => {
|
|
489
|
+
if (info.reason === "idle-timeout") {
|
|
490
|
+
debugLog(`[Node] Idle timeout for buyer ${info.buyerPeerId.slice(0, 12)}... — settling channel`);
|
|
491
|
+
void spm.settleSession(info.buyerPeerId, { cleanupOnFailure: true });
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
788
495
|
// Start DHT
|
|
789
496
|
this._dht = new DHTNode(this._createDHTConfig(dhtPort, bootstrapNodes));
|
|
790
497
|
await this._dht.start();
|
|
@@ -843,13 +550,23 @@ export class AntseedNode extends EventEmitter {
|
|
|
843
550
|
])),
|
|
844
551
|
reannounceIntervalMs: DEFAULT_DHT_CONFIG.reannounceIntervalMs,
|
|
845
552
|
signalingPort: actualSignalingPort,
|
|
846
|
-
...(this.
|
|
553
|
+
...(this._channelsClient ? { channelsClient: this._channelsClient } : {}),
|
|
554
|
+
...(this._stakingClient ? { stakingClient: this._stakingClient, paymentsEnabled: true } : {}),
|
|
847
555
|
};
|
|
848
556
|
this._announcer = new PeerAnnouncer(announcerConfig);
|
|
849
557
|
this._announcer.startPeriodicAnnounce();
|
|
850
558
|
// Serve metadata on the signaling port (HTTP requests are auto-detected)
|
|
851
559
|
this._connectionManager.setMetadataProvider(() => this._announcer?.getLatestMetadata() ?? null);
|
|
852
560
|
}
|
|
561
|
+
// Create seller request handler
|
|
562
|
+
this._sellerHandler = new SellerRequestHandler({
|
|
563
|
+
providers: this._providers,
|
|
564
|
+
sellerPaymentManager: this._sellerPaymentManager,
|
|
565
|
+
sessionTracker: this._sessionTracker,
|
|
566
|
+
channelsClient: this._channelsClient,
|
|
567
|
+
announcer: this._announcer,
|
|
568
|
+
emit: (event, ...args) => this.emit(event, ...args),
|
|
569
|
+
});
|
|
853
570
|
// Listen for incoming connections
|
|
854
571
|
this._connectionManager.on("connection", (conn) => {
|
|
855
572
|
this._handleIncomingConnection(conn);
|
|
@@ -884,460 +601,161 @@ export class AntseedNode extends EventEmitter {
|
|
|
884
601
|
this._peerLookup = new PeerLookup(lookupConfig);
|
|
885
602
|
// Initialize buyer-side payment manager if payments config is provided
|
|
886
603
|
const payments = this._config.payments;
|
|
887
|
-
if (payments?.enabled && payments.rpcUrl && payments.
|
|
604
|
+
if (payments?.enabled && payments.rpcUrl && payments.depositsAddress && payments.channelsAddress && payments.usdcAddress) {
|
|
888
605
|
const paymentsDir = join(dataDir, "payments");
|
|
889
|
-
// Create shared
|
|
890
|
-
if (!this.
|
|
606
|
+
// Create shared ChannelStore for both buyer and seller payment managers
|
|
607
|
+
if (!this._channelStore) {
|
|
891
608
|
try {
|
|
892
|
-
this.
|
|
893
|
-
debugLog("[Node]
|
|
609
|
+
this._channelStore = new ChannelStore(paymentsDir);
|
|
610
|
+
debugLog("[Node] ChannelStore initialized (buyer)");
|
|
894
611
|
}
|
|
895
612
|
catch (err) {
|
|
896
|
-
debugWarn(`[Node]
|
|
613
|
+
debugWarn(`[Node] ChannelStore unavailable: ${err instanceof Error ? err.message : err}`);
|
|
897
614
|
}
|
|
898
615
|
}
|
|
899
|
-
if (this.
|
|
616
|
+
if (this._channelStore) {
|
|
900
617
|
const buyerPaymentConfig = {
|
|
901
618
|
rpcUrl: payments.rpcUrl,
|
|
902
|
-
|
|
619
|
+
depositsContractAddress: payments.depositsAddress,
|
|
620
|
+
channelsContractAddress: payments.channelsAddress,
|
|
903
621
|
usdcAddress: payments.usdcAddress,
|
|
904
|
-
|
|
622
|
+
identityRegistryAddress: payments.identityRegistryAddress ?? '',
|
|
905
623
|
chainId: payments.chainId ?? 8453,
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
624
|
+
defaultAuthDurationSecs: payments.defaultAuthDurationSecs ?? 900, // 15 min — seller must call reserve() promptly
|
|
625
|
+
maxPerRequestUsdc: BigInt(payments.maxPerRequestUsdc ?? "500000"), // $0.50 default — covers most LLM requests
|
|
626
|
+
maxReserveAmountUsdc: BigInt(payments.maxReserveAmountUsdc ?? "1000000"), // $1.00 default per session (matches FIRST_SIGN_CAP)
|
|
909
627
|
dataDir: paymentsDir,
|
|
910
628
|
};
|
|
911
|
-
this._buyerPaymentManager = new BuyerPaymentManager(identity, buyerPaymentConfig, this.
|
|
912
|
-
debugLog(`[Node] Buyer payment manager initialized (wallet=${
|
|
913
|
-
|
|
914
|
-
|
|
629
|
+
this._buyerPaymentManager = new BuyerPaymentManager(identity, buyerPaymentConfig, this._channelStore);
|
|
630
|
+
debugLog(`[Node] Buyer payment manager initialized (wallet=${identity.wallet.address.slice(0, 10)}... chainId=${buyerPaymentConfig.chainId} deposits=${buyerPaymentConfig.depositsContractAddress.slice(0, 10)}...)`);
|
|
631
|
+
// Create negotiator that wraps the BPM with 402 handling and per-request auth
|
|
632
|
+
this._buyerNegotiator = new BuyerPaymentNegotiator(identity, this._buyerPaymentManager, this._depositsClient, this._channelsClient, this._channelStore, {}, this);
|
|
633
|
+
debugLog(`[Node] Buyer payment negotiator initialized`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// Create buyer request handler
|
|
637
|
+
this._buyerHandler = new BuyerRequestHandler({
|
|
638
|
+
requestTimeoutMs: this._config.requestTimeoutMs,
|
|
639
|
+
maxStreamBufferBytes: this._config.maxStreamBufferBytes,
|
|
640
|
+
maxStreamDurationMs: this._config.maxStreamDurationMs,
|
|
641
|
+
}, {
|
|
642
|
+
negotiator: this._buyerNegotiator,
|
|
643
|
+
getConnection: (peer) => this._getOrCreateConnection(peer),
|
|
644
|
+
getMux: (peerId, conn) => this._getOrCreateMux(peerId, conn),
|
|
645
|
+
registerPaymentMux: (peerId, pmux) => this._paymentMuxes.set(peerId, pmux),
|
|
646
|
+
});
|
|
915
647
|
debugLog(`[Node] Buyer ready — DHT running on port ${this._dht.getPort()}`);
|
|
916
648
|
}
|
|
917
649
|
_handleIncomingConnection(conn) {
|
|
918
650
|
debugLog(`[Node] Incoming connection from ${conn.remotePeerId.slice(0, 12)}...`);
|
|
919
651
|
const buyerPeerId = conn.remotePeerId;
|
|
920
|
-
const mux = new ProxyMux(conn);
|
|
921
652
|
// Create PaymentMux alongside ProxyMux (seller-side)
|
|
922
653
|
const paymentMux = new PaymentMux(conn);
|
|
923
654
|
if (this._sellerPaymentManager) {
|
|
924
655
|
const spm = this._sellerPaymentManager;
|
|
925
656
|
paymentMux.onSpendingAuth((payload) => {
|
|
926
|
-
void spm.handleSpendingAuth(buyerPeerId, payload
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
657
|
+
void spm.handleSpendingAuth(buyerPeerId, payload, paymentMux)
|
|
658
|
+
.then((status) => {
|
|
659
|
+
if (status === 'rejected') {
|
|
660
|
+
debugWarn(`[Node] SpendingAuth rejected for buyer ${buyerPeerId.slice(0, 12)}... — notifying via payment:auth-rejected event`);
|
|
661
|
+
this.emit('payment:auth-rejected', { buyerPeerId, reason: 'invalid_or_non_monotonic' });
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
.catch((err) => {
|
|
665
|
+
debugWarn(`[Node] SpendingAuth handler error for ${buyerPeerId.slice(0, 12)}...: ${err instanceof Error ? err.message : err}`);
|
|
666
|
+
});
|
|
930
667
|
});
|
|
931
668
|
}
|
|
932
669
|
else {
|
|
933
|
-
// No SellerPaymentManager — reject SpendingAuth to prevent
|
|
934
|
-
// accepting payment claims without EIP-712 signature verification
|
|
935
670
|
paymentMux.onSpendingAuth(() => {
|
|
936
671
|
debugWarn(`[Node] SpendingAuth rejected — SellerPaymentManager not configured`);
|
|
937
672
|
});
|
|
938
673
|
}
|
|
939
674
|
this._paymentMuxes.set(buyerPeerId, paymentMux);
|
|
940
|
-
|
|
941
|
-
mux.onProxyRequest(async (request) => {
|
|
942
|
-
debugLog(`[Node] Seller received request: ${request.method} ${request.path} (reqId=${request.requestId.slice(0, 8)})`);
|
|
943
|
-
// Reject with 402 if no active payment session and escrow client is configured.
|
|
944
|
-
// Also send PaymentRequired via PaymentMux so the buyer knows what to sign.
|
|
945
|
-
const spmAuthorized = this._sellerPaymentManager?.hasSession(buyerPeerId) ?? false;
|
|
946
|
-
if (this._escrowClient && !spmAuthorized) {
|
|
947
|
-
// Pass buyerPeerId so seller can suggest higher amount for returning buyers,
|
|
948
|
-
// and include per-direction pricing from the first registered provider.
|
|
949
|
-
// Retry init if it failed at startup (e.g. RPC was unreachable)
|
|
950
|
-
await this._sellerPaymentManager?.ensureInitialized();
|
|
951
|
-
const firstProvider = this._providers[0];
|
|
952
|
-
const providerPricing = firstProvider?.pricing?.defaults;
|
|
953
|
-
const requirements = this._sellerPaymentManager?.getPaymentRequirements(request.requestId, buyerPeerId, providerPricing);
|
|
954
|
-
if (requirements) {
|
|
955
|
-
debugLog(`[Node] No payment session for ${buyerPeerId.slice(0, 12)}... — sending 402 + PaymentRequired`);
|
|
956
|
-
const paymentBody = JSON.stringify({
|
|
957
|
-
error: 'payment_required',
|
|
958
|
-
sellerEvmAddr: requirements.sellerEvmAddr,
|
|
959
|
-
tokenRate: requirements.tokenRate,
|
|
960
|
-
firstSignCap: requirements.firstSignCap,
|
|
961
|
-
suggestedAmount: requirements.suggestedAmount,
|
|
962
|
-
});
|
|
963
|
-
mux.sendProxyResponse({
|
|
964
|
-
requestId: request.requestId,
|
|
965
|
-
statusCode: 402,
|
|
966
|
-
headers: { "content-type": "application/json" },
|
|
967
|
-
body: new TextEncoder().encode(paymentBody),
|
|
968
|
-
});
|
|
969
|
-
paymentMux.sendPaymentRequired(requirements);
|
|
970
|
-
}
|
|
971
|
-
else {
|
|
972
|
-
// init() failed — on-chain data not cached. Tell buyer to retry
|
|
973
|
-
// rather than letting them wait 10s for a PaymentRequired that won't come.
|
|
974
|
-
debugWarn(`[Node] No payment session and seller not ready (init failed?) — returning 402`);
|
|
975
|
-
mux.sendProxyResponse({
|
|
976
|
-
requestId: request.requestId,
|
|
977
|
-
statusCode: 402,
|
|
978
|
-
headers: { "content-type": "application/json" },
|
|
979
|
-
body: new TextEncoder().encode(JSON.stringify({
|
|
980
|
-
error: 'payment_required',
|
|
981
|
-
message: 'Seller not ready, try again later',
|
|
982
|
-
})),
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
const requestedService = this._extractRequestedService(request);
|
|
988
|
-
const requestedProvider = this._extractRequestedProvider(request);
|
|
989
|
-
const matchesService = (provider) => provider.services.length === 0
|
|
990
|
-
|| (requestedService !== null && provider.services.includes(requestedService))
|
|
991
|
-
|| this._providers.length === 1;
|
|
992
|
-
let provider;
|
|
993
|
-
if (requestedProvider) {
|
|
994
|
-
provider = this._providers.find((candidate) => candidate.name.toLowerCase() === requestedProvider && matchesService(candidate));
|
|
995
|
-
}
|
|
996
|
-
if (!provider) {
|
|
997
|
-
provider = this._providers.find((candidate) => matchesService(candidate));
|
|
998
|
-
}
|
|
999
|
-
if (!provider) {
|
|
1000
|
-
debugWarn(`[Node] No matching provider for ${request.path}`);
|
|
1001
|
-
mux.sendProxyResponse({
|
|
1002
|
-
requestId: request.requestId,
|
|
1003
|
-
statusCode: 502,
|
|
1004
|
-
headers: { "content-type": "text/plain" },
|
|
1005
|
-
body: new TextEncoder().encode("No matching provider"),
|
|
1006
|
-
});
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
// Track active seller session at request start so runtime state reflects
|
|
1010
|
-
// in-flight work immediately (not only after metering persistence).
|
|
1011
|
-
this._getOrCreateSellerSession(buyerPeerId, provider.name);
|
|
1012
|
-
request.headers['x-antseed-buyer-peer-id'] = buyerPeerId;
|
|
1013
|
-
debugLog(`[Node] Routing to provider "${provider.name}"`);
|
|
1014
|
-
const startTime = Date.now();
|
|
1015
|
-
let statusCode = 500;
|
|
1016
|
-
let responseBody = new Uint8Array(0);
|
|
1017
|
-
let streamedResponseStarted = false;
|
|
1018
|
-
this._adjustProviderLoad(provider.name, 1);
|
|
1019
|
-
try {
|
|
1020
|
-
try {
|
|
1021
|
-
const response = await this._executeProviderRequest(provider, request, {
|
|
1022
|
-
onResponseStart: (streamResponseStart) => {
|
|
1023
|
-
streamedResponseStarted = true;
|
|
1024
|
-
statusCode = streamResponseStart.statusCode;
|
|
1025
|
-
mux.sendProxyResponse(streamResponseStart);
|
|
1026
|
-
},
|
|
1027
|
-
onResponseChunk: (chunk) => {
|
|
1028
|
-
if (!streamedResponseStarted)
|
|
1029
|
-
return;
|
|
1030
|
-
mux.sendProxyChunk(chunk);
|
|
1031
|
-
},
|
|
1032
|
-
});
|
|
1033
|
-
statusCode = response.statusCode;
|
|
1034
|
-
responseBody = response.body;
|
|
1035
|
-
debugLog(`[Node] Provider responded: status=${statusCode} (${Date.now() - startTime}ms, ${responseBody.length}b)`);
|
|
1036
|
-
if (!streamedResponseStarted) {
|
|
1037
|
-
mux.sendProxyResponse(response);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
catch (err) {
|
|
1041
|
-
const message = err instanceof Error ? err.message : "Internal error";
|
|
1042
|
-
debugWarn(`[Node] Provider error after ${Date.now() - startTime}ms: ${message}`);
|
|
1043
|
-
responseBody = new TextEncoder().encode(message);
|
|
1044
|
-
if (streamedResponseStarted) {
|
|
1045
|
-
mux.sendProxyChunk({
|
|
1046
|
-
requestId: request.requestId,
|
|
1047
|
-
data: new TextEncoder().encode(`event: error\ndata: ${message}\n\n`),
|
|
1048
|
-
done: false,
|
|
1049
|
-
});
|
|
1050
|
-
mux.sendProxyChunk({
|
|
1051
|
-
requestId: request.requestId,
|
|
1052
|
-
data: new Uint8Array(0),
|
|
1053
|
-
done: true,
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
else {
|
|
1057
|
-
statusCode = 500;
|
|
1058
|
-
mux.sendProxyResponse({
|
|
1059
|
-
requestId: request.requestId,
|
|
1060
|
-
statusCode: 500,
|
|
1061
|
-
headers: { "content-type": "text/plain" },
|
|
1062
|
-
body: responseBody,
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
// Record metering
|
|
1067
|
-
const latencyMs = Date.now() - startTime;
|
|
1068
|
-
const requestPricing = this._resolveProviderPricing(provider, request);
|
|
1069
|
-
await this._recordMetering(buyerPeerId, provider.name, requestPricing, request, statusCode, latencyMs, request.body.length, responseBody.length, responseBody);
|
|
1070
|
-
// Send receipt to buyer after each request
|
|
1071
|
-
if (this._sellerPaymentManager?.hasSession(buyerPeerId)) {
|
|
1072
|
-
const usage = parseResponseUsage(responseBody);
|
|
1073
|
-
const totalTokens = usage.inputTokens + usage.outputTokens;
|
|
1074
|
-
debugLog(`[Node] Sending receipt: buyer=${buyerPeerId.slice(0, 12)}... tokens=${totalTokens} (in=${usage.inputTokens} out=${usage.outputTokens})`);
|
|
1075
|
-
await this._sellerPaymentManager.sendReceipt(buyerPeerId, paymentMux, responseBody, BigInt(totalTokens));
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
finally {
|
|
1079
|
-
this._adjustProviderLoad(provider.name, -1);
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
675
|
+
const { mux } = this._sellerHandler.handleConnection(conn, buyerPeerId, paymentMux);
|
|
1082
676
|
this._muxes.set(buyerPeerId, mux);
|
|
1083
677
|
this._wireConnection(conn, buyerPeerId);
|
|
1084
678
|
this.emit("connection", conn);
|
|
1085
679
|
}
|
|
1086
|
-
async _executeProviderRequest(provider, request, streamCallbacks) {
|
|
1087
|
-
if (streamCallbacks && provider.handleRequestStream) {
|
|
1088
|
-
return provider.handleRequestStream(request, streamCallbacks);
|
|
1089
|
-
}
|
|
1090
|
-
return provider.handleRequest(request);
|
|
1091
|
-
}
|
|
1092
|
-
_stripStreamingHeader(response) {
|
|
1093
|
-
if (response.headers[ANTSEED_STREAMING_RESPONSE_HEADER] !== "1") {
|
|
1094
|
-
return response;
|
|
1095
|
-
}
|
|
1096
|
-
const headers = { ...response.headers };
|
|
1097
|
-
delete headers[ANTSEED_STREAMING_RESPONSE_HEADER];
|
|
1098
|
-
return {
|
|
1099
|
-
...response,
|
|
1100
|
-
headers,
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
_parseJsonBody(body) {
|
|
1104
|
-
try {
|
|
1105
|
-
return JSON.parse(new TextDecoder().decode(body));
|
|
1106
|
-
}
|
|
1107
|
-
catch {
|
|
1108
|
-
return null;
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
_extractRequestedService(request) {
|
|
1112
|
-
const contentType = request.headers["content-type"] ?? request.headers["Content-Type"] ?? "";
|
|
1113
|
-
if (!contentType.toLowerCase().includes("application/json")) {
|
|
1114
|
-
return null;
|
|
1115
|
-
}
|
|
1116
|
-
const parsed = this._parseJsonBody(request.body);
|
|
1117
|
-
if (!parsed || typeof parsed !== "object") {
|
|
1118
|
-
return null;
|
|
1119
|
-
}
|
|
1120
|
-
const service = parsed["model"];
|
|
1121
|
-
if (typeof service !== "string" || service.trim().length === 0) {
|
|
1122
|
-
return null;
|
|
1123
|
-
}
|
|
1124
|
-
return service.trim();
|
|
1125
|
-
}
|
|
1126
|
-
_extractRequestedProvider(request) {
|
|
1127
|
-
const providers = Object.entries(request.headers)
|
|
1128
|
-
.filter(([header]) => header.toLowerCase() === "x-antseed-provider")
|
|
1129
|
-
.map(([, value]) => value.trim().toLowerCase())
|
|
1130
|
-
.filter((value) => value.length > 0);
|
|
1131
|
-
return providers[0] ?? null;
|
|
1132
|
-
}
|
|
1133
|
-
_resolveProviderPricing(provider, request) {
|
|
1134
|
-
const requestedService = this._extractRequestedService(request);
|
|
1135
|
-
if (requestedService) {
|
|
1136
|
-
const servicePricing = provider.pricing.services?.[requestedService];
|
|
1137
|
-
if (servicePricing) {
|
|
1138
|
-
return servicePricing;
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
return provider.pricing.defaults;
|
|
1142
|
-
}
|
|
1143
|
-
_getOrCreateSellerSession(buyerPeerId, providerName) {
|
|
1144
|
-
if (!this._identity) {
|
|
1145
|
-
return null;
|
|
1146
|
-
}
|
|
1147
|
-
let session = this._sessions.get(buyerPeerId);
|
|
1148
|
-
if (!session) {
|
|
1149
|
-
const now = Date.now();
|
|
1150
|
-
const sessionId = randomUUID();
|
|
1151
|
-
// Generate 32-byte sessionIdBytes from UUID for on-chain use
|
|
1152
|
-
const sessionIdBytes = createHash("sha256").update(sessionId).digest();
|
|
1153
|
-
session = {
|
|
1154
|
-
sessionId,
|
|
1155
|
-
sessionIdBytes: new Uint8Array(sessionIdBytes),
|
|
1156
|
-
startedAt: now,
|
|
1157
|
-
lastActivityAt: now,
|
|
1158
|
-
totalRequests: 0,
|
|
1159
|
-
totalTokens: 0,
|
|
1160
|
-
totalLatencyMs: 0,
|
|
1161
|
-
totalCostCents: 0,
|
|
1162
|
-
provider: providerName,
|
|
1163
|
-
};
|
|
1164
|
-
this._sessions.set(buyerPeerId, session);
|
|
1165
|
-
}
|
|
1166
|
-
session.provider = providerName;
|
|
1167
|
-
session.lastActivityAt = Date.now();
|
|
1168
|
-
this._emitSellerSessionUpdated(buyerPeerId, session);
|
|
1169
|
-
return session;
|
|
1170
|
-
}
|
|
1171
|
-
_emitSellerSessionUpdated(buyerPeerId, session) {
|
|
1172
|
-
this.emit("session:updated", {
|
|
1173
|
-
buyerPeerId,
|
|
1174
|
-
sessionId: session.sessionId,
|
|
1175
|
-
provider: session.provider,
|
|
1176
|
-
startedAt: session.startedAt,
|
|
1177
|
-
lastActivityAt: session.lastActivityAt,
|
|
1178
|
-
totalRequests: session.totalRequests,
|
|
1179
|
-
totalTokens: session.totalTokens,
|
|
1180
|
-
avgLatencyMs: session.totalRequests > 0 ? session.totalLatencyMs / session.totalRequests : 0,
|
|
1181
|
-
settling: Boolean(session.settling),
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
/** Estimate tokens from byte lengths (rough: ~4 chars per token). */
|
|
1185
|
-
/** Fallback token estimation from byte lengths (~4 bytes per token). */
|
|
1186
|
-
_estimateTokens(inputBytes, outputBytes) {
|
|
1187
|
-
const inputTokens = Math.max(1, Math.round(inputBytes / 4));
|
|
1188
|
-
const outputTokens = Math.max(1, Math.round(outputBytes / 4));
|
|
1189
|
-
return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, method: 'content-length', confidence: 'low' };
|
|
1190
|
-
}
|
|
1191
|
-
async _recordMetering(buyerPeerId, providerName, providerPricingUsdPerMillion, request, statusCode, latencyMs, inputBytes, outputBytes, responseBody) {
|
|
1192
|
-
if (!this._identity)
|
|
1193
|
-
return;
|
|
1194
|
-
const sellerPeerId = this._identity.peerId;
|
|
1195
|
-
const isSSE = request.headers["accept"]?.includes("text/event-stream") ?? false;
|
|
1196
|
-
// Use actual token counts from provider response when available,
|
|
1197
|
-
// falling back to byte-based estimation.
|
|
1198
|
-
const providerUsage = parseResponseUsage(responseBody);
|
|
1199
|
-
let tokens;
|
|
1200
|
-
if (providerUsage.inputTokens > 0 || providerUsage.outputTokens > 0) {
|
|
1201
|
-
const totalTokens = providerUsage.inputTokens + providerUsage.outputTokens;
|
|
1202
|
-
tokens = {
|
|
1203
|
-
inputTokens: providerUsage.inputTokens,
|
|
1204
|
-
outputTokens: providerUsage.outputTokens,
|
|
1205
|
-
totalTokens,
|
|
1206
|
-
method: 'provider-usage',
|
|
1207
|
-
confidence: 'high',
|
|
1208
|
-
};
|
|
1209
|
-
debugLog(`[Node] Metering: provider-usage tokens=${totalTokens} (in=${providerUsage.inputTokens} out=${providerUsage.outputTokens})`);
|
|
1210
|
-
}
|
|
1211
|
-
else {
|
|
1212
|
-
tokens = this._estimateTokens(inputBytes, outputBytes);
|
|
1213
|
-
debugLog(`[Node] Metering: estimated tokens=${tokens.totalTokens} from ${inputBytes}+${outputBytes} bytes`);
|
|
1214
|
-
}
|
|
1215
|
-
// Get or create session for this buyer
|
|
1216
|
-
const session = this._getOrCreateSellerSession(buyerPeerId, providerName);
|
|
1217
|
-
if (!session)
|
|
1218
|
-
return;
|
|
1219
|
-
session.totalRequests++;
|
|
1220
|
-
session.totalTokens += tokens.totalTokens;
|
|
1221
|
-
session.totalLatencyMs += latencyMs;
|
|
1222
|
-
session.provider = providerName;
|
|
1223
|
-
session.lastActivityAt = Date.now();
|
|
1224
|
-
this._emitSellerSessionUpdated(buyerPeerId, session);
|
|
1225
|
-
const metering = this._metering;
|
|
1226
|
-
if (!metering) {
|
|
1227
|
-
this._scheduleSettlementTimer(buyerPeerId);
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
// Record metering event
|
|
1231
|
-
const event = {
|
|
1232
|
-
eventId: randomUUID(),
|
|
1233
|
-
sessionId: session.sessionId,
|
|
1234
|
-
timestamp: Date.now(),
|
|
1235
|
-
provider: providerName,
|
|
1236
|
-
sellerPeerId,
|
|
1237
|
-
buyerPeerId,
|
|
1238
|
-
tokens: { ...tokens, method: "content-length", confidence: "low" },
|
|
1239
|
-
latencyMs,
|
|
1240
|
-
statusCode,
|
|
1241
|
-
wasStreaming: isSSE,
|
|
1242
|
-
};
|
|
1243
|
-
try {
|
|
1244
|
-
metering.insertEvent(event);
|
|
1245
|
-
}
|
|
1246
|
-
catch (err) {
|
|
1247
|
-
debugWarn(`[Node] Failed to record metering event: ${err instanceof Error ? err.message : err}`);
|
|
1248
|
-
}
|
|
1249
|
-
if (this._receiptGenerator) {
|
|
1250
|
-
const estimatedCostUsd = (tokens.inputTokens * providerPricingUsdPerMillion.inputUsdPerMillion +
|
|
1251
|
-
tokens.outputTokens * providerPricingUsdPerMillion.outputUsdPerMillion) /
|
|
1252
|
-
1_000_000;
|
|
1253
|
-
const effectiveUsdPerThousandTokens = tokens.totalTokens > 0 ? (estimatedCostUsd / tokens.totalTokens) * 1000 : 0;
|
|
1254
|
-
// Receipt unit pricing uses USD cents per 1,000 tokens.
|
|
1255
|
-
const unitPriceCentsPerThousandTokens = Math.max(0, effectiveUsdPerThousandTokens * 100);
|
|
1256
|
-
const receipt = this._receiptGenerator.generate(session.sessionId, event.eventId, providerName, buyerPeerId, event.tokens, unitPriceCentsPerThousandTokens);
|
|
1257
|
-
try {
|
|
1258
|
-
metering.insertReceipt(receipt);
|
|
1259
|
-
session.totalCostCents += receipt.costCents;
|
|
1260
|
-
}
|
|
1261
|
-
catch (err) {
|
|
1262
|
-
debugWarn(`[Node] Failed to record usage receipt: ${err instanceof Error ? err.message : err}`);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
// Upsert session
|
|
1266
|
-
const sessionMetrics = {
|
|
1267
|
-
sessionId: session.sessionId,
|
|
1268
|
-
sellerPeerId,
|
|
1269
|
-
buyerPeerId,
|
|
1270
|
-
provider: providerName,
|
|
1271
|
-
startedAt: session.startedAt,
|
|
1272
|
-
endedAt: null,
|
|
1273
|
-
totalRequests: session.totalRequests,
|
|
1274
|
-
totalTokens: session.totalTokens,
|
|
1275
|
-
totalCostCents: session.totalCostCents,
|
|
1276
|
-
avgLatencyMs: session.totalLatencyMs / session.totalRequests,
|
|
1277
|
-
peerSwitches: 0,
|
|
1278
|
-
disputedReceipts: 0,
|
|
1279
|
-
};
|
|
1280
|
-
try {
|
|
1281
|
-
metering.upsertSession(sessionMetrics);
|
|
1282
|
-
}
|
|
1283
|
-
catch (err) {
|
|
1284
|
-
debugWarn(`[Node] Failed to upsert session: ${err instanceof Error ? err.message : err}`);
|
|
1285
|
-
}
|
|
1286
|
-
this._scheduleSettlementTimer(buyerPeerId);
|
|
1287
|
-
}
|
|
1288
680
|
async _initializePayments(dataDir) {
|
|
1289
681
|
const payments = this._config.payments;
|
|
1290
682
|
if (!payments || !payments.enabled) {
|
|
1291
683
|
return;
|
|
1292
684
|
}
|
|
1293
|
-
// Initialize
|
|
1294
|
-
if (payments.rpcUrl && payments.
|
|
1295
|
-
this.
|
|
685
|
+
// Initialize DepositsClient
|
|
686
|
+
if (payments.rpcUrl && payments.depositsAddress && payments.usdcAddress) {
|
|
687
|
+
this._depositsClient = new DepositsClient({
|
|
688
|
+
rpcUrl: payments.rpcUrl,
|
|
689
|
+
contractAddress: payments.depositsAddress,
|
|
690
|
+
usdcAddress: payments.usdcAddress,
|
|
691
|
+
});
|
|
692
|
+
debugLog(`[Node] DepositsClient initialized (contract=${payments.depositsAddress.slice(0, 10)}...)`);
|
|
693
|
+
}
|
|
694
|
+
// Initialize ChannelsClient
|
|
695
|
+
if (payments.rpcUrl && payments.channelsAddress) {
|
|
696
|
+
this._channelsClient = new ChannelsClient({
|
|
1296
697
|
rpcUrl: payments.rpcUrl,
|
|
1297
|
-
contractAddress: payments.
|
|
698
|
+
contractAddress: payments.channelsAddress,
|
|
699
|
+
});
|
|
700
|
+
debugLog(`[Node] ChannelsClient initialized (contract=${payments.channelsAddress.slice(0, 10)}...)`);
|
|
701
|
+
}
|
|
702
|
+
// Initialize StakingClient
|
|
703
|
+
if (payments.rpcUrl && payments.stakingAddress && payments.usdcAddress) {
|
|
704
|
+
this._stakingClient = new StakingClient({
|
|
705
|
+
rpcUrl: payments.rpcUrl,
|
|
706
|
+
contractAddress: payments.stakingAddress,
|
|
1298
707
|
usdcAddress: payments.usdcAddress,
|
|
1299
708
|
});
|
|
1300
|
-
debugLog(`[Node]
|
|
709
|
+
debugLog(`[Node] StakingClient initialized (contract=${payments.stakingAddress.slice(0, 10)}...)`);
|
|
1301
710
|
}
|
|
1302
|
-
// Initialize IdentityClient
|
|
1303
|
-
if (payments.rpcUrl && payments.
|
|
711
|
+
// Initialize IdentityClient (ERC-8004 IdentityRegistry)
|
|
712
|
+
if (payments.rpcUrl && payments.identityRegistryAddress) {
|
|
1304
713
|
this._identityClient = new IdentityClient({
|
|
1305
714
|
rpcUrl: payments.rpcUrl,
|
|
1306
|
-
contractAddress: payments.
|
|
715
|
+
contractAddress: payments.identityRegistryAddress,
|
|
1307
716
|
});
|
|
1308
|
-
debugLog(`[Node] IdentityClient initialized (contract=${payments.
|
|
717
|
+
debugLog(`[Node] IdentityClient initialized (contract=${payments.identityRegistryAddress.slice(0, 10)}...)`);
|
|
1309
718
|
}
|
|
1310
|
-
// Initialize
|
|
719
|
+
// Initialize ChannelStore for persistent payment channels (shared instance)
|
|
1311
720
|
const paymentsDir = join(dataDir, "payments");
|
|
1312
|
-
if (!this.
|
|
721
|
+
if (!this._channelStore) {
|
|
1313
722
|
try {
|
|
1314
|
-
this.
|
|
1315
|
-
debugLog("[Node]
|
|
723
|
+
this._channelStore = new ChannelStore(paymentsDir);
|
|
724
|
+
debugLog("[Node] ChannelStore initialized");
|
|
1316
725
|
}
|
|
1317
726
|
catch (err) {
|
|
1318
|
-
debugWarn(`[Node]
|
|
727
|
+
debugWarn(`[Node] ChannelStore unavailable: ${err instanceof Error ? err.message : err}`);
|
|
1319
728
|
}
|
|
1320
729
|
}
|
|
1321
730
|
// Initialize SellerPaymentManager for seller role
|
|
1322
|
-
if (this._config.role === 'seller' && this._identity && this.
|
|
1323
|
-
payments.rpcUrl && payments.
|
|
731
|
+
if (this._config.role === 'seller' && this._identity && this._channelStore &&
|
|
732
|
+
payments.rpcUrl && payments.channelsAddress) {
|
|
1324
733
|
const sellerConfig = {
|
|
1325
734
|
rpcUrl: payments.rpcUrl,
|
|
1326
|
-
|
|
1327
|
-
usdcAddress: payments.usdcAddress,
|
|
735
|
+
channelsContractAddress: payments.channelsAddress,
|
|
1328
736
|
chainId: payments.chainId ?? 8453,
|
|
1329
737
|
dataDir: paymentsDir,
|
|
1330
|
-
|
|
1331
|
-
provenSignAmountUsdc: payments.provenSignAmountUsdc,
|
|
738
|
+
...(payments.minBudgetPerRequest ? { minBudgetPerRequest: payments.minBudgetPerRequest } : {}),
|
|
1332
739
|
};
|
|
1333
|
-
this._sellerPaymentManager = new SellerPaymentManager(this._identity, sellerConfig, this.
|
|
1334
|
-
await this._sellerPaymentManager.init();
|
|
740
|
+
this._sellerPaymentManager = new SellerPaymentManager(this._identity, sellerConfig, this._channelStore);
|
|
1335
741
|
debugLog(`[Node] SellerPaymentManager initialized`);
|
|
1336
742
|
// Startup recovery: check for timed-out sessions
|
|
1337
743
|
await this._sellerPaymentManager.checkTimeouts();
|
|
1338
|
-
//
|
|
744
|
+
// Initialize CloseRequested polling cursor to current block
|
|
745
|
+
try {
|
|
746
|
+
this._closeRequestedFromBlock = await this._sellerPaymentManager.channelsClient.getBlockNumber();
|
|
747
|
+
}
|
|
748
|
+
catch {
|
|
749
|
+
this._closeRequestedFromBlock = 0;
|
|
750
|
+
}
|
|
751
|
+
// Start periodic timeout checker + CloseRequested poller (every 60s)
|
|
1339
752
|
this._timeoutCheckerInterval = setInterval(() => {
|
|
1340
753
|
void this._sellerPaymentManager?.checkTimeouts();
|
|
754
|
+
if (this._sellerPaymentManager) {
|
|
755
|
+
void this._sellerPaymentManager.pollCloseRequested(this._closeRequestedFromBlock).then((nextBlock) => {
|
|
756
|
+
this._closeRequestedFromBlock = nextBlock;
|
|
757
|
+
});
|
|
758
|
+
}
|
|
1341
759
|
}, 60_000);
|
|
1342
760
|
if (typeof this._timeoutCheckerInterval.unref === "function") {
|
|
1343
761
|
this._timeoutCheckerInterval.unref();
|
|
@@ -1352,77 +770,6 @@ export class AntseedNode extends EventEmitter {
|
|
|
1352
770
|
debugWarn(`[Node] Failed to load payment balances: ${err instanceof Error ? err.message : err}`);
|
|
1353
771
|
});
|
|
1354
772
|
}
|
|
1355
|
-
_scheduleSettlementTimer(buyerPeerId) {
|
|
1356
|
-
const existing = this._settlementTimers.get(buyerPeerId);
|
|
1357
|
-
if (existing) {
|
|
1358
|
-
clearTimeout(existing);
|
|
1359
|
-
}
|
|
1360
|
-
const idleMs = this._config.payments?.settlementIdleMs ?? 30_000;
|
|
1361
|
-
const timer = setTimeout(() => {
|
|
1362
|
-
void this._finalizeSession(buyerPeerId, "idle-timeout");
|
|
1363
|
-
}, idleMs);
|
|
1364
|
-
if (typeof timer.unref === "function") {
|
|
1365
|
-
timer.unref();
|
|
1366
|
-
}
|
|
1367
|
-
this._settlementTimers.set(buyerPeerId, timer);
|
|
1368
|
-
}
|
|
1369
|
-
async _finalizeSession(buyerPeerId, reason) {
|
|
1370
|
-
const session = this._sessions.get(buyerPeerId);
|
|
1371
|
-
if (!session || session.settling) {
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
session.settling = true;
|
|
1375
|
-
const timer = this._settlementTimers.get(buyerPeerId);
|
|
1376
|
-
if (timer) {
|
|
1377
|
-
clearTimeout(timer);
|
|
1378
|
-
this._settlementTimers.delete(buyerPeerId);
|
|
1379
|
-
}
|
|
1380
|
-
if (!this._metering || !this._identity) {
|
|
1381
|
-
this._sessions.delete(buyerPeerId);
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
const now = Date.now();
|
|
1385
|
-
const baseMetrics = {
|
|
1386
|
-
sessionId: session.sessionId,
|
|
1387
|
-
sellerPeerId: this._identity.peerId,
|
|
1388
|
-
buyerPeerId,
|
|
1389
|
-
provider: session.provider,
|
|
1390
|
-
startedAt: session.startedAt,
|
|
1391
|
-
endedAt: now,
|
|
1392
|
-
totalRequests: session.totalRequests,
|
|
1393
|
-
totalTokens: session.totalTokens,
|
|
1394
|
-
totalCostCents: session.totalCostCents,
|
|
1395
|
-
avgLatencyMs: session.totalRequests > 0 ? session.totalLatencyMs / session.totalRequests : 0,
|
|
1396
|
-
peerSwitches: 0,
|
|
1397
|
-
disputedReceipts: 0,
|
|
1398
|
-
};
|
|
1399
|
-
try {
|
|
1400
|
-
this._metering.upsertSession(baseMetrics);
|
|
1401
|
-
this._sessions.delete(buyerPeerId);
|
|
1402
|
-
this.emit("session:finalized", {
|
|
1403
|
-
buyerPeerId,
|
|
1404
|
-
sessionId: session.sessionId,
|
|
1405
|
-
reason,
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
catch (err) {
|
|
1409
|
-
session.settling = false;
|
|
1410
|
-
debugWarn(`[Node] Failed to finalize session ${session.sessionId}: ${err instanceof Error ? err.message : err}`);
|
|
1411
|
-
const retry = setTimeout(() => {
|
|
1412
|
-
void this._finalizeSession(buyerPeerId, "retry");
|
|
1413
|
-
}, 10_000);
|
|
1414
|
-
if (typeof retry.unref === "function") {
|
|
1415
|
-
retry.unref();
|
|
1416
|
-
}
|
|
1417
|
-
this._settlementTimers.set(buyerPeerId, retry);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
async _finalizeAllSessions(reason) {
|
|
1421
|
-
if (this._sessions.size === 0)
|
|
1422
|
-
return;
|
|
1423
|
-
const buyers = [...this._sessions.keys()];
|
|
1424
|
-
await Promise.allSettled(buyers.map((buyerPeerId) => this._finalizeSession(buyerPeerId, reason)));
|
|
1425
|
-
}
|
|
1426
773
|
async _getOrCreateConnection(peer) {
|
|
1427
774
|
if (!this._connectionManager || !this._identity) {
|
|
1428
775
|
throw new Error("Node not started");
|
|
@@ -1507,336 +854,6 @@ export class AntseedNode extends EventEmitter {
|
|
|
1507
854
|
this._muxes.set(peerId, mux);
|
|
1508
855
|
return mux;
|
|
1509
856
|
}
|
|
1510
|
-
// ── Buyer-side payment helpers ─────────────────────────────────
|
|
1511
|
-
/**
|
|
1512
|
-
* Create a PaymentMux for a buyer-side outbound connection and register
|
|
1513
|
-
* buyer-side handlers (lock confirm, lock reject, seller receipt, top-up request).
|
|
1514
|
-
*/
|
|
1515
|
-
_getOrCreateBuyerPaymentMux(peerId, conn) {
|
|
1516
|
-
const existing = this._paymentMuxes.get(peerId);
|
|
1517
|
-
if (existing)
|
|
1518
|
-
return existing;
|
|
1519
|
-
const pmux = new PaymentMux(conn);
|
|
1520
|
-
this._paymentMuxes.set(peerId, pmux);
|
|
1521
|
-
const bpm = this._buyerPaymentManager;
|
|
1522
|
-
if (!bpm)
|
|
1523
|
-
return pmux;
|
|
1524
|
-
pmux.onAuthAck((payload) => {
|
|
1525
|
-
bpm.handleAuthAck(peerId, payload);
|
|
1526
|
-
});
|
|
1527
|
-
pmux.onSellerReceipt((receipt) => {
|
|
1528
|
-
void bpm.handleSellerReceipt(peerId, receipt, pmux);
|
|
1529
|
-
});
|
|
1530
|
-
pmux.onTopUpRequest((request) => {
|
|
1531
|
-
void bpm.handleTopUpRequest(peerId, request, pmux);
|
|
1532
|
-
});
|
|
1533
|
-
pmux.onPaymentRequired((payload) => {
|
|
1534
|
-
const pending = this._pendingPaymentRequired.get(peerId);
|
|
1535
|
-
if (pending) {
|
|
1536
|
-
clearTimeout(pending.timer);
|
|
1537
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
1538
|
-
pending.resolve(payload);
|
|
1539
|
-
}
|
|
1540
|
-
else {
|
|
1541
|
-
// Buffer: 402 and PaymentRequired can arrive in the same I/O tick,
|
|
1542
|
-
// before _doNegotiatePayment registers its listener.
|
|
1543
|
-
this._bufferedPaymentRequired.set(peerId, payload);
|
|
1544
|
-
debugLog(`[Node] PaymentRequired from ${peerId.slice(0, 12)}... buffered (listener not yet registered)`);
|
|
1545
|
-
}
|
|
1546
|
-
});
|
|
1547
|
-
return pmux;
|
|
1548
|
-
}
|
|
1549
|
-
/**
|
|
1550
|
-
* Wait for the seller's PaymentRequired message, sign a SpendingAuth with
|
|
1551
|
-
* the seller's real requirements, and wait for AuthAck.
|
|
1552
|
-
* Uses a per-peer mutex so concurrent requests wait for the first negotiation.
|
|
1553
|
-
*/
|
|
1554
|
-
/** Read requireManualApproval from config.json so changes take effect without restart. */
|
|
1555
|
-
async _isManualApprovalEnabled() {
|
|
1556
|
-
const now = Date.now();
|
|
1557
|
-
if (this._manualApprovalCache && now - this._manualApprovalCache.at < 5_000) {
|
|
1558
|
-
return this._manualApprovalCache.value;
|
|
1559
|
-
}
|
|
1560
|
-
try {
|
|
1561
|
-
const configPath = this._config.configPath
|
|
1562
|
-
?? join(this._config.dataDir ?? join(homedir(), '.antseed'), 'config.json');
|
|
1563
|
-
const raw = await readFile(configPath, 'utf-8');
|
|
1564
|
-
const parsed = JSON.parse(raw);
|
|
1565
|
-
const buyer = parsed.buyer && typeof parsed.buyer === 'object' ? parsed.buyer : {};
|
|
1566
|
-
const value = Boolean(buyer.requireManualApproval);
|
|
1567
|
-
this._manualApprovalCache = { value, at: now };
|
|
1568
|
-
return value;
|
|
1569
|
-
}
|
|
1570
|
-
catch {
|
|
1571
|
-
const value = Boolean(this._config.requireManualApproval);
|
|
1572
|
-
this._manualApprovalCache = { value, at: now };
|
|
1573
|
-
return value;
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Wait for the seller's PaymentRequired payload for a given peer.
|
|
1578
|
-
* Returns a buffered payload immediately when available, otherwise waits up to timeoutMs.
|
|
1579
|
-
*/
|
|
1580
|
-
async _awaitPaymentRequired(peerId, conn, timeoutMs) {
|
|
1581
|
-
const buffered = this._bufferedPaymentRequired.get(peerId);
|
|
1582
|
-
if (buffered) {
|
|
1583
|
-
return buffered;
|
|
1584
|
-
}
|
|
1585
|
-
// Ensure the buyer-side PaymentMux exists before waiting so incoming frames
|
|
1586
|
-
// are captured even when 402 and PaymentRequired arrive close together.
|
|
1587
|
-
this._getOrCreateBuyerPaymentMux(peerId, conn);
|
|
1588
|
-
return await new Promise((resolve) => {
|
|
1589
|
-
const already = this._bufferedPaymentRequired.get(peerId);
|
|
1590
|
-
if (already) {
|
|
1591
|
-
resolve(already);
|
|
1592
|
-
return;
|
|
1593
|
-
}
|
|
1594
|
-
const existing = this._pendingPaymentRequired.get(peerId);
|
|
1595
|
-
if (existing) {
|
|
1596
|
-
const wrapper = {
|
|
1597
|
-
resolve: (payload) => {
|
|
1598
|
-
clearTimeout(existing.timer);
|
|
1599
|
-
clearTimeout(wrapper.timer);
|
|
1600
|
-
if (this._pendingPaymentRequired.get(peerId) === wrapper) {
|
|
1601
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
1602
|
-
}
|
|
1603
|
-
existing.resolve(payload);
|
|
1604
|
-
resolve(payload);
|
|
1605
|
-
},
|
|
1606
|
-
reject: (err) => {
|
|
1607
|
-
clearTimeout(existing.timer);
|
|
1608
|
-
clearTimeout(wrapper.timer);
|
|
1609
|
-
if (this._pendingPaymentRequired.get(peerId) === wrapper) {
|
|
1610
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
1611
|
-
}
|
|
1612
|
-
existing.reject(err);
|
|
1613
|
-
resolve(null);
|
|
1614
|
-
},
|
|
1615
|
-
timer: setTimeout(() => {
|
|
1616
|
-
clearTimeout(existing.timer);
|
|
1617
|
-
if (this._pendingPaymentRequired.get(peerId) === wrapper) {
|
|
1618
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
1619
|
-
}
|
|
1620
|
-
resolve(null);
|
|
1621
|
-
}, timeoutMs),
|
|
1622
|
-
};
|
|
1623
|
-
this._pendingPaymentRequired.set(peerId, wrapper);
|
|
1624
|
-
return;
|
|
1625
|
-
}
|
|
1626
|
-
const timer = setTimeout(() => {
|
|
1627
|
-
if (this._pendingPaymentRequired.get(peerId)?.timer === timer) {
|
|
1628
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
1629
|
-
}
|
|
1630
|
-
resolve(null);
|
|
1631
|
-
}, timeoutMs);
|
|
1632
|
-
this._pendingPaymentRequired.set(peerId, {
|
|
1633
|
-
resolve: (payload) => {
|
|
1634
|
-
clearTimeout(timer);
|
|
1635
|
-
if (this._pendingPaymentRequired.get(peerId)?.timer === timer) {
|
|
1636
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
1637
|
-
}
|
|
1638
|
-
resolve(payload);
|
|
1639
|
-
},
|
|
1640
|
-
reject: () => {
|
|
1641
|
-
clearTimeout(timer);
|
|
1642
|
-
if (this._pendingPaymentRequired.get(peerId)?.timer === timer) {
|
|
1643
|
-
this._pendingPaymentRequired.delete(peerId);
|
|
1644
|
-
}
|
|
1645
|
-
resolve(null);
|
|
1646
|
-
},
|
|
1647
|
-
timer,
|
|
1648
|
-
});
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
|
-
/**
|
|
1652
|
-
* Apply a pre-signed SpendingAuth from the x-antseed-spending-auth header.
|
|
1653
|
-
* Sends it to the seller via PaymentMux and waits for AuthAck.
|
|
1654
|
-
*/
|
|
1655
|
-
async _applyExternalSpendingAuth(peer, conn, headerValue) {
|
|
1656
|
-
const pmux = this._getOrCreateBuyerPaymentMux(peer.peerId, conn);
|
|
1657
|
-
let payload;
|
|
1658
|
-
try {
|
|
1659
|
-
const decoded = Buffer.from(headerValue, 'base64').toString('utf-8');
|
|
1660
|
-
payload = JSON.parse(decoded);
|
|
1661
|
-
}
|
|
1662
|
-
catch {
|
|
1663
|
-
throw new Error('Invalid x-antseed-spending-auth header: failed to decode');
|
|
1664
|
-
}
|
|
1665
|
-
debugLog(`[Node] External SpendingAuth: session=${payload.sessionId.slice(0, 18)}... amount=${payload.maxAmountUsdc}`);
|
|
1666
|
-
// Store session so handleAuthAck can find it
|
|
1667
|
-
if (this._sessionStore) {
|
|
1668
|
-
const sellerEvmAddr = payload.sellerEvmAddr ?? '';
|
|
1669
|
-
this._sessionStore.upsertSession({
|
|
1670
|
-
sessionId: payload.sessionId,
|
|
1671
|
-
peerId: peer.peerId,
|
|
1672
|
-
role: 'buyer',
|
|
1673
|
-
sellerEvmAddr,
|
|
1674
|
-
buyerEvmAddr: payload.buyerEvmAddr,
|
|
1675
|
-
nonce: payload.nonce,
|
|
1676
|
-
authMax: payload.maxAmountUsdc,
|
|
1677
|
-
deadline: payload.deadline,
|
|
1678
|
-
previousSessionId: payload.previousSessionId,
|
|
1679
|
-
previousConsumption: payload.previousConsumption,
|
|
1680
|
-
tokensDelivered: '0',
|
|
1681
|
-
requestCount: 0,
|
|
1682
|
-
reservedAt: Date.now(),
|
|
1683
|
-
settledAt: null,
|
|
1684
|
-
settledAmount: null,
|
|
1685
|
-
status: 'active',
|
|
1686
|
-
createdAt: Date.now(),
|
|
1687
|
-
updatedAt: Date.now(),
|
|
1688
|
-
});
|
|
1689
|
-
}
|
|
1690
|
-
// Send the pre-signed SpendingAuth to the seller
|
|
1691
|
-
pmux.sendSpendingAuth(payload);
|
|
1692
|
-
debugLog(`[Node] External SpendingAuth sent to seller ${peer.peerId.slice(0, 12)}..., waiting for AuthAck...`);
|
|
1693
|
-
await this._waitForLockConfirmation(peer.peerId);
|
|
1694
|
-
debugLog(`[Node] AuthAck received from seller ${peer.peerId.slice(0, 12)}...`);
|
|
1695
|
-
this._buyerLockedPeers.add(peer.peerId);
|
|
1696
|
-
this.emit('payment:signed', {
|
|
1697
|
-
peerId: peer.peerId,
|
|
1698
|
-
sellerEvmAddr: payload.sellerEvmAddr ?? '',
|
|
1699
|
-
amount: payload.maxAmountUsdc,
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
async _negotiatePayment(peer, conn) {
|
|
1703
|
-
// Per-peer mutex: if another request is already negotiating, wait for it
|
|
1704
|
-
const existing = this._paymentNegotiationLocks.get(peer.peerId);
|
|
1705
|
-
if (existing) {
|
|
1706
|
-
await existing;
|
|
1707
|
-
return;
|
|
1708
|
-
}
|
|
1709
|
-
const negotiation = this._doNegotiatePayment(peer, conn);
|
|
1710
|
-
this._paymentNegotiationLocks.set(peer.peerId, negotiation);
|
|
1711
|
-
try {
|
|
1712
|
-
await negotiation;
|
|
1713
|
-
}
|
|
1714
|
-
finally {
|
|
1715
|
-
this._paymentNegotiationLocks.delete(peer.peerId);
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
async _doNegotiatePayment(peer, conn) {
|
|
1719
|
-
const bpm = this._buyerPaymentManager;
|
|
1720
|
-
if (!bpm) {
|
|
1721
|
-
throw new Error('Payment negotiation unavailable — no escrow contract configured');
|
|
1722
|
-
}
|
|
1723
|
-
// If already locked from a previous successful negotiation, skip
|
|
1724
|
-
if (this._buyerLockedPeers.has(peer.peerId))
|
|
1725
|
-
return;
|
|
1726
|
-
const pmux = this._getOrCreateBuyerPaymentMux(peer.peerId, conn);
|
|
1727
|
-
// Check if PaymentRequired was already buffered (arrives in same I/O tick as 402)
|
|
1728
|
-
const buffered = this._bufferedPaymentRequired.get(peer.peerId);
|
|
1729
|
-
if (buffered) {
|
|
1730
|
-
this._bufferedPaymentRequired.delete(peer.peerId);
|
|
1731
|
-
debugLog(`[Node] Using buffered PaymentRequired from ${peer.peerId.slice(0, 12)}...`);
|
|
1732
|
-
}
|
|
1733
|
-
const PAYMENT_REQUIRED_TIMEOUT_MS = 10_000;
|
|
1734
|
-
const requirements = buffered ?? await new Promise((resolve, reject) => {
|
|
1735
|
-
const timer = setTimeout(() => {
|
|
1736
|
-
this._pendingPaymentRequired.delete(peer.peerId);
|
|
1737
|
-
reject(new Error(`PaymentRequired timeout from seller ${peer.peerId.slice(0, 12)}...`));
|
|
1738
|
-
}, PAYMENT_REQUIRED_TIMEOUT_MS);
|
|
1739
|
-
this._pendingPaymentRequired.set(peer.peerId, { resolve, reject, timer });
|
|
1740
|
-
});
|
|
1741
|
-
debugLog(`[Node] PaymentRequired from ${peer.peerId.slice(0, 12)}...: rate=${requirements.tokenRate} cap=${requirements.firstSignCap} suggested=${requirements.suggestedAmount}`);
|
|
1742
|
-
// Fetch on-chain context so the UI can show the buyer real data
|
|
1743
|
-
let approvalContext = null;
|
|
1744
|
-
if (this._escrowClient && this._identity) {
|
|
1745
|
-
try {
|
|
1746
|
-
const buyerAddr = identityToEvmAddress(this._identity);
|
|
1747
|
-
approvalContext = await this._escrowClient.getBuyerApprovalContext(buyerAddr, requirements.sellerEvmAddr);
|
|
1748
|
-
debugLog(`[Node] Approval context: balance=${approvalContext.buyerBalance.available} isFirstSign=${approvalContext.isFirstSign} cooldown=${approvalContext.cooldownRemainingSecs}s`);
|
|
1749
|
-
}
|
|
1750
|
-
catch (err) {
|
|
1751
|
-
debugWarn(`[Node] Failed to fetch approval context: ${err instanceof Error ? err.message : err}`);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
// Cap amount before building approval info so the displayed amount
|
|
1755
|
-
// matches what will actually be signed via authorizeSpending.
|
|
1756
|
-
let amount;
|
|
1757
|
-
try {
|
|
1758
|
-
amount = BigInt(requirements.suggestedAmount);
|
|
1759
|
-
}
|
|
1760
|
-
catch {
|
|
1761
|
-
throw new Error(`Invalid suggestedAmount from seller ${peer.peerId.slice(0, 12)}...: "${requirements.suggestedAmount}"`);
|
|
1762
|
-
}
|
|
1763
|
-
if (approvalContext) {
|
|
1764
|
-
const available = approvalContext.buyerBalance.available;
|
|
1765
|
-
if (amount > available)
|
|
1766
|
-
amount = available;
|
|
1767
|
-
if (approvalContext.isFirstSign) {
|
|
1768
|
-
const cap = approvalContext.firstSignCap;
|
|
1769
|
-
if (amount > cap)
|
|
1770
|
-
amount = cap;
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
if (amount <= 0n) {
|
|
1774
|
-
throw new Error(`Insufficient escrow balance to authorize payment to ${peer.peerId.slice(0, 12)}...`);
|
|
1775
|
-
}
|
|
1776
|
-
const approvalInfo = {
|
|
1777
|
-
peerId: peer.peerId,
|
|
1778
|
-
sellerEvmAddr: requirements.sellerEvmAddr,
|
|
1779
|
-
tokenRate: requirements.tokenRate,
|
|
1780
|
-
firstSignCap: requirements.firstSignCap,
|
|
1781
|
-
suggestedAmount: amount.toString(),
|
|
1782
|
-
buyerAvailableUsdc: approvalContext ? approvalContext.buyerBalance.available.toString() : null,
|
|
1783
|
-
isFirstSign: approvalContext?.isFirstSign ?? null,
|
|
1784
|
-
cooldownRemainingSecs: approvalContext?.cooldownRemainingSecs ?? null,
|
|
1785
|
-
};
|
|
1786
|
-
this.emit('payment:required', approvalInfo);
|
|
1787
|
-
try {
|
|
1788
|
-
await bpm.authorizeSpending(peer.peerId, requirements.sellerEvmAddr, pmux, amount);
|
|
1789
|
-
debugLog(`[Node] SpendingAuth sent to seller ${peer.peerId.slice(0, 12)}..., waiting for AuthAck...`);
|
|
1790
|
-
await this._waitForLockConfirmation(peer.peerId);
|
|
1791
|
-
debugLog(`[Node] AuthAck received from seller ${peer.peerId.slice(0, 12)}...`);
|
|
1792
|
-
this._buyerLockedPeers.add(peer.peerId);
|
|
1793
|
-
// Notify listeners what was signed
|
|
1794
|
-
this.emit('payment:signed', {
|
|
1795
|
-
peerId: peer.peerId,
|
|
1796
|
-
sellerEvmAddr: requirements.sellerEvmAddr,
|
|
1797
|
-
amount: amount.toString(),
|
|
1798
|
-
tokenRate: requirements.tokenRate,
|
|
1799
|
-
});
|
|
1800
|
-
}
|
|
1801
|
-
catch (err) {
|
|
1802
|
-
debugWarn(`[Node] Payment negotiation failed for ${peer.peerId.slice(0, 12)}...: ${err instanceof Error ? err.message : err}`);
|
|
1803
|
-
throw err;
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
/**
|
|
1807
|
-
* Poll until the lock for a seller is confirmed or rejected.
|
|
1808
|
-
* Polls every 200ms with a 30-second timeout.
|
|
1809
|
-
*/
|
|
1810
|
-
async _waitForLockConfirmation(sellerPeerId) {
|
|
1811
|
-
const bpm = this._buyerPaymentManager;
|
|
1812
|
-
if (!bpm)
|
|
1813
|
-
return;
|
|
1814
|
-
const pollIntervalMs = 200;
|
|
1815
|
-
const timeoutMs = 30_000;
|
|
1816
|
-
const deadline = Date.now() + timeoutMs;
|
|
1817
|
-
while (Date.now() < deadline) {
|
|
1818
|
-
if (bpm.isLockConfirmed(sellerPeerId)) {
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
if (bpm.isLockRejected(sellerPeerId)) {
|
|
1822
|
-
throw new Error(`Lock rejected by seller ${sellerPeerId.slice(0, 12)}...`);
|
|
1823
|
-
}
|
|
1824
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1825
|
-
}
|
|
1826
|
-
throw new Error(`Lock confirmation timed out for seller ${sellerPeerId.slice(0, 12)}... (${timeoutMs}ms)`);
|
|
1827
|
-
}
|
|
1828
|
-
/**
|
|
1829
|
-
* Clean up buyer payment sessions on shutdown.
|
|
1830
|
-
* Sessions are persisted in SessionStore and will be resumed on next connect.
|
|
1831
|
-
*/
|
|
1832
|
-
async _endAllBuyerSessions() {
|
|
1833
|
-
const bpm = this._buyerPaymentManager;
|
|
1834
|
-
if (!bpm)
|
|
1835
|
-
return;
|
|
1836
|
-
// Sessions persist in SQLite; no explicit end needed.
|
|
1837
|
-
// The buyer will reference them as previousSession on next connect.
|
|
1838
|
-
debugLog(`[Node] Buyer sessions persisted for next reconnection`);
|
|
1839
|
-
}
|
|
1840
857
|
_resolvePublicAddress(result) {
|
|
1841
858
|
const metadataPublicAddress = result.metadata.publicAddress?.trim();
|
|
1842
859
|
if (metadataPublicAddress && parsePublicAddress(metadataPublicAddress) !== null) {
|
|
@@ -1892,59 +909,13 @@ export class AntseedNode extends EventEmitter {
|
|
|
1892
909
|
defaultOutputUsdPerMillion: firstProvider?.defaultPricing.outputUsdPerMillion,
|
|
1893
910
|
maxConcurrency: firstProvider?.maxConcurrency,
|
|
1894
911
|
currentLoad: firstProvider?.currentLoad,
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
onChainSessionCount: result.metadata.onChainSessionCount,
|
|
1898
|
-
onChainDisputeCount: result.metadata.onChainDisputeCount,
|
|
1899
|
-
trustScore: result.metadata.onChainReputation,
|
|
912
|
+
onChainChannelCount: result.metadata.onChainChannelCount,
|
|
913
|
+
onChainGhostCount: result.metadata.onChainGhostCount,
|
|
1900
914
|
};
|
|
1901
915
|
}
|
|
1902
|
-
_adjustProviderLoad(providerName, delta) {
|
|
1903
|
-
const nextLoad = Math.max(0, (this._providerLoadCounts.get(providerName) ?? 0) + delta);
|
|
1904
|
-
this._providerLoadCounts.set(providerName, nextLoad);
|
|
1905
|
-
if (!this._announcer)
|
|
1906
|
-
return;
|
|
1907
|
-
this._announcer.updateLoad(providerName, nextLoad);
|
|
1908
|
-
this._scheduleMetadataRefresh();
|
|
1909
|
-
}
|
|
1910
|
-
_scheduleMetadataRefresh() {
|
|
1911
|
-
if (!this._announcer || this._metadataRefreshTimer) {
|
|
1912
|
-
return;
|
|
1913
|
-
}
|
|
1914
|
-
const timer = setTimeout(() => {
|
|
1915
|
-
this._metadataRefreshTimer = null;
|
|
1916
|
-
const announcer = this._announcer;
|
|
1917
|
-
if (!announcer)
|
|
1918
|
-
return;
|
|
1919
|
-
void announcer.refreshMetadata().catch((err) => {
|
|
1920
|
-
debugWarn(`[Node] Failed to refresh metadata snapshot: ${err instanceof Error ? err.message : err}`);
|
|
1921
|
-
});
|
|
1922
|
-
}, AntseedNode._METADATA_REFRESH_DEBOUNCE_MS);
|
|
1923
|
-
this._metadataRefreshTimer = timer;
|
|
1924
|
-
this._unrefTimer(timer);
|
|
1925
|
-
}
|
|
1926
|
-
_unrefTimer(timer) {
|
|
1927
|
-
if (typeof timer.unref === "function") {
|
|
1928
|
-
timer.unref();
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
916
|
}
|
|
1932
917
|
function parsePeerAddress(address) {
|
|
1933
918
|
const parts = address.split(":");
|
|
1934
919
|
return { host: parts[0], port: parseInt(parts[1] ?? "6882", 10) };
|
|
1935
920
|
}
|
|
1936
|
-
function concatChunks(chunks) {
|
|
1937
|
-
if (chunks.length === 0)
|
|
1938
|
-
return new Uint8Array(0);
|
|
1939
|
-
if (chunks.length === 1)
|
|
1940
|
-
return chunks[0];
|
|
1941
|
-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1942
|
-
const output = new Uint8Array(totalLength);
|
|
1943
|
-
let offset = 0;
|
|
1944
|
-
for (const chunk of chunks) {
|
|
1945
|
-
output.set(chunk, offset);
|
|
1946
|
-
offset += chunk.length;
|
|
1947
|
-
}
|
|
1948
|
-
return output;
|
|
1949
|
-
}
|
|
1950
921
|
//# sourceMappingURL=node.js.map
|