@antseed/node 0.1.0 → 0.1.2
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/LICENSE +674 -0
- package/README.md +7 -5
- package/dist/discovery/http-metadata-resolver.d.ts +6 -0
- package/dist/discovery/http-metadata-resolver.d.ts.map +1 -1
- package/dist/discovery/http-metadata-resolver.js +32 -4
- package/dist/discovery/http-metadata-resolver.js.map +1 -1
- package/dist/discovery/peer-lookup.d.ts +1 -0
- package/dist/discovery/peer-lookup.d.ts.map +1 -1
- package/dist/discovery/peer-lookup.js +10 -25
- package/dist/discovery/peer-lookup.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/seller-provider.d.ts +13 -1
- package/dist/interfaces/seller-provider.d.ts.map +1 -1
- package/dist/node.d.ts +13 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +146 -21
- package/dist/node.js.map +1 -1
- package/dist/proxy/proxy-mux.d.ts +3 -1
- package/dist/proxy/proxy-mux.d.ts.map +1 -1
- package/dist/proxy/proxy-mux.js +9 -5
- package/dist/proxy/proxy-mux.js.map +1 -1
- package/dist/types/http.d.ts +1 -0
- package/dist/types/http.d.ts.map +1 -1
- package/dist/types/http.js +1 -1
- package/dist/types/http.js.map +1 -1
- package/package.json +14 -10
- package/contracts/AntseedEscrow.sol +0 -310
- package/contracts/MockUSDC.sol +0 -64
- package/contracts/README.md +0 -102
- package/src/config/encryption.test.ts +0 -49
- package/src/config/encryption.ts +0 -53
- package/src/config/plugin-config-manager.test.ts +0 -92
- package/src/config/plugin-config-manager.ts +0 -153
- package/src/config/plugin-loader.ts +0 -90
- package/src/discovery/announcer.ts +0 -169
- package/src/discovery/bootstrap.ts +0 -57
- package/src/discovery/default-metadata-resolver.ts +0 -18
- package/src/discovery/dht-health.ts +0 -136
- package/src/discovery/dht-node.ts +0 -191
- package/src/discovery/http-metadata-resolver.ts +0 -47
- package/src/discovery/index.ts +0 -15
- package/src/discovery/metadata-codec.ts +0 -453
- package/src/discovery/metadata-resolver.ts +0 -7
- package/src/discovery/metadata-server.ts +0 -73
- package/src/discovery/metadata-validator.ts +0 -172
- package/src/discovery/peer-lookup.ts +0 -122
- package/src/discovery/peer-metadata.ts +0 -34
- package/src/discovery/peer-selector.ts +0 -134
- package/src/discovery/profile-manager.ts +0 -131
- package/src/discovery/profile-search.ts +0 -100
- package/src/discovery/reputation-verifier.ts +0 -54
- package/src/index.ts +0 -61
- package/src/interfaces/buyer-router.ts +0 -21
- package/src/interfaces/plugin.ts +0 -36
- package/src/interfaces/seller-provider.ts +0 -81
- package/src/metering/index.ts +0 -6
- package/src/metering/receipt-generator.ts +0 -105
- package/src/metering/receipt-verifier.ts +0 -102
- package/src/metering/session-tracker.ts +0 -145
- package/src/metering/storage.ts +0 -600
- package/src/metering/token-counter.ts +0 -127
- package/src/metering/usage-aggregator.ts +0 -236
- package/src/node.ts +0 -1698
- package/src/p2p/connection-auth.ts +0 -152
- package/src/p2p/connection-manager.ts +0 -916
- package/src/p2p/handshake.ts +0 -162
- package/src/p2p/ice-config.ts +0 -59
- package/src/p2p/identity.ts +0 -110
- package/src/p2p/index.ts +0 -11
- package/src/p2p/keepalive.ts +0 -118
- package/src/p2p/message-protocol.ts +0 -171
- package/src/p2p/nat-traversal.ts +0 -169
- package/src/p2p/payment-codec.ts +0 -165
- package/src/p2p/payment-mux.ts +0 -153
- package/src/p2p/reconnect.ts +0 -117
- package/src/payments/balance-manager.ts +0 -77
- package/src/payments/buyer-payment-manager.ts +0 -414
- package/src/payments/disputes.ts +0 -72
- package/src/payments/evm/escrow-client.ts +0 -263
- package/src/payments/evm/keypair.ts +0 -31
- package/src/payments/evm/signatures.ts +0 -103
- package/src/payments/evm/wallet.ts +0 -42
- package/src/payments/index.ts +0 -50
- package/src/payments/settlement.ts +0 -40
- package/src/payments/types.ts +0 -79
- package/src/proxy/index.ts +0 -3
- package/src/proxy/provider-detection.ts +0 -78
- package/src/proxy/proxy-mux.ts +0 -173
- package/src/proxy/request-codec.ts +0 -294
- package/src/reputation/index.ts +0 -6
- package/src/reputation/rating-manager.ts +0 -118
- package/src/reputation/report-manager.ts +0 -91
- package/src/reputation/trust-engine.ts +0 -120
- package/src/reputation/trust-score.ts +0 -74
- package/src/reputation/uptime-tracker.ts +0 -155
- package/src/routing/default-router.ts +0 -75
- package/src/types/bittorrent-dht.d.ts +0 -19
- package/src/types/buyer.ts +0 -37
- package/src/types/capability.ts +0 -34
- package/src/types/connection.ts +0 -29
- package/src/types/http.ts +0 -20
- package/src/types/index.ts +0 -14
- package/src/types/metering.ts +0 -175
- package/src/types/nat-api.d.ts +0 -29
- package/src/types/peer-profile.ts +0 -25
- package/src/types/peer.ts +0 -62
- package/src/types/plugin-config.ts +0 -31
- package/src/types/protocol.ts +0 -162
- package/src/types/provider.ts +0 -40
- package/src/types/rating.ts +0 -23
- package/src/types/report.ts +0 -30
- package/src/types/seller.ts +0 -38
- package/src/types/staking.ts +0 -23
- package/src/utils/debug.ts +0 -30
- package/src/utils/hex.ts +0 -14
- package/tests/balance-manager.test.ts +0 -156
- package/tests/bootstrap.test.ts +0 -108
- package/tests/buyer-payment-manager.test.ts +0 -358
- package/tests/connection-auth.test.ts +0 -87
- package/tests/default-router.test.ts +0 -148
- package/tests/evm-keypair.test.ts +0 -173
- package/tests/identity.test.ts +0 -133
- package/tests/message-protocol.test.ts +0 -212
- package/tests/metadata-codec.test.ts +0 -165
- package/tests/metadata-validator.test.ts +0 -261
- package/tests/metering-storage.test.ts +0 -244
- package/tests/payment-codec.test.ts +0 -95
- package/tests/payment-mux.test.ts +0 -191
- package/tests/peer-selector.test.ts +0 -184
- package/tests/provider-detection.test.ts +0 -107
- package/tests/proxy-mux-security.test.ts +0 -38
- package/tests/receipt.test.ts +0 -215
- package/tests/reputation-integration.test.ts +0 -195
- package/tests/request-codec.test.ts +0 -144
- package/tests/token-counter.test.ts +0 -122
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -7
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import * as os from 'node:os';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
|
-
import { randomBytes } from 'node:crypto';
|
|
6
|
-
import { BalanceManager } from '../src/payments/balance-manager.js';
|
|
7
|
-
import type { Transaction } from '../src/payments/types.js';
|
|
8
|
-
import type { WalletInfo } from '../src/payments/types.js';
|
|
9
|
-
|
|
10
|
-
function tmpDir(): string {
|
|
11
|
-
return path.join(os.tmpdir(), `antseed-bm-test-${randomBytes(8).toString('hex')}`);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const dirsToClean: string[] = [];
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
for (const dir of dirsToClean) {
|
|
18
|
-
try {
|
|
19
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
20
|
-
} catch {}
|
|
21
|
-
}
|
|
22
|
-
dirsToClean.length = 0;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
function makeTx(overrides?: Partial<Transaction>): Transaction {
|
|
26
|
-
return {
|
|
27
|
-
txId: `tx-${Math.random().toString(36).slice(2)}`,
|
|
28
|
-
type: 'escrow_lock',
|
|
29
|
-
amountUSD: 10,
|
|
30
|
-
from: 'buyer',
|
|
31
|
-
to: 'seller',
|
|
32
|
-
timestamp: Date.now(),
|
|
33
|
-
status: 'confirmed',
|
|
34
|
-
...overrides,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('BalanceManager.getBalance', () => {
|
|
39
|
-
it('should compute unified balance from crypto + escrow', () => {
|
|
40
|
-
const bm = new BalanceManager();
|
|
41
|
-
const wallet: WalletInfo = { address: '0x1', chainId: 'base-local', balanceETH: '0', balanceUSDC: '50.5' };
|
|
42
|
-
const result = bm.getBalance(wallet, 10);
|
|
43
|
-
|
|
44
|
-
expect(result.cryptoUSDC).toBeCloseTo(50.5);
|
|
45
|
-
expect(result.inEscrowUSDC).toBe(10);
|
|
46
|
-
expect(result.totalUSD).toBeCloseTo(60.5);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should handle null wallet', () => {
|
|
50
|
-
const bm = new BalanceManager();
|
|
51
|
-
const result = bm.getBalance(null, 0);
|
|
52
|
-
expect(result.cryptoUSDC).toBe(0);
|
|
53
|
-
expect(result.totalUSD).toBe(0);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should include escrow in total', () => {
|
|
57
|
-
const bm = new BalanceManager();
|
|
58
|
-
const wallet: WalletInfo = { address: '0x1', chainId: 'base-local', balanceETH: '0', balanceUSDC: '100' };
|
|
59
|
-
const result = bm.getBalance(wallet, 5);
|
|
60
|
-
expect(result.totalUSD).toBe(105);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should handle both null wallet and zero escrow', () => {
|
|
64
|
-
const bm = new BalanceManager();
|
|
65
|
-
const result = bm.getBalance(null, 0);
|
|
66
|
-
expect(result.totalUSD).toBe(0);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('BalanceManager.recordTransaction / getTransactionHistory', () => {
|
|
71
|
-
it('should record and retrieve transactions', () => {
|
|
72
|
-
const bm = new BalanceManager();
|
|
73
|
-
const tx = makeTx();
|
|
74
|
-
bm.recordTransaction(tx);
|
|
75
|
-
|
|
76
|
-
const history = bm.getTransactionHistory();
|
|
77
|
-
expect(history).toHaveLength(1);
|
|
78
|
-
expect(history[0]!.txId).toBe(tx.txId);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should filter by type', () => {
|
|
82
|
-
const bm = new BalanceManager();
|
|
83
|
-
bm.recordTransaction(makeTx({ type: 'escrow_lock' }));
|
|
84
|
-
bm.recordTransaction(makeTx({ type: 'escrow_release' }));
|
|
85
|
-
bm.recordTransaction(makeTx({ type: 'escrow_lock' }));
|
|
86
|
-
|
|
87
|
-
expect(bm.getTransactionHistory('escrow_lock')).toHaveLength(2);
|
|
88
|
-
expect(bm.getTransactionHistory('escrow_release')).toHaveLength(1);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should support limit and offset', () => {
|
|
92
|
-
const bm = new BalanceManager();
|
|
93
|
-
for (let i = 0; i < 10; i++) {
|
|
94
|
-
bm.recordTransaction(makeTx({ txId: `tx-${i}` }));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const page = bm.getTransactionHistory(undefined, 3, 2);
|
|
98
|
-
expect(page).toHaveLength(3);
|
|
99
|
-
expect(page[0]!.txId).toBe('tx-2');
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('BalanceManager.getTotalEarnings / getTotalSpending', () => {
|
|
104
|
-
it('should sum escrow_release transactions for earnings', () => {
|
|
105
|
-
const bm = new BalanceManager();
|
|
106
|
-
bm.recordTransaction(makeTx({ type: 'escrow_release', amountUSD: 10 }));
|
|
107
|
-
bm.recordTransaction(makeTx({ type: 'escrow_release', amountUSD: 20 }));
|
|
108
|
-
bm.recordTransaction(makeTx({ type: 'escrow_lock', amountUSD: 100 }));
|
|
109
|
-
|
|
110
|
-
expect(bm.getTotalEarnings()).toBe(30);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should sum escrow_lock transactions for spending', () => {
|
|
114
|
-
const bm = new BalanceManager();
|
|
115
|
-
bm.recordTransaction(makeTx({ type: 'escrow_lock', amountUSD: 5 }));
|
|
116
|
-
bm.recordTransaction(makeTx({ type: 'escrow_lock', amountUSD: 15 }));
|
|
117
|
-
bm.recordTransaction(makeTx({ type: 'escrow_release', amountUSD: 100 }));
|
|
118
|
-
|
|
119
|
-
expect(bm.getTotalSpending()).toBe(20);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should filter by since timestamp', () => {
|
|
123
|
-
const bm = new BalanceManager();
|
|
124
|
-
const now = Date.now();
|
|
125
|
-
bm.recordTransaction(makeTx({ type: 'escrow_release', amountUSD: 10, timestamp: now - 2000 }));
|
|
126
|
-
bm.recordTransaction(makeTx({ type: 'escrow_release', amountUSD: 20, timestamp: now - 500 }));
|
|
127
|
-
|
|
128
|
-
expect(bm.getTotalEarnings(now - 1000)).toBe(20);
|
|
129
|
-
expect(bm.getTotalEarnings()).toBe(30);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('BalanceManager.save / load', () => {
|
|
134
|
-
it('should persist and restore transactions', async () => {
|
|
135
|
-
const dir = tmpDir();
|
|
136
|
-
dirsToClean.push(dir);
|
|
137
|
-
|
|
138
|
-
const bm1 = new BalanceManager();
|
|
139
|
-
bm1.recordTransaction(makeTx({ txId: 'saved-tx', amountUSD: 42 }));
|
|
140
|
-
await bm1.save(dir);
|
|
141
|
-
|
|
142
|
-
const bm2 = new BalanceManager();
|
|
143
|
-
await bm2.load(dir);
|
|
144
|
-
|
|
145
|
-
const history = bm2.getTransactionHistory();
|
|
146
|
-
expect(history).toHaveLength(1);
|
|
147
|
-
expect(history[0]!.txId).toBe('saved-tx');
|
|
148
|
-
expect(history[0]!.amountUSD).toBe(42);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should start fresh if load file does not exist', async () => {
|
|
152
|
-
const bm = new BalanceManager();
|
|
153
|
-
await bm.load('/nonexistent/path');
|
|
154
|
-
expect(bm.getTransactionHistory()).toEqual([]);
|
|
155
|
-
});
|
|
156
|
-
});
|
package/tests/bootstrap.test.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
OFFICIAL_BOOTSTRAP_NODES,
|
|
4
|
-
parseBootstrapList,
|
|
5
|
-
mergeBootstrapNodes,
|
|
6
|
-
toBootstrapConfig,
|
|
7
|
-
type BootstrapNode,
|
|
8
|
-
} from '../src/discovery/bootstrap.js';
|
|
9
|
-
|
|
10
|
-
describe('OFFICIAL_BOOTSTRAP_NODES', () => {
|
|
11
|
-
it('should contain at least one node', () => {
|
|
12
|
-
expect(OFFICIAL_BOOTSTRAP_NODES.length).toBeGreaterThan(0);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should have valid host and port for every node', () => {
|
|
16
|
-
for (const node of OFFICIAL_BOOTSTRAP_NODES) {
|
|
17
|
-
expect(node.host).toBeTruthy();
|
|
18
|
-
expect(node.port).toBeGreaterThanOrEqual(1);
|
|
19
|
-
expect(node.port).toBeLessThanOrEqual(65535);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('parseBootstrapList', () => {
|
|
25
|
-
it('should parse valid host:port entries', () => {
|
|
26
|
-
const result = parseBootstrapList(['example.com:6881', '10.0.0.1:8080']);
|
|
27
|
-
expect(result).toEqual([
|
|
28
|
-
{ host: 'example.com', port: 6881 },
|
|
29
|
-
{ host: '10.0.0.1', port: 8080 },
|
|
30
|
-
]);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should parse IPv6 addresses with port', () => {
|
|
34
|
-
const result = parseBootstrapList(['::1:6881']);
|
|
35
|
-
expect(result).toHaveLength(1);
|
|
36
|
-
expect(result[0]!.host).toBe('::1');
|
|
37
|
-
expect(result[0]!.port).toBe(6881);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should throw on missing port', () => {
|
|
41
|
-
expect(() => parseBootstrapList(['example.com'])).toThrow('missing port');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should throw on invalid port (0)', () => {
|
|
45
|
-
expect(() => parseBootstrapList(['example.com:0'])).toThrow('Invalid port');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should throw on port > 65535', () => {
|
|
49
|
-
expect(() => parseBootstrapList(['example.com:99999'])).toThrow('Invalid port');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should throw on non-numeric port', () => {
|
|
53
|
-
expect(() => parseBootstrapList(['example.com:abc'])).toThrow('Invalid port');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should handle an empty array', () => {
|
|
57
|
-
expect(parseBootstrapList([])).toEqual([]);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('mergeBootstrapNodes', () => {
|
|
62
|
-
it('should combine two disjoint lists', () => {
|
|
63
|
-
const official: BootstrapNode[] = [{ host: 'a.com', port: 1 }];
|
|
64
|
-
const user: BootstrapNode[] = [{ host: 'b.com', port: 2 }];
|
|
65
|
-
const result = mergeBootstrapNodes(official, user);
|
|
66
|
-
expect(result).toHaveLength(2);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should deduplicate by host:port', () => {
|
|
70
|
-
const official: BootstrapNode[] = [{ host: 'a.com', port: 1, label: 'Official' }];
|
|
71
|
-
const user: BootstrapNode[] = [{ host: 'a.com', port: 1, label: 'User' }];
|
|
72
|
-
const result = mergeBootstrapNodes(official, user);
|
|
73
|
-
expect(result).toHaveLength(1);
|
|
74
|
-
// Official entry comes first and should win
|
|
75
|
-
expect(result[0]!.label).toBe('Official');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should keep user nodes that have different ports', () => {
|
|
79
|
-
const official: BootstrapNode[] = [{ host: 'a.com', port: 1 }];
|
|
80
|
-
const user: BootstrapNode[] = [{ host: 'a.com', port: 2 }];
|
|
81
|
-
const result = mergeBootstrapNodes(official, user);
|
|
82
|
-
expect(result).toHaveLength(2);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should return empty when both inputs are empty', () => {
|
|
86
|
-
expect(mergeBootstrapNodes([], [])).toEqual([]);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('toBootstrapConfig', () => {
|
|
91
|
-
it('should strip labels and return only host/port', () => {
|
|
92
|
-
const nodes: BootstrapNode[] = [
|
|
93
|
-
{ host: 'a.com', port: 1, label: 'A' },
|
|
94
|
-
{ host: 'b.com', port: 2 },
|
|
95
|
-
];
|
|
96
|
-
const result = toBootstrapConfig(nodes);
|
|
97
|
-
expect(result).toEqual([
|
|
98
|
-
{ host: 'a.com', port: 1 },
|
|
99
|
-
{ host: 'b.com', port: 2 },
|
|
100
|
-
]);
|
|
101
|
-
// Ensure label is not present
|
|
102
|
-
expect(result[0]).not.toHaveProperty('label');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should return empty array for empty input', () => {
|
|
106
|
-
expect(toBootstrapConfig([])).toEqual([]);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { BuyerPaymentManager, type BuyerPaymentConfig } from '../src/payments/buyer-payment-manager.js';
|
|
3
|
-
import type { PaymentMux } from '../src/p2p/payment-mux.js';
|
|
4
|
-
import type { Identity } from '../src/p2p/identity.js';
|
|
5
|
-
import type {
|
|
6
|
-
SessionLockConfirmPayload,
|
|
7
|
-
SessionLockRejectPayload,
|
|
8
|
-
SellerReceiptPayload,
|
|
9
|
-
TopUpRequestPayload,
|
|
10
|
-
} from '../src/types/protocol.js';
|
|
11
|
-
import * as ed from '@noble/ed25519';
|
|
12
|
-
|
|
13
|
-
// --- Helpers ---
|
|
14
|
-
|
|
15
|
-
async function createTestIdentity(): Promise<Identity> {
|
|
16
|
-
const privateKey = ed.utils.randomPrivateKey();
|
|
17
|
-
const publicKey = await ed.getPublicKeyAsync(privateKey);
|
|
18
|
-
const peerId = Array.from(publicKey)
|
|
19
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
20
|
-
.join('');
|
|
21
|
-
return { peerId: peerId as any, privateKey, publicKey };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function createMockPaymentMux(): PaymentMux & {
|
|
25
|
-
_sentLockAuths: any[];
|
|
26
|
-
_sentBuyerAcks: any[];
|
|
27
|
-
_sentSessionEnds: any[];
|
|
28
|
-
_sentTopUpAuths: any[];
|
|
29
|
-
} {
|
|
30
|
-
const mux = {
|
|
31
|
-
_sentLockAuths: [] as any[],
|
|
32
|
-
_sentBuyerAcks: [] as any[],
|
|
33
|
-
_sentSessionEnds: [] as any[],
|
|
34
|
-
_sentTopUpAuths: [] as any[],
|
|
35
|
-
sendSessionLockAuth: vi.fn(function (this: any, payload: any) {
|
|
36
|
-
this._sentLockAuths.push(payload);
|
|
37
|
-
}),
|
|
38
|
-
sendBuyerAck: vi.fn(function (this: any, payload: any) {
|
|
39
|
-
this._sentBuyerAcks.push(payload);
|
|
40
|
-
}),
|
|
41
|
-
sendSessionEnd: vi.fn(function (this: any, payload: any) {
|
|
42
|
-
this._sentSessionEnds.push(payload);
|
|
43
|
-
}),
|
|
44
|
-
sendTopUpAuth: vi.fn(function (this: any, payload: any) {
|
|
45
|
-
this._sentTopUpAuths.push(payload);
|
|
46
|
-
}),
|
|
47
|
-
// Unused but required by type
|
|
48
|
-
sendSessionLockConfirm: vi.fn(),
|
|
49
|
-
sendSessionLockReject: vi.fn(),
|
|
50
|
-
sendSellerReceipt: vi.fn(),
|
|
51
|
-
sendTopUpRequest: vi.fn(),
|
|
52
|
-
sendDisputeNotify: vi.fn(),
|
|
53
|
-
onSessionLockAuth: vi.fn(),
|
|
54
|
-
onSessionLockConfirm: vi.fn(),
|
|
55
|
-
onSessionLockReject: vi.fn(),
|
|
56
|
-
onSellerReceipt: vi.fn(),
|
|
57
|
-
onBuyerAck: vi.fn(),
|
|
58
|
-
onSessionEnd: vi.fn(),
|
|
59
|
-
onTopUpRequest: vi.fn(),
|
|
60
|
-
onTopUpAuth: vi.fn(),
|
|
61
|
-
onDisputeNotify: vi.fn(),
|
|
62
|
-
handleFrame: vi.fn(),
|
|
63
|
-
} as unknown as PaymentMux & {
|
|
64
|
-
_sentLockAuths: any[];
|
|
65
|
-
_sentBuyerAcks: any[];
|
|
66
|
-
_sentSessionEnds: any[];
|
|
67
|
-
_sentTopUpAuths: any[];
|
|
68
|
-
};
|
|
69
|
-
return mux;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const DEFAULT_CONFIG: BuyerPaymentConfig = {
|
|
73
|
-
defaultLockAmountUSDC: '1000000',
|
|
74
|
-
rpcUrl: 'http://127.0.0.1:8545',
|
|
75
|
-
contractAddress: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
|
|
76
|
-
usdcAddress: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
|
|
77
|
-
autoAck: true,
|
|
78
|
-
autoTopUp: true,
|
|
79
|
-
maxSessionBudgetUSDC: '10000000',
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const SELLER_PEER_ID = 'seller-peer-0123456789abcdef';
|
|
83
|
-
const SELLER_EVM_ADDRESS = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8';
|
|
84
|
-
|
|
85
|
-
// --- Tests ---
|
|
86
|
-
|
|
87
|
-
describe('BuyerPaymentManager', () => {
|
|
88
|
-
let identity: Identity;
|
|
89
|
-
let manager: BuyerPaymentManager;
|
|
90
|
-
let mux: ReturnType<typeof createMockPaymentMux>;
|
|
91
|
-
|
|
92
|
-
beforeEach(async () => {
|
|
93
|
-
identity = await createTestIdentity();
|
|
94
|
-
manager = new BuyerPaymentManager(identity, DEFAULT_CONFIG);
|
|
95
|
-
mux = createMockPaymentMux();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('initiateLock', () => {
|
|
99
|
-
it('sends SessionLockAuth with correct session ID and amount', async () => {
|
|
100
|
-
const sessionId = await manager.initiateLock(
|
|
101
|
-
SELLER_PEER_ID,
|
|
102
|
-
SELLER_EVM_ADDRESS,
|
|
103
|
-
mux,
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
expect(sessionId).toMatch(/^0x[0-9a-f]{64}$/);
|
|
107
|
-
expect(mux.sendSessionLockAuth).toHaveBeenCalledTimes(1);
|
|
108
|
-
|
|
109
|
-
const sentPayload = mux._sentLockAuths[0]!;
|
|
110
|
-
expect(sentPayload.sessionId).toBe(sessionId);
|
|
111
|
-
expect(sentPayload.lockedAmount).toBe('1000000');
|
|
112
|
-
expect(typeof sentPayload.buyerSig).toBe('string');
|
|
113
|
-
expect(sentPayload.buyerSig.length).toBeGreaterThan(0);
|
|
114
|
-
|
|
115
|
-
// Session should be in pending state
|
|
116
|
-
const session = manager.getSession(SELLER_PEER_ID);
|
|
117
|
-
expect(session).toBeDefined();
|
|
118
|
-
expect(session!.status).toBe('pending');
|
|
119
|
-
expect(session!.lockedAmount).toBe(1000000n);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('uses custom lock amount when specified', async () => {
|
|
123
|
-
await manager.initiateLock(
|
|
124
|
-
SELLER_PEER_ID,
|
|
125
|
-
SELLER_EVM_ADDRESS,
|
|
126
|
-
mux,
|
|
127
|
-
'5000000',
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const sentPayload = mux._sentLockAuths[0]!;
|
|
131
|
-
expect(sentPayload.lockedAmount).toBe('5000000');
|
|
132
|
-
|
|
133
|
-
const session = manager.getSession(SELLER_PEER_ID);
|
|
134
|
-
expect(session!.lockedAmount).toBe(5000000n);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('handleLockConfirm', () => {
|
|
139
|
-
it('marks session as confirmed with tx signature', async () => {
|
|
140
|
-
const sessionId = await manager.initiateLock(
|
|
141
|
-
SELLER_PEER_ID,
|
|
142
|
-
SELLER_EVM_ADDRESS,
|
|
143
|
-
mux,
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
const payload: SessionLockConfirmPayload = {
|
|
147
|
-
sessionId,
|
|
148
|
-
txSignature: '0xabc123def456',
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
manager.handleLockConfirm(SELLER_PEER_ID, payload);
|
|
152
|
-
|
|
153
|
-
const session = manager.getSession(SELLER_PEER_ID);
|
|
154
|
-
expect(session!.status).toBe('confirmed');
|
|
155
|
-
expect(session!.txSignature).toBe('0xabc123def456');
|
|
156
|
-
expect(manager.isLockConfirmed(SELLER_PEER_ID)).toBe(true);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('ignores confirmation for unknown seller', () => {
|
|
160
|
-
// Should not throw
|
|
161
|
-
manager.handleLockConfirm('unknown-peer', {
|
|
162
|
-
sessionId: '0x' + 'a'.repeat(64),
|
|
163
|
-
txSignature: '0xabc',
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('handleLockReject', () => {
|
|
169
|
-
it('removes session on rejection', async () => {
|
|
170
|
-
const sessionId = await manager.initiateLock(
|
|
171
|
-
SELLER_PEER_ID,
|
|
172
|
-
SELLER_EVM_ADDRESS,
|
|
173
|
-
mux,
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const payload: SessionLockRejectPayload = {
|
|
177
|
-
sessionId,
|
|
178
|
-
reason: 'Insufficient buyer balance',
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
manager.handleLockReject(SELLER_PEER_ID, payload);
|
|
182
|
-
|
|
183
|
-
expect(manager.getSession(SELLER_PEER_ID)).toBeUndefined();
|
|
184
|
-
expect(manager.isLockRejected(SELLER_PEER_ID)).toBe(true);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('handleSellerReceipt (auto-ack)', () => {
|
|
189
|
-
it('auto-acknowledges receipt with Ed25519 signature', async () => {
|
|
190
|
-
const sessionId = await manager.initiateLock(
|
|
191
|
-
SELLER_PEER_ID,
|
|
192
|
-
SELLER_EVM_ADDRESS,
|
|
193
|
-
mux,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// Confirm the lock first
|
|
197
|
-
manager.handleLockConfirm(SELLER_PEER_ID, {
|
|
198
|
-
sessionId,
|
|
199
|
-
txSignature: '0xabc',
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const receipt: SellerReceiptPayload = {
|
|
203
|
-
sessionId,
|
|
204
|
-
runningTotal: '50000',
|
|
205
|
-
requestCount: 1,
|
|
206
|
-
responseHash: 'c'.repeat(64),
|
|
207
|
-
sellerSig: 'd'.repeat(128),
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
await manager.handleSellerReceipt(SELLER_PEER_ID, receipt, mux);
|
|
211
|
-
|
|
212
|
-
expect(mux.sendBuyerAck).toHaveBeenCalledTimes(1);
|
|
213
|
-
const ackPayload = mux._sentBuyerAcks[0]!;
|
|
214
|
-
expect(ackPayload.sessionId).toBe(sessionId);
|
|
215
|
-
expect(ackPayload.runningTotal).toBe('50000');
|
|
216
|
-
expect(ackPayload.requestCount).toBe(1);
|
|
217
|
-
expect(typeof ackPayload.buyerSig).toBe('string');
|
|
218
|
-
expect(ackPayload.buyerSig.length).toBeGreaterThan(0);
|
|
219
|
-
|
|
220
|
-
// Session should update running total
|
|
221
|
-
const session = manager.getSession(SELLER_PEER_ID);
|
|
222
|
-
expect(session!.lastRunningTotal).toBe(50000n);
|
|
223
|
-
expect(session!.lastRequestCount).toBe(1);
|
|
224
|
-
expect(session!.status).toBe('active');
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe('endSession', () => {
|
|
229
|
-
it('sends SessionEnd with ECDSA settlement signature', async () => {
|
|
230
|
-
const sessionId = await manager.initiateLock(
|
|
231
|
-
SELLER_PEER_ID,
|
|
232
|
-
SELLER_EVM_ADDRESS,
|
|
233
|
-
mux,
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
// Confirm and process a receipt
|
|
237
|
-
manager.handleLockConfirm(SELLER_PEER_ID, {
|
|
238
|
-
sessionId,
|
|
239
|
-
txSignature: '0xabc',
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
await manager.handleSellerReceipt(SELLER_PEER_ID, {
|
|
243
|
-
sessionId,
|
|
244
|
-
runningTotal: '100000',
|
|
245
|
-
requestCount: 3,
|
|
246
|
-
responseHash: 'c'.repeat(64),
|
|
247
|
-
sellerSig: 'd'.repeat(128),
|
|
248
|
-
}, mux);
|
|
249
|
-
|
|
250
|
-
await manager.endSession(SELLER_PEER_ID, mux, 90);
|
|
251
|
-
|
|
252
|
-
expect(mux.sendSessionEnd).toHaveBeenCalledTimes(1);
|
|
253
|
-
const endPayload = mux._sentSessionEnds[0]!;
|
|
254
|
-
expect(endPayload.sessionId).toBe(sessionId);
|
|
255
|
-
expect(endPayload.runningTotal).toBe('100000');
|
|
256
|
-
expect(endPayload.requestCount).toBe(3);
|
|
257
|
-
expect(endPayload.score).toBe(90);
|
|
258
|
-
expect(typeof endPayload.buyerSig).toBe('string');
|
|
259
|
-
expect(endPayload.buyerSig.length).toBeGreaterThan(0);
|
|
260
|
-
|
|
261
|
-
// Session should be ended
|
|
262
|
-
const session = manager.getSession(SELLER_PEER_ID);
|
|
263
|
-
expect(session!.status).toBe('ended');
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe('handleTopUpRequest (sufficient balance)', () => {
|
|
268
|
-
it('auto-approves top-up when budget allows and balance sufficient', async () => {
|
|
269
|
-
const sessionId = await manager.initiateLock(
|
|
270
|
-
SELLER_PEER_ID,
|
|
271
|
-
SELLER_EVM_ADDRESS,
|
|
272
|
-
mux,
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
manager.handleLockConfirm(SELLER_PEER_ID, {
|
|
276
|
-
sessionId,
|
|
277
|
-
txSignature: '0xabc',
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Mock the escrow client to return sufficient balance
|
|
281
|
-
const mockGetBuyerAccount = vi.fn().mockResolvedValue({
|
|
282
|
-
deposited: 20000000n,
|
|
283
|
-
committed: 1000000n,
|
|
284
|
-
available: 19000000n,
|
|
285
|
-
});
|
|
286
|
-
(manager as any)._escrowClient = {
|
|
287
|
-
...manager.escrowClient,
|
|
288
|
-
getBuyerAccount: mockGetBuyerAccount,
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const request: TopUpRequestPayload = {
|
|
292
|
-
sessionId,
|
|
293
|
-
additionalAmount: '2000000',
|
|
294
|
-
currentRunningTotal: '800000',
|
|
295
|
-
currentLockedAmount: '1000000',
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
await manager.handleTopUpRequest(SELLER_PEER_ID, request, mux);
|
|
299
|
-
|
|
300
|
-
expect(mux.sendTopUpAuth).toHaveBeenCalledTimes(1);
|
|
301
|
-
const authPayload = mux._sentTopUpAuths[0]!;
|
|
302
|
-
expect(authPayload.sessionId).toBe(sessionId);
|
|
303
|
-
expect(authPayload.additionalAmount).toBe('2000000');
|
|
304
|
-
expect(typeof authPayload.buyerSig).toBe('string');
|
|
305
|
-
|
|
306
|
-
// Session locked amount should be updated
|
|
307
|
-
const session = manager.getSession(SELLER_PEER_ID);
|
|
308
|
-
expect(session!.lockedAmount).toBe(3000000n); // 1M + 2M
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
describe('handleTopUpRequest (insufficient balance)', () => {
|
|
313
|
-
it('ends session when balance is insufficient for top-up', async () => {
|
|
314
|
-
const sessionId = await manager.initiateLock(
|
|
315
|
-
SELLER_PEER_ID,
|
|
316
|
-
SELLER_EVM_ADDRESS,
|
|
317
|
-
mux,
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
manager.handleLockConfirm(SELLER_PEER_ID, {
|
|
321
|
-
sessionId,
|
|
322
|
-
txSignature: '0xabc',
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// Mock the escrow client to return insufficient balance
|
|
326
|
-
const mockGetBuyerAccount = vi.fn().mockResolvedValue({
|
|
327
|
-
deposited: 1000000n,
|
|
328
|
-
committed: 1000000n,
|
|
329
|
-
available: 0n,
|
|
330
|
-
});
|
|
331
|
-
(manager as any)._escrowClient = {
|
|
332
|
-
...manager.escrowClient,
|
|
333
|
-
getBuyerAccount: mockGetBuyerAccount,
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const request: TopUpRequestPayload = {
|
|
337
|
-
sessionId,
|
|
338
|
-
additionalAmount: '2000000',
|
|
339
|
-
currentRunningTotal: '800000',
|
|
340
|
-
currentLockedAmount: '1000000',
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
await manager.handleTopUpRequest(SELLER_PEER_ID, request, mux);
|
|
344
|
-
|
|
345
|
-
// Should NOT send top-up auth
|
|
346
|
-
expect(mux.sendTopUpAuth).not.toHaveBeenCalled();
|
|
347
|
-
|
|
348
|
-
// Should end the session instead
|
|
349
|
-
expect(mux.sendSessionEnd).toHaveBeenCalledTimes(1);
|
|
350
|
-
const endPayload = mux._sentSessionEnds[0]!;
|
|
351
|
-
expect(endPayload.sessionId).toBe(sessionId);
|
|
352
|
-
expect(endPayload.score).toBe(80); // default score
|
|
353
|
-
|
|
354
|
-
const session = manager.getSession(SELLER_PEER_ID);
|
|
355
|
-
expect(session!.status).toBe('ended');
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import * as ed from '@noble/ed25519';
|
|
3
|
-
import { toPeerId } from '../src/types/peer.js';
|
|
4
|
-
import { bytesToHex } from '../src/utils/hex.js';
|
|
5
|
-
import {
|
|
6
|
-
NonceReplayGuard,
|
|
7
|
-
buildConnectionAuthEnvelope,
|
|
8
|
-
verifyConnectionAuthEnvelope,
|
|
9
|
-
} from '../src/p2p/connection-auth.js';
|
|
10
|
-
|
|
11
|
-
async function createIdentity(): Promise<{ peerId: string; privateKey: Uint8Array }> {
|
|
12
|
-
const privateKey = ed.utils.randomPrivateKey();
|
|
13
|
-
const publicKey = await ed.getPublicKeyAsync(privateKey);
|
|
14
|
-
const peerId = toPeerId(bytesToHex(publicKey));
|
|
15
|
-
return { peerId, privateKey };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe('connection-auth', () => {
|
|
19
|
-
it('accepts valid signed intro auth', async () => {
|
|
20
|
-
const { peerId, privateKey } = await createIdentity();
|
|
21
|
-
const nowMs = 1_700_000_000_000;
|
|
22
|
-
const auth = buildConnectionAuthEnvelope('intro', peerId, privateKey, nowMs);
|
|
23
|
-
|
|
24
|
-
const result = verifyConnectionAuthEnvelope({
|
|
25
|
-
type: 'intro',
|
|
26
|
-
auth,
|
|
27
|
-
nowMs,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
expect(result.ok).toBe(true);
|
|
31
|
-
expect(result.peerId).toBe(peerId);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('rejects payload type mismatch', async () => {
|
|
35
|
-
const { peerId, privateKey } = await createIdentity();
|
|
36
|
-
const nowMs = 1_700_000_000_000;
|
|
37
|
-
const auth = buildConnectionAuthEnvelope('intro', peerId, privateKey, nowMs);
|
|
38
|
-
|
|
39
|
-
const result = verifyConnectionAuthEnvelope({
|
|
40
|
-
type: 'hello',
|
|
41
|
-
auth,
|
|
42
|
-
nowMs,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
expect(result.ok).toBe(false);
|
|
46
|
-
expect(result.reason).toContain('signature');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('rejects stale auth timestamps', async () => {
|
|
50
|
-
const { peerId, privateKey } = await createIdentity();
|
|
51
|
-
const auth = buildConnectionAuthEnvelope('hello', peerId, privateKey, 1_000);
|
|
52
|
-
|
|
53
|
-
const result = verifyConnectionAuthEnvelope({
|
|
54
|
-
type: 'hello',
|
|
55
|
-
auth,
|
|
56
|
-
nowMs: 100_000,
|
|
57
|
-
maxSkewMs: 30_000,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
expect(result.ok).toBe(false);
|
|
61
|
-
expect(result.reason).toContain('timestamp');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('rejects replayed nonces when replay guard is enabled', async () => {
|
|
65
|
-
const { peerId, privateKey } = await createIdentity();
|
|
66
|
-
const guard = new NonceReplayGuard();
|
|
67
|
-
const nowMs = 1_700_000_000_000;
|
|
68
|
-
const auth = buildConnectionAuthEnvelope('intro', peerId, privateKey, nowMs);
|
|
69
|
-
|
|
70
|
-
const first = verifyConnectionAuthEnvelope({
|
|
71
|
-
type: 'intro',
|
|
72
|
-
auth,
|
|
73
|
-
nowMs,
|
|
74
|
-
replayGuard: guard,
|
|
75
|
-
});
|
|
76
|
-
const second = verifyConnectionAuthEnvelope({
|
|
77
|
-
type: 'intro',
|
|
78
|
-
auth,
|
|
79
|
-
nowMs,
|
|
80
|
-
replayGuard: guard,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(first.ok).toBe(true);
|
|
84
|
-
expect(second.ok).toBe(false);
|
|
85
|
-
expect(second.reason).toContain('replayed');
|
|
86
|
-
});
|
|
87
|
-
});
|