@antseed/node 0.2.128 → 0.2.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/index.d.ts +1 -1
- package/dist/billing/index.d.ts.map +1 -1
- package/dist/billing/index.js +1 -1
- package/dist/billing/index.js.map +1 -1
- package/dist/buyer-identity-context.d.ts +87 -0
- package/dist/buyer-identity-context.d.ts.map +1 -0
- package/dist/buyer-identity-context.js +226 -0
- package/dist/buyer-identity-context.js.map +1 -0
- package/dist/discovery/metadata-codec.d.ts.map +1 -1
- package/dist/discovery/metadata-codec.js +95 -4
- package/dist/discovery/metadata-codec.js.map +1 -1
- package/dist/discovery/metadata-validator.d.ts.map +1 -1
- package/dist/discovery/metadata-validator.js +6 -4
- package/dist/discovery/metadata-validator.js.map +1 -1
- package/dist/discovery/peer-metadata.d.ts +1 -1
- package/dist/discovery/peer-metadata.d.ts.map +1 -1
- package/dist/discovery/peer-metadata.js +1 -1
- package/dist/discovery/peer-metadata.js.map +1 -1
- package/dist/discovery/service-catalog.d.ts +6 -2
- package/dist/discovery/service-catalog.d.ts.map +1 -1
- package/dist/discovery/service-catalog.js +13 -8
- package/dist/discovery/service-catalog.js.map +1 -1
- package/dist/health/model-health-checker.d.ts +1 -1
- package/dist/health/model-health-checker.d.ts.map +1 -1
- package/dist/health/model-health-checker.js +4 -3
- package/dist/health/model-health-checker.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/seller-provider.d.ts +4 -2
- package/dist/interfaces/seller-provider.d.ts.map +1 -1
- package/dist/node.d.ts +45 -11
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +221 -132
- package/dist/node.js.map +1 -1
- package/dist/p2p/connection-manager.d.ts.map +1 -1
- package/dist/p2p/connection-manager.js +7 -1
- package/dist/p2p/connection-manager.js.map +1 -1
- package/dist/p2p/outbound-connection.d.ts +14 -0
- package/dist/p2p/outbound-connection.d.ts.map +1 -0
- package/dist/p2p/outbound-connection.js +88 -0
- package/dist/p2p/outbound-connection.js.map +1 -0
- package/dist/payments/channel-store.d.ts +4 -0
- package/dist/payments/channel-store.d.ts.map +1 -1
- package/dist/payments/channel-store.js +53 -6
- package/dist/payments/channel-store.js.map +1 -1
- package/dist/payments/seller-payment-manager.d.ts +60 -1
- package/dist/payments/seller-payment-manager.d.ts.map +1 -1
- package/dist/payments/seller-payment-manager.js +399 -10
- package/dist/payments/seller-payment-manager.js.map +1 -1
- package/dist/resources/resource-ownership-store.d.ts +30 -0
- package/dist/resources/resource-ownership-store.d.ts.map +1 -0
- package/dist/resources/resource-ownership-store.js +72 -0
- package/dist/resources/resource-ownership-store.js.map +1 -0
- package/dist/seller-request-handler.d.ts +34 -0
- package/dist/seller-request-handler.d.ts.map +1 -1
- package/dist/seller-request-handler.js +322 -56
- package/dist/seller-request-handler.js.map +1 -1
- package/dist/storage/migrations/channels/006_add_video_payments.d.ts +3 -0
- package/dist/storage/migrations/channels/006_add_video_payments.d.ts.map +1 -0
- package/dist/storage/migrations/channels/006_add_video_payments.js +24 -0
- package/dist/storage/migrations/channels/006_add_video_payments.js.map +1 -0
- package/dist/storage/migrations/channels/index.d.ts.map +1 -1
- package/dist/storage/migrations/channels/index.js +2 -1
- package/dist/storage/migrations/channels/index.js.map +1 -1
- package/dist/storage/migrations/metering/003_create_resource_tables.d.ts +3 -0
- package/dist/storage/migrations/metering/003_create_resource_tables.d.ts.map +1 -0
- package/dist/storage/migrations/metering/003_create_resource_tables.js +34 -0
- package/dist/storage/migrations/metering/003_create_resource_tables.js.map +1 -0
- package/dist/storage/migrations/metering/index.d.ts.map +1 -1
- package/dist/storage/migrations/metering/index.js +2 -1
- package/dist/storage/migrations/metering/index.js.map +1 -1
- package/dist/verification/samples.d.ts.map +1 -1
- package/dist/verification/samples.js +2 -0
- package/dist/verification/samples.js.map +1 -1
- package/package.json +4 -4
package/dist/node.js
CHANGED
|
@@ -7,6 +7,7 @@ import { TrustSignalsClient } from './payments/evm/trust-signals-client.js';
|
|
|
7
7
|
import { loadOrCreateIdentity } from "./p2p/identity.js";
|
|
8
8
|
import { peerIdToAddress } from "./types/peer.js";
|
|
9
9
|
import { MeteringStorage } from "./metering/storage.js";
|
|
10
|
+
import { ResourceOwnershipStore } from "./resources/resource-ownership-store.js";
|
|
10
11
|
import { ReceiptGenerator } from "./metering/receipt-generator.js";
|
|
11
12
|
import { SellerSessionTracker, } from "./metering/seller-session-tracker.js";
|
|
12
13
|
import { ConnectionState } from "./types/connection.js";
|
|
@@ -30,6 +31,7 @@ import { FrameDecoder, encodeFrame } from "./p2p/message-protocol.js";
|
|
|
30
31
|
import { KeepaliveManager, buildPongPayload } from "./p2p/keepalive.js";
|
|
31
32
|
import { MessageType } from "./types/protocol.js";
|
|
32
33
|
import { NatTraversal } from "./p2p/nat-traversal.js";
|
|
34
|
+
import { getOrOpenOutboundConnection } from "./p2p/outbound-connection.js";
|
|
33
35
|
import { signUtf8 } from "./p2p/identity.js";
|
|
34
36
|
import { BalanceManager, DepositsClient, ChannelsClient, FreeUsageClient, BuyerFreeUsageManager, SellerFreeUsageManager, SellerFreeTierLimiter, StakingClient, ChannelStore, CHANNEL_KIND, CHANNEL_ROLE, CHANNEL_STATUS, } from "./payments/index.js";
|
|
35
37
|
import { debugLog, debugWarn } from "./utils/debug.js";
|
|
@@ -46,6 +48,7 @@ import { SellerRequestHandler } from "./seller-request-handler.js";
|
|
|
46
48
|
import { BuyerRequestHandler, } from "./buyer-request-handler.js";
|
|
47
49
|
import { buildSybilContext, computeOnChainSybilRisk, } from "./reputation/sybil-risk.js";
|
|
48
50
|
import { buyerFault } from "./errors.js";
|
|
51
|
+
import { BuyerIdentityContext, DEFAULT_BUYER_IDENTITY, isValidBuyerIdentityName, } from "./buyer-identity-context.js";
|
|
49
52
|
/** Store the trust score on the peer; leaves the fields untouched when the peer is unscored. */
|
|
50
53
|
function applyTrust(peer) {
|
|
51
54
|
const trust = computeTrustScore(peer);
|
|
@@ -92,6 +95,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
92
95
|
_keepalives = new Map();
|
|
93
96
|
_nat = null;
|
|
94
97
|
_metering = null;
|
|
98
|
+
_resourceOwnership = null;
|
|
95
99
|
_receiptGenerator = null;
|
|
96
100
|
_balanceManager = null;
|
|
97
101
|
_depositsClient = null;
|
|
@@ -109,6 +113,8 @@ export class AntseedNode extends EventEmitter {
|
|
|
109
113
|
_verificationMuxes = new Map();
|
|
110
114
|
_sweepMuxes = new Map();
|
|
111
115
|
_peerCapabilities = new Map();
|
|
116
|
+
/** Buyer identities beyond the default one, by name. */
|
|
117
|
+
_buyerIdentities = new Map();
|
|
112
118
|
/** Seller-side deposit-sweep relayer (initialized when configured + enabled). */
|
|
113
119
|
_depositRelayer = null;
|
|
114
120
|
/** Seller-side request handler (provider matching, execution, load tracking). */
|
|
@@ -293,6 +299,11 @@ export class AntseedNode extends EventEmitter {
|
|
|
293
299
|
if (!this._started) {
|
|
294
300
|
return;
|
|
295
301
|
}
|
|
302
|
+
const extraIdentities = [...this._buyerIdentities.values()];
|
|
303
|
+
this._buyerIdentities.clear();
|
|
304
|
+
await Promise.all(extraIdentities.map((context) => context.stop().catch((err) => {
|
|
305
|
+
debugWarn(`[Node] Failed to stop buyer identity "${context.name}": ${err instanceof Error ? err.message : err}`);
|
|
306
|
+
})));
|
|
296
307
|
// Give in-transit NeedAuth messages time to arrive on the DataChannel,
|
|
297
308
|
// then wait for their handlers to finish. This ensures the seller has a
|
|
298
309
|
// valid SpendingAuth for settlement before we close the connection.
|
|
@@ -371,6 +382,15 @@ export class AntseedNode extends EventEmitter {
|
|
|
371
382
|
}
|
|
372
383
|
this._metering = null;
|
|
373
384
|
}
|
|
385
|
+
if (this._resourceOwnership) {
|
|
386
|
+
try {
|
|
387
|
+
this._resourceOwnership.close();
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
// ignore close errors
|
|
391
|
+
}
|
|
392
|
+
this._resourceOwnership = null;
|
|
393
|
+
}
|
|
374
394
|
if (this._verificationStorage) {
|
|
375
395
|
try {
|
|
376
396
|
this._verificationStorage.close();
|
|
@@ -798,7 +818,12 @@ export class AntseedNode extends EventEmitter {
|
|
|
798
818
|
* Eagerly open a connection to a peer and wire up the mux.
|
|
799
819
|
* Subsequent sendRequest / sendRequestStream calls will reuse this connection.
|
|
800
820
|
*/
|
|
801
|
-
async connectToPeer(peer) {
|
|
821
|
+
async connectToPeer(peer, buyerIdentity) {
|
|
822
|
+
const context = this._buyerContext(buyerIdentity);
|
|
823
|
+
if (context) {
|
|
824
|
+
await context.connect(peer);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
802
827
|
const conn = await this._getOrCreateConnection(peer);
|
|
803
828
|
this._getOrCreateMux(peer.peerId, conn);
|
|
804
829
|
const negotiator = this._buyerNegotiator;
|
|
@@ -821,7 +846,9 @@ export class AntseedNode extends EventEmitter {
|
|
|
821
846
|
* whichever cumulative is higher.
|
|
822
847
|
*/
|
|
823
848
|
async requestChannelClose(sellerPeerId, opts = {}) {
|
|
824
|
-
const
|
|
849
|
+
const { buyerIdentity, ...closeOptions } = opts;
|
|
850
|
+
const context = this._buyerContext(buyerIdentity);
|
|
851
|
+
const negotiator = context ? context.negotiator : this._buyerNegotiator;
|
|
825
852
|
if (!negotiator) {
|
|
826
853
|
throw new Error('Buyer payments are not configured on this node');
|
|
827
854
|
}
|
|
@@ -829,19 +856,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
829
856
|
throw new Error('Node not started');
|
|
830
857
|
}
|
|
831
858
|
const peerId = sellerPeerId;
|
|
832
|
-
|
|
833
|
-
if (!conn || (conn.state !== ConnectionState.Open && conn.state !== ConnectionState.Authenticated)) {
|
|
834
|
-
const peer = await this.findPeer(sellerPeerId);
|
|
835
|
-
if (!peer) {
|
|
836
|
-
throw new Error(`Seller ${sellerPeerId.slice(0, 12)}... is not connected and could not be found on the network. ` +
|
|
837
|
-
`A cooperative close needs the seller online — otherwise use the on-chain request-close flow.`);
|
|
838
|
-
}
|
|
839
|
-
await this.connectToPeer(peer);
|
|
840
|
-
conn = this._connectionManager.getConnection(peer.peerId);
|
|
841
|
-
if (!conn) {
|
|
842
|
-
throw new Error(`Failed to establish a connection to seller ${sellerPeerId.slice(0, 12)}...`);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
859
|
+
const conn = await this._sellerConnectionForClose(peerId, context);
|
|
845
860
|
// A seller that predates this feature drops the 0x59 frame silently, so
|
|
846
861
|
// without this the buyer would wait out the full 60s response timeout and
|
|
847
862
|
// get a vague "did not answer". Note the capability must come from the
|
|
@@ -856,15 +871,41 @@ export class AntseedNode extends EventEmitter {
|
|
|
856
871
|
`(missing ${CONNECTION_CAPABILITY_COOPERATIVE_CLOSE_V1}). ` +
|
|
857
872
|
`Use the on-chain request-close flow instead.`);
|
|
858
873
|
}
|
|
859
|
-
return negotiator.requestChannelClose(peerId, conn,
|
|
874
|
+
return negotiator.requestChannelClose(peerId, conn, closeOptions);
|
|
875
|
+
}
|
|
876
|
+
/** The paying identity's open connection to a seller, reconnecting when needed. */
|
|
877
|
+
async _sellerConnectionForClose(peerId, context) {
|
|
878
|
+
if (context) {
|
|
879
|
+
const live = context.liveConnection(peerId);
|
|
880
|
+
if (live)
|
|
881
|
+
return live;
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
const live = this._connectionManager.getConnection(peerId);
|
|
885
|
+
if (live && (live.state === ConnectionState.Open || live.state === ConnectionState.Authenticated))
|
|
886
|
+
return live;
|
|
887
|
+
}
|
|
888
|
+
const peer = await this.findPeer(peerId);
|
|
889
|
+
if (!peer) {
|
|
890
|
+
throw new Error(`Seller ${peerId.slice(0, 12)}... is not connected and could not be found on the network. ` +
|
|
891
|
+
`A cooperative close needs the seller online — otherwise use the on-chain request-close flow.`);
|
|
892
|
+
}
|
|
893
|
+
if (context)
|
|
894
|
+
return context.connect(peer);
|
|
895
|
+
await this.connectToPeer(peer);
|
|
896
|
+
const conn = this._connectionManager.getConnection(peer.peerId);
|
|
897
|
+
if (!conn) {
|
|
898
|
+
throw new Error(`Failed to establish a connection to seller ${peerId.slice(0, 12)}...`);
|
|
899
|
+
}
|
|
900
|
+
return conn;
|
|
860
901
|
}
|
|
861
902
|
/**
|
|
862
903
|
* Query session stats for a specific seller peer.
|
|
863
904
|
* Combines channel store data (authoritative payment/session info) with
|
|
864
905
|
* metering events when available.
|
|
865
906
|
*/
|
|
866
|
-
getMeteringStatsByPeer(sellerPeerId) {
|
|
867
|
-
const buyerAddress = this.
|
|
907
|
+
getMeteringStatsByPeer(sellerPeerId, buyerIdentity) {
|
|
908
|
+
const { address: buyerAddress, paymentManager } = this._buyerAccount(buyerIdentity);
|
|
868
909
|
const channel = (buyerAddress != null)
|
|
869
910
|
? (this._channelStore?.getActiveChannelByPeerAndBuyer(sellerPeerId, CHANNEL_ROLE.BUYER, buyerAddress)
|
|
870
911
|
?? this._channelStore?.getLatestChannelByPeerAndBuyer(sellerPeerId, CHANNEL_ROLE.BUYER, buyerAddress))
|
|
@@ -875,7 +916,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
875
916
|
? this._channelStore?.getTotalsByPeerAndBuyer(sellerPeerId, CHANNEL_ROLE.BUYER, buyerAddress)
|
|
876
917
|
: this._channelStore?.getTotalsByPeer(sellerPeerId, CHANNEL_ROLE.BUYER)
|
|
877
918
|
?? null;
|
|
878
|
-
const liveTotals =
|
|
919
|
+
const liveTotals = paymentManager?.getResponseTokenTotals(sellerPeerId);
|
|
879
920
|
const inputTokens = (liveTotals != null) ? liveTotals.input
|
|
880
921
|
: (channel != null) ? Number(channel.tokensDelivered || '0')
|
|
881
922
|
: 0;
|
|
@@ -887,7 +928,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
887
928
|
inputTokens,
|
|
888
929
|
outputTokens,
|
|
889
930
|
totalTokens: inputTokens + outputTokens,
|
|
890
|
-
reservedUsdc:
|
|
931
|
+
reservedUsdc: paymentManager?.getReserveCeiling(sellerPeerId)?.toString() ?? null,
|
|
891
932
|
consumedUsdc: channel?.authMax ?? null,
|
|
892
933
|
channelStatus: channel?.status ?? null,
|
|
893
934
|
reservedAt: channel?.reservedAt ?? null,
|
|
@@ -906,17 +947,20 @@ export class AntseedNode extends EventEmitter {
|
|
|
906
947
|
* Combines the persistent ChannelStore (session metadata + cumulative signed
|
|
907
948
|
* amount) with the in-memory reserve ceiling tracked by BuyerPaymentManager.
|
|
908
949
|
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
* SpendingAuth amount stored in authMax.
|
|
950
|
+
* One-off channels use their own persisted confirmed reserve, never the
|
|
951
|
+
* seller's session reserve. Unavailable reserves are null rather than the
|
|
952
|
+
* unrelated cumulative SpendingAuth amount stored in authMax.
|
|
912
953
|
*/
|
|
913
|
-
getActiveBuyerChannels() {
|
|
914
|
-
const buyerAddress = this.
|
|
954
|
+
getActiveBuyerChannels(buyerIdentity) {
|
|
955
|
+
const { address: buyerAddress, paymentManager } = this._buyerAccount(buyerIdentity);
|
|
915
956
|
if (!buyerAddress || !this._channelStore)
|
|
916
957
|
return [];
|
|
917
|
-
const stored = this._channelStore.
|
|
958
|
+
const stored = this._channelStore.getBuyerPaymentChannels(buyerAddress)
|
|
959
|
+
.filter((channel) => channel.status === CHANNEL_STATUS.ACTIVE);
|
|
918
960
|
return stored.map((c) => {
|
|
919
|
-
const liveReserve =
|
|
961
|
+
const liveReserve = c.channelKind === CHANNEL_KIND.ONE_OFF
|
|
962
|
+
? BigInt(c.confirmedReserveAmount ?? '0')
|
|
963
|
+
: paymentManager?.getReserveCeiling(c.peerId);
|
|
920
964
|
return {
|
|
921
965
|
channelId: c.sessionId,
|
|
922
966
|
peerId: c.peerId,
|
|
@@ -938,14 +982,16 @@ export class AntseedNode extends EventEmitter {
|
|
|
938
982
|
});
|
|
939
983
|
}
|
|
940
984
|
/** All buyer channels (any local status), used for history views. */
|
|
941
|
-
getAllBuyerChannels() {
|
|
942
|
-
const buyerAddress = this.
|
|
985
|
+
getAllBuyerChannels(buyerIdentity) {
|
|
986
|
+
const { address: buyerAddress, paymentManager } = this._buyerAccount(buyerIdentity);
|
|
943
987
|
if (!buyerAddress || !this._channelStore)
|
|
944
988
|
return [];
|
|
945
|
-
const stored = this._channelStore.
|
|
989
|
+
const stored = this._channelStore.getBuyerPaymentChannels(buyerAddress);
|
|
946
990
|
return stored.map((c) => {
|
|
947
991
|
const liveReserve = c.status === CHANNEL_STATUS.ACTIVE
|
|
948
|
-
?
|
|
992
|
+
? c.channelKind === CHANNEL_KIND.ONE_OFF
|
|
993
|
+
? BigInt(c.confirmedReserveAmount ?? '0')
|
|
994
|
+
: paymentManager?.getReserveCeiling(c.peerId)
|
|
949
995
|
: null;
|
|
950
996
|
return {
|
|
951
997
|
channelId: c.sessionId,
|
|
@@ -973,8 +1019,8 @@ export class AntseedNode extends EventEmitter {
|
|
|
973
1019
|
* columns are semantically overloaded vs seller rows. Set in
|
|
974
1020
|
* buyer-payment-manager.recordAndPersistTokens.
|
|
975
1021
|
*/
|
|
976
|
-
getBuyerUsageTotals() {
|
|
977
|
-
const buyerAddress = this.
|
|
1022
|
+
getBuyerUsageTotals(buyerIdentity) {
|
|
1023
|
+
const { address: buyerAddress } = this._buyerAccount(buyerIdentity);
|
|
978
1024
|
if (!buyerAddress || !this._channelStore)
|
|
979
1025
|
return EMPTY_BUYER_USAGE;
|
|
980
1026
|
// Paid channels plus free-usage sessions — both carry real traffic and
|
|
@@ -1027,6 +1073,9 @@ export class AntseedNode extends EventEmitter {
|
|
|
1027
1073
|
inputTokens: s.inputTokens,
|
|
1028
1074
|
cachedInputTokens: s.cachedInputTokens,
|
|
1029
1075
|
outputTokens: s.outputTokens,
|
|
1076
|
+
outputImages: s.outputImages,
|
|
1077
|
+
videoGenerations: s.videoGenerations,
|
|
1078
|
+
videoSeconds: s.videoSeconds,
|
|
1030
1079
|
requestCount: s.requestCount,
|
|
1031
1080
|
}));
|
|
1032
1081
|
return {
|
|
@@ -1041,14 +1090,135 @@ export class AntseedNode extends EventEmitter {
|
|
|
1041
1090
|
};
|
|
1042
1091
|
}
|
|
1043
1092
|
async sendRequest(peer, req, options) {
|
|
1044
|
-
|
|
1045
|
-
throw buyerFault("Node not started or not in buyer mode", "node-not-started");
|
|
1046
|
-
return this._buyerHandler.sendRequest(peer, req, undefined, options);
|
|
1093
|
+
return this._dispatchBuyerRequest(peer, req, undefined, options);
|
|
1047
1094
|
}
|
|
1048
1095
|
async sendRequestStream(peer, req, callbacks, options) {
|
|
1096
|
+
return this._dispatchBuyerRequest(peer, req, callbacks, options);
|
|
1097
|
+
}
|
|
1098
|
+
_dispatchBuyerRequest(peer, req, callbacks, options) {
|
|
1049
1099
|
if (!this._buyerHandler)
|
|
1050
1100
|
throw buyerFault("Node not started or not in buyer mode", "node-not-started");
|
|
1051
|
-
|
|
1101
|
+
const { buyerIdentity, ...executionOptions } = options ?? {};
|
|
1102
|
+
const context = this._buyerContext(buyerIdentity);
|
|
1103
|
+
return context
|
|
1104
|
+
? context.sendRequest(peer, req, callbacks, executionOptions)
|
|
1105
|
+
: this._buyerHandler.sendRequest(peer, req, callbacks, executionOptions);
|
|
1106
|
+
}
|
|
1107
|
+
/** Buyer payment settings shared by every identity; null when payments are off. */
|
|
1108
|
+
_buyerPaymentConfig(paymentsDir) {
|
|
1109
|
+
const payments = this._config.payments;
|
|
1110
|
+
if (!payments?.enabled || !payments.rpcUrl || !payments.depositsAddress || !payments.channelsAddress || !payments.usdcAddress) {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
rpcUrl: payments.rpcUrl,
|
|
1115
|
+
...(payments.fallbackRpcUrls ? { fallbackRpcUrls: payments.fallbackRpcUrls } : {}),
|
|
1116
|
+
depositsContractAddress: payments.depositsAddress,
|
|
1117
|
+
channelsContractAddress: payments.channelsAddress,
|
|
1118
|
+
usdcAddress: payments.usdcAddress,
|
|
1119
|
+
identityRegistryAddress: payments.identityRegistryAddress ?? '',
|
|
1120
|
+
chainId: payments.chainId ?? 8453,
|
|
1121
|
+
defaultAuthDurationSecs: payments.defaultAuthDurationSecs ?? 900, // 15 min — seller must call reserve() promptly
|
|
1122
|
+
maxPerRequestUsdc: BigInt(payments.maxPerRequestUsdc ?? "500000"), // $0.50 default — covers most LLM requests
|
|
1123
|
+
maxReserveAmountUsdc: BigInt(payments.maxReserveAmountUsdc ?? "1000000"), // $1.00 default per session (matches FIRST_SIGN_CAP)
|
|
1124
|
+
maxVideoRequestUsdc: BigInt(payments.maxVideoRequestUsdc ?? "5000000"), // $5.00 default per video
|
|
1125
|
+
disableMetadataV2Services: payments.disableMetadataV2Services ?? false,
|
|
1126
|
+
dataDir: paymentsDir,
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Let this buyer node also pay as another wallet. The identity reuses the
|
|
1131
|
+
* node's discovery, routing and chain clients; sellers see it as its own
|
|
1132
|
+
* buyer with its own connections, channels and deposits. Select it per
|
|
1133
|
+
* request with `buyerIdentity`.
|
|
1134
|
+
*/
|
|
1135
|
+
async addBuyerIdentity(name, identity) {
|
|
1136
|
+
if (!this._started || !this._buyerHandler || !this._identity) {
|
|
1137
|
+
throw new Error("Buyer identities can only be added to a started buyer node");
|
|
1138
|
+
}
|
|
1139
|
+
if (!isValidBuyerIdentityName(name) || name === DEFAULT_BUYER_IDENTITY) {
|
|
1140
|
+
throw new Error(`Invalid buyer identity name "${name}"`);
|
|
1141
|
+
}
|
|
1142
|
+
if (this._buyerIdentities.has(name)) {
|
|
1143
|
+
throw new Error(`Buyer identity "${name}" is already loaded`);
|
|
1144
|
+
}
|
|
1145
|
+
const address = identity.wallet.address.toLowerCase();
|
|
1146
|
+
if (this.buyerIdentities().some((entry) => entry.address.toLowerCase() === address)) {
|
|
1147
|
+
throw new Error(`Wallet ${identity.wallet.address} is already loaded as another buyer identity`);
|
|
1148
|
+
}
|
|
1149
|
+
const payments = this._config.payments;
|
|
1150
|
+
const dataDir = this._config.dataDir ?? join(homedir(), ".antseed");
|
|
1151
|
+
const context = await BuyerIdentityContext.create({
|
|
1152
|
+
name,
|
|
1153
|
+
identity,
|
|
1154
|
+
paymentConfig: this._buyerPaymentConfig(join(dataDir, "payments")),
|
|
1155
|
+
freeUsage: this._freeUsageClient && payments?.freeUsageAddress
|
|
1156
|
+
? {
|
|
1157
|
+
chainId: payments.chainId ?? 8453,
|
|
1158
|
+
freeUsageContractAddress: payments.freeUsageAddress,
|
|
1159
|
+
defaultAuthDurationSecs: payments.defaultAuthDurationSecs ?? 3600,
|
|
1160
|
+
disableMetadataV2Services: payments.disableMetadataV2Services ?? false,
|
|
1161
|
+
}
|
|
1162
|
+
: null,
|
|
1163
|
+
requestHandler: {
|
|
1164
|
+
requestTimeoutMs: this._config.requestTimeoutMs,
|
|
1165
|
+
maxStreamBufferBytes: this._config.maxStreamBufferBytes,
|
|
1166
|
+
maxStreamDurationMs: this._config.maxStreamDurationMs,
|
|
1167
|
+
},
|
|
1168
|
+
maxUploadBodyBytes: this._config.maxUploadBodyBytes,
|
|
1169
|
+
requireSecureTransport: this._config.requireSecureTransport,
|
|
1170
|
+
shared: {
|
|
1171
|
+
channelStore: this._channelStore,
|
|
1172
|
+
depositsClient: this._depositsClient,
|
|
1173
|
+
channelsClient: this._channelsClient,
|
|
1174
|
+
sellerAddressResolver: this._sellerAddressResolver,
|
|
1175
|
+
verificationStorage: this._verificationStorage,
|
|
1176
|
+
verificationSampler: this._verificationSampler,
|
|
1177
|
+
peerCapabilities: this._peerCapabilities,
|
|
1178
|
+
isChainReachable: () => this._rpcHealth?.reachable ?? true,
|
|
1179
|
+
onChainReadFailure: () => this._rpcHealth?.reportFailure(),
|
|
1180
|
+
},
|
|
1181
|
+
emit: (event, payload) => { this.emit(event, payload); },
|
|
1182
|
+
});
|
|
1183
|
+
this._buyerIdentities.set(name, context);
|
|
1184
|
+
debugLog(`[Node] Buyer identity "${name}" loaded (wallet=${identity.wallet.address.slice(0, 10)}...)`);
|
|
1185
|
+
return { name, address: identity.wallet.address };
|
|
1186
|
+
}
|
|
1187
|
+
/** Close an identity's connections, letting sellers settle what they already served. */
|
|
1188
|
+
async removeBuyerIdentity(name) {
|
|
1189
|
+
const context = this._buyerIdentities.get(name);
|
|
1190
|
+
if (!context)
|
|
1191
|
+
return;
|
|
1192
|
+
this._buyerIdentities.delete(name);
|
|
1193
|
+
await context.stop();
|
|
1194
|
+
}
|
|
1195
|
+
hasBuyerIdentity(name) {
|
|
1196
|
+
return name === DEFAULT_BUYER_IDENTITY ? this._identity !== null : this._buyerIdentities.has(name);
|
|
1197
|
+
}
|
|
1198
|
+
/** Every identity this node can pay as, the default one first. */
|
|
1199
|
+
buyerIdentities() {
|
|
1200
|
+
const identities = [];
|
|
1201
|
+
if (this._identity)
|
|
1202
|
+
identities.push({ name: DEFAULT_BUYER_IDENTITY, address: this._identity.wallet.address });
|
|
1203
|
+
for (const context of this._buyerIdentities.values()) {
|
|
1204
|
+
identities.push({ name: context.name, address: context.address });
|
|
1205
|
+
}
|
|
1206
|
+
return identities;
|
|
1207
|
+
}
|
|
1208
|
+
_buyerContext(name) {
|
|
1209
|
+
if (name === undefined || name === DEFAULT_BUYER_IDENTITY)
|
|
1210
|
+
return null;
|
|
1211
|
+
const context = this._buyerIdentities.get(name);
|
|
1212
|
+
if (!context)
|
|
1213
|
+
throw buyerFault(`Unknown buyer identity "${name}"`, "invalid-request");
|
|
1214
|
+
return context;
|
|
1215
|
+
}
|
|
1216
|
+
/** Wallet address and payment manager behind an identity name. */
|
|
1217
|
+
_buyerAccount(name) {
|
|
1218
|
+
const context = this._buyerContext(name);
|
|
1219
|
+
if (context)
|
|
1220
|
+
return { address: context.address, paymentManager: context.paymentManager };
|
|
1221
|
+
return { address: this._identity?.wallet.address ?? null, paymentManager: this._buyerPaymentManager };
|
|
1052
1222
|
}
|
|
1053
1223
|
_createDHTConfig(port, bootstrapNodes) {
|
|
1054
1224
|
return {
|
|
@@ -1135,7 +1305,10 @@ export class AntseedNode extends EventEmitter {
|
|
|
1135
1305
|
this._muxes.get(peerId)?.abortPendingUploads();
|
|
1136
1306
|
this._muxes.delete(peerId);
|
|
1137
1307
|
this._paymentMuxes.delete(peerId);
|
|
1138
|
-
this.
|
|
1308
|
+
// Other buyer identities share this map; keep it while one is still connected.
|
|
1309
|
+
if (![...this._buyerIdentities.values()].some((context) => context.liveConnection(peerId))) {
|
|
1310
|
+
this._peerCapabilities.delete(peerId);
|
|
1311
|
+
}
|
|
1139
1312
|
this._verificationMuxes.get(peerId)?.close();
|
|
1140
1313
|
this._verificationMuxes.delete(peerId);
|
|
1141
1314
|
this._sweepMuxes.delete(peerId);
|
|
@@ -1191,6 +1364,12 @@ export class AntseedNode extends EventEmitter {
|
|
|
1191
1364
|
catch (err) {
|
|
1192
1365
|
debugWarn(`[Node] Metering storage unavailable: ${err instanceof Error ? err.message : err}`);
|
|
1193
1366
|
}
|
|
1367
|
+
try {
|
|
1368
|
+
this._resourceOwnership = new ResourceOwnershipStore(join(dataDir, "metering.db"));
|
|
1369
|
+
}
|
|
1370
|
+
catch (err) {
|
|
1371
|
+
debugWarn(`[Node] Resource ownership storage unavailable; video services will be refused: ${err instanceof Error ? err.message : err}`);
|
|
1372
|
+
}
|
|
1194
1373
|
if (this._metering) {
|
|
1195
1374
|
this._receiptGenerator = new ReceiptGenerator({
|
|
1196
1375
|
peerId: identity.peerId,
|
|
@@ -1326,6 +1505,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
1326
1505
|
channelsClient: this._channelsClient,
|
|
1327
1506
|
announcer: this._announcer,
|
|
1328
1507
|
maxUploadBodyBytes: this._config.maxUploadBodyBytes,
|
|
1508
|
+
resourceOwnershipStore: this._resourceOwnership,
|
|
1329
1509
|
...(this._config.payments?.reserveEstimateOverdraftUsdc != null
|
|
1330
1510
|
? { reserveEstimateOverdraftUsdc: BigInt(this._config.payments.reserveEstimateOverdraftUsdc) }
|
|
1331
1511
|
: {}),
|
|
@@ -1386,24 +1566,11 @@ export class AntseedNode extends EventEmitter {
|
|
|
1386
1566
|
}
|
|
1387
1567
|
}
|
|
1388
1568
|
if (this._channelStore) {
|
|
1389
|
-
const buyerPaymentConfig =
|
|
1390
|
-
rpcUrl: payments.rpcUrl,
|
|
1391
|
-
...(payments.fallbackRpcUrls ? { fallbackRpcUrls: payments.fallbackRpcUrls } : {}),
|
|
1392
|
-
depositsContractAddress: payments.depositsAddress,
|
|
1393
|
-
channelsContractAddress: payments.channelsAddress,
|
|
1394
|
-
usdcAddress: payments.usdcAddress,
|
|
1395
|
-
identityRegistryAddress: payments.identityRegistryAddress ?? '',
|
|
1396
|
-
chainId: payments.chainId ?? 8453,
|
|
1397
|
-
defaultAuthDurationSecs: payments.defaultAuthDurationSecs ?? 900, // 15 min — seller must call reserve() promptly
|
|
1398
|
-
maxPerRequestUsdc: BigInt(payments.maxPerRequestUsdc ?? "500000"), // $0.50 default — covers most LLM requests
|
|
1399
|
-
maxReserveAmountUsdc: BigInt(payments.maxReserveAmountUsdc ?? "1000000"), // $1.00 default per session (matches FIRST_SIGN_CAP)
|
|
1400
|
-
disableMetadataV2Services: payments.disableMetadataV2Services ?? false,
|
|
1401
|
-
dataDir: paymentsDir,
|
|
1402
|
-
};
|
|
1569
|
+
const buyerPaymentConfig = this._buyerPaymentConfig(paymentsDir);
|
|
1403
1570
|
this._buyerPaymentManager = new BuyerPaymentManager(identity, buyerPaymentConfig, this._channelStore, this._sellerAddressResolver ?? undefined);
|
|
1404
1571
|
// Re-emit per-request spend so callers can attribute it to whatever
|
|
1405
1572
|
// issued the request — the node only knows the seller and requestId.
|
|
1406
|
-
this._buyerPaymentManager.setSpendListener((event) => this.emit('payment:spend', event));
|
|
1573
|
+
this._buyerPaymentManager.setSpendListener((event) => this.emit('payment:spend', { ...event, buyerIdentity: DEFAULT_BUYER_IDENTITY }));
|
|
1407
1574
|
debugLog(`[Node] Buyer payment manager initialized (wallet=${identity.wallet.address.slice(0, 10)}... chainId=${buyerPaymentConfig.chainId} deposits=${buyerPaymentConfig.depositsContractAddress.slice(0, 10)}...)`);
|
|
1408
1575
|
// Create negotiator that wraps the BPM with 402 handling and per-request auth
|
|
1409
1576
|
this._buyerNegotiator = new BuyerPaymentNegotiator(identity, this._buyerPaymentManager, this._depositsClient, this._channelsClient, this._channelStore, {
|
|
@@ -1765,81 +1932,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
1765
1932
|
if (!this._connectionManager || !this._identity) {
|
|
1766
1933
|
throw buyerFault("Node not started", "node-not-started");
|
|
1767
1934
|
}
|
|
1768
|
-
|
|
1769
|
-
const peerCapabilities = new Set(peer.capabilities ?? peer.metadata?.capabilities ?? []);
|
|
1770
|
-
this._peerCapabilities.set(peer.peerId, peerCapabilities);
|
|
1771
|
-
let endpointChanged = false;
|
|
1772
|
-
// Check if the peer's endpoint has changed (e.g. IP rotation).
|
|
1773
|
-
// Only applies to outbound connections where we registered the endpoint;
|
|
1774
|
-
// inbound connections (peer connected to us) have no registered endpoint
|
|
1775
|
-
// and are not subject to pinned-peer routing.
|
|
1776
|
-
if (existing && peer.publicAddress) {
|
|
1777
|
-
const currentEndpoint = ConnectionManager.resolvePeerEndpoint(peer.peerId);
|
|
1778
|
-
const { host: newHost, port: newPort } = parsePeerAddress(peer.publicAddress);
|
|
1779
|
-
if (currentEndpoint && (currentEndpoint.host !== newHost || currentEndpoint.port !== newPort)) {
|
|
1780
|
-
debugLog(`[Node] Peer ${peer.peerId.slice(0, 12)}... endpoint changed from ${currentEndpoint.host}:${currentEndpoint.port} to ${newHost}:${newPort}, reconnecting`);
|
|
1781
|
-
existing.close();
|
|
1782
|
-
this._peerCapabilities.set(peer.peerId, peerCapabilities);
|
|
1783
|
-
endpointChanged = true;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
if (existing && !endpointChanged &&
|
|
1787
|
-
existing.state !== ConnectionState.Closed &&
|
|
1788
|
-
existing.state !== ConnectionState.Failed) {
|
|
1789
|
-
debugLog(`[Node] Reusing existing connection to ${peer.peerId.slice(0, 12)}... (state=${existing.state})`);
|
|
1790
|
-
// If still connecting, wait for it to reach Open or Authenticated
|
|
1791
|
-
if (existing.state === ConnectionState.Connecting) {
|
|
1792
|
-
debugLog(`[Node] Waiting for connection to open...`);
|
|
1793
|
-
await new Promise((resolve, reject) => {
|
|
1794
|
-
const onState = (state) => {
|
|
1795
|
-
if (state === ConnectionState.Open || state === ConnectionState.Authenticated) {
|
|
1796
|
-
existing.off("stateChange", onState);
|
|
1797
|
-
resolve();
|
|
1798
|
-
}
|
|
1799
|
-
else if (state === ConnectionState.Failed || state === ConnectionState.Closed) {
|
|
1800
|
-
existing.off("stateChange", onState);
|
|
1801
|
-
reject(new Error(`Connection to ${peer.peerId} failed`));
|
|
1802
|
-
}
|
|
1803
|
-
};
|
|
1804
|
-
existing.on("stateChange", onState);
|
|
1805
|
-
});
|
|
1806
|
-
}
|
|
1807
|
-
return existing;
|
|
1808
|
-
}
|
|
1809
|
-
// Register the peer endpoint so ConnectionManager can resolve it
|
|
1810
|
-
if (peer.publicAddress) {
|
|
1811
|
-
const { host, port } = parsePeerAddress(peer.publicAddress);
|
|
1812
|
-
this._connectionManager.registerPeerEndpoint(peer.peerId, { host, port });
|
|
1813
|
-
debugLog(`[Node] Connecting to ${peer.peerId.slice(0, 12)}... at ${host}:${port}`);
|
|
1814
|
-
}
|
|
1815
|
-
else {
|
|
1816
|
-
debugWarn(`[Node] Peer ${peer.peerId.slice(0, 12)}... has no public address`);
|
|
1817
|
-
}
|
|
1818
|
-
const connConfig = {
|
|
1819
|
-
remotePeerId: peer.peerId,
|
|
1820
|
-
isInitiator: true,
|
|
1821
|
-
remoteCapabilities: [...peerCapabilities],
|
|
1822
|
-
};
|
|
1823
|
-
const conn = this._connectionManager.createConnection(connConfig);
|
|
1824
|
-
// Wait for connection to open
|
|
1825
|
-
await new Promise((resolve, reject) => {
|
|
1826
|
-
const onState = (state) => {
|
|
1827
|
-
debugLog(`[Node] Connection state: ${state}`);
|
|
1828
|
-
if (state === ConnectionState.Open || state === ConnectionState.Authenticated) {
|
|
1829
|
-
conn.off("stateChange", onState);
|
|
1830
|
-
resolve();
|
|
1831
|
-
}
|
|
1832
|
-
else if (state === ConnectionState.Failed || state === ConnectionState.Closed) {
|
|
1833
|
-
conn.off("stateChange", onState);
|
|
1834
|
-
reject(new Error(`Connection to ${peer.peerId} failed`));
|
|
1835
|
-
}
|
|
1836
|
-
};
|
|
1837
|
-
conn.on("stateChange", onState);
|
|
1838
|
-
});
|
|
1839
|
-
debugLog(`[Node] Connected to ${peer.peerId.slice(0, 12)}... via ${conn.transportDescription}`);
|
|
1840
|
-
this._peerCapabilities.set(peer.peerId, peerCapabilities);
|
|
1841
|
-
this._wireConnection(conn, peer.peerId);
|
|
1842
|
-
return conn;
|
|
1935
|
+
return getOrOpenOutboundConnection(this._connectionManager, peer, this._peerCapabilities, (conn) => this._wireConnection(conn, peer.peerId));
|
|
1843
1936
|
}
|
|
1844
1937
|
_getOrCreateMux(peerId, conn) {
|
|
1845
1938
|
const existing = this._muxes.get(peerId);
|
|
@@ -2135,10 +2228,6 @@ export class AntseedNode extends EventEmitter {
|
|
|
2135
2228
|
&& cachedPrice === 0;
|
|
2136
2229
|
}
|
|
2137
2230
|
}
|
|
2138
|
-
function parsePeerAddress(address) {
|
|
2139
|
-
const parts = address.split(":");
|
|
2140
|
-
return { host: parts[0], port: parseInt(parts[1] ?? "6882", 10) };
|
|
2141
|
-
}
|
|
2142
2231
|
/**
|
|
2143
2232
|
* Predicate used by `discoverPeers(service)` to filter the enumerated peer
|
|
2144
2233
|
* list against an optional service hint. Matches case-insensitively against
|