@sardis/sdk 0.2.0

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/dist/browser/index.js +7049 -0
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/browser/sardis.umd.js +7071 -0
  7. package/dist/browser/sardis.umd.js.map +1 -0
  8. package/dist/cjs/client.js +644 -0
  9. package/dist/cjs/client.js.map +1 -0
  10. package/dist/cjs/demo.js +699 -0
  11. package/dist/cjs/demo.js.map +1 -0
  12. package/dist/cjs/errors.js +630 -0
  13. package/dist/cjs/errors.js.map +1 -0
  14. package/dist/cjs/index.js +131 -0
  15. package/dist/cjs/index.js.map +1 -0
  16. package/dist/cjs/integrations/index.js +21 -0
  17. package/dist/cjs/integrations/index.js.map +1 -0
  18. package/dist/cjs/integrations/langchain.js +339 -0
  19. package/dist/cjs/integrations/langchain.js.map +1 -0
  20. package/dist/cjs/integrations/openai.js +505 -0
  21. package/dist/cjs/integrations/openai.js.map +1 -0
  22. package/dist/cjs/integrations/vercel-ai.js +198 -0
  23. package/dist/cjs/integrations/vercel-ai.js.map +1 -0
  24. package/dist/cjs/resources/a2a.js +158 -0
  25. package/dist/cjs/resources/a2a.js.map +1 -0
  26. package/dist/cjs/resources/agents.js +142 -0
  27. package/dist/cjs/resources/agents.js.map +1 -0
  28. package/dist/cjs/resources/base.js +124 -0
  29. package/dist/cjs/resources/base.js.map +1 -0
  30. package/dist/cjs/resources/cards.js +43 -0
  31. package/dist/cjs/resources/cards.js.map +1 -0
  32. package/dist/cjs/resources/holds.js +64 -0
  33. package/dist/cjs/resources/holds.js.map +1 -0
  34. package/dist/cjs/resources/index.js +31 -0
  35. package/dist/cjs/resources/index.js.map +1 -0
  36. package/dist/cjs/resources/ledger.js +43 -0
  37. package/dist/cjs/resources/ledger.js.map +1 -0
  38. package/dist/cjs/resources/marketplace.js +88 -0
  39. package/dist/cjs/resources/marketplace.js.map +1 -0
  40. package/dist/cjs/resources/payments.js +33 -0
  41. package/dist/cjs/resources/payments.js.map +1 -0
  42. package/dist/cjs/resources/policies.js +31 -0
  43. package/dist/cjs/resources/policies.js.map +1 -0
  44. package/dist/cjs/resources/transactions.js +37 -0
  45. package/dist/cjs/resources/transactions.js.map +1 -0
  46. package/dist/cjs/resources/ucp.js +133 -0
  47. package/dist/cjs/resources/ucp.js.map +1 -0
  48. package/dist/cjs/resources/wallets.js +109 -0
  49. package/dist/cjs/resources/wallets.js.map +1 -0
  50. package/dist/cjs/resources/webhooks.js +81 -0
  51. package/dist/cjs/resources/webhooks.js.map +1 -0
  52. package/dist/cjs/types.js +11 -0
  53. package/dist/cjs/types.js.map +1 -0
  54. package/dist/client.d.ts +419 -0
  55. package/dist/client.d.ts.map +1 -0
  56. package/dist/client.js +637 -0
  57. package/dist/client.js.map +1 -0
  58. package/dist/demo.d.ts +335 -0
  59. package/dist/demo.d.ts.map +1 -0
  60. package/dist/demo.js +694 -0
  61. package/dist/demo.js.map +1 -0
  62. package/dist/errors.d.ts +522 -0
  63. package/dist/errors.d.ts.map +1 -0
  64. package/dist/errors.js +612 -0
  65. package/dist/errors.js.map +1 -0
  66. package/dist/index.d.ts +83 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +85 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/integrations/index.d.ts +4 -0
  71. package/dist/integrations/index.d.ts.map +1 -0
  72. package/dist/integrations/index.js +5 -0
  73. package/dist/integrations/index.js.map +1 -0
  74. package/dist/integrations/langchain.d.ts +68 -0
  75. package/dist/integrations/langchain.d.ts.map +1 -0
  76. package/dist/integrations/langchain.js +335 -0
  77. package/dist/integrations/langchain.js.map +1 -0
  78. package/dist/integrations/openai.d.ts +97 -0
  79. package/dist/integrations/openai.d.ts.map +1 -0
  80. package/dist/integrations/openai.js +467 -0
  81. package/dist/integrations/openai.js.map +1 -0
  82. package/dist/integrations/vercel-ai.d.ts +180 -0
  83. package/dist/integrations/vercel-ai.d.ts.map +1 -0
  84. package/dist/integrations/vercel-ai.js +194 -0
  85. package/dist/integrations/vercel-ai.js.map +1 -0
  86. package/dist/resources/a2a.d.ts +254 -0
  87. package/dist/resources/a2a.d.ts.map +1 -0
  88. package/dist/resources/a2a.js +154 -0
  89. package/dist/resources/a2a.js.map +1 -0
  90. package/dist/resources/agents.d.ts +111 -0
  91. package/dist/resources/agents.d.ts.map +1 -0
  92. package/dist/resources/agents.js +138 -0
  93. package/dist/resources/agents.js.map +1 -0
  94. package/dist/resources/base.d.ts +115 -0
  95. package/dist/resources/base.d.ts.map +1 -0
  96. package/dist/resources/base.js +120 -0
  97. package/dist/resources/base.js.map +1 -0
  98. package/dist/resources/cards.d.ts +19 -0
  99. package/dist/resources/cards.d.ts.map +1 -0
  100. package/dist/resources/cards.js +39 -0
  101. package/dist/resources/cards.js.map +1 -0
  102. package/dist/resources/holds.d.ts +44 -0
  103. package/dist/resources/holds.d.ts.map +1 -0
  104. package/dist/resources/holds.js +60 -0
  105. package/dist/resources/holds.js.map +1 -0
  106. package/dist/resources/index.d.ts +16 -0
  107. package/dist/resources/index.d.ts.map +1 -0
  108. package/dist/resources/index.js +16 -0
  109. package/dist/resources/index.js.map +1 -0
  110. package/dist/resources/ledger.d.ts +38 -0
  111. package/dist/resources/ledger.d.ts.map +1 -0
  112. package/dist/resources/ledger.js +39 -0
  113. package/dist/resources/ledger.js.map +1 -0
  114. package/dist/resources/marketplace.d.ts +60 -0
  115. package/dist/resources/marketplace.d.ts.map +1 -0
  116. package/dist/resources/marketplace.js +84 -0
  117. package/dist/resources/marketplace.js.map +1 -0
  118. package/dist/resources/payments.d.ts +24 -0
  119. package/dist/resources/payments.d.ts.map +1 -0
  120. package/dist/resources/payments.js +29 -0
  121. package/dist/resources/payments.js.map +1 -0
  122. package/dist/resources/policies.d.ts +23 -0
  123. package/dist/resources/policies.d.ts.map +1 -0
  124. package/dist/resources/policies.js +27 -0
  125. package/dist/resources/policies.js.map +1 -0
  126. package/dist/resources/transactions.d.ts +32 -0
  127. package/dist/resources/transactions.d.ts.map +1 -0
  128. package/dist/resources/transactions.js +33 -0
  129. package/dist/resources/transactions.js.map +1 -0
  130. package/dist/resources/ucp.d.ts +218 -0
  131. package/dist/resources/ucp.d.ts.map +1 -0
  132. package/dist/resources/ucp.js +129 -0
  133. package/dist/resources/ucp.js.map +1 -0
  134. package/dist/resources/wallets.d.ts +71 -0
  135. package/dist/resources/wallets.d.ts.map +1 -0
  136. package/dist/resources/wallets.js +105 -0
  137. package/dist/resources/wallets.js.map +1 -0
  138. package/dist/resources/webhooks.d.ts +57 -0
  139. package/dist/resources/webhooks.d.ts.map +1 -0
  140. package/dist/resources/webhooks.js +77 -0
  141. package/dist/resources/webhooks.js.map +1 -0
  142. package/dist/types.d.ts +1045 -0
  143. package/dist/types.d.ts.map +1 -0
  144. package/dist/types.js +10 -0
  145. package/dist/types.js.map +1 -0
  146. package/package.json +114 -0
