@framers/agentos-ext-wallet 0.2.0 → 0.2.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.
@@ -1,285 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Unit tests for card tools (IssueCard, CardStatus, FreezeCard,
4
- * UnfreezeCard, CardSpendingSummary, PayWithCard).
5
- *
6
- * Tests cover: tool metadata, successful execution, error handling,
7
- * and spending policy enforcement.
8
- */
9
-
10
- import { describe, it, expect, beforeEach, vi } from 'vitest';
11
- import { createIssueCardTool } from '../src/tools/IssueCardTool.js';
12
- import { createCardStatusTool } from '../src/tools/CardStatusTool.js';
13
- import { createFreezeCardTool } from '../src/tools/FreezeCardTool.js';
14
- import { createUnfreezeCardTool } from '../src/tools/UnfreezeCardTool.js';
15
- import { createCardSpendingSummaryTool } from '../src/tools/CardSpendingSummaryTool.js';
16
- import { createPayWithCardTool } from '../src/tools/PayWithCardTool.js';
17
- import { CardManager } from '../src/cards/CardManager.js';
18
- import { MccCategoryMap } from '../src/cards/MccCategoryMap.js';
19
- import { SpendingPolicyEnforcer } from '../src/SpendingPolicyEnforcer.js';
20
- import { DEFAULT_SPENDING_POLICY } from '../src/types.js';
21
- import type { ICardAdapter, ICardStore, AgentCardRecord, CardTransactionRecord, CardConfig, CardState, CardTxStatus } from '../src/cards/types.js';
22
-
23
- /* ------------------------------------------------------------------ */
24
- /* Test helpers */
25
- /* ------------------------------------------------------------------ */
26
-
27
- const CTX = {
28
- gmiId: 'card-tool-test-agent',
29
- personaId: 'persona-1',
30
- userContext: { userId: 'user-1' } as any,
31
- };
32
-
33
- function createMockAdapter(): ICardAdapter {
34
- return {
35
- createCard: vi.fn(async (params) => ({
36
- token: 'lt-card-token',
37
- last_four: '9999',
38
- type: params.type,
39
- state: params.state || 'OPEN',
40
- spend_limit: params.spend_limit,
41
- spend_limit_duration: params.spend_limit_duration,
42
- pan: '4111111111119999',
43
- cvv: '456',
44
- exp_month: '06',
45
- exp_year: '2029',
46
- funding: { token: 'fund-1', type: 'DEPOSITORY_CHECKING' },
47
- })),
48
- getCard: vi.fn(async () => ({
49
- token: 'lt-card-token',
50
- last_four: '9999',
51
- type: 'VIRTUAL' as const,
52
- state: 'OPEN' as const,
53
- spend_limit: 50000,
54
- spend_limit_duration: 'MONTHLY' as const,
55
- pan: '4111111111119999',
56
- cvv: '456',
57
- exp_month: '06',
58
- exp_year: '2029',
59
- funding: { token: 'fund-1', type: 'DEPOSITORY_CHECKING' },
60
- })),
61
- updateCard: vi.fn(async () => ({} as any)),
62
- pauseCard: vi.fn(async () => {}),
63
- resumeCard: vi.fn(async () => {}),
64
- closeCard: vi.fn(async () => {}),
65
- listTransactions: vi.fn(async () => []),
66
- createAuthRule: vi.fn(async () => {}),
67
- };
68
- }
69
-
70
- function createMockStore(): ICardStore {
71
- const cards = new Map<string, AgentCardRecord>();
72
- const txs: CardTransactionRecord[] = [];
73
- return {
74
- async getCard(agentId: string) { return [...cards.values()].find(c => c.agentId === agentId) || null; },
75
- async saveCard(record: AgentCardRecord) { cards.set(record.id, record); },
76
- async updateCardState(agentId: string, state: CardState) {
77
- for (const card of cards.values()) { if (card.agentId === agentId) card.state = state; }
78
- },
79
- async insertTransaction(record: CardTransactionRecord) { txs.push(record); },
80
- async updateTransactionStatus(id: string, status: CardTxStatus) { const tx = txs.find(t => t.id === id); if (tx) tx.status = status; },
81
- async getTransactions(cardId: string, limit = 20) { return txs.filter(t => t.cardId === cardId).slice(-limit).reverse(); },
82
- async getTransactionsByPeriod(cardId: string, since: number) { return txs.filter(t => t.cardId === cardId && t.createdAt >= since); },
83
- };
84
- }
85
-
86
- const CARD_CONFIG: CardConfig = { enabled: true, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false };
87
-
88
- function createTestCardManager() {
89
- const ledger: any[] = [];
90
- const ledgerStore = {
91
- async sumByPeriod(agentId: string, periodKey: string) { return ledger.filter(e => e.agentId === agentId && e.periodKey === periodKey).reduce((s, e) => s + e.amountUsd, 0); },
92
- async sumByCategoryAndPeriod(agentId: string, category: string, periodKey: string) { return ledger.filter(e => e.agentId === agentId && e.category === category && e.periodKey === periodKey).reduce((s, e) => s + e.amountUsd, 0); },
93
- async insert(entry: any) { ledger.push(entry); },
94
- };
95
-
96
- return new CardManager({
97
- adapter: createMockAdapter(),
98
- store: createMockStore(),
99
- policyEnforcer: new SpendingPolicyEnforcer(DEFAULT_SPENDING_POLICY, ledgerStore),
100
- mccMap: new MccCategoryMap(),
101
- config: CARD_CONFIG,
102
- });
103
- }
104
-
105
- /* ------------------------------------------------------------------ */
106
- /* IssueCardTool */
107
- /* ------------------------------------------------------------------ */
108
-
109
- describe('IssueCardTool', () => {
110
- it('should have correct metadata', () => {
111
- const tool = createIssueCardTool(createTestCardManager());
112
- expect(tool.id).toBe('wallet-issue-card-v1');
113
- expect(tool.name).toBe('issue_virtual_card');
114
- expect(tool.hasSideEffects).toBe(true);
115
- });
116
-
117
- it('should issue card successfully', async () => {
118
- const tool = createIssueCardTool(createTestCardManager());
119
- const result = await tool.execute({}, CTX);
120
- expect(result.success).toBe(true);
121
- expect(result.output?.last4).toBe('9999');
122
- expect(result.output?.state).toBe('OPEN');
123
- });
124
-
125
- it('should return error for duplicate card', async () => {
126
- const mgr = createTestCardManager();
127
- const tool = createIssueCardTool(mgr);
128
- await tool.execute({}, CTX);
129
- const result = await tool.execute({}, CTX);
130
- expect(result.success).toBe(false);
131
- expect(result.error).toContain('already has a card');
132
- });
133
- });
134
-
135
- /* ------------------------------------------------------------------ */
136
- /* CardStatusTool */
137
- /* ------------------------------------------------------------------ */
138
-
139
- describe('CardStatusTool', () => {
140
- it('should have correct metadata', () => {
141
- const tool = createCardStatusTool(createTestCardManager());
142
- expect(tool.id).toBe('wallet-card-status-v1');
143
- expect(tool.name).toBe('card_status');
144
- expect(tool.hasSideEffects).toBe(false);
145
- });
146
-
147
- it('should return error when no card exists', async () => {
148
- const tool = createCardStatusTool(createTestCardManager());
149
- const result = await tool.execute({}, CTX);
150
- expect(result.success).toBe(false);
151
- expect(result.error).toContain('No card found');
152
- });
153
-
154
- it('should return card status after issuance', async () => {
155
- const mgr = createTestCardManager();
156
- await mgr.issueCard(CTX.gmiId);
157
- const tool = createCardStatusTool(mgr);
158
- const result = await tool.execute({}, CTX);
159
- expect(result.success).toBe(true);
160
- expect(result.output?.last4).toBe('9999');
161
- expect(result.output?.state).toBe('OPEN');
162
- });
163
- });
164
-
165
- /* ------------------------------------------------------------------ */
166
- /* FreezeCardTool */
167
- /* ------------------------------------------------------------------ */
168
-
169
- describe('FreezeCardTool', () => {
170
- it('should have correct metadata', () => {
171
- const tool = createFreezeCardTool(createTestCardManager());
172
- expect(tool.id).toBe('wallet-freeze-card-v1');
173
- expect(tool.name).toBe('freeze_card');
174
- expect(tool.hasSideEffects).toBe(true);
175
- });
176
-
177
- it('should freeze card successfully', async () => {
178
- const mgr = createTestCardManager();
179
- await mgr.issueCard(CTX.gmiId);
180
- const tool = createFreezeCardTool(mgr);
181
- const result = await tool.execute({}, CTX);
182
- expect(result.success).toBe(true);
183
- expect(result.output?.state).toBe('PAUSED');
184
- });
185
-
186
- it('should return error when no card exists', async () => {
187
- const tool = createFreezeCardTool(createTestCardManager());
188
- const result = await tool.execute({}, CTX);
189
- expect(result.success).toBe(false);
190
- expect(result.error).toContain('No card found');
191
- });
192
- });
193
-
194
- /* ------------------------------------------------------------------ */
195
- /* UnfreezeCardTool */
196
- /* ------------------------------------------------------------------ */
197
-
198
- describe('UnfreezeCardTool', () => {
199
- it('should have correct metadata', () => {
200
- const tool = createUnfreezeCardTool(createTestCardManager());
201
- expect(tool.id).toBe('wallet-unfreeze-card-v1');
202
- expect(tool.name).toBe('unfreeze_card');
203
- expect(tool.hasSideEffects).toBe(true);
204
- });
205
-
206
- it('should unfreeze card successfully', async () => {
207
- const mgr = createTestCardManager();
208
- await mgr.issueCard(CTX.gmiId);
209
- await mgr.freezeCard(CTX.gmiId);
210
- const tool = createUnfreezeCardTool(mgr);
211
- const result = await tool.execute({}, CTX);
212
- expect(result.success).toBe(true);
213
- expect(result.output?.state).toBe('OPEN');
214
- });
215
- });
216
-
217
- /* ------------------------------------------------------------------ */
218
- /* CardSpendingSummaryTool */
219
- /* ------------------------------------------------------------------ */
220
-
221
- describe('CardSpendingSummaryTool', () => {
222
- it('should have correct metadata', () => {
223
- const tool = createCardSpendingSummaryTool(createTestCardManager());
224
- expect(tool.id).toBe('wallet-card-spending-summary-v1');
225
- expect(tool.name).toBe('card_spending_summary');
226
- expect(tool.hasSideEffects).toBe(false);
227
- });
228
-
229
- it('should return empty summary for new card', async () => {
230
- const mgr = createTestCardManager();
231
- await mgr.issueCard(CTX.gmiId);
232
- const tool = createCardSpendingSummaryTool(mgr);
233
- const result = await tool.execute({}, CTX);
234
- expect(result.success).toBe(true);
235
- expect(result.output?.totalUsd).toBe(0);
236
- });
237
-
238
- it('should return error when no card exists', async () => {
239
- const tool = createCardSpendingSummaryTool(createTestCardManager());
240
- const result = await tool.execute({}, CTX);
241
- expect(result.success).toBe(false);
242
- });
243
- });
244
-
245
- /* ------------------------------------------------------------------ */
246
- /* PayWithCardTool */
247
- /* ------------------------------------------------------------------ */
248
-
249
- describe('PayWithCardTool', () => {
250
- it('should have correct metadata', () => {
251
- const tool = createPayWithCardTool(createTestCardManager());
252
- expect(tool.id).toBe('wallet-pay-with-card-v1');
253
- expect(tool.name).toBe('pay_with_card');
254
- expect(tool.hasSideEffects).toBe(true);
255
- });
256
-
257
- it('should return card details for payment', async () => {
258
- const mgr = createTestCardManager();
259
- await mgr.issueCard(CTX.gmiId);
260
- const tool = createPayWithCardTool(mgr);
261
- const result = await tool.execute({}, CTX);
262
- expect(result.success).toBe(true);
263
- expect(result.output?.pan).toBe('4111111111119999');
264
- expect(result.output?.cvv).toBe('456');
265
- expect(result.output?.expMonth).toBe('06');
266
- expect(result.output?.expYear).toBe('2029');
267
- });
268
-
269
- it('should block when spending policy denies', async () => {
270
- const mgr = createTestCardManager();
271
- await mgr.issueCard(CTX.gmiId);
272
- const tool = createPayWithCardTool(mgr);
273
- // perTransactionLimitUsd = 20 in DEFAULT_SPENDING_POLICY
274
- const result = await tool.execute({ estimatedAmountUsd: 25, category: 'shopping' }, CTX);
275
- expect(result.success).toBe(false);
276
- expect(result.details?.policyViolation).toBe(true);
277
- });
278
-
279
- it('should return error when no card exists', async () => {
280
- const tool = createPayWithCardTool(createTestCardManager());
281
- const result = await tool.execute({}, CTX);
282
- expect(result.success).toBe(false);
283
- expect(result.error).toContain('No card found');
284
- });
285
- });
@@ -1,171 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Integration tests for the wallet extension pack.
4
- *
5
- * Tests the full lifecycle: extension pack creation → wallet creation →
6
- * balance check → send with policy enforcement → history retrieval.
7
- * Also tests the createExtensionPack factory itself.
8
- */
9
-
10
- import { describe, it, expect } from 'vitest';
11
- import { createExtensionPack } from '../src/index.js';
12
- import type { ExtensionPack } from '../src/index.js';
13
-
14
- /* ------------------------------------------------------------------ */
15
- /* Extension pack factory */
16
- /* ------------------------------------------------------------------ */
17
-
18
- describe('createExtensionPack', () => {
19
- it('should create a valid extension pack with 3 tool descriptors', () => {
20
- const pack = createExtensionPack({ options: { enabled: true, chains: ['solana'] } });
21
-
22
- expect(pack.name).toBe('@framers/agentos-ext-wallet');
23
- expect(pack.version).toBe('0.2.0');
24
- expect(pack.descriptors.length).toBe(3);
25
- expect(pack.descriptors.every(d => d.kind === 'tool')).toBe(true);
26
- });
27
-
28
- it('should register check_wallet_balance tool', () => {
29
- const pack = createExtensionPack({});
30
- const balanceTool = pack.descriptors.find(d => d.id === 'check_wallet_balance');
31
-
32
- expect(balanceTool).toBeTruthy();
33
- expect(balanceTool!.kind).toBe('tool');
34
- expect((balanceTool!.payload as any).name).toBe('check_wallet_balance');
35
- });
36
-
37
- it('should register send_crypto tool', () => {
38
- const pack = createExtensionPack({});
39
- const sendTool = pack.descriptors.find(d => d.id === 'send_crypto');
40
-
41
- expect(sendTool).toBeTruthy();
42
- expect((sendTool!.payload as any).hasSideEffects).toBe(true);
43
- });
44
-
45
- it('should register wallet_transaction_history tool', () => {
46
- const pack = createExtensionPack({});
47
- const historyTool = pack.descriptors.find(d => d.id === 'wallet_transaction_history');
48
-
49
- expect(historyTool).toBeTruthy();
50
- expect((historyTool!.payload as any).hasSideEffects).toBe(false);
51
- });
52
-
53
- it('should call onActivate without error', async () => {
54
- const logs: string[] = [];
55
- const pack = createExtensionPack({
56
- options: { chains: ['solana', 'ethereum'] },
57
- logger: { info: (m: string) => logs.push(m) },
58
- });
59
-
60
- await pack.onActivate?.();
61
- expect(logs.length).toBe(1);
62
- expect(logs[0]).toContain('solana, ethereum');
63
- });
64
-
65
- it('should use WALLET_MASTER_SECRET from secrets', () => {
66
- // Should not throw — just verifies the factory accepts secrets
67
- const pack = createExtensionPack({
68
- secrets: { WALLET_MASTER_SECRET: 'my-production-secret' },
69
- });
70
- expect(pack.descriptors.length).toBe(3);
71
- });
72
-
73
- it('should default to solana chain when no options provided', () => {
74
- const logs: string[] = [];
75
- const pack = createExtensionPack({
76
- logger: { info: (m: string) => logs.push(m) },
77
- });
78
-
79
- // Trigger onActivate to see the default chains
80
- pack.onActivate?.();
81
- // The pack was created successfully with defaults
82
- expect(pack.descriptors.length).toBe(3);
83
- });
84
- });
85
-
86
- /* ------------------------------------------------------------------ */
87
- /* Full lifecycle integration */
88
- /* ------------------------------------------------------------------ */
89
-
90
- describe('wallet lifecycle (integration)', () => {
91
- let pack: ExtensionPack;
92
- let tools: Map<string, any>;
93
-
94
- const CTX = {
95
- gmiId: 'integration-agent',
96
- personaId: 'persona-1',
97
- userContext: { userId: 'user-1' } as any,
98
- };
99
-
100
- // Note: These integration tests use the in-memory stores provided by
101
- // createExtensionPack, NOT real blockchain connections. The chain adapters
102
- // will fail on actual RPC calls, but we can still test the full tool
103
- // pipeline through the manager layer using mock-friendly scenarios.
104
-
105
- it('should create extension pack and extract tools', () => {
106
- pack = createExtensionPack({
107
- options: { enabled: true, chains: ['solana'] },
108
- });
109
-
110
- tools = new Map<string, any>();
111
- for (const desc of pack.descriptors) {
112
- if (desc.kind === 'tool') {
113
- tools.set((desc.payload as any).name, desc.payload);
114
- }
115
- }
116
-
117
- expect(tools.size).toBe(3);
118
- expect(tools.has('check_wallet_balance')).toBe(true);
119
- expect(tools.has('send_crypto')).toBe(true);
120
- expect(tools.has('wallet_transaction_history')).toBe(true);
121
- });
122
-
123
- it('check_wallet_balance should fail gracefully when no wallet exists', async () => {
124
- pack = createExtensionPack({ options: { chains: ['solana'] } });
125
- const balanceTool = pack.descriptors.find(d => d.id === 'check_wallet_balance')!.payload as any;
126
-
127
- const result = await balanceTool.execute({ chain: 'solana' }, CTX);
128
-
129
- expect(result.success).toBe(false);
130
- expect(result.error).toContain('No solana wallet found');
131
- });
132
-
133
- it('send_crypto should validate amount before attempting send', async () => {
134
- pack = createExtensionPack({ options: { chains: ['solana'] } });
135
- const sendTool = pack.descriptors.find(d => d.id === 'send_crypto')!.payload as any;
136
-
137
- const result = await sendTool.execute({ to: 'addr', amount: '-1' }, CTX);
138
-
139
- expect(result.success).toBe(false);
140
- expect(result.error).toContain('Invalid amount');
141
- });
142
-
143
- it('wallet_transaction_history should fail gracefully when no wallet exists', async () => {
144
- pack = createExtensionPack({ options: { chains: ['solana'] } });
145
- const historyTool = pack.descriptors.find(d => d.id === 'wallet_transaction_history')!.payload as any;
146
-
147
- const result = await historyTool.execute({}, CTX);
148
-
149
- expect(result.success).toBe(false);
150
- expect(result.error).toContain('No solana wallet found');
151
- });
152
-
153
- it('tools should have valid inputSchema', () => {
154
- pack = createExtensionPack({});
155
-
156
- for (const desc of pack.descriptors) {
157
- const tool = desc.payload as any;
158
- expect(tool.inputSchema).toBeTruthy();
159
- expect(tool.inputSchema.type).toBe('object');
160
- }
161
- });
162
-
163
- it('all tools should have category=wallet', () => {
164
- pack = createExtensionPack({});
165
-
166
- for (const desc of pack.descriptors) {
167
- const tool = desc.payload as any;
168
- expect(tool.category).toBe('wallet');
169
- }
170
- });
171
- });