@veridex/agentic-payments 0.1.1-beta.1
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/CHANGELOG.md +108 -0
- package/MIGRATION.md +307 -0
- package/README.md +395 -0
- package/dist/index.d.mts +2327 -0
- package/dist/index.d.ts +2327 -0
- package/dist/index.js +5815 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5759 -0
- package/dist/index.mjs.map +1 -0
- package/examples/basic-agent.ts +126 -0
- package/examples/mcp-claude.ts +75 -0
- package/examples/ucp-checkout.ts +92 -0
- package/examples/x402-integration.ts +75 -0
- package/package.json +36 -0
- package/src/AgentWallet.ts +432 -0
- package/src/chains/AptosChainClient.ts +29 -0
- package/src/chains/ChainClient.ts +73 -0
- package/src/chains/ChainClientFactory.ts +113 -0
- package/src/chains/EVMChainClient.ts +39 -0
- package/src/chains/SolanaChainClient.ts +37 -0
- package/src/chains/StarknetChainClient.ts +36 -0
- package/src/chains/SuiChainClient.ts +28 -0
- package/src/index.ts +83 -0
- package/src/mcp/MCPServer.ts +73 -0
- package/src/mcp/schemas.ts +60 -0
- package/src/monitoring/AlertManager.ts +258 -0
- package/src/monitoring/AuditLogger.ts +86 -0
- package/src/monitoring/BalanceCache.ts +44 -0
- package/src/monitoring/ComplianceExporter.ts +52 -0
- package/src/oracle/PythFeeds.ts +60 -0
- package/src/oracle/PythOracle.ts +121 -0
- package/src/performance/ConnectionPool.ts +217 -0
- package/src/performance/NonceManager.ts +91 -0
- package/src/performance/ParallelRouteFinder.ts +438 -0
- package/src/performance/TransactionPoller.ts +201 -0
- package/src/performance/TransactionQueue.ts +565 -0
- package/src/performance/index.ts +46 -0
- package/src/react/hooks.ts +298 -0
- package/src/routing/BridgeOrchestrator.ts +18 -0
- package/src/routing/CrossChainRouter.ts +501 -0
- package/src/routing/DEXAggregator.ts +448 -0
- package/src/routing/FeeEstimator.ts +43 -0
- package/src/session/SessionKeyManager.ts +312 -0
- package/src/session/SessionStorage.ts +80 -0
- package/src/session/SpendingTracker.ts +71 -0
- package/src/types/agent.ts +105 -0
- package/src/types/errors.ts +115 -0
- package/src/types/mcp.ts +22 -0
- package/src/types/ucp.ts +47 -0
- package/src/types/x402.ts +170 -0
- package/src/ucp/CapabilityNegotiator.ts +44 -0
- package/src/ucp/CredentialProvider.ts +73 -0
- package/src/ucp/PaymentTokenizer.ts +169 -0
- package/src/ucp/TransportAdapter.ts +18 -0
- package/src/ucp/UCPClient.ts +143 -0
- package/src/x402/NonceManager.ts +26 -0
- package/src/x402/PaymentParser.ts +225 -0
- package/src/x402/PaymentSigner.ts +305 -0
- package/src/x402/X402Client.ts +364 -0
- package/src/x402/adapters/CronosFacilitatorAdapter.ts +109 -0
- package/tests/alerts.test.ts +208 -0
- package/tests/chains.test.ts +242 -0
- package/tests/integration.test.ts +315 -0
- package/tests/monitoring.test.ts +435 -0
- package/tests/performance.test.ts +303 -0
- package/tests/property.test.ts +186 -0
- package/tests/react-hooks.test.ts +262 -0
- package/tests/session.test.ts +376 -0
- package/tests/ucp.test.ts +253 -0
- package/tests/x402.test.ts +385 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +10 -0
- package/vitest.config.ts +16 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain Client Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for multi-chain client implementations and the ChainClientFactory.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
8
|
+
import { ChainClientFactory } from '../src/chains/ChainClientFactory';
|
|
9
|
+
import { AgentChainClient } from '../src/chains/ChainClient';
|
|
10
|
+
|
|
11
|
+
// Mock the @veridex/sdk modules
|
|
12
|
+
vi.mock('@veridex/sdk', async () => {
|
|
13
|
+
return {
|
|
14
|
+
getChainPreset: vi.fn().mockImplementation((chain: string) => {
|
|
15
|
+
const presets: Record<string, any> = {
|
|
16
|
+
'base': {
|
|
17
|
+
type: 'evm',
|
|
18
|
+
testnet: {
|
|
19
|
+
chainId: 84532,
|
|
20
|
+
wormholeChainId: 30,
|
|
21
|
+
rpcUrl: 'https://sepolia.base.org',
|
|
22
|
+
name: 'Base Sepolia',
|
|
23
|
+
explorerUrl: 'https://sepolia.basescan.org',
|
|
24
|
+
contracts: {
|
|
25
|
+
hub: '0x1234567890123456789012345678901234567890',
|
|
26
|
+
wormholeCoreBridge: '0x0987654321098765432109876543210987654321',
|
|
27
|
+
vaultFactory: '0xfactory',
|
|
28
|
+
vaultImplementation: '0ximpl',
|
|
29
|
+
tokenBridge: '0xbridge',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
mainnet: {
|
|
33
|
+
chainId: 8453,
|
|
34
|
+
wormholeChainId: 30,
|
|
35
|
+
rpcUrl: 'https://mainnet.base.org',
|
|
36
|
+
name: 'Base',
|
|
37
|
+
explorerUrl: 'https://basescan.org',
|
|
38
|
+
contracts: {
|
|
39
|
+
hub: '0x1234567890123456789012345678901234567890',
|
|
40
|
+
wormholeCoreBridge: '0x0987654321098765432109876543210987654321',
|
|
41
|
+
vaultFactory: '0xfactory',
|
|
42
|
+
vaultImplementation: '0ximpl',
|
|
43
|
+
tokenBridge: '0xbridge',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
'solana': {
|
|
48
|
+
type: 'solana',
|
|
49
|
+
testnet: {
|
|
50
|
+
chainId: 0,
|
|
51
|
+
wormholeChainId: 1,
|
|
52
|
+
rpcUrl: 'https://api.devnet.solana.com',
|
|
53
|
+
name: 'Solana Devnet',
|
|
54
|
+
contracts: {
|
|
55
|
+
hub: 'programId123',
|
|
56
|
+
wormholeCoreBridge: 'wormhole123',
|
|
57
|
+
tokenBridge: 'tokenbridge123',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
mainnet: {
|
|
61
|
+
chainId: 0,
|
|
62
|
+
wormholeChainId: 1,
|
|
63
|
+
rpcUrl: 'https://api.mainnet-beta.solana.com',
|
|
64
|
+
name: 'Solana',
|
|
65
|
+
contracts: {
|
|
66
|
+
hub: 'programId123',
|
|
67
|
+
wormholeCoreBridge: 'wormhole123',
|
|
68
|
+
tokenBridge: 'tokenbridge123',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
'aptos': {
|
|
73
|
+
type: 'aptos',
|
|
74
|
+
testnet: {
|
|
75
|
+
chainId: 0,
|
|
76
|
+
wormholeChainId: 22,
|
|
77
|
+
rpcUrl: 'https://fullnode.testnet.aptoslabs.com',
|
|
78
|
+
name: 'Aptos Testnet',
|
|
79
|
+
contracts: {
|
|
80
|
+
hub: '0xmodule',
|
|
81
|
+
wormholeCoreBridge: '0xwormhole',
|
|
82
|
+
tokenBridge: '0xtokenbridge',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
mainnet: {
|
|
86
|
+
chainId: 0,
|
|
87
|
+
wormholeChainId: 22,
|
|
88
|
+
rpcUrl: 'https://fullnode.mainnet.aptoslabs.com',
|
|
89
|
+
name: 'Aptos',
|
|
90
|
+
contracts: {
|
|
91
|
+
hub: '0xmodule',
|
|
92
|
+
wormholeCoreBridge: '0xwormhole',
|
|
93
|
+
tokenBridge: '0xtokenbridge',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
'sui': {
|
|
98
|
+
type: 'sui',
|
|
99
|
+
testnet: {
|
|
100
|
+
chainId: 0,
|
|
101
|
+
wormholeChainId: 21,
|
|
102
|
+
rpcUrl: 'https://fullnode.testnet.sui.io',
|
|
103
|
+
name: 'Sui Testnet',
|
|
104
|
+
contracts: {
|
|
105
|
+
hub: '0xpackage',
|
|
106
|
+
wormholeCoreBridge: '0xwormhole',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
mainnet: {
|
|
110
|
+
chainId: 0,
|
|
111
|
+
wormholeChainId: 21,
|
|
112
|
+
rpcUrl: 'https://fullnode.mainnet.sui.io',
|
|
113
|
+
name: 'Sui',
|
|
114
|
+
contracts: {
|
|
115
|
+
hub: '0xpackage',
|
|
116
|
+
wormholeCoreBridge: '0xwormhole',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
'starknet': {
|
|
121
|
+
type: 'starknet',
|
|
122
|
+
testnet: {
|
|
123
|
+
chainId: 0,
|
|
124
|
+
wormholeChainId: 18,
|
|
125
|
+
rpcUrl: 'https://starknet-sepolia.public.blastapi.io',
|
|
126
|
+
name: 'Starknet Sepolia',
|
|
127
|
+
contracts: {
|
|
128
|
+
hub: '0xspoke',
|
|
129
|
+
wormholeCoreBridge: '0xbridge',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
mainnet: {
|
|
133
|
+
chainId: 0,
|
|
134
|
+
wormholeChainId: 18,
|
|
135
|
+
rpcUrl: 'https://starknet-mainnet.public.blastapi.io',
|
|
136
|
+
name: 'Starknet',
|
|
137
|
+
contracts: {
|
|
138
|
+
hub: '0xspoke',
|
|
139
|
+
wormholeCoreBridge: '0xbridge',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
return presets[chain] || presets['base'];
|
|
145
|
+
}),
|
|
146
|
+
ChainName: {},
|
|
147
|
+
NetworkType: {},
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Mock chain-specific clients
|
|
152
|
+
vi.mock('@veridex/sdk/chains/evm', () => ({
|
|
153
|
+
EVMClient: vi.fn().mockImplementation(() => ({
|
|
154
|
+
getConfig: () => ({ name: 'Base Sepolia', chainId: 84532 }),
|
|
155
|
+
getProvider: () => ({}),
|
|
156
|
+
})),
|
|
157
|
+
EVMClientConfig: {},
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
vi.mock('@veridex/sdk/chains/solana', () => ({
|
|
161
|
+
SolanaClient: vi.fn().mockImplementation(() => ({
|
|
162
|
+
getConfig: () => ({ name: 'Solana Devnet' }),
|
|
163
|
+
getConnection: () => ({}),
|
|
164
|
+
})),
|
|
165
|
+
SolanaClientConfig: {},
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
vi.mock('@veridex/sdk/chains/aptos', () => ({
|
|
169
|
+
AptosClient: vi.fn().mockImplementation(() => ({
|
|
170
|
+
getConfig: () => ({ name: 'Aptos Testnet' }),
|
|
171
|
+
getClient: () => ({}),
|
|
172
|
+
})),
|
|
173
|
+
AptosClientConfig: {},
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
vi.mock('@veridex/sdk/chains/sui', () => ({
|
|
177
|
+
SuiClient: vi.fn().mockImplementation(() => ({
|
|
178
|
+
getConfig: () => ({ name: 'Sui Testnet' }),
|
|
179
|
+
getClient: () => ({}),
|
|
180
|
+
})),
|
|
181
|
+
SuiClientConfig: {},
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
vi.mock('@veridex/sdk/chains/starknet', () => ({
|
|
185
|
+
StarknetClient: vi.fn().mockImplementation(() => ({
|
|
186
|
+
getConfig: () => ({ name: 'Starknet Sepolia' }),
|
|
187
|
+
getProvider: () => ({}),
|
|
188
|
+
})),
|
|
189
|
+
StarknetClientConfig: {},
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
describe('ChainClientFactory', () => {
|
|
193
|
+
describe('createClient', () => {
|
|
194
|
+
it('should create EVM client for base chain', () => {
|
|
195
|
+
const client = ChainClientFactory.createClient('base', 'testnet');
|
|
196
|
+
expect(client).toBeDefined();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should create Solana client', () => {
|
|
200
|
+
const client = ChainClientFactory.createClient('solana', 'testnet');
|
|
201
|
+
expect(client).toBeDefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should create Aptos client', () => {
|
|
205
|
+
const client = ChainClientFactory.createClient('aptos', 'testnet');
|
|
206
|
+
expect(client).toBeDefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should create Sui client', () => {
|
|
210
|
+
const client = ChainClientFactory.createClient('sui', 'testnet');
|
|
211
|
+
expect(client).toBeDefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should create Starknet client', () => {
|
|
215
|
+
const client = ChainClientFactory.createClient('starknet', 'testnet');
|
|
216
|
+
expect(client).toBeDefined();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should use mainnet when specified', () => {
|
|
220
|
+
const client = ChainClientFactory.createClient('base', 'mainnet');
|
|
221
|
+
expect(client).toBeDefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should use custom RPC URL when provided', () => {
|
|
225
|
+
const customRpc = 'https://custom-rpc.example.com';
|
|
226
|
+
const client = ChainClientFactory.createClient('base', 'testnet', customRpc);
|
|
227
|
+
expect(client).toBeDefined();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('AgentChainClient Interface', () => {
|
|
233
|
+
it('should have getNativeTokenPriceUSD method', () => {
|
|
234
|
+
const client = ChainClientFactory.createClient('base', 'testnet');
|
|
235
|
+
expect(typeof client.getNativeTokenPriceUSD).toBe('function');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should have getTokenPriceUSD method', () => {
|
|
239
|
+
const client = ChainClientFactory.createClient('base', 'testnet');
|
|
240
|
+
expect(typeof client.getTokenPriceUSD).toBe('function');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests for Agent SDK
|
|
3
|
+
*
|
|
4
|
+
* End-to-end tests verifying complete flows work correctly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
8
|
+
import { SessionKeyManager } from '../src/session/SessionKeyManager';
|
|
9
|
+
import { SpendingTracker } from '../src/session/SpendingTracker';
|
|
10
|
+
import { StoredSession, SessionKeyConfig } from '../src/session/SessionStorage';
|
|
11
|
+
import { PaymentTokenizer } from '../src/ucp/PaymentTokenizer';
|
|
12
|
+
import { AlertManager } from '../src/monitoring/AlertManager';
|
|
13
|
+
import { AuditLogger } from '../src/monitoring/AuditLogger';
|
|
14
|
+
|
|
15
|
+
// Mock the crypto functions from @veridex/sdk
|
|
16
|
+
vi.mock('@veridex/sdk', async () => {
|
|
17
|
+
const { ethers } = await import('ethers');
|
|
18
|
+
return {
|
|
19
|
+
generateSecp256k1KeyPair: vi.fn().mockImplementation(() => {
|
|
20
|
+
const wallet = ethers.Wallet.createRandom();
|
|
21
|
+
return {
|
|
22
|
+
publicKey: wallet.signingKey.publicKey,
|
|
23
|
+
privateKey: new Uint8Array(Buffer.from(wallet.privateKey.slice(2), 'hex'))
|
|
24
|
+
};
|
|
25
|
+
}),
|
|
26
|
+
computeSessionKeyHash: vi.fn().mockImplementation((pubKey) =>
|
|
27
|
+
ethers.keccak256(pubKey)
|
|
28
|
+
),
|
|
29
|
+
deriveEncryptionKey: vi.fn().mockResolvedValue({} as CryptoKey),
|
|
30
|
+
encrypt: vi.fn().mockImplementation((data: Uint8Array) =>
|
|
31
|
+
Promise.resolve(new Uint8Array([...data, 1, 2, 3]))
|
|
32
|
+
),
|
|
33
|
+
decrypt: vi.fn().mockImplementation((data: Uint8Array) =>
|
|
34
|
+
Promise.resolve(data.slice(0, data.length - 3))
|
|
35
|
+
),
|
|
36
|
+
VeridexSDK: class { },
|
|
37
|
+
createSDK: vi.fn(),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Helper to create mock sessions
|
|
42
|
+
function createMockSession(overrides: Partial<{
|
|
43
|
+
keyHash: string;
|
|
44
|
+
dailyLimitUSD: number;
|
|
45
|
+
perTransactionLimitUSD: number;
|
|
46
|
+
expiryTimestamp: number;
|
|
47
|
+
dailySpentUSD: number;
|
|
48
|
+
totalSpentUSD: number;
|
|
49
|
+
}> = {}): StoredSession {
|
|
50
|
+
const {
|
|
51
|
+
keyHash = '0x' + 'a'.repeat(64),
|
|
52
|
+
dailyLimitUSD = 100,
|
|
53
|
+
perTransactionLimitUSD = 25,
|
|
54
|
+
expiryTimestamp = Date.now() + 3600000,
|
|
55
|
+
dailySpentUSD = 0,
|
|
56
|
+
totalSpentUSD = 0,
|
|
57
|
+
} = overrides;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
keyHash,
|
|
61
|
+
encryptedPrivateKey: '0x' + 'b'.repeat(64),
|
|
62
|
+
publicKey: '0x04' + 'c'.repeat(128),
|
|
63
|
+
config: {
|
|
64
|
+
dailyLimitUSD,
|
|
65
|
+
perTransactionLimitUSD,
|
|
66
|
+
expiryTimestamp,
|
|
67
|
+
allowedChains: [30],
|
|
68
|
+
},
|
|
69
|
+
metadata: {
|
|
70
|
+
createdAt: Date.now(),
|
|
71
|
+
lastUsedAt: Date.now(),
|
|
72
|
+
totalSpentUSD,
|
|
73
|
+
dailySpentUSD,
|
|
74
|
+
dailyResetAt: Date.now() + 86400000,
|
|
75
|
+
transactionCount: 0,
|
|
76
|
+
},
|
|
77
|
+
masterKeyHash: '0x' + 'd'.repeat(64),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe('Integration Tests', () => {
|
|
82
|
+
describe('17.1: Agent creates session and makes payment', () => {
|
|
83
|
+
let sessionManager: SessionKeyManager;
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
sessionManager = new SessionKeyManager();
|
|
87
|
+
if (typeof localStorage !== 'undefined') {
|
|
88
|
+
localStorage.clear();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should create session and verify limits are enforced', async () => {
|
|
93
|
+
const masterKey = {
|
|
94
|
+
credentialId: 'test-credential-123',
|
|
95
|
+
publicKeyX: BigInt('0x' + '1'.repeat(64)),
|
|
96
|
+
publicKeyY: BigInt('0x' + '2'.repeat(64)),
|
|
97
|
+
keyHash: '0x' + 'a'.repeat(64),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const config: SessionKeyConfig = {
|
|
101
|
+
dailyLimitUSD: 100,
|
|
102
|
+
perTransactionLimitUSD: 25,
|
|
103
|
+
expiryTimestamp: Date.now() + 60 * 60 * 1000,
|
|
104
|
+
allowedChains: [30],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Create session
|
|
108
|
+
const session = await sessionManager.createSession(masterKey, config);
|
|
109
|
+
expect(session).toBeDefined();
|
|
110
|
+
expect(session.keyHash).toBeDefined();
|
|
111
|
+
|
|
112
|
+
// Check initial limits
|
|
113
|
+
const result1 = sessionManager.checkLimits(session, 20);
|
|
114
|
+
expect(result1.allowed).toBe(true);
|
|
115
|
+
|
|
116
|
+
// Record spending
|
|
117
|
+
await sessionManager.recordSpending(session, 20);
|
|
118
|
+
expect(session.metadata.dailySpentUSD).toBe(20);
|
|
119
|
+
|
|
120
|
+
// Check limits again - $85 exceeds per-transaction limit ($25)
|
|
121
|
+
const result2 = sessionManager.checkLimits(session, 85);
|
|
122
|
+
expect(result2.allowed).toBe(false);
|
|
123
|
+
expect(result2.reason).toContain('per-transaction limit');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should reject transactions exceeding per-transaction limit', async () => {
|
|
127
|
+
const session = createMockSession({
|
|
128
|
+
dailyLimitUSD: 100,
|
|
129
|
+
perTransactionLimitUSD: 25,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const result = sessionManager.checkLimits(session, 30);
|
|
133
|
+
expect(result.allowed).toBe(false);
|
|
134
|
+
expect(result.reason).toContain('per-transaction limit');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('17.2: Agent exceeds limit and receives error', () => {
|
|
139
|
+
it('should enforce spending limits and provide clear error', async () => {
|
|
140
|
+
const sessionManager = new SessionKeyManager();
|
|
141
|
+
const session = createMockSession({
|
|
142
|
+
dailyLimitUSD: 50,
|
|
143
|
+
perTransactionLimitUSD: 50,
|
|
144
|
+
dailySpentUSD: 45,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const result = sessionManager.checkLimits(session, 10);
|
|
148
|
+
|
|
149
|
+
expect(result.allowed).toBe(false);
|
|
150
|
+
expect(result.reason).toBeDefined();
|
|
151
|
+
expect(result.remainingDailyLimitUSD).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('17.3: Master key revokes session', () => {
|
|
156
|
+
it('should immediately invalidate session on revocation', async () => {
|
|
157
|
+
const sessionManager = new SessionKeyManager();
|
|
158
|
+
const masterKey = {
|
|
159
|
+
credentialId: 'test-credential',
|
|
160
|
+
publicKeyX: BigInt(1),
|
|
161
|
+
publicKeyY: BigInt(2),
|
|
162
|
+
keyHash: '0xmaster',
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const config: SessionKeyConfig = {
|
|
166
|
+
dailyLimitUSD: 100,
|
|
167
|
+
perTransactionLimitUSD: 25,
|
|
168
|
+
expiryTimestamp: Date.now() + 3600000,
|
|
169
|
+
allowedChains: [30],
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Create session
|
|
173
|
+
const session = await sessionManager.createSession(masterKey, config);
|
|
174
|
+
expect(session).toBeDefined();
|
|
175
|
+
|
|
176
|
+
// Verify session works initially
|
|
177
|
+
const resultBefore = sessionManager.checkLimits(session, 10);
|
|
178
|
+
expect(resultBefore.allowed).toBe(true);
|
|
179
|
+
|
|
180
|
+
// Revoke session
|
|
181
|
+
await sessionManager.revokeSession(session.keyHash);
|
|
182
|
+
|
|
183
|
+
// Session should no longer be loadable
|
|
184
|
+
const loaded = await sessionManager.loadSession(session.keyHash);
|
|
185
|
+
expect(loaded).toBeNull();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('17.4: Payment tokenization flow', () => {
|
|
190
|
+
it('should tokenize session and validate tokens', async () => {
|
|
191
|
+
const tokenizer = new PaymentTokenizer();
|
|
192
|
+
const session = createMockSession({
|
|
193
|
+
dailyLimitUSD: 100,
|
|
194
|
+
perTransactionLimitUSD: 25,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Tokenize
|
|
198
|
+
const token = await tokenizer.tokenize(session);
|
|
199
|
+
expect(token.token).toBeDefined();
|
|
200
|
+
expect(token.limits.dailyLimitUSD).toBe(100);
|
|
201
|
+
|
|
202
|
+
// Validate
|
|
203
|
+
const validation = tokenizer.validate(token.token);
|
|
204
|
+
expect(validation.valid).toBe(true);
|
|
205
|
+
expect(validation.session?.keyHash).toBe(session.keyHash);
|
|
206
|
+
|
|
207
|
+
// Revoke
|
|
208
|
+
tokenizer.revoke(token.token);
|
|
209
|
+
const afterRevoke = tokenizer.validate(token.token);
|
|
210
|
+
expect(afterRevoke.valid).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should revoke all tokens when session is revoked', async () => {
|
|
214
|
+
const tokenizer = new PaymentTokenizer();
|
|
215
|
+
const session = createMockSession({ keyHash: '0xsession123' });
|
|
216
|
+
|
|
217
|
+
// Create multiple tokens
|
|
218
|
+
const token1 = await tokenizer.tokenize(session);
|
|
219
|
+
const token2 = await tokenizer.tokenize(session);
|
|
220
|
+
|
|
221
|
+
// Both should be valid
|
|
222
|
+
expect(tokenizer.validate(token1.token).valid).toBe(true);
|
|
223
|
+
expect(tokenizer.validate(token2.token).valid).toBe(true);
|
|
224
|
+
|
|
225
|
+
// Revoke all for session
|
|
226
|
+
const count = tokenizer.revokeAllForSession('0xsession123');
|
|
227
|
+
expect(count).toBe(2);
|
|
228
|
+
|
|
229
|
+
// Both should be invalid
|
|
230
|
+
expect(tokenizer.validate(token1.token).valid).toBe(false);
|
|
231
|
+
expect(tokenizer.validate(token2.token).valid).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('17.5: Monitoring and alerts integration', () => {
|
|
236
|
+
it('should log payments and trigger alerts', async () => {
|
|
237
|
+
const logger = new AuditLogger();
|
|
238
|
+
const alertManager = new AlertManager({
|
|
239
|
+
spendingThresholds: [0.8],
|
|
240
|
+
highValueThresholdUSD: 50,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const alerts: any[] = [];
|
|
244
|
+
alertManager.onAlert((alert) => alerts.push(alert));
|
|
245
|
+
|
|
246
|
+
// Log a payment
|
|
247
|
+
await logger.log({
|
|
248
|
+
txHash: '0x123',
|
|
249
|
+
status: 'confirmed',
|
|
250
|
+
chain: 30,
|
|
251
|
+
token: 'USDC',
|
|
252
|
+
amount: BigInt(100),
|
|
253
|
+
amountUSD: 100,
|
|
254
|
+
recipient: '0xrecipient',
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
}, 'session-1');
|
|
257
|
+
|
|
258
|
+
// Trigger spending alert
|
|
259
|
+
alertManager.checkSpending('session-1', 85, 100);
|
|
260
|
+
|
|
261
|
+
// Should have warning alert
|
|
262
|
+
expect(alerts.length).toBeGreaterThan(0);
|
|
263
|
+
expect(alerts[0].type).toBe('WARNING');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should require approval for high-value transactions', () => {
|
|
267
|
+
const alertManager = new AlertManager({
|
|
268
|
+
highValueThresholdUSD: 100,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(alertManager.isHighValueTransaction(50)).toBe(false);
|
|
272
|
+
expect(alertManager.isHighValueTransaction(150)).toBe(true);
|
|
273
|
+
|
|
274
|
+
// Request approval
|
|
275
|
+
const approval = alertManager.requestApproval('tx-high', 500);
|
|
276
|
+
expect(approval.approved).toBe(false);
|
|
277
|
+
|
|
278
|
+
// Approve
|
|
279
|
+
const approved = alertManager.approveTransaction('tx-high', 'master-key');
|
|
280
|
+
expect(approved).toBe(true);
|
|
281
|
+
|
|
282
|
+
// Verify approval
|
|
283
|
+
const status = alertManager.checkApproval('tx-high');
|
|
284
|
+
expect(status.approved).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('17.6: Full payment flow with spending tracking', () => {
|
|
289
|
+
it('should track spending across multiple transactions', async () => {
|
|
290
|
+
const sessionManager = new SessionKeyManager();
|
|
291
|
+
const session = createMockSession({
|
|
292
|
+
dailyLimitUSD: 100,
|
|
293
|
+
perTransactionLimitUSD: 50,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Transaction 1: $30
|
|
297
|
+
let result = sessionManager.checkLimits(session, 30);
|
|
298
|
+
expect(result.allowed).toBe(true);
|
|
299
|
+
await sessionManager.recordSpending(session, 30);
|
|
300
|
+
|
|
301
|
+
// Transaction 2: $40
|
|
302
|
+
result = sessionManager.checkLimits(session, 40);
|
|
303
|
+
expect(result.allowed).toBe(true);
|
|
304
|
+
await sessionManager.recordSpending(session, 40);
|
|
305
|
+
|
|
306
|
+
// Transaction 3: $35 (would exceed daily)
|
|
307
|
+
result = sessionManager.checkLimits(session, 35);
|
|
308
|
+
expect(result.allowed).toBe(false);
|
|
309
|
+
|
|
310
|
+
// Transaction 4: $25 (just under remaining)
|
|
311
|
+
result = sessionManager.checkLimits(session, 25);
|
|
312
|
+
expect(result.allowed).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|