@antseed/node 0.2.127 → 0.2.129
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/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/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +38 -7
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +189 -127
- 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/seller-free-usage-manager.d.ts.map +1 -1
- package/dist/payments/seller-free-usage-manager.js +8 -0
- package/dist/payments/seller-free-usage-manager.js.map +1 -1
- package/package.json +3 -3
package/dist/node.js
CHANGED
|
@@ -30,6 +30,7 @@ import { FrameDecoder, encodeFrame } from "./p2p/message-protocol.js";
|
|
|
30
30
|
import { KeepaliveManager, buildPongPayload } from "./p2p/keepalive.js";
|
|
31
31
|
import { MessageType } from "./types/protocol.js";
|
|
32
32
|
import { NatTraversal } from "./p2p/nat-traversal.js";
|
|
33
|
+
import { getOrOpenOutboundConnection } from "./p2p/outbound-connection.js";
|
|
33
34
|
import { signUtf8 } from "./p2p/identity.js";
|
|
34
35
|
import { BalanceManager, DepositsClient, ChannelsClient, FreeUsageClient, BuyerFreeUsageManager, SellerFreeUsageManager, SellerFreeTierLimiter, StakingClient, ChannelStore, CHANNEL_KIND, CHANNEL_ROLE, CHANNEL_STATUS, } from "./payments/index.js";
|
|
35
36
|
import { debugLog, debugWarn } from "./utils/debug.js";
|
|
@@ -46,6 +47,7 @@ import { SellerRequestHandler } from "./seller-request-handler.js";
|
|
|
46
47
|
import { BuyerRequestHandler, } from "./buyer-request-handler.js";
|
|
47
48
|
import { buildSybilContext, computeOnChainSybilRisk, } from "./reputation/sybil-risk.js";
|
|
48
49
|
import { buyerFault } from "./errors.js";
|
|
50
|
+
import { BuyerIdentityContext, DEFAULT_BUYER_IDENTITY, isValidBuyerIdentityName, } from "./buyer-identity-context.js";
|
|
49
51
|
/** Store the trust score on the peer; leaves the fields untouched when the peer is unscored. */
|
|
50
52
|
function applyTrust(peer) {
|
|
51
53
|
const trust = computeTrustScore(peer);
|
|
@@ -109,6 +111,8 @@ export class AntseedNode extends EventEmitter {
|
|
|
109
111
|
_verificationMuxes = new Map();
|
|
110
112
|
_sweepMuxes = new Map();
|
|
111
113
|
_peerCapabilities = new Map();
|
|
114
|
+
/** Buyer identities beyond the default one, by name. */
|
|
115
|
+
_buyerIdentities = new Map();
|
|
112
116
|
/** Seller-side deposit-sweep relayer (initialized when configured + enabled). */
|
|
113
117
|
_depositRelayer = null;
|
|
114
118
|
/** Seller-side request handler (provider matching, execution, load tracking). */
|
|
@@ -293,6 +297,11 @@ export class AntseedNode extends EventEmitter {
|
|
|
293
297
|
if (!this._started) {
|
|
294
298
|
return;
|
|
295
299
|
}
|
|
300
|
+
const extraIdentities = [...this._buyerIdentities.values()];
|
|
301
|
+
this._buyerIdentities.clear();
|
|
302
|
+
await Promise.all(extraIdentities.map((context) => context.stop().catch((err) => {
|
|
303
|
+
debugWarn(`[Node] Failed to stop buyer identity "${context.name}": ${err instanceof Error ? err.message : err}`);
|
|
304
|
+
})));
|
|
296
305
|
// Give in-transit NeedAuth messages time to arrive on the DataChannel,
|
|
297
306
|
// then wait for their handlers to finish. This ensures the seller has a
|
|
298
307
|
// valid SpendingAuth for settlement before we close the connection.
|
|
@@ -798,7 +807,12 @@ export class AntseedNode extends EventEmitter {
|
|
|
798
807
|
* Eagerly open a connection to a peer and wire up the mux.
|
|
799
808
|
* Subsequent sendRequest / sendRequestStream calls will reuse this connection.
|
|
800
809
|
*/
|
|
801
|
-
async connectToPeer(peer) {
|
|
810
|
+
async connectToPeer(peer, buyerIdentity) {
|
|
811
|
+
const context = this._buyerContext(buyerIdentity);
|
|
812
|
+
if (context) {
|
|
813
|
+
await context.connect(peer);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
802
816
|
const conn = await this._getOrCreateConnection(peer);
|
|
803
817
|
this._getOrCreateMux(peer.peerId, conn);
|
|
804
818
|
const negotiator = this._buyerNegotiator;
|
|
@@ -821,7 +835,9 @@ export class AntseedNode extends EventEmitter {
|
|
|
821
835
|
* whichever cumulative is higher.
|
|
822
836
|
*/
|
|
823
837
|
async requestChannelClose(sellerPeerId, opts = {}) {
|
|
824
|
-
const
|
|
838
|
+
const { buyerIdentity, ...closeOptions } = opts;
|
|
839
|
+
const context = this._buyerContext(buyerIdentity);
|
|
840
|
+
const negotiator = context ? context.negotiator : this._buyerNegotiator;
|
|
825
841
|
if (!negotiator) {
|
|
826
842
|
throw new Error('Buyer payments are not configured on this node');
|
|
827
843
|
}
|
|
@@ -829,19 +845,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
829
845
|
throw new Error('Node not started');
|
|
830
846
|
}
|
|
831
847
|
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
|
-
}
|
|
848
|
+
const conn = await this._sellerConnectionForClose(peerId, context);
|
|
845
849
|
// A seller that predates this feature drops the 0x59 frame silently, so
|
|
846
850
|
// without this the buyer would wait out the full 60s response timeout and
|
|
847
851
|
// get a vague "did not answer". Note the capability must come from the
|
|
@@ -856,15 +860,41 @@ export class AntseedNode extends EventEmitter {
|
|
|
856
860
|
`(missing ${CONNECTION_CAPABILITY_COOPERATIVE_CLOSE_V1}). ` +
|
|
857
861
|
`Use the on-chain request-close flow instead.`);
|
|
858
862
|
}
|
|
859
|
-
return negotiator.requestChannelClose(peerId, conn,
|
|
863
|
+
return negotiator.requestChannelClose(peerId, conn, closeOptions);
|
|
864
|
+
}
|
|
865
|
+
/** The paying identity's open connection to a seller, reconnecting when needed. */
|
|
866
|
+
async _sellerConnectionForClose(peerId, context) {
|
|
867
|
+
if (context) {
|
|
868
|
+
const live = context.liveConnection(peerId);
|
|
869
|
+
if (live)
|
|
870
|
+
return live;
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
const live = this._connectionManager.getConnection(peerId);
|
|
874
|
+
if (live && (live.state === ConnectionState.Open || live.state === ConnectionState.Authenticated))
|
|
875
|
+
return live;
|
|
876
|
+
}
|
|
877
|
+
const peer = await this.findPeer(peerId);
|
|
878
|
+
if (!peer) {
|
|
879
|
+
throw new Error(`Seller ${peerId.slice(0, 12)}... is not connected and could not be found on the network. ` +
|
|
880
|
+
`A cooperative close needs the seller online — otherwise use the on-chain request-close flow.`);
|
|
881
|
+
}
|
|
882
|
+
if (context)
|
|
883
|
+
return context.connect(peer);
|
|
884
|
+
await this.connectToPeer(peer);
|
|
885
|
+
const conn = this._connectionManager.getConnection(peer.peerId);
|
|
886
|
+
if (!conn) {
|
|
887
|
+
throw new Error(`Failed to establish a connection to seller ${peerId.slice(0, 12)}...`);
|
|
888
|
+
}
|
|
889
|
+
return conn;
|
|
860
890
|
}
|
|
861
891
|
/**
|
|
862
892
|
* Query session stats for a specific seller peer.
|
|
863
893
|
* Combines channel store data (authoritative payment/session info) with
|
|
864
894
|
* metering events when available.
|
|
865
895
|
*/
|
|
866
|
-
getMeteringStatsByPeer(sellerPeerId) {
|
|
867
|
-
const buyerAddress = this.
|
|
896
|
+
getMeteringStatsByPeer(sellerPeerId, buyerIdentity) {
|
|
897
|
+
const { address: buyerAddress, paymentManager } = this._buyerAccount(buyerIdentity);
|
|
868
898
|
const channel = (buyerAddress != null)
|
|
869
899
|
? (this._channelStore?.getActiveChannelByPeerAndBuyer(sellerPeerId, CHANNEL_ROLE.BUYER, buyerAddress)
|
|
870
900
|
?? this._channelStore?.getLatestChannelByPeerAndBuyer(sellerPeerId, CHANNEL_ROLE.BUYER, buyerAddress))
|
|
@@ -875,7 +905,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
875
905
|
? this._channelStore?.getTotalsByPeerAndBuyer(sellerPeerId, CHANNEL_ROLE.BUYER, buyerAddress)
|
|
876
906
|
: this._channelStore?.getTotalsByPeer(sellerPeerId, CHANNEL_ROLE.BUYER)
|
|
877
907
|
?? null;
|
|
878
|
-
const liveTotals =
|
|
908
|
+
const liveTotals = paymentManager?.getResponseTokenTotals(sellerPeerId);
|
|
879
909
|
const inputTokens = (liveTotals != null) ? liveTotals.input
|
|
880
910
|
: (channel != null) ? Number(channel.tokensDelivered || '0')
|
|
881
911
|
: 0;
|
|
@@ -887,7 +917,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
887
917
|
inputTokens,
|
|
888
918
|
outputTokens,
|
|
889
919
|
totalTokens: inputTokens + outputTokens,
|
|
890
|
-
reservedUsdc:
|
|
920
|
+
reservedUsdc: paymentManager?.getReserveCeiling(sellerPeerId)?.toString() ?? null,
|
|
891
921
|
consumedUsdc: channel?.authMax ?? null,
|
|
892
922
|
channelStatus: channel?.status ?? null,
|
|
893
923
|
reservedAt: channel?.reservedAt ?? null,
|
|
@@ -910,13 +940,13 @@ export class AntseedNode extends EventEmitter {
|
|
|
910
940
|
* available, return null rather than substituting the unrelated cumulative
|
|
911
941
|
* SpendingAuth amount stored in authMax.
|
|
912
942
|
*/
|
|
913
|
-
getActiveBuyerChannels() {
|
|
914
|
-
const buyerAddress = this.
|
|
943
|
+
getActiveBuyerChannels(buyerIdentity) {
|
|
944
|
+
const { address: buyerAddress, paymentManager } = this._buyerAccount(buyerIdentity);
|
|
915
945
|
if (!buyerAddress || !this._channelStore)
|
|
916
946
|
return [];
|
|
917
947
|
const stored = this._channelStore.getActiveChannelsByBuyer(CHANNEL_ROLE.BUYER, buyerAddress);
|
|
918
948
|
return stored.map((c) => {
|
|
919
|
-
const liveReserve =
|
|
949
|
+
const liveReserve = paymentManager?.getReserveCeiling(c.peerId);
|
|
920
950
|
return {
|
|
921
951
|
channelId: c.sessionId,
|
|
922
952
|
peerId: c.peerId,
|
|
@@ -938,14 +968,14 @@ export class AntseedNode extends EventEmitter {
|
|
|
938
968
|
});
|
|
939
969
|
}
|
|
940
970
|
/** All buyer channels (any local status), used for history views. */
|
|
941
|
-
getAllBuyerChannels() {
|
|
942
|
-
const buyerAddress = this.
|
|
971
|
+
getAllBuyerChannels(buyerIdentity) {
|
|
972
|
+
const { address: buyerAddress, paymentManager } = this._buyerAccount(buyerIdentity);
|
|
943
973
|
if (!buyerAddress || !this._channelStore)
|
|
944
974
|
return [];
|
|
945
975
|
const stored = this._channelStore.getAllChannelsByBuyer('buyer', buyerAddress);
|
|
946
976
|
return stored.map((c) => {
|
|
947
977
|
const liveReserve = c.status === CHANNEL_STATUS.ACTIVE
|
|
948
|
-
?
|
|
978
|
+
? paymentManager?.getReserveCeiling(c.peerId)
|
|
949
979
|
: null;
|
|
950
980
|
return {
|
|
951
981
|
channelId: c.sessionId,
|
|
@@ -973,8 +1003,8 @@ export class AntseedNode extends EventEmitter {
|
|
|
973
1003
|
* columns are semantically overloaded vs seller rows. Set in
|
|
974
1004
|
* buyer-payment-manager.recordAndPersistTokens.
|
|
975
1005
|
*/
|
|
976
|
-
getBuyerUsageTotals() {
|
|
977
|
-
const buyerAddress = this.
|
|
1006
|
+
getBuyerUsageTotals(buyerIdentity) {
|
|
1007
|
+
const { address: buyerAddress } = this._buyerAccount(buyerIdentity);
|
|
978
1008
|
if (!buyerAddress || !this._channelStore)
|
|
979
1009
|
return EMPTY_BUYER_USAGE;
|
|
980
1010
|
// Paid channels plus free-usage sessions — both carry real traffic and
|
|
@@ -1041,14 +1071,134 @@ export class AntseedNode extends EventEmitter {
|
|
|
1041
1071
|
};
|
|
1042
1072
|
}
|
|
1043
1073
|
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);
|
|
1074
|
+
return this._dispatchBuyerRequest(peer, req, undefined, options);
|
|
1047
1075
|
}
|
|
1048
1076
|
async sendRequestStream(peer, req, callbacks, options) {
|
|
1077
|
+
return this._dispatchBuyerRequest(peer, req, callbacks, options);
|
|
1078
|
+
}
|
|
1079
|
+
_dispatchBuyerRequest(peer, req, callbacks, options) {
|
|
1049
1080
|
if (!this._buyerHandler)
|
|
1050
1081
|
throw buyerFault("Node not started or not in buyer mode", "node-not-started");
|
|
1051
|
-
|
|
1082
|
+
const { buyerIdentity, ...executionOptions } = options ?? {};
|
|
1083
|
+
const context = this._buyerContext(buyerIdentity);
|
|
1084
|
+
return context
|
|
1085
|
+
? context.sendRequest(peer, req, callbacks, executionOptions)
|
|
1086
|
+
: this._buyerHandler.sendRequest(peer, req, callbacks, executionOptions);
|
|
1087
|
+
}
|
|
1088
|
+
/** Buyer payment settings shared by every identity; null when payments are off. */
|
|
1089
|
+
_buyerPaymentConfig(paymentsDir) {
|
|
1090
|
+
const payments = this._config.payments;
|
|
1091
|
+
if (!payments?.enabled || !payments.rpcUrl || !payments.depositsAddress || !payments.channelsAddress || !payments.usdcAddress) {
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
return {
|
|
1095
|
+
rpcUrl: payments.rpcUrl,
|
|
1096
|
+
...(payments.fallbackRpcUrls ? { fallbackRpcUrls: payments.fallbackRpcUrls } : {}),
|
|
1097
|
+
depositsContractAddress: payments.depositsAddress,
|
|
1098
|
+
channelsContractAddress: payments.channelsAddress,
|
|
1099
|
+
usdcAddress: payments.usdcAddress,
|
|
1100
|
+
identityRegistryAddress: payments.identityRegistryAddress ?? '',
|
|
1101
|
+
chainId: payments.chainId ?? 8453,
|
|
1102
|
+
defaultAuthDurationSecs: payments.defaultAuthDurationSecs ?? 900, // 15 min — seller must call reserve() promptly
|
|
1103
|
+
maxPerRequestUsdc: BigInt(payments.maxPerRequestUsdc ?? "500000"), // $0.50 default — covers most LLM requests
|
|
1104
|
+
maxReserveAmountUsdc: BigInt(payments.maxReserveAmountUsdc ?? "1000000"), // $1.00 default per session (matches FIRST_SIGN_CAP)
|
|
1105
|
+
disableMetadataV2Services: payments.disableMetadataV2Services ?? false,
|
|
1106
|
+
dataDir: paymentsDir,
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Let this buyer node also pay as another wallet. The identity reuses the
|
|
1111
|
+
* node's discovery, routing and chain clients; sellers see it as its own
|
|
1112
|
+
* buyer with its own connections, channels and deposits. Select it per
|
|
1113
|
+
* request with `buyerIdentity`.
|
|
1114
|
+
*/
|
|
1115
|
+
async addBuyerIdentity(name, identity) {
|
|
1116
|
+
if (!this._started || !this._buyerHandler || !this._identity) {
|
|
1117
|
+
throw new Error("Buyer identities can only be added to a started buyer node");
|
|
1118
|
+
}
|
|
1119
|
+
if (!isValidBuyerIdentityName(name) || name === DEFAULT_BUYER_IDENTITY) {
|
|
1120
|
+
throw new Error(`Invalid buyer identity name "${name}"`);
|
|
1121
|
+
}
|
|
1122
|
+
if (this._buyerIdentities.has(name)) {
|
|
1123
|
+
throw new Error(`Buyer identity "${name}" is already loaded`);
|
|
1124
|
+
}
|
|
1125
|
+
const address = identity.wallet.address.toLowerCase();
|
|
1126
|
+
if (this.buyerIdentities().some((entry) => entry.address.toLowerCase() === address)) {
|
|
1127
|
+
throw new Error(`Wallet ${identity.wallet.address} is already loaded as another buyer identity`);
|
|
1128
|
+
}
|
|
1129
|
+
const payments = this._config.payments;
|
|
1130
|
+
const dataDir = this._config.dataDir ?? join(homedir(), ".antseed");
|
|
1131
|
+
const context = await BuyerIdentityContext.create({
|
|
1132
|
+
name,
|
|
1133
|
+
identity,
|
|
1134
|
+
paymentConfig: this._buyerPaymentConfig(join(dataDir, "payments")),
|
|
1135
|
+
freeUsage: this._freeUsageClient && payments?.freeUsageAddress
|
|
1136
|
+
? {
|
|
1137
|
+
chainId: payments.chainId ?? 8453,
|
|
1138
|
+
freeUsageContractAddress: payments.freeUsageAddress,
|
|
1139
|
+
defaultAuthDurationSecs: payments.defaultAuthDurationSecs ?? 3600,
|
|
1140
|
+
disableMetadataV2Services: payments.disableMetadataV2Services ?? false,
|
|
1141
|
+
}
|
|
1142
|
+
: null,
|
|
1143
|
+
requestHandler: {
|
|
1144
|
+
requestTimeoutMs: this._config.requestTimeoutMs,
|
|
1145
|
+
maxStreamBufferBytes: this._config.maxStreamBufferBytes,
|
|
1146
|
+
maxStreamDurationMs: this._config.maxStreamDurationMs,
|
|
1147
|
+
},
|
|
1148
|
+
maxUploadBodyBytes: this._config.maxUploadBodyBytes,
|
|
1149
|
+
requireSecureTransport: this._config.requireSecureTransport,
|
|
1150
|
+
shared: {
|
|
1151
|
+
channelStore: this._channelStore,
|
|
1152
|
+
depositsClient: this._depositsClient,
|
|
1153
|
+
channelsClient: this._channelsClient,
|
|
1154
|
+
sellerAddressResolver: this._sellerAddressResolver,
|
|
1155
|
+
verificationStorage: this._verificationStorage,
|
|
1156
|
+
verificationSampler: this._verificationSampler,
|
|
1157
|
+
peerCapabilities: this._peerCapabilities,
|
|
1158
|
+
isChainReachable: () => this._rpcHealth?.reachable ?? true,
|
|
1159
|
+
onChainReadFailure: () => this._rpcHealth?.reportFailure(),
|
|
1160
|
+
},
|
|
1161
|
+
emit: (event, payload) => { this.emit(event, payload); },
|
|
1162
|
+
});
|
|
1163
|
+
this._buyerIdentities.set(name, context);
|
|
1164
|
+
debugLog(`[Node] Buyer identity "${name}" loaded (wallet=${identity.wallet.address.slice(0, 10)}...)`);
|
|
1165
|
+
return { name, address: identity.wallet.address };
|
|
1166
|
+
}
|
|
1167
|
+
/** Close an identity's connections, letting sellers settle what they already served. */
|
|
1168
|
+
async removeBuyerIdentity(name) {
|
|
1169
|
+
const context = this._buyerIdentities.get(name);
|
|
1170
|
+
if (!context)
|
|
1171
|
+
return;
|
|
1172
|
+
this._buyerIdentities.delete(name);
|
|
1173
|
+
await context.stop();
|
|
1174
|
+
}
|
|
1175
|
+
hasBuyerIdentity(name) {
|
|
1176
|
+
return name === DEFAULT_BUYER_IDENTITY ? this._identity !== null : this._buyerIdentities.has(name);
|
|
1177
|
+
}
|
|
1178
|
+
/** Every identity this node can pay as, the default one first. */
|
|
1179
|
+
buyerIdentities() {
|
|
1180
|
+
const identities = [];
|
|
1181
|
+
if (this._identity)
|
|
1182
|
+
identities.push({ name: DEFAULT_BUYER_IDENTITY, address: this._identity.wallet.address });
|
|
1183
|
+
for (const context of this._buyerIdentities.values()) {
|
|
1184
|
+
identities.push({ name: context.name, address: context.address });
|
|
1185
|
+
}
|
|
1186
|
+
return identities;
|
|
1187
|
+
}
|
|
1188
|
+
_buyerContext(name) {
|
|
1189
|
+
if (name === undefined || name === DEFAULT_BUYER_IDENTITY)
|
|
1190
|
+
return null;
|
|
1191
|
+
const context = this._buyerIdentities.get(name);
|
|
1192
|
+
if (!context)
|
|
1193
|
+
throw buyerFault(`Unknown buyer identity "${name}"`, "invalid-request");
|
|
1194
|
+
return context;
|
|
1195
|
+
}
|
|
1196
|
+
/** Wallet address and payment manager behind an identity name. */
|
|
1197
|
+
_buyerAccount(name) {
|
|
1198
|
+
const context = this._buyerContext(name);
|
|
1199
|
+
if (context)
|
|
1200
|
+
return { address: context.address, paymentManager: context.paymentManager };
|
|
1201
|
+
return { address: this._identity?.wallet.address ?? null, paymentManager: this._buyerPaymentManager };
|
|
1052
1202
|
}
|
|
1053
1203
|
_createDHTConfig(port, bootstrapNodes) {
|
|
1054
1204
|
return {
|
|
@@ -1135,7 +1285,10 @@ export class AntseedNode extends EventEmitter {
|
|
|
1135
1285
|
this._muxes.get(peerId)?.abortPendingUploads();
|
|
1136
1286
|
this._muxes.delete(peerId);
|
|
1137
1287
|
this._paymentMuxes.delete(peerId);
|
|
1138
|
-
this.
|
|
1288
|
+
// Other buyer identities share this map; keep it while one is still connected.
|
|
1289
|
+
if (![...this._buyerIdentities.values()].some((context) => context.liveConnection(peerId))) {
|
|
1290
|
+
this._peerCapabilities.delete(peerId);
|
|
1291
|
+
}
|
|
1139
1292
|
this._verificationMuxes.get(peerId)?.close();
|
|
1140
1293
|
this._verificationMuxes.delete(peerId);
|
|
1141
1294
|
this._sweepMuxes.delete(peerId);
|
|
@@ -1386,24 +1539,11 @@ export class AntseedNode extends EventEmitter {
|
|
|
1386
1539
|
}
|
|
1387
1540
|
}
|
|
1388
1541
|
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
|
-
};
|
|
1542
|
+
const buyerPaymentConfig = this._buyerPaymentConfig(paymentsDir);
|
|
1403
1543
|
this._buyerPaymentManager = new BuyerPaymentManager(identity, buyerPaymentConfig, this._channelStore, this._sellerAddressResolver ?? undefined);
|
|
1404
1544
|
// Re-emit per-request spend so callers can attribute it to whatever
|
|
1405
1545
|
// issued the request — the node only knows the seller and requestId.
|
|
1406
|
-
this._buyerPaymentManager.setSpendListener((event) => this.emit('payment:spend', event));
|
|
1546
|
+
this._buyerPaymentManager.setSpendListener((event) => this.emit('payment:spend', { ...event, buyerIdentity: DEFAULT_BUYER_IDENTITY }));
|
|
1407
1547
|
debugLog(`[Node] Buyer payment manager initialized (wallet=${identity.wallet.address.slice(0, 10)}... chainId=${buyerPaymentConfig.chainId} deposits=${buyerPaymentConfig.depositsContractAddress.slice(0, 10)}...)`);
|
|
1408
1548
|
// Create negotiator that wraps the BPM with 402 handling and per-request auth
|
|
1409
1549
|
this._buyerNegotiator = new BuyerPaymentNegotiator(identity, this._buyerPaymentManager, this._depositsClient, this._channelsClient, this._channelStore, {
|
|
@@ -1765,81 +1905,7 @@ export class AntseedNode extends EventEmitter {
|
|
|
1765
1905
|
if (!this._connectionManager || !this._identity) {
|
|
1766
1906
|
throw buyerFault("Node not started", "node-not-started");
|
|
1767
1907
|
}
|
|
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;
|
|
1908
|
+
return getOrOpenOutboundConnection(this._connectionManager, peer, this._peerCapabilities, (conn) => this._wireConnection(conn, peer.peerId));
|
|
1843
1909
|
}
|
|
1844
1910
|
_getOrCreateMux(peerId, conn) {
|
|
1845
1911
|
const existing = this._muxes.get(peerId);
|
|
@@ -2135,10 +2201,6 @@ export class AntseedNode extends EventEmitter {
|
|
|
2135
2201
|
&& cachedPrice === 0;
|
|
2136
2202
|
}
|
|
2137
2203
|
}
|
|
2138
|
-
function parsePeerAddress(address) {
|
|
2139
|
-
const parts = address.split(":");
|
|
2140
|
-
return { host: parts[0], port: parseInt(parts[1] ?? "6882", 10) };
|
|
2141
|
-
}
|
|
2142
2204
|
/**
|
|
2143
2205
|
* Predicate used by `discoverPeers(service)` to filter the enumerated peer
|
|
2144
2206
|
* list against an optional service hint. Matches case-insensitively against
|