package/dist/demo.js ADDED
@@ -0,0 +1,694 @@
1
+ /**
2
+ * Sardis Demo Mode
3
+ *
4
+ * A fully functional simulation of the Sardis API for:
5
+ * - YC demos
6
+ * - Local development
7
+ * - Integration testing
8
+ * - Hackathons
9
+ *
10
+ * No real transactions, no API key needed.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { SardisDemoClient } from '@sardis/sdk/demo'
15
+ *
16
+ * const sardis = new SardisDemoClient()
17
+ *
18
+ * // Create a demo wallet
19
+ * const wallet = await sardis.wallets.create({ chain: 'base' })
20
+ *
21
+ * // Make a demo payment
22
+ * const payment = await sardis.payments.execute({
23
+ * walletId: wallet.id,
24
+ * to: 'demo_merchant_openai',
25
+ * amount: '50.00',
26
+ * token: 'USDC',
27
+ * })
28
+ *
29
+ * console.log(payment.txHash) // 0x1234... (simulated)
30
+ * ```
31
+ */
32
+ import { randomBytes } from 'crypto';
33
+ // =============================================================================
34
+ // Demo Data Store
35
+ // =============================================================================
36
+ class DemoStore {
37
+ constructor() {
38
+ this.wallets = new Map();
39
+ this.transactions = new Map();
40
+ this.holds = new Map();
41
+ this.onRampQuotes = new Map();
42
+ this.onRampTransfers = new Map();
43
+ this.offRampQuotes = new Map();
44
+ this.offRampTransfers = new Map();
45
+ this.blockNumber = 1000000;
46
+ // Pre-populate with demo data
47
+ this.seedData();
48
+ }
49
+ seedData() {
50
+ // Create default demo wallet
51
+ const defaultWallet = {
52
+ id: 'wallet_demo_default',
53
+ address: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28',
54
+ chain: 'base',
55
+ balance: '10000.00',
56
+ policy: {
57
+ id: 'policy_demo_default',
58
+ name: 'Demo Policy',
59
+ dailyLimit: 1000,
60
+ monthlyLimit: 10000,
61
+ singleTxLimit: 500,
62
+ blockedCategories: ['gambling', 'adult'],
63
+ allowedMerchants: null,
64
+ spent: { today: 0, thisMonth: 0 },
65
+ },
66
+ createdAt: new Date().toISOString(),
67
+ };
68
+ this.wallets.set(defaultWallet.id, defaultWallet);
69
+ }
70
+ generateId(prefix) {
71
+ return `${prefix}_${randomBytes(8).toString('hex')}`;
72
+ }
73
+ generateTxHash() {
74
+ return `0x${randomBytes(32).toString('hex')}`;
75
+ }
76
+ generateAddress() {
77
+ return `0x${randomBytes(20).toString('hex')}`;
78
+ }
79
+ nextBlock() {
80
+ return ++this.blockNumber;
81
+ }
82
+ }
83
+ // =============================================================================
84
+ // Demo Client
85
+ // =============================================================================
86
+ export class SardisDemoClient {
87
+ constructor(options) {
88
+ this.store = new DemoStore();
89
+ this.simulatedDelay = options?.simulatedDelay ?? 500;
90
+ this.wallets = new DemoWalletsResource(this.store, this.simulatedDelay);
91
+ this.payments = new DemoPaymentsResource(this.store, this.simulatedDelay);
92
+ this.holds = new DemoHoldsResource(this.store, this.simulatedDelay);
93
+ this.policy = new DemoPolicyResource(this.store, this.simulatedDelay);
94
+ this.ramp = new DemoRampResource(this.store, this.simulatedDelay);
95
+ }
96
+ /**
97
+ * Get the default demo wallet ID.
98
+ */
99
+ get defaultWalletId() {
100
+ return 'wallet_demo_default';
101
+ }
102
+ /**
103
+ * Reset all demo data to initial state.
104
+ */
105
+ reset() {
106
+ this.store = new DemoStore();
107
+ this.wallets = new DemoWalletsResource(this.store, this.simulatedDelay);
108
+ this.payments = new DemoPaymentsResource(this.store, this.simulatedDelay);
109
+ this.holds = new DemoHoldsResource(this.store, this.simulatedDelay);
110
+ this.policy = new DemoPolicyResource(this.store, this.simulatedDelay);
111
+ this.ramp = new DemoRampResource(this.store, this.simulatedDelay);
112
+ }
113
+ }
114
+ // =============================================================================
115
+ // Resources
116
+ // =============================================================================
117
+ class DemoWalletsResource {
118
+ constructor(store, delay) {
119
+ this.store = store;
120
+ this.delay = delay;
121
+ }
122
+ async simulate(result) {
123
+ await new Promise((r) => setTimeout(r, this.delay));
124
+ return result;
125
+ }
126
+ async create(params) {
127
+ const wallet = {
128
+ id: this.store.generateId('wallet'),
129
+ address: this.store.generateAddress(),
130
+ chain: params.chain || 'base',
131
+ balance: '0.00',
132
+ policy: {
133
+ id: params.policyId || 'policy_demo_default',
134
+ name: 'Demo Policy',
135
+ dailyLimit: 1000,
136
+ monthlyLimit: 10000,
137
+ singleTxLimit: 500,
138
+ blockedCategories: ['gambling', 'adult'],
139
+ allowedMerchants: null,
140
+ spent: { today: 0, thisMonth: 0 },
141
+ },
142
+ createdAt: new Date().toISOString(),
143
+ };
144
+ this.store.wallets.set(wallet.id, wallet);
145
+ return this.simulate(wallet);
146
+ }
147
+ async get(walletId) {
148
+ const wallet = this.store.wallets.get(walletId);
149
+ if (!wallet) {
150
+ throw new Error(`Wallet not found: ${walletId}`);
151
+ }
152
+ return this.simulate(wallet);
153
+ }
154
+ async getBalance(walletId) {
155
+ const wallet = await this.get(walletId);
156
+ // Calculate held amount from active holds
157
+ let held = 0;
158
+ for (const hold of this.store.holds.values()) {
159
+ if (hold.walletId === walletId && hold.status === 'active') {
160
+ held += parseFloat(hold.amount) - parseFloat(hold.capturedAmount);
161
+ }
162
+ }
163
+ return this.simulate({
164
+ available: (parseFloat(wallet.balance) - held).toFixed(2),
165
+ pending: '0.00',
166
+ held: held.toFixed(2),
167
+ });
168
+ }
169
+ async list() {
170
+ return this.simulate(Array.from(this.store.wallets.values()));
171
+ }
172
+ async fund(walletId, amount) {
173
+ const wallet = await this.get(walletId);
174
+ wallet.balance = (parseFloat(wallet.balance) + parseFloat(amount)).toFixed(2);
175
+ return this.simulate(wallet);
176
+ }
177
+ }
178
+ class DemoPaymentsResource {
179
+ constructor(store, delay) {
180
+ this.store = store;
181
+ this.delay = delay;
182
+ }
183
+ async simulate(result) {
184
+ await new Promise((r) => setTimeout(r, this.delay));
185
+ return result;
186
+ }
187
+ async execute(params) {
188
+ const wallet = this.store.wallets.get(params.walletId);
189
+ if (!wallet) {
190
+ throw new Error(`Wallet not found: ${params.walletId}`);
191
+ }
192
+ const amount = parseFloat(params.amount);
193
+ // Policy check
194
+ const policy = wallet.policy;
195
+ if (amount > policy.singleTxLimit) {
196
+ throw new Error(`Payment of $${amount} exceeds single transaction limit of $${policy.singleTxLimit}`);
197
+ }
198
+ if (policy.spent.today + amount > policy.dailyLimit) {
199
+ throw new Error(`Payment would exceed daily limit. Remaining: $${policy.dailyLimit - policy.spent.today}`);
200
+ }
201
+ if (params.category && policy.blockedCategories.includes(params.category.toLowerCase())) {
202
+ throw new Error(`Category '${params.category}' is blocked by policy`);
203
+ }
204
+ // Balance check
205
+ if (amount > parseFloat(wallet.balance)) {
206
+ throw new Error(`Insufficient balance. Available: $${wallet.balance}, Required: $${amount}`);
207
+ }
208
+ // Create transaction
209
+ const tx = {
210
+ id: this.store.generateId('tx'),
211
+ walletId: params.walletId,
212
+ type: 'payment',
213
+ status: 'confirmed',
214
+ amount: params.amount,
215
+ token: params.token || 'USDC',
216
+ chain: params.chain || wallet.chain,
217
+ from: wallet.address,
218
+ to: params.to,
219
+ merchant: params.merchant,
220
+ category: params.category,
221
+ txHash: this.store.generateTxHash(),
222
+ blockNumber: this.store.nextBlock(),
223
+ memo: params.memo,
224
+ createdAt: new Date().toISOString(),
225
+ confirmedAt: new Date().toISOString(),
226
+ };
227
+ // Update wallet balance
228
+ wallet.balance = (parseFloat(wallet.balance) - amount).toFixed(2);
229
+ // Update spending
230
+ policy.spent.today += amount;
231
+ policy.spent.thisMonth += amount;
232
+ this.store.transactions.set(tx.id, tx);
233
+ return this.simulate(tx);
234
+ }
235
+ async get(transactionId) {
236
+ const tx = this.store.transactions.get(transactionId);
237
+ if (!tx) {
238
+ throw new Error(`Transaction not found: ${transactionId}`);
239
+ }
240
+ return this.simulate(tx);
241
+ }
242
+ async list(walletId) {
243
+ const transactions = Array.from(this.store.transactions.values()).filter((tx) => tx.walletId === walletId);
244
+ return this.simulate(transactions);
245
+ }
246
+ }
247
+ class DemoHoldsResource {
248
+ constructor(store, delay) {
249
+ this.store = store;
250
+ this.delay = delay;
251
+ }
252
+ async simulate(result) {
253
+ await new Promise((r) => setTimeout(r, this.delay));
254
+ return result;
255
+ }
256
+ async create(params) {
257
+ const wallet = this.store.wallets.get(params.walletId);
258
+ if (!wallet) {
259
+ throw new Error(`Wallet not found: ${params.walletId}`);
260
+ }
261
+ const amount = parseFloat(params.amount);
262
+ // Balance check (including existing holds)
263
+ let totalHeld = 0;
264
+ for (const hold of this.store.holds.values()) {
265
+ if (hold.walletId === params.walletId && hold.status === 'active') {
266
+ totalHeld += parseFloat(hold.amount) - parseFloat(hold.capturedAmount);
267
+ }
268
+ }
269
+ const available = parseFloat(wallet.balance) - totalHeld;
270
+ if (amount > available) {
271
+ throw new Error(`Insufficient available balance for hold. Available: $${available.toFixed(2)}`);
272
+ }
273
+ const expiresAt = new Date();
274
+ expiresAt.setHours(expiresAt.getHours() + (params.expiresInHours || 24));
275
+ const hold = {
276
+ id: this.store.generateId('hold'),
277
+ walletId: params.walletId,
278
+ amount: params.amount,
279
+ capturedAmount: '0.00',
280
+ merchant: params.merchant,
281
+ status: 'active',
282
+ expiresAt: expiresAt.toISOString(),
283
+ createdAt: new Date().toISOString(),
284
+ };
285
+ this.store.holds.set(hold.id, hold);
286
+ return this.simulate(hold);
287
+ }
288
+ async capture(holdId, amount) {
289
+ const hold = this.store.holds.get(holdId);
290
+ if (!hold) {
291
+ throw new Error(`Hold not found: ${holdId}`);
292
+ }
293
+ if (hold.status !== 'active') {
294
+ throw new Error(`Hold is not active: ${hold.status}`);
295
+ }
296
+ const captureAmount = amount ? parseFloat(amount) : parseFloat(hold.amount);
297
+ const maxCapture = parseFloat(hold.amount) - parseFloat(hold.capturedAmount);
298
+ if (captureAmount > maxCapture) {
299
+ throw new Error(`Capture amount $${captureAmount} exceeds remaining hold $${maxCapture}`);
300
+ }
301
+ // Update wallet balance
302
+ const wallet = this.store.wallets.get(hold.walletId);
303
+ wallet.balance = (parseFloat(wallet.balance) - captureAmount).toFixed(2);
304
+ // Update hold
305
+ hold.capturedAmount = (parseFloat(hold.capturedAmount) + captureAmount).toFixed(2);
306
+ if (parseFloat(hold.capturedAmount) >= parseFloat(hold.amount)) {
307
+ hold.status = 'captured';
308
+ }
309
+ // Create transaction
310
+ const tx = {
311
+ id: this.store.generateId('tx'),
312
+ walletId: hold.walletId,
313
+ type: 'capture',
314
+ status: 'confirmed',
315
+ amount: captureAmount.toFixed(2),
316
+ token: 'USDC',
317
+ chain: wallet.chain,
318
+ from: wallet.address,
319
+ to: hold.merchant,
320
+ merchant: hold.merchant,
321
+ txHash: this.store.generateTxHash(),
322
+ blockNumber: this.store.nextBlock(),
323
+ createdAt: new Date().toISOString(),
324
+ confirmedAt: new Date().toISOString(),
325
+ };
326
+ this.store.transactions.set(tx.id, tx);
327
+ return this.simulate(hold);
328
+ }
329
+ async void(holdId) {
330
+ const hold = this.store.holds.get(holdId);
331
+ if (!hold) {
332
+ throw new Error(`Hold not found: ${holdId}`);
333
+ }
334
+ if (hold.status !== 'active') {
335
+ throw new Error(`Hold is not active: ${hold.status}`);
336
+ }
337
+ hold.status = 'voided';
338
+ return this.simulate(hold);
339
+ }
340
+ async get(holdId) {
341
+ const hold = this.store.holds.get(holdId);
342
+ if (!hold) {
343
+ throw new Error(`Hold not found: ${holdId}`);
344
+ }
345
+ return this.simulate(hold);
346
+ }
347
+ async list(walletId) {
348
+ const holds = Array.from(this.store.holds.values()).filter((h) => h.walletId === walletId);
349
+ return this.simulate(holds);
350
+ }
351
+ }
352
+ class DemoPolicyResource {
353
+ constructor(store, delay) {
354
+ this.store = store;
355
+ this.delay = delay;
356
+ }
357
+ async simulate(result) {
358
+ await new Promise((r) => setTimeout(r, this.delay));
359
+ return result;
360
+ }
361
+ async check(params) {
362
+ const wallet = this.store.wallets.get(params.walletId);
363
+ if (!wallet) {
364
+ throw new Error(`Wallet not found: ${params.walletId}`);
365
+ }
366
+ const amount = parseFloat(params.amount);
367
+ const policy = wallet.policy;
368
+ // Check single transaction limit
369
+ if (amount > policy.singleTxLimit) {
370
+ return this.simulate({
371
+ allowed: false,
372
+ reason: `Amount $${amount} exceeds single transaction limit of $${policy.singleTxLimit}`,
373
+ remainingDailyLimit: policy.dailyLimit - policy.spent.today,
374
+ remainingMonthlyLimit: policy.monthlyLimit - policy.spent.thisMonth,
375
+ requiresApproval: false,
376
+ });
377
+ }
378
+ // Check daily limit
379
+ if (policy.spent.today + amount > policy.dailyLimit) {
380
+ return this.simulate({
381
+ allowed: false,
382
+ reason: `Would exceed daily limit. Remaining: $${policy.dailyLimit - policy.spent.today}`,
383
+ remainingDailyLimit: policy.dailyLimit - policy.spent.today,
384
+ remainingMonthlyLimit: policy.monthlyLimit - policy.spent.thisMonth,
385
+ requiresApproval: false,
386
+ });
387
+ }
388
+ // Check monthly limit
389
+ if (policy.spent.thisMonth + amount > policy.monthlyLimit) {
390
+ return this.simulate({
391
+ allowed: false,
392
+ reason: `Would exceed monthly limit. Remaining: $${policy.monthlyLimit - policy.spent.thisMonth}`,
393
+ remainingDailyLimit: policy.dailyLimit - policy.spent.today,
394
+ remainingMonthlyLimit: policy.monthlyLimit - policy.spent.thisMonth,
395
+ requiresApproval: false,
396
+ });
397
+ }
398
+ // Check blocked categories
399
+ if (params.category && policy.blockedCategories.includes(params.category.toLowerCase())) {
400
+ return this.simulate({
401
+ allowed: false,
402
+ reason: `Category '${params.category}' is blocked by policy`,
403
+ remainingDailyLimit: policy.dailyLimit - policy.spent.today,
404
+ remainingMonthlyLimit: policy.monthlyLimit - policy.spent.thisMonth,
405
+ requiresApproval: false,
406
+ });
407
+ }
408
+ // Check allowed merchants (if whitelist mode)
409
+ if (policy.allowedMerchants && params.merchant) {
410
+ if (!policy.allowedMerchants.includes(params.merchant.toLowerCase())) {
411
+ return this.simulate({
412
+ allowed: false,
413
+ reason: `Merchant '${params.merchant}' is not in allowed list`,
414
+ remainingDailyLimit: policy.dailyLimit - policy.spent.today,
415
+ remainingMonthlyLimit: policy.monthlyLimit - policy.spent.thisMonth,
416
+ requiresApproval: false,
417
+ });
418
+ }
419
+ }
420
+ // All checks passed
421
+ return this.simulate({
422
+ allowed: true,
423
+ remainingDailyLimit: policy.dailyLimit - policy.spent.today - amount,
424
+ remainingMonthlyLimit: policy.monthlyLimit - policy.spent.thisMonth - amount,
425
+ requiresApproval: amount > 250, // Require approval for >$250
426
+ });
427
+ }
428
+ async getSpending(walletId) {
429
+ const wallet = this.store.wallets.get(walletId);
430
+ if (!wallet) {
431
+ throw new Error(`Wallet not found: ${walletId}`);
432
+ }
433
+ const transactions = Array.from(this.store.transactions.values()).filter((tx) => tx.walletId === walletId && tx.type === 'payment');
434
+ const byCategory = {};
435
+ const byMerchant = {};
436
+ for (const tx of transactions) {
437
+ const amount = parseFloat(tx.amount);
438
+ if (tx.category) {
439
+ byCategory[tx.category] = (byCategory[tx.category] || 0) + amount;
440
+ }
441
+ if (tx.merchant) {
442
+ byMerchant[tx.merchant] = (byMerchant[tx.merchant] || 0) + amount;
443
+ }
444
+ }
445
+ return this.simulate({
446
+ today: wallet.policy.spent.today,
447
+ thisWeek: wallet.policy.spent.today * 3, // Simulated
448
+ thisMonth: wallet.policy.spent.thisMonth,
449
+ byCategory,
450
+ byMerchant,
451
+ });
452
+ }
453
+ }
454
+ // =============================================================================
455
+ // Ramp Resource (Fiat On/Off Ramp)
456
+ // =============================================================================
457
+ class DemoRampResource {
458
+ constructor(store, delay) {
459
+ this.store = store;
460
+ this.delay = delay;
461
+ }
462
+ async simulate(result) {
463
+ await new Promise((r) => setTimeout(r, this.delay));
464
+ return result;
465
+ }
466
+ /**
467
+ * Get supported currencies for on-ramp
468
+ */
469
+ async getSupportedCurrencies() {
470
+ return this.simulate({
471
+ fiat: ['USD', 'EUR', 'GBP', 'TRY'],
472
+ crypto: ['USDC', 'USDT', 'ETH'],
473
+ paymentMethods: ['card', 'bank_transfer', 'apple_pay', 'google_pay'],
474
+ });
475
+ }
476
+ /**
477
+ * Get a quote for fiat -> crypto conversion
478
+ */
479
+ async getOnRampQuote(params) {
480
+ const sourceAmount = parseFloat(params.sourceAmount);
481
+ const sourceCurrency = params.sourceCurrency.toUpperCase();
482
+ const destCurrency = params.destinationCurrency?.toUpperCase() || 'USDC';
483
+ // Simulated exchange rates (as of demo)
484
+ const rates = {
485
+ USD: 1.0,
486
+ EUR: 1.08,
487
+ GBP: 1.27,
488
+ TRY: 0.031,
489
+ };
490
+ const usdAmount = sourceAmount * (rates[sourceCurrency] || 1.0);
491
+ // Fee: 1.5% for cards, 0.5% for bank transfers
492
+ const feeRate = params.paymentMethod === 'bank_transfer' ? 0.005 : 0.015;
493
+ const fee = usdAmount * feeRate;
494
+ const destinationAmount = (usdAmount - fee).toFixed(2);
495
+ const expiresAt = new Date();
496
+ expiresAt.setMinutes(expiresAt.getMinutes() + 5);
497
+ const quote = {
498
+ id: this.store.generateId('quote_on'),
499
+ sourceAmount: params.sourceAmount,
500
+ sourceCurrency,
501
+ destinationAmount,
502
+ destinationCurrency: destCurrency,
503
+ exchangeRate: (rates[sourceCurrency] || 1.0).toFixed(4),
504
+ fee: fee.toFixed(2),
505
+ totalCost: sourceAmount.toFixed(2),
506
+ expiresAt: expiresAt.toISOString(),
507
+ provider: 'onramper', // Simulated provider
508
+ };
509
+ this.store.onRampQuotes.set(quote.id, quote);
510
+ return this.simulate(quote);
511
+ }
512
+ /**
513
+ * Execute on-ramp: fiat -> crypto to wallet
514
+ */
515
+ async executeOnRamp(params) {
516
+ const quote = this.store.onRampQuotes.get(params.quoteId);
517
+ if (!quote) {
518
+ throw new Error(`Quote not found or expired: ${params.quoteId}`);
519
+ }
520
+ const wallet = this.store.wallets.get(params.walletId);
521
+ if (!wallet) {
522
+ throw new Error(`Wallet not found: ${params.walletId}`);
523
+ }
524
+ // Check if quote expired
525
+ if (new Date(quote.expiresAt) < new Date()) {
526
+ throw new Error('Quote has expired. Please get a new quote.');
527
+ }
528
+ // Simulate KYC check for amounts > $500
529
+ const amount = parseFloat(quote.destinationAmount);
530
+ const kycRequired = amount > 500;
531
+ const kycStatus = kycRequired
532
+ ? params.kycData
533
+ ? 'approved'
534
+ : 'pending'
535
+ : 'not_required';
536
+ if (kycRequired && !params.kycData) {
537
+ throw new Error('KYC verification required for amounts over $500');
538
+ }
539
+ const transfer = {
540
+ id: this.store.generateId('onramp'),
541
+ quoteId: params.quoteId,
542
+ walletId: params.walletId,
543
+ status: 'processing',
544
+ sourceAmount: quote.sourceAmount,
545
+ sourceCurrency: quote.sourceCurrency,
546
+ destinationAmount: quote.destinationAmount,
547
+ destinationCurrency: quote.destinationCurrency,
548
+ destinationAddress: wallet.address,
549
+ provider: quote.provider,
550
+ paymentMethod: params.paymentMethod,
551
+ kycStatus,
552
+ createdAt: new Date().toISOString(),
553
+ };
554
+ this.store.onRampTransfers.set(transfer.id, transfer);
555
+ // Simulate async completion (in real scenario, webhook would update)
556
+ setTimeout(() => {
557
+ const t = this.store.onRampTransfers.get(transfer.id);
558
+ if (t && t.status === 'processing') {
559
+ t.status = 'completed';
560
+ t.completedAt = new Date().toISOString();
561
+ t.txHash = this.store.generateTxHash();
562
+ // Credit the wallet
563
+ const w = this.store.wallets.get(params.walletId);
564
+ if (w) {
565
+ w.balance = (parseFloat(w.balance) + parseFloat(quote.destinationAmount)).toFixed(2);
566
+ }
567
+ }
568
+ }, this.delay * 4); // Complete after 4x delay
569
+ return this.simulate(transfer);
570
+ }
571
+ /**
572
+ * Get on-ramp transfer status
573
+ */
574
+ async getOnRampStatus(transferId) {
575
+ const transfer = this.store.onRampTransfers.get(transferId);
576
+ if (!transfer) {
577
+ throw new Error(`Transfer not found: ${transferId}`);
578
+ }
579
+ return this.simulate(transfer);
580
+ }
581
+ /**
582
+ * Get a quote for crypto -> fiat conversion (off-ramp)
583
+ */
584
+ async getOffRampQuote(params) {
585
+ const sourceAmount = parseFloat(params.sourceAmount);
586
+ const destCurrency = params.destinationCurrency.toUpperCase();
587
+ // Simulated exchange rates
588
+ const rates = {
589
+ USD: 1.0,
590
+ EUR: 0.92,
591
+ GBP: 0.79,
592
+ TRY: 32.5,
593
+ };
594
+ const destAmount = sourceAmount * (rates[destCurrency] || 1.0);
595
+ // Fee: 1% for off-ramp
596
+ const fee = sourceAmount * 0.01;
597
+ const netAmount = (destAmount - fee * (rates[destCurrency] || 1.0)).toFixed(2);
598
+ const expiresAt = new Date();
599
+ expiresAt.setMinutes(expiresAt.getMinutes() + 5);
600
+ const quote = {
601
+ id: this.store.generateId('quote_off'),
602
+ sourceAmount: params.sourceAmount,
603
+ sourceCurrency: params.sourceCurrency?.toUpperCase() || 'USDC',
604
+ destinationAmount: destAmount.toFixed(2),
605
+ destinationCurrency: destCurrency,
606
+ exchangeRate: (rates[destCurrency] || 1.0).toFixed(4),
607
+ fee: fee.toFixed(2),
608
+ netAmount,
609
+ expiresAt: expiresAt.toISOString(),
610
+ provider: 'onramper',
611
+ };
612
+ this.store.offRampQuotes.set(quote.id, quote);
613
+ return this.simulate(quote);
614
+ }
615
+ /**
616
+ * Execute off-ramp: crypto -> fiat to bank account
617
+ */
618
+ async executeOffRamp(params) {
619
+ const quote = this.store.offRampQuotes.get(params.quoteId);
620
+ if (!quote) {
621
+ throw new Error(`Quote not found or expired: ${params.quoteId}`);
622
+ }
623
+ const wallet = this.store.wallets.get(params.walletId);
624
+ if (!wallet) {
625
+ throw new Error(`Wallet not found: ${params.walletId}`);
626
+ }
627
+ // Check if quote expired
628
+ if (new Date(quote.expiresAt) < new Date()) {
629
+ throw new Error('Quote has expired. Please get a new quote.');
630
+ }
631
+ // Check balance
632
+ const sourceAmount = parseFloat(quote.sourceAmount);
633
+ if (sourceAmount > parseFloat(wallet.balance)) {
634
+ throw new Error(`Insufficient balance. Available: $${wallet.balance}, Required: $${sourceAmount}`);
635
+ }
636
+ // Deduct from wallet immediately
637
+ wallet.balance = (parseFloat(wallet.balance) - sourceAmount).toFixed(2);
638
+ const transfer = {
639
+ id: this.store.generateId('offramp'),
640
+ quoteId: params.quoteId,
641
+ walletId: params.walletId,
642
+ status: 'processing',
643
+ sourceAmount: quote.sourceAmount,
644
+ sourceCurrency: quote.sourceCurrency,
645
+ destinationAmount: quote.netAmount,
646
+ destinationCurrency: quote.destinationCurrency,
647
+ bankAccount: {
648
+ last4: params.bankAccount.accountNumber.slice(-4),
649
+ bankName: params.bankAccount.bankName,
650
+ },
651
+ provider: quote.provider,
652
+ createdAt: new Date().toISOString(),
653
+ txHash: this.store.generateTxHash(),
654
+ };
655
+ this.store.offRampTransfers.set(transfer.id, transfer);
656
+ // Simulate async completion
657
+ setTimeout(() => {
658
+ const t = this.store.offRampTransfers.get(transfer.id);
659
+ if (t && t.status === 'processing') {
660
+ t.status = 'completed';
661
+ t.completedAt = new Date().toISOString();
662
+ }
663
+ }, this.delay * 6); // Bank transfers take longer
664
+ return this.simulate(transfer);
665
+ }
666
+ /**
667
+ * Get off-ramp transfer status
668
+ */
669
+ async getOffRampStatus(transferId) {
670
+ const transfer = this.store.offRampTransfers.get(transferId);
671
+ if (!transfer) {
672
+ throw new Error(`Transfer not found: ${transferId}`);
673
+ }
674
+ return this.simulate(transfer);
675
+ }
676
+ /**
677
+ * List all ramp transfers for a wallet
678
+ */
679
+ async listTransfers(walletId) {
680
+ const onRamp = Array.from(this.store.onRampTransfers.values()).filter((t) => t.walletId === walletId);
681
+ const offRamp = Array.from(this.store.offRampTransfers.values()).filter((t) => t.walletId === walletId);
682
+ return this.simulate({ onRamp, offRamp });
683
+ }
684
+ }
685
+ // =============================================================================
686
+ // Factory Function
687
+ // =============================================================================
688
+ /**
689
+ * Create a demo client for testing without API.
690
+ */
691
+ export function createDemoClient(options) {
692
+ return new SardisDemoClient(options);
693
+ }
694
+ //# sourceMappingURL=demo.js.map