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