@antseed/node 0.2.25 → 0.2.27
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 +74 -1
- package/dist/discovery/announcer.d.ts +2 -0
- package/dist/discovery/announcer.d.ts.map +1 -1
- package/dist/discovery/announcer.js +10 -6
- 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/reputation-verifier.d.ts +7 -4
- package/dist/discovery/reputation-verifier.d.ts.map +1 -1
- package/dist/discovery/reputation-verifier.js +33 -9
- package/dist/discovery/reputation-verifier.js.map +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +60 -33
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +640 -324
- package/dist/node.js.map +1 -1
- package/dist/p2p/connection-manager.d.ts.map +1 -1
- package/dist/p2p/connection-manager.js +6 -0
- package/dist/p2p/connection-manager.js.map +1 -1
- package/dist/p2p/identity.d.ts +27 -3
- package/dist/p2p/identity.d.ts.map +1 -1
- package/dist/p2p/identity.js +52 -13
- package/dist/p2p/identity.js.map +1 -1
- package/dist/p2p/index.d.ts +0 -2
- package/dist/p2p/index.d.ts.map +1 -1
- package/dist/p2p/index.js +0 -2
- package/dist/p2p/index.js.map +1 -1
- package/dist/p2p/payment-codec.d.ts +7 -13
- package/dist/p2p/payment-codec.d.ts.map +1 -1
- package/dist/p2p/payment-codec.js +32 -50
- package/dist/p2p/payment-codec.js.map +1 -1
- package/dist/p2p/payment-mux.d.ts +11 -20
- package/dist/p2p/payment-mux.d.ts.map +1 -1
- package/dist/p2p/payment-mux.js +36 -52
- package/dist/p2p/payment-mux.js.map +1 -1
- package/dist/payments/buyer-payment-manager.d.ts +34 -93
- package/dist/payments/buyer-payment-manager.d.ts.map +1 -1
- package/dist/payments/buyer-payment-manager.js +184 -189
- package/dist/payments/buyer-payment-manager.js.map +1 -1
- package/dist/payments/chain-config.d.ts +38 -0
- package/dist/payments/chain-config.d.ts.map +1 -0
- package/dist/payments/chain-config.js +60 -0
- package/dist/payments/chain-config.js.map +1 -0
- package/dist/payments/evm/ants-token-client.d.ts +16 -0
- package/dist/payments/evm/ants-token-client.d.ts.map +1 -0
- package/dist/payments/evm/ants-token-client.js +66 -0
- package/dist/payments/evm/ants-token-client.js.map +1 -0
- package/dist/payments/evm/base-evm-client.d.ts +13 -0
- package/dist/payments/evm/base-evm-client.d.ts.map +1 -0
- package/dist/payments/evm/base-evm-client.js +38 -0
- package/dist/payments/evm/base-evm-client.js.map +1 -0
- package/dist/payments/evm/emissions-client.d.ts +23 -0
- package/dist/payments/evm/emissions-client.d.ts.map +1 -0
- package/dist/payments/evm/emissions-client.js +84 -0
- package/dist/payments/evm/emissions-client.js.map +1 -0
- package/dist/payments/evm/escrow-client.d.ts +57 -36
- package/dist/payments/evm/escrow-client.d.ts.map +1 -1
- package/dist/payments/evm/escrow-client.js +200 -93
- package/dist/payments/evm/escrow-client.js.map +1 -1
- package/dist/payments/evm/identity-client.d.ts +34 -0
- package/dist/payments/evm/identity-client.d.ts.map +1 -0
- package/dist/payments/evm/identity-client.js +125 -0
- package/dist/payments/evm/identity-client.js.map +1 -0
- package/dist/payments/evm/signatures.d.ts +18 -5
- package/dist/payments/evm/signatures.d.ts.map +1 -1
- package/dist/payments/evm/signatures.js +21 -12
- package/dist/payments/evm/signatures.js.map +1 -1
- package/dist/payments/evm/subpool-client.d.ts +30 -0
- package/dist/payments/evm/subpool-client.d.ts.map +1 -0
- package/dist/payments/evm/subpool-client.js +158 -0
- package/dist/payments/evm/subpool-client.js.map +1 -0
- package/dist/payments/index.d.ts +20 -7
- package/dist/payments/index.d.ts.map +1 -1
- package/dist/payments/index.js +17 -6
- package/dist/payments/index.js.map +1 -1
- package/dist/payments/readiness.d.ts +12 -0
- package/dist/payments/readiness.d.ts.map +1 -0
- package/dist/payments/readiness.js +64 -0
- package/dist/payments/readiness.js.map +1 -0
- package/dist/payments/seller-payment-manager.d.ts +74 -34
- package/dist/payments/seller-payment-manager.d.ts.map +1 -1
- package/dist/payments/seller-payment-manager.js +327 -118
- package/dist/payments/seller-payment-manager.js.map +1 -1
- package/dist/payments/session-store.d.ts +65 -0
- package/dist/payments/session-store.d.ts.map +1 -0
- package/dist/payments/session-store.js +243 -0
- package/dist/payments/session-store.js.map +1 -0
- package/dist/payments/usdc-utils.d.ts +9 -0
- package/dist/payments/usdc-utils.d.ts.map +1 -0
- package/dist/payments/usdc-utils.js +17 -0
- package/dist/payments/usdc-utils.js.map +1 -0
- package/dist/types/http.d.ts +2 -0
- package/dist/types/http.d.ts.map +1 -1
- package/dist/types/http.js +2 -0
- package/dist/types/http.js.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/metering.d.ts +1 -1
- package/dist/types/metering.d.ts.map +1 -1
- package/dist/types/protocol.d.ts +43 -60
- package/dist/types/protocol.d.ts.map +1 -1
- package/dist/types/protocol.js +5 -8
- package/dist/types/protocol.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,122 +1,63 @@
|
|
|
1
|
-
import { type AbstractSigner
|
|
1
|
+
import { type AbstractSigner } from 'ethers';
|
|
2
2
|
import type { Identity } from '../p2p/identity.js';
|
|
3
3
|
import type { PaymentMux } from '../p2p/payment-mux.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { AuthAckPayload, SellerReceiptPayload, TopUpRequestPayload } from '../types/protocol.js';
|
|
5
5
|
import { BaseEscrowClient } from './evm/escrow-client.js';
|
|
6
|
+
import { IdentityClient } from './evm/identity-client.js';
|
|
7
|
+
import { SessionStore, type StoredSession } from './session-store.js';
|
|
6
8
|
export interface BuyerPaymentConfig {
|
|
7
|
-
/** Default lock amount in USDC base units (6 decimals). e.g. "1000000" = 1 USDC */
|
|
8
|
-
defaultLockAmountUSDC: string;
|
|
9
|
-
/** Base JSON-RPC endpoint */
|
|
10
9
|
rpcUrl: string;
|
|
11
|
-
/** Deployed AntseedEscrow contract address */
|
|
12
10
|
contractAddress: string;
|
|
13
|
-
/** USDC token contract address */
|
|
14
11
|
usdcAddress: string;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
export type BuyerSessionStatus = 'pending' | 'confirmed' | 'active' | 'ending' | 'ended';
|
|
23
|
-
export interface BuyerSessionState {
|
|
24
|
-
sessionId: string;
|
|
25
|
-
sellerPeerId: string;
|
|
26
|
-
sellerEvmAddress: string;
|
|
27
|
-
lockedAmount: bigint;
|
|
28
|
-
status: BuyerSessionStatus;
|
|
29
|
-
txSignature: string | null;
|
|
30
|
-
lastRunningTotal: bigint;
|
|
31
|
-
lastRequestCount: number;
|
|
32
|
-
createdAt: number;
|
|
33
|
-
updatedAt: number;
|
|
12
|
+
identityAddress: string;
|
|
13
|
+
chainId: number;
|
|
14
|
+
defaultMaxAmountUsdc: bigint;
|
|
15
|
+
defaultAuthDurationSecs: number;
|
|
16
|
+
autoAck: boolean;
|
|
17
|
+
dataDir: string;
|
|
34
18
|
}
|
|
35
19
|
/**
|
|
36
|
-
* Manages buyer-side
|
|
37
|
-
*
|
|
38
|
-
* Handles the full lifecycle: lock initiation, receipt acknowledgement,
|
|
39
|
-
* top-up approval, and session settlement.
|
|
20
|
+
* Manages buyer-side payment sessions using EIP-712 SpendingAuth
|
|
21
|
+
* with persistent session storage.
|
|
40
22
|
*/
|
|
41
23
|
export declare class BuyerPaymentManager {
|
|
42
24
|
private readonly _identity;
|
|
43
25
|
private _signer;
|
|
44
26
|
private readonly _escrowClient;
|
|
45
27
|
private readonly _config;
|
|
46
|
-
private readonly
|
|
47
|
-
|
|
28
|
+
private readonly _sessionStore;
|
|
29
|
+
/** In-memory map of active confirmed sessions by seller peerId for fast lookups. */
|
|
30
|
+
private readonly _confirmedPeers;
|
|
31
|
+
/** Peers that explicitly rejected our spending auth. */
|
|
32
|
+
private readonly _rejectedPeers;
|
|
33
|
+
private _nonceCounter;
|
|
34
|
+
constructor(identity: Identity, config: BuyerPaymentConfig, sessionStore: SessionStore);
|
|
48
35
|
get signer(): AbstractSigner;
|
|
49
|
-
/** @deprecated Use .signer instead */
|
|
50
|
-
get wallet(): Wallet;
|
|
51
|
-
/** Replace the signer at runtime (e.g. with a WalletConnect signer). */
|
|
52
36
|
setSigner(signer: AbstractSigner): void;
|
|
53
37
|
get escrowClient(): BaseEscrowClient;
|
|
54
|
-
/** Get a snapshot of all active sessions. */
|
|
55
|
-
getActiveSessions(): BuyerSessionState[];
|
|
56
|
-
/** Get the session for a given seller peer, if it exists. */
|
|
57
|
-
getSession(sellerPeerId: string): BuyerSessionState | undefined;
|
|
58
|
-
/**
|
|
59
|
-
* Generate a session ID, sign a lock authorization, and send it
|
|
60
|
-
* to the seller via PaymentMux.
|
|
61
|
-
*/
|
|
62
|
-
initiateLock(sellerPeerId: string, sellerEvmAddress: string, paymentMux: PaymentMux, lockAmount?: string): Promise<string>;
|
|
63
|
-
/**
|
|
64
|
-
* Called when the seller confirms the lock was committed on-chain.
|
|
65
|
-
*/
|
|
66
|
-
handleLockConfirm(sellerPeerId: string, payload: SessionLockConfirmPayload): void;
|
|
67
|
-
/**
|
|
68
|
-
* Called when the seller rejects the lock.
|
|
69
|
-
*/
|
|
70
|
-
handleLockReject(sellerPeerId: string, payload: SessionLockRejectPayload): void;
|
|
71
38
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
39
|
+
* Sign and send an EIP-712 SpendingAuth to a seller.
|
|
40
|
+
* Loads the latest session to build the proof chain.
|
|
74
41
|
*/
|
|
42
|
+
authorizeSpending(sellerPeerId: string, sellerEvmAddr: string, paymentMux: PaymentMux, maxAmount?: bigint): Promise<string>;
|
|
43
|
+
handleAuthAck(sellerPeerId: string, payload: AuthAckPayload): void;
|
|
75
44
|
handleSellerReceipt(sellerPeerId: string, receipt: SellerReceiptPayload, paymentMux: PaymentMux): Promise<void>;
|
|
76
|
-
/**
|
|
77
|
-
* Handle a top-up request from the seller.
|
|
78
|
-
* If autoTopUp is enabled and budget allows, sign and send TopUpAuth.
|
|
79
|
-
* Otherwise, end the session.
|
|
80
|
-
*/
|
|
81
45
|
handleTopUpRequest(sellerPeerId: string, request: TopUpRequestPayload, paymentMux: PaymentMux): Promise<void>;
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
46
|
+
/** Check if a session has been confirmed via AuthAck. */
|
|
47
|
+
isAuthorized(sellerPeerId: string): boolean;
|
|
48
|
+
/** Alias for isAuthorized (used by polling loop). */
|
|
49
|
+
isLockConfirmed(sellerPeerId: string): boolean;
|
|
50
|
+
/** Check if the lock was explicitly rejected (not just never-contacted). */
|
|
51
|
+
isLockRejected(sellerPeerId: string): boolean;
|
|
52
|
+
/** Mark a peer as having rejected our spending auth. */
|
|
53
|
+
markRejected(sellerPeerId: string): void;
|
|
54
|
+
getSessionHistory(sellerPeerId: string): StoredSession[];
|
|
91
55
|
deposit(amount: bigint): Promise<string>;
|
|
92
|
-
/**
|
|
93
|
-
* Withdraw USDC from the escrow contract.
|
|
94
|
-
* @param amount Amount in USDC base units (6 decimals).
|
|
95
|
-
*/
|
|
96
56
|
withdraw(amount: bigint): Promise<string>;
|
|
97
|
-
/**
|
|
98
|
-
* Get the buyer's on-chain escrow balance.
|
|
99
|
-
*/
|
|
100
57
|
getBalance(): Promise<{
|
|
101
|
-
deposited: bigint;
|
|
102
|
-
committed: bigint;
|
|
103
58
|
available: bigint;
|
|
59
|
+
reserved: bigint;
|
|
104
60
|
}>;
|
|
105
|
-
|
|
106
|
-
* Release an expired lock (buyer reclaims funds).
|
|
107
|
-
*/
|
|
108
|
-
releaseExpiredLock(sessionId: string): Promise<string>;
|
|
109
|
-
/**
|
|
110
|
-
* Respond to a dispute opened by the seller.
|
|
111
|
-
*/
|
|
112
|
-
respondToDispute(sessionId: string): Promise<string>;
|
|
113
|
-
/**
|
|
114
|
-
* Check if a session lock has been confirmed (for polling).
|
|
115
|
-
*/
|
|
116
|
-
isLockConfirmed(sellerPeerId: string): boolean;
|
|
117
|
-
/**
|
|
118
|
-
* Check if a session lock has been rejected (for polling).
|
|
119
|
-
*/
|
|
120
|
-
isLockRejected(sellerPeerId: string): boolean;
|
|
61
|
+
submitFeedback(sellerPeerId: string, qualityScore: number, identityClient: IdentityClient): Promise<string | null>;
|
|
121
62
|
}
|
|
122
63
|
//# sourceMappingURL=buyer-payment-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buyer-payment-manager.d.ts","sourceRoot":"","sources":["../../src/payments/buyer-payment-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"buyer-payment-manager.d.ts","sourceRoot":"","sources":["../../src/payments/buyer-payment-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { encodeBytes32String } from 'ethers';
|
|
2
3
|
import { BaseEscrowClient } from './evm/escrow-client.js';
|
|
3
4
|
import { identityToEvmWallet, identityToEvmAddress } from './evm/keypair.js';
|
|
4
|
-
import {
|
|
5
|
+
import { signSpendingAuth, makeEscrowDomain, buildAckMessage, signMessageEd25519, verifyMessageEd25519, buildReceiptMessage, } from './evm/signatures.js';
|
|
5
6
|
import { bytesToHex, hexToBytes } from '../utils/hex.js';
|
|
6
7
|
import { debugLog, debugWarn } from '../utils/debug.js';
|
|
8
|
+
const ZERO_SESSION_ID = '0x' + '0'.repeat(64);
|
|
7
9
|
/**
|
|
8
|
-
* Manages buyer-side
|
|
9
|
-
*
|
|
10
|
-
* Handles the full lifecycle: lock initiation, receipt acknowledgement,
|
|
11
|
-
* top-up approval, and session settlement.
|
|
10
|
+
* Manages buyer-side payment sessions using EIP-712 SpendingAuth
|
|
11
|
+
* with persistent session storage.
|
|
12
12
|
*/
|
|
13
13
|
export class BuyerPaymentManager {
|
|
14
14
|
_identity;
|
|
15
15
|
_signer;
|
|
16
16
|
_escrowClient;
|
|
17
17
|
_config;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
_sessionStore;
|
|
19
|
+
/** In-memory map of active confirmed sessions by seller peerId for fast lookups. */
|
|
20
|
+
_confirmedPeers = new Set();
|
|
21
|
+
/** Peers that explicitly rejected our spending auth. */
|
|
22
|
+
_rejectedPeers = new Set();
|
|
23
|
+
_nonceCounter;
|
|
24
|
+
constructor(identity, config, sessionStore) {
|
|
20
25
|
this._identity = identity;
|
|
21
26
|
this._config = config;
|
|
22
27
|
this._signer = identityToEvmWallet(identity);
|
|
@@ -25,121 +30,165 @@ export class BuyerPaymentManager {
|
|
|
25
30
|
contractAddress: config.contractAddress,
|
|
26
31
|
usdcAddress: config.usdcAddress,
|
|
27
32
|
});
|
|
33
|
+
this._sessionStore = sessionStore;
|
|
34
|
+
// Restore nonce counter from persisted sessions to avoid duplicates across restarts
|
|
35
|
+
this._nonceCounter = sessionStore.getMaxNonce('buyer');
|
|
28
36
|
}
|
|
29
37
|
get signer() {
|
|
30
38
|
return this._signer;
|
|
31
39
|
}
|
|
32
|
-
/** @deprecated Use .signer instead */
|
|
33
|
-
get wallet() {
|
|
34
|
-
return this._signer;
|
|
35
|
-
}
|
|
36
|
-
/** Replace the signer at runtime (e.g. with a WalletConnect signer). */
|
|
37
40
|
setSigner(signer) {
|
|
38
41
|
this._signer = signer;
|
|
39
42
|
}
|
|
40
43
|
get escrowClient() {
|
|
41
44
|
return this._escrowClient;
|
|
42
45
|
}
|
|
43
|
-
|
|
44
|
-
getActiveSessions() {
|
|
45
|
-
return [...this._sessions.values()].filter((s) => s.status !== 'ended');
|
|
46
|
-
}
|
|
47
|
-
/** Get the session for a given seller peer, if it exists. */
|
|
48
|
-
getSession(sellerPeerId) {
|
|
49
|
-
return this._sessions.get(sellerPeerId);
|
|
50
|
-
}
|
|
51
|
-
// ── Lock initiation ─────────────────────────────────────────────
|
|
46
|
+
// ── Spending Authorization ────────────────────────────────────
|
|
52
47
|
/**
|
|
53
|
-
*
|
|
54
|
-
* to the
|
|
48
|
+
* Sign and send an EIP-712 SpendingAuth to a seller.
|
|
49
|
+
* Loads the latest session to build the proof chain.
|
|
55
50
|
*/
|
|
56
|
-
async
|
|
57
|
-
const amount =
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
async authorizeSpending(sellerPeerId, sellerEvmAddr, paymentMux, maxAmount) {
|
|
52
|
+
const amount = maxAmount ?? this._config.defaultMaxAmountUsdc;
|
|
53
|
+
// Clear confirmation state so we wait for a fresh AuthAck on the new session
|
|
54
|
+
this._confirmedPeers.delete(sellerPeerId);
|
|
55
|
+
// Load latest session to build proof chain.
|
|
56
|
+
// Only chain if the session is settled or active-with-delivery (the seller
|
|
57
|
+
// will settle it on-chain before reserving the new one).
|
|
58
|
+
// Fall back to first-sign for timed-out/ghost sessions.
|
|
59
|
+
const latestSession = this._sessionStore.getLatestSession(sellerPeerId, 'buyer');
|
|
60
|
+
const canChain = latestSession
|
|
61
|
+
&& latestSession.status !== 'timeout'
|
|
62
|
+
&& latestSession.status !== 'ghost'
|
|
63
|
+
&& BigInt(latestSession.tokensDelivered) > 0n;
|
|
64
|
+
const previousConsumption = canChain
|
|
65
|
+
? BigInt(latestSession.tokensDelivered)
|
|
66
|
+
: 0n;
|
|
67
|
+
const previousSessionId = canChain
|
|
68
|
+
? latestSession.sessionId
|
|
69
|
+
: ZERO_SESSION_ID;
|
|
70
|
+
// Generate a 32-byte session ID
|
|
60
71
|
const sessionIdBytes = randomBytes(32);
|
|
61
72
|
const sessionId = '0x' + sessionIdBytes.toString('hex');
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
const nonce = ++this._nonceCounter;
|
|
74
|
+
const deadline = Math.floor(Date.now() / 1000) + this._config.defaultAuthDurationSecs;
|
|
75
|
+
debugLog(`[BuyerPayment] authorizeSpending: session=${sessionId.slice(0, 18)}... seller=${sellerPeerId.slice(0, 12)}... amount=${amount}`);
|
|
76
|
+
// Sign EIP-712 SpendingAuth
|
|
77
|
+
const domain = makeEscrowDomain(this._config.chainId, this._config.contractAddress);
|
|
78
|
+
const msg = {
|
|
79
|
+
seller: sellerEvmAddr,
|
|
80
|
+
sessionId,
|
|
81
|
+
maxAmount: amount,
|
|
82
|
+
nonce,
|
|
83
|
+
deadline,
|
|
84
|
+
previousConsumption,
|
|
85
|
+
previousSessionId,
|
|
86
|
+
};
|
|
87
|
+
const buyerSig = await signSpendingAuth(this._signer, domain, msg);
|
|
88
|
+
const buyerEvmAddr = identityToEvmAddress(this._identity);
|
|
89
|
+
// Store session
|
|
67
90
|
const now = Date.now();
|
|
68
91
|
const session = {
|
|
69
92
|
sessionId,
|
|
70
|
-
sellerPeerId,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
peerId: sellerPeerId,
|
|
94
|
+
role: 'buyer',
|
|
95
|
+
sellerEvmAddr,
|
|
96
|
+
buyerEvmAddr,
|
|
97
|
+
nonce,
|
|
98
|
+
authMax: amount.toString(),
|
|
99
|
+
deadline,
|
|
100
|
+
previousSessionId,
|
|
101
|
+
previousConsumption: previousConsumption.toString(),
|
|
102
|
+
tokensDelivered: '0',
|
|
103
|
+
requestCount: 0,
|
|
104
|
+
reservedAt: now,
|
|
105
|
+
settledAt: null,
|
|
106
|
+
settledAmount: null,
|
|
107
|
+
status: 'active',
|
|
77
108
|
createdAt: now,
|
|
78
109
|
updatedAt: now,
|
|
79
110
|
};
|
|
80
|
-
this.
|
|
81
|
-
// Send
|
|
82
|
-
paymentMux.
|
|
111
|
+
this._sessionStore.upsertSession(session);
|
|
112
|
+
// Send SpendingAuth via PaymentMux
|
|
113
|
+
paymentMux.sendSpendingAuth({
|
|
83
114
|
sessionId,
|
|
84
|
-
|
|
115
|
+
maxAmountUsdc: amount.toString(),
|
|
116
|
+
nonce,
|
|
117
|
+
deadline,
|
|
85
118
|
buyerSig,
|
|
119
|
+
buyerEvmAddr,
|
|
120
|
+
previousConsumption: previousConsumption.toString(),
|
|
121
|
+
previousSessionId,
|
|
86
122
|
});
|
|
87
123
|
return sessionId;
|
|
88
124
|
}
|
|
89
|
-
// ──
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*/
|
|
93
|
-
handleLockConfirm(sellerPeerId, payload) {
|
|
94
|
-
const session = this._sessions.get(sellerPeerId);
|
|
125
|
+
// ── AuthAck handler ───────────────────────────────────────────
|
|
126
|
+
handleAuthAck(sellerPeerId, payload) {
|
|
127
|
+
const session = this._sessionStore.getActiveSessionByPeer(sellerPeerId, 'buyer');
|
|
95
128
|
if (!session) {
|
|
96
|
-
debugWarn(`[BuyerPayment]
|
|
129
|
+
debugWarn(`[BuyerPayment] AuthAck for unknown seller: ${sellerPeerId.slice(0, 12)}...`);
|
|
97
130
|
return;
|
|
98
131
|
}
|
|
99
132
|
if (session.sessionId !== payload.sessionId) {
|
|
100
|
-
debugWarn(`[BuyerPayment]
|
|
133
|
+
debugWarn(`[BuyerPayment] AuthAck session mismatch: expected=${session.sessionId.slice(0, 18)}... got=${payload.sessionId.slice(0, 18)}...`);
|
|
101
134
|
return;
|
|
102
135
|
}
|
|
103
|
-
|
|
104
|
-
session.
|
|
105
|
-
session.updatedAt = Date.now();
|
|
106
|
-
debugLog(`[BuyerPayment] Lock confirmed: session=${session.sessionId.slice(0, 18)}... tx=${payload.txSignature.slice(0, 12)}...`);
|
|
136
|
+
this._confirmedPeers.add(sellerPeerId);
|
|
137
|
+
debugLog(`[BuyerPayment] AuthAck confirmed: session=${session.sessionId.slice(0, 18)}...`);
|
|
107
138
|
}
|
|
108
|
-
|
|
109
|
-
* Called when the seller rejects the lock.
|
|
110
|
-
*/
|
|
111
|
-
handleLockReject(sellerPeerId, payload) {
|
|
112
|
-
const session = this._sessions.get(sellerPeerId);
|
|
113
|
-
if (!session) {
|
|
114
|
-
debugWarn(`[BuyerPayment] Lock reject for unknown seller: ${sellerPeerId.slice(0, 12)}...`);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
debugWarn(`[BuyerPayment] Lock rejected: session=${session.sessionId.slice(0, 18)}... reason=${payload.reason}`);
|
|
118
|
-
this._sessions.delete(sellerPeerId);
|
|
119
|
-
}
|
|
120
|
-
// ── Receipt handling ────────────────────────────────────────────
|
|
121
|
-
/**
|
|
122
|
-
* Handle a running-total receipt from the seller.
|
|
123
|
-
* If autoAck is enabled, automatically counter-sign and send BuyerAck.
|
|
124
|
-
*/
|
|
139
|
+
// ── Seller Receipt handler ────────────────────────────────────
|
|
125
140
|
async handleSellerReceipt(sellerPeerId, receipt, paymentMux) {
|
|
126
|
-
const session = this.
|
|
141
|
+
const session = this._sessionStore.getActiveSessionByPeer(sellerPeerId, 'buyer');
|
|
127
142
|
if (!session) {
|
|
128
143
|
debugWarn(`[BuyerPayment] Receipt for unknown seller: ${sellerPeerId.slice(0, 12)}...`);
|
|
129
144
|
return;
|
|
130
145
|
}
|
|
131
|
-
if (session.
|
|
132
|
-
session.
|
|
146
|
+
if (session.sessionId !== receipt.sessionId) {
|
|
147
|
+
debugWarn(`[BuyerPayment] Receipt session ID mismatch: active=${session.sessionId.slice(0, 18)}... receipt=${receipt.sessionId.slice(0, 18)}... — discarding stale receipt`);
|
|
148
|
+
return;
|
|
133
149
|
}
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
// Verify seller's Ed25519 signature
|
|
151
|
+
try {
|
|
152
|
+
const sellerPublicKey = hexToBytes(sellerPeerId);
|
|
153
|
+
const sessionIdBytes = hexToBytes(receipt.sessionId.replace(/^0x/, ''));
|
|
154
|
+
const responseHashBytes = hexToBytes(receipt.responseHash);
|
|
155
|
+
const receiptMsg = buildReceiptMessage(sessionIdBytes, BigInt(receipt.runningTotal), receipt.requestCount, responseHashBytes);
|
|
156
|
+
const sigBytes = hexToBytes(receipt.sellerSig);
|
|
157
|
+
const valid = await verifyMessageEd25519(sellerPublicKey, sigBytes, receiptMsg);
|
|
158
|
+
if (!valid) {
|
|
159
|
+
debugWarn(`[BuyerPayment] Invalid seller receipt signature from ${sellerPeerId.slice(0, 12)}...`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
debugWarn(`[BuyerPayment] Failed to verify receipt: ${err instanceof Error ? err.message : err}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Validate monotonic increase: runningTotal must exceed previous
|
|
168
|
+
const newTotal = BigInt(receipt.runningTotal);
|
|
169
|
+
const prevTotal = BigInt(session.tokensDelivered);
|
|
170
|
+
if (newTotal <= prevTotal) {
|
|
171
|
+
debugWarn(`[BuyerPayment] Receipt runningTotal not monotonic: new=${newTotal} prev=${prevTotal}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Note: we don't compare token count against authMax (USDC) here because
|
|
175
|
+
// they're in different units (tokens vs USDC base units). The on-chain
|
|
176
|
+
// settle() caps chargeAmount = min(tokenCount * tokenRate, maxAmount),
|
|
177
|
+
// so the buyer's EIP-712 signature is the real USDC protection.
|
|
138
178
|
debugLog(`[BuyerPayment] Receipt: session=${session.sessionId.slice(0, 18)}... total=${receipt.runningTotal} count=${receipt.requestCount}`);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
179
|
+
// Atomically update tokens delivered and store receipt
|
|
180
|
+
this._sessionStore.updateDeliveredAndInsertReceipt(session.sessionId, receipt.runningTotal, receipt.requestCount, {
|
|
181
|
+
sessionId: session.sessionId,
|
|
182
|
+
runningTotal: receipt.runningTotal,
|
|
183
|
+
requestCount: receipt.requestCount,
|
|
184
|
+
responseHash: receipt.responseHash,
|
|
185
|
+
sellerSig: receipt.sellerSig,
|
|
186
|
+
buyerAckSig: null,
|
|
187
|
+
createdAt: Date.now(),
|
|
188
|
+
});
|
|
189
|
+
// Auto-ack if configured
|
|
190
|
+
if (this._config.autoAck) {
|
|
191
|
+
const sessionIdBytes = hexToBytes(session.sessionId.replace(/^0x/, ''));
|
|
143
192
|
const ackMsg = buildAckMessage(sessionIdBytes, BigInt(receipt.runningTotal), receipt.requestCount);
|
|
144
193
|
const sigBytes = await signMessageEd25519(this._identity, ackMsg);
|
|
145
194
|
const buyerSig = bytesToHex(sigBytes);
|
|
@@ -152,129 +201,75 @@ export class BuyerPaymentManager {
|
|
|
152
201
|
debugLog(`[BuyerPayment] Auto-ack sent for session=${session.sessionId.slice(0, 18)}...`);
|
|
153
202
|
}
|
|
154
203
|
}
|
|
155
|
-
// ──
|
|
156
|
-
/**
|
|
157
|
-
* Handle a top-up request from the seller.
|
|
158
|
-
* If autoTopUp is enabled and budget allows, sign and send TopUpAuth.
|
|
159
|
-
* Otherwise, end the session.
|
|
160
|
-
*/
|
|
204
|
+
// ── TopUp handler ─────────────────────────────────────────────
|
|
161
205
|
async handleTopUpRequest(sellerPeerId, request, paymentMux) {
|
|
162
|
-
const session = this.
|
|
206
|
+
const session = this._sessionStore.getActiveSessionByPeer(sellerPeerId, 'buyer');
|
|
163
207
|
if (!session) {
|
|
164
208
|
debugWarn(`[BuyerPayment] Top-up for unknown seller: ${sellerPeerId.slice(0, 12)}...`);
|
|
165
209
|
return;
|
|
166
210
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const account = await this._escrowClient.getBuyerAccount(buyerAddr);
|
|
176
|
-
if (account.available >= additionalAmount) {
|
|
177
|
-
// Sign extend-lock authorization
|
|
178
|
-
const messageHash = buildExtendLockMessageHash(session.sessionId, session.sellerEvmAddress, additionalAmount);
|
|
179
|
-
const buyerSig = await signMessageEcdsa(this._signer, messageHash);
|
|
180
|
-
session.lockedAmount = newTotal;
|
|
181
|
-
session.updatedAt = Date.now();
|
|
182
|
-
paymentMux.sendTopUpAuth({
|
|
183
|
-
sessionId: session.sessionId,
|
|
184
|
-
additionalAmount: request.additionalAmount,
|
|
185
|
-
buyerSig,
|
|
186
|
-
});
|
|
187
|
-
debugLog(`[BuyerPayment] Top-up authorized: session=${session.sessionId.slice(0, 18)}...`);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
debugWarn(`[BuyerPayment] Insufficient balance for top-up. Available=${account.available}, requested=${additionalAmount}`);
|
|
191
|
-
}
|
|
192
|
-
// Cannot or will not top up — end the session
|
|
193
|
-
debugLog(`[BuyerPayment] Declining top-up, ending session=${session.sessionId.slice(0, 18)}...`);
|
|
194
|
-
await this.endSession(sellerPeerId, paymentMux, 80);
|
|
211
|
+
debugLog(`[BuyerPayment] TopUp request: session=${session.sessionId.slice(0, 18)}... currentUsed=${request.currentUsed} currentMax=${request.currentMax}`);
|
|
212
|
+
// Sign a new SpendingAuth with increased cap
|
|
213
|
+
const currentMax = BigInt(session.authMax);
|
|
214
|
+
const additionalAmount = BigInt(request.requestedAdditional);
|
|
215
|
+
const newMax = currentMax + additionalAmount;
|
|
216
|
+
// The new auth embeds the current consumption as previousConsumption
|
|
217
|
+
await this.authorizeSpending(sellerPeerId, session.sellerEvmAddr, paymentMux, newMax);
|
|
218
|
+
debugLog(`[BuyerPayment] TopUp authorized: new auth sent with max=${newMax}`);
|
|
195
219
|
}
|
|
196
|
-
// ──
|
|
197
|
-
/**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
// ── Queries ───────────────────────────────────────────────────
|
|
221
|
+
/** Check if a session has been confirmed via AuthAck. */
|
|
222
|
+
isAuthorized(sellerPeerId) {
|
|
223
|
+
return this._confirmedPeers.has(sellerPeerId);
|
|
224
|
+
}
|
|
225
|
+
/** Alias for isAuthorized (used by polling loop). */
|
|
226
|
+
isLockConfirmed(sellerPeerId) {
|
|
227
|
+
return this.isAuthorized(sellerPeerId);
|
|
228
|
+
}
|
|
229
|
+
/** Check if the lock was explicitly rejected (not just never-contacted). */
|
|
230
|
+
isLockRejected(sellerPeerId) {
|
|
231
|
+
return this._rejectedPeers.has(sellerPeerId);
|
|
232
|
+
}
|
|
233
|
+
/** Mark a peer as having rejected our spending auth. */
|
|
234
|
+
markRejected(sellerPeerId) {
|
|
235
|
+
this._rejectedPeers.add(sellerPeerId);
|
|
236
|
+
debugLog(`[BuyerPayment] Peer ${sellerPeerId.slice(0, 12)}... marked as rejected`);
|
|
237
|
+
}
|
|
238
|
+
getSessionHistory(sellerPeerId) {
|
|
239
|
+
const sessions = [];
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
let session = this._sessionStore.getLatestSession(sellerPeerId, 'buyer');
|
|
242
|
+
while (session && !seen.has(session.sessionId)) {
|
|
243
|
+
seen.add(session.sessionId);
|
|
244
|
+
sessions.unshift(session);
|
|
245
|
+
if (session.previousSessionId === ZERO_SESSION_ID)
|
|
246
|
+
break;
|
|
247
|
+
session = this._sessionStore.getSession(session.previousSessionId);
|
|
209
248
|
}
|
|
210
|
-
|
|
211
|
-
session.updatedAt = Date.now();
|
|
212
|
-
debugLog(`[BuyerPayment] Ending session=${session.sessionId.slice(0, 18)}... total=${session.lastRunningTotal} score=${score}`);
|
|
213
|
-
// Sign settlement message with ECDSA
|
|
214
|
-
const messageHash = buildSettlementMessageHash(session.sessionId, session.lastRunningTotal, score);
|
|
215
|
-
const buyerSig = await signMessageEcdsa(this._signer, messageHash);
|
|
216
|
-
paymentMux.sendSessionEnd({
|
|
217
|
-
sessionId: session.sessionId,
|
|
218
|
-
runningTotal: session.lastRunningTotal.toString(),
|
|
219
|
-
requestCount: session.lastRequestCount,
|
|
220
|
-
score,
|
|
221
|
-
buyerSig,
|
|
222
|
-
});
|
|
223
|
-
session.status = 'ended';
|
|
224
|
-
session.updatedAt = Date.now();
|
|
225
|
-
debugLog(`[BuyerPayment] Session ended: ${session.sessionId.slice(0, 18)}...`);
|
|
249
|
+
return sessions;
|
|
226
250
|
}
|
|
227
|
-
// ── Escrow operations
|
|
228
|
-
/**
|
|
229
|
-
* Deposit USDC into the escrow contract.
|
|
230
|
-
* @param amount Amount in USDC base units (6 decimals).
|
|
231
|
-
*/
|
|
251
|
+
// ── Escrow operations ─────────────────────────────────────────
|
|
232
252
|
async deposit(amount) {
|
|
233
253
|
debugLog(`[BuyerPayment] Depositing ${amount} to escrow`);
|
|
234
254
|
return this._escrowClient.deposit(this._signer, amount);
|
|
235
255
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Withdraw USDC from the escrow contract.
|
|
238
|
-
* @param amount Amount in USDC base units (6 decimals).
|
|
239
|
-
*/
|
|
240
256
|
async withdraw(amount) {
|
|
241
|
-
debugLog(`[BuyerPayment]
|
|
242
|
-
return this._escrowClient.
|
|
257
|
+
debugLog(`[BuyerPayment] Requesting withdrawal of ${amount} from escrow`);
|
|
258
|
+
return this._escrowClient.requestWithdrawal(this._signer, amount);
|
|
243
259
|
}
|
|
244
|
-
/**
|
|
245
|
-
* Get the buyer's on-chain escrow balance.
|
|
246
|
-
*/
|
|
247
260
|
async getBalance() {
|
|
248
261
|
const buyerAddr = identityToEvmAddress(this._identity);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
// ── Dispute helpers ─────────────────────────────────────────────
|
|
252
|
-
/**
|
|
253
|
-
* Release an expired lock (buyer reclaims funds).
|
|
254
|
-
*/
|
|
255
|
-
async releaseExpiredLock(sessionId) {
|
|
256
|
-
debugLog(`[BuyerPayment] Releasing expired lock: session=${sessionId.slice(0, 18)}...`);
|
|
257
|
-
return this._escrowClient.releaseExpiredLock(this._signer, sessionId);
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Respond to a dispute opened by the seller.
|
|
261
|
-
*/
|
|
262
|
-
async respondToDispute(sessionId) {
|
|
263
|
-
debugLog(`[BuyerPayment] Responding to dispute: session=${sessionId.slice(0, 18)}...`);
|
|
264
|
-
return this._escrowClient.respondDispute(this._signer, sessionId);
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Check if a session lock has been confirmed (for polling).
|
|
268
|
-
*/
|
|
269
|
-
isLockConfirmed(sellerPeerId) {
|
|
270
|
-
const session = this._sessions.get(sellerPeerId);
|
|
271
|
-
return session?.status === 'confirmed' || session?.status === 'active';
|
|
262
|
+
const info = await this._escrowClient.getBuyerBalance(buyerAddr);
|
|
263
|
+
return { available: info.available, reserved: info.reserved };
|
|
272
264
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
265
|
+
// ── Feedback (Task 6) ─────────────────────────────────────────
|
|
266
|
+
async submitFeedback(sellerPeerId, qualityScore, identityClient) {
|
|
267
|
+
const session = this._sessionStore.getLatestSession(sellerPeerId, 'buyer');
|
|
268
|
+
if (!session || session.status !== 'settled')
|
|
269
|
+
return null;
|
|
270
|
+
const tokenId = await identityClient.getTokenId(session.sellerEvmAddr);
|
|
271
|
+
const tag = encodeBytes32String('quality');
|
|
272
|
+
return identityClient.submitFeedback(this._signer, tokenId, qualityScore, tag);
|
|
278
273
|
}
|
|
279
274
|
}
|
|
280
275
|
//# sourceMappingURL=buyer-payment-manager.js.map
|