cspr402 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +108 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +207 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +400 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/onboard.d.ts +4 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +192 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/onboard.test.d.ts +2 -0
- package/dist/commands/onboard.test.d.ts.map +1 -0
- package/dist/commands/onboard.test.js +48 -0
- package/dist/commands/onboard.test.js.map +1 -0
- package/dist/commands/purchase.d.ts +2 -0
- package/dist/commands/purchase.d.ts.map +1 -0
- package/dist/commands/purchase.js +206 -0
- package/dist/commands/purchase.js.map +1 -0
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.d.ts.map +1 -0
- package/dist/commands/wallet.js +161 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +329 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +101 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +197 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +337 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mpp.d.ts +57 -0
- package/dist/mpp.d.ts.map +1 -0
- package/dist/mpp.js +165 -0
- package/dist/mpp.js.map +1 -0
- package/dist/ows.d.ts +190 -0
- package/dist/ows.d.ts.map +1 -0
- package/dist/ows.js +565 -0
- package/dist/ows.js.map +1 -0
- package/dist/soroban.d.ts +92 -0
- package/dist/soroban.d.ts.map +1 -0
- package/dist/soroban.js +313 -0
- package/dist/soroban.js.map +1 -0
- package/dist/stellar.d.ts +53 -0
- package/dist/stellar.d.ts.map +1 -0
- package/dist/stellar.js +180 -0
- package/dist/stellar.js.map +1 -0
- package/dist/version-check.d.ts +5 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +203 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +80 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Agent-local config file. Persisted at ~/.cards402/config.json after
|
|
3
|
+
// a successful `cards402 onboard --claim` so the SDK can load the api
|
|
4
|
+
// key on subsequent runs without the agent having to re-paste secrets.
|
|
5
|
+
//
|
|
6
|
+
// The file lives on the agent's machine and is readable only by the
|
|
7
|
+
// agent's user (chmod 0600). It holds the raw api key — same secret
|
|
8
|
+
// the older env-var workflow stored in process.env, just written to
|
|
9
|
+
// disk in a well-known place so the SDK can find it automatically.
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.loadCards402Config = loadCards402Config;
|
|
45
|
+
exports.assertSafeBaseUrl = assertSafeBaseUrl;
|
|
46
|
+
exports.saveCards402Config = saveCards402Config;
|
|
47
|
+
exports.resolveCredentials = resolveCredentials;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const crypto = __importStar(require("crypto"));
|
|
52
|
+
// Adversarial audit F5-config: config files should be tiny. A 16 KB
|
|
53
|
+
// cap leaves plenty of room for a fat api_key + url + wallet_name
|
|
54
|
+
// while refusing a maliciously-enlarged file that would otherwise be
|
|
55
|
+
// parsed in full and flowed into request headers downstream.
|
|
56
|
+
const MAX_CONFIG_BYTES = 16 * 1024;
|
|
57
|
+
function defaultConfigDir() {
|
|
58
|
+
return (process.env.CSPR402_CONFIG_DIR ||
|
|
59
|
+
process.env.CARDS402_CONFIG_DIR ||
|
|
60
|
+
path.join(os.homedir(), '.cspr402'));
|
|
61
|
+
}
|
|
62
|
+
function defaultConfigPath() {
|
|
63
|
+
return path.join(defaultConfigDir(), 'config.json');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Load the agent's on-disk config, or return null if it doesn't exist.
|
|
67
|
+
* Never throws on missing file — only on corrupt JSON.
|
|
68
|
+
*
|
|
69
|
+
* On load we also tighten the file mode to 0600 if it's been loosened
|
|
70
|
+
* since the write (e.g. a bug in an older SDK version that wrote with
|
|
71
|
+
* default permissions, or an attacker pre-creating the file
|
|
72
|
+
* world-readable to farm credentials off the next onboarding). We
|
|
73
|
+
* warn rather than refuse the load, so operators with an existing
|
|
74
|
+
* loose config aren't hard-broken, but the mode is normalised
|
|
75
|
+
* immediately.
|
|
76
|
+
*/
|
|
77
|
+
// F3-config (2026-04-16): validate the api_key shape before accepting
|
|
78
|
+
// it. A corrupt or tampered key containing CRLF, NUL, or other control
|
|
79
|
+
// chars would flow into the X-Api-Key HTTP header and trigger Node's
|
|
80
|
+
// ERR_INVALID_CHAR on every fetch call — same bug class as the backend's
|
|
81
|
+
// X-Request-ID audit (F1-app). Accept printable ASCII only (the backend
|
|
82
|
+
// mints keys as `cards402_<48 hex>`, which is pure ASCII alnum + underscore).
|
|
83
|
+
const API_KEY_SHAPE = /^[\x20-\x7e]+$/;
|
|
84
|
+
function loadCards402Config(configPath) {
|
|
85
|
+
const p = configPath || defaultConfigPath();
|
|
86
|
+
try {
|
|
87
|
+
// F1-config (2026-04-16): platform-independent checks (symlink,
|
|
88
|
+
// regular-file, size cap) are now run on ALL platforms including
|
|
89
|
+
// Windows. Pre-fix the entire block was gated on
|
|
90
|
+
// `process.platform !== 'win32'`, which meant Windows agents
|
|
91
|
+
// skipped the size cap — a planted 1 GB config.json (or an NTFS
|
|
92
|
+
// junction to a large file) would be fully loaded via readFileSync
|
|
93
|
+
// and OOM the agent. NTFS supports symlinks and junctions, and
|
|
94
|
+
// fs.lstatSync correctly reports them, so the symlink defense
|
|
95
|
+
// is also meaningful on Windows. Only the Unix permission-bit
|
|
96
|
+
// checks (chmod) are platform-gated now.
|
|
97
|
+
let stat;
|
|
98
|
+
try {
|
|
99
|
+
stat = fs.lstatSync(p);
|
|
100
|
+
}
|
|
101
|
+
catch (statErr) {
|
|
102
|
+
if (statErr.code === 'ENOENT')
|
|
103
|
+
return null;
|
|
104
|
+
throw statErr;
|
|
105
|
+
}
|
|
106
|
+
if (stat.isSymbolicLink()) {
|
|
107
|
+
throw new Error(`cards402 config at ${p} is a symbolic link. Refusing to load. ` +
|
|
108
|
+
`Remove the link and re-run 'cards402 onboard --claim <code>' to create a real file.`);
|
|
109
|
+
}
|
|
110
|
+
if (!stat.isFile()) {
|
|
111
|
+
throw new Error(`cards402 config at ${p} is not a regular file. ` +
|
|
112
|
+
`Remove it and re-run 'cards402 onboard --claim <code>'.`);
|
|
113
|
+
}
|
|
114
|
+
// F5-config: enforce a size cap BEFORE reading the file into
|
|
115
|
+
// memory or doing any further work on it. Config files are
|
|
116
|
+
// tiny; anything bigger than MAX_CONFIG_BYTES is either
|
|
117
|
+
// corruption or an attempt to flood request headers.
|
|
118
|
+
if (stat.size > MAX_CONFIG_BYTES) {
|
|
119
|
+
throw new Error(`cards402 config at ${p} is ${stat.size} bytes (max ${MAX_CONFIG_BYTES}). ` +
|
|
120
|
+
`Refusing to load — the file is either corrupted or has been tampered with. ` +
|
|
121
|
+
`Rotate your api key via the dashboard and re-run 'cards402 onboard'.`);
|
|
122
|
+
}
|
|
123
|
+
// Unix-only: tighten loose permission bits. chmod on a regular
|
|
124
|
+
// file (we verified above that it's not a symlink) is safe and
|
|
125
|
+
// only affects this file. Skipped on Windows where mode bits are
|
|
126
|
+
// simulated and chmod can fail or no-op unpredictably.
|
|
127
|
+
if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) {
|
|
128
|
+
try {
|
|
129
|
+
fs.chmodSync(p, 0o600);
|
|
130
|
+
process.stderr.write(`⚠ cards402 config at ${p} had loose permissions (${(stat.mode & 0o777).toString(8)}) — tightened to 600.\n` +
|
|
131
|
+
' If this is unexpected, rotate your api key via the dashboard.\n');
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
/* non-fatal — we at least tried */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
138
|
+
const config = JSON.parse(raw);
|
|
139
|
+
// F3-config (2026-04-16): validate api_key shape before accepting.
|
|
140
|
+
// A corrupt or tampered key with CRLF / NUL / non-printable bytes
|
|
141
|
+
// would crash every HTTP request downstream via Node's
|
|
142
|
+
// ERR_INVALID_CHAR header validation.
|
|
143
|
+
if (typeof config.api_key === 'string' && !API_KEY_SHAPE.test(config.api_key)) {
|
|
144
|
+
throw new Error(`cards402 config at ${p} contains an api_key with non-printable characters. ` +
|
|
145
|
+
`The file may be corrupted or tampered with. Rotate your key and re-run 'cards402 onboard'.`);
|
|
146
|
+
}
|
|
147
|
+
return config;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
if (err.code === 'ENOENT')
|
|
151
|
+
return null;
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Validate a base URL for safety before storing it in the config or
|
|
157
|
+
* using it for API calls. Rejects everything that isn't HTTPS unless
|
|
158
|
+
* the explicit CARDS402_ALLOW_INSECURE_BASE_URL escape hatch is set,
|
|
159
|
+
* which only exists so local dev against http://localhost:4000 still
|
|
160
|
+
* works. Returns the parsed URL.string() on success, throws on reject.
|
|
161
|
+
*
|
|
162
|
+
* Called from:
|
|
163
|
+
* - onboard, when persisting the api_url returned by the claim
|
|
164
|
+
* endpoint (defends against a MITM or compromised backend that
|
|
165
|
+
* injects http:// or a foreign origin into the response)
|
|
166
|
+
* - resolveCredentials, when an env-var override is used for
|
|
167
|
+
* baseUrl (defends against a user being tricked into setting
|
|
168
|
+
* CARDS402_BASE_URL to an attacker target)
|
|
169
|
+
*/
|
|
170
|
+
function assertSafeBaseUrl(url, opts = {}) {
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = new URL(url);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
throw new Error(`Invalid base URL: ${url}`);
|
|
177
|
+
}
|
|
178
|
+
// F4-config: reject embedded userinfo. `https://api.cards402.com/v1@evil.com/`
|
|
179
|
+
// parses as username='api.cards402.com/v1', password='', hostname='evil.com'
|
|
180
|
+
// — the whole string looks plausibly cards402-ish in log output, but every
|
|
181
|
+
// request would go to evil.com carrying the user's api_key in the
|
|
182
|
+
// Authorization header. There's no legitimate reason for a cards402 base
|
|
183
|
+
// URL to include credentials, so refuse any URL with a non-empty username
|
|
184
|
+
// or password.
|
|
185
|
+
if (parsed.username !== '' || parsed.password !== '') {
|
|
186
|
+
throw new Error(`Refusing base URL ${JSON.stringify(url)} with embedded credentials. ` +
|
|
187
|
+
`Use a bare https://host/path form — the api key is sent via the ` +
|
|
188
|
+
`Authorization header, never in the URL.`);
|
|
189
|
+
}
|
|
190
|
+
if (parsed.protocol !== 'https:') {
|
|
191
|
+
if (process.env.CSPR402_ALLOW_INSECURE_BASE_URL === '1' ||
|
|
192
|
+
process.env.CARDS402_ALLOW_INSECURE_BASE_URL === '1') {
|
|
193
|
+
return parsed.toString();
|
|
194
|
+
}
|
|
195
|
+
throw new Error(`Refusing to use non-HTTPS base URL (${url})${opts.context ? ` for ${opts.context}` : ''}. ` +
|
|
196
|
+
`Set CARDS402_ALLOW_INSECURE_BASE_URL=1 to override for local development.`);
|
|
197
|
+
}
|
|
198
|
+
return parsed.toString();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Write the config file atomically with 0600 permissions so only the
|
|
202
|
+
* owner can read it. Creates the parent directory on demand.
|
|
203
|
+
*
|
|
204
|
+
* Atomicity: write to `<path>.tmp-<pid>-<rand>` first, fsync, then
|
|
205
|
+
* rename over the target. A mid-write crash (power loss, OOM, Ctrl-C
|
|
206
|
+
* between write and flush) leaves the old file intact instead of a
|
|
207
|
+
* truncated new one that loadCards402Config would explode on.
|
|
208
|
+
*
|
|
209
|
+
* Permission hardening: the `mode` option on writeFileSync only
|
|
210
|
+
* applies when the file is being CREATED, so a stale 0644 file from
|
|
211
|
+
* an earlier buggy version would retain its wide permissions forever.
|
|
212
|
+
* We fsync+rename so the temp path is always freshly created with
|
|
213
|
+
* 0600, then the rename replaces the target atomically.
|
|
214
|
+
*/
|
|
215
|
+
function saveCards402Config(config, configPath) {
|
|
216
|
+
const p = configPath || defaultConfigPath();
|
|
217
|
+
const dir = path.dirname(p);
|
|
218
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
219
|
+
// F2-config: mkdirSync's mode option only applies to directories
|
|
220
|
+
// it actually CREATES. An existing ~/.cards402 directory at 0755
|
|
221
|
+
// (from an older buggy SDK version, a package install, or a
|
|
222
|
+
// manual mkdir) silently stays loose, and the config file sits
|
|
223
|
+
// inside a world-traversable parent. Explicit chmod after mkdir
|
|
224
|
+
// guarantees the directory ends up at 0700 regardless of its
|
|
225
|
+
// pre-existing state. Skip on Windows — mode bits are simulated
|
|
226
|
+
// and the chmod can fail or no-op unpredictably.
|
|
227
|
+
if (process.platform !== 'win32') {
|
|
228
|
+
try {
|
|
229
|
+
fs.chmodSync(dir, 0o700);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* non-fatal — best effort */
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// F3-config: crypto.randomBytes is strictly safer than Math.random
|
|
236
|
+
// for a temp-file suffix. Collision is already near-zero in
|
|
237
|
+
// practice but Math.random is seeded from the clock — two
|
|
238
|
+
// containers starting in the same millisecond could in principle
|
|
239
|
+
// produce the same sequence. Crypto random adds no meaningful
|
|
240
|
+
// cost and eliminates the class of concern.
|
|
241
|
+
const tmp = `${p}.tmp-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
|
|
242
|
+
const body = JSON.stringify(config, null, 2);
|
|
243
|
+
let committed = false;
|
|
244
|
+
try {
|
|
245
|
+
const fd = fs.openSync(tmp, 'w', 0o600);
|
|
246
|
+
try {
|
|
247
|
+
fs.writeFileSync(fd, body);
|
|
248
|
+
fs.fsyncSync(fd);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
fs.closeSync(fd);
|
|
252
|
+
}
|
|
253
|
+
// Atomic rename. POSIX guarantees this replaces an existing file
|
|
254
|
+
// with the same semantics; on Windows rename-over-existing also
|
|
255
|
+
// works from Node 10+.
|
|
256
|
+
fs.renameSync(tmp, p);
|
|
257
|
+
committed = true;
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
// F3-config: clean up the temp file on any failure before the
|
|
261
|
+
// rename commits. A leaked ~/.cards402/config.json.tmp-* file
|
|
262
|
+
// holding a fresh api_key would otherwise linger on disk with
|
|
263
|
+
// the same 0600 permissions as the target but under a path no
|
|
264
|
+
// one checks — easy to miss during credential rotation.
|
|
265
|
+
if (!committed) {
|
|
266
|
+
try {
|
|
267
|
+
fs.unlinkSync(tmp);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
/* temp already gone or never created — fine */
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Belt-and-braces: some filesystems (FAT on USB sticks) drop the
|
|
275
|
+
// mode on rename. Force-tighten after.
|
|
276
|
+
try {
|
|
277
|
+
fs.chmodSync(p, 0o600);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
/* non-fatal — best effort */
|
|
281
|
+
}
|
|
282
|
+
return { path: p };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Resolve an api key + base URL at SDK call time, in priority order:
|
|
286
|
+
* 1. Explicit `apiKey` / `baseUrl` passed to the call
|
|
287
|
+
* 2. CSPR402_API_KEY / CSPR402_BASE_URL env vars
|
|
288
|
+
* 3. ~/.cspr402/config.json
|
|
289
|
+
*
|
|
290
|
+
* The two fields resolve independently — passing `apiKey` to a call
|
|
291
|
+
* that needs its `baseUrl` to come from config.json used to silently
|
|
292
|
+
* drop the config lookup because the early-return on `opts.apiKey`
|
|
293
|
+
* was only consulting env vars for baseUrl. Now both fields walk the
|
|
294
|
+
* full priority chain and only stop once each is filled.
|
|
295
|
+
*/
|
|
296
|
+
function resolveCredentials(opts = {}) {
|
|
297
|
+
let apiKey = opts.apiKey;
|
|
298
|
+
let baseUrl = opts.baseUrl;
|
|
299
|
+
if (!apiKey && process.env.CSPR402_API_KEY)
|
|
300
|
+
apiKey = process.env.CSPR402_API_KEY;
|
|
301
|
+
if (!apiKey && process.env.CARDS402_API_KEY)
|
|
302
|
+
apiKey = process.env.CARDS402_API_KEY;
|
|
303
|
+
if (!baseUrl && process.env.CSPR402_BASE_URL)
|
|
304
|
+
baseUrl = process.env.CSPR402_BASE_URL;
|
|
305
|
+
if (!baseUrl && process.env.CARDS402_BASE_URL)
|
|
306
|
+
baseUrl = process.env.CARDS402_BASE_URL;
|
|
307
|
+
if (!apiKey || !baseUrl) {
|
|
308
|
+
// Only load config if at least one field is still missing — saves
|
|
309
|
+
// a filesystem read on the common case where env + opts fully cover it.
|
|
310
|
+
const cfg = loadCards402Config();
|
|
311
|
+
if (cfg) {
|
|
312
|
+
if (!apiKey)
|
|
313
|
+
apiKey = cfg.api_key;
|
|
314
|
+
if (!baseUrl)
|
|
315
|
+
baseUrl = cfg.api_url;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Refuse any non-HTTPS baseUrl (env, opts, or config) unless the
|
|
319
|
+
// explicit local-dev escape hatch is set. Without this, an attacker
|
|
320
|
+
// who tricks the user into setting CARDS402_BASE_URL=http://evil/
|
|
321
|
+
// sees the api key in every request's Authorization header. The
|
|
322
|
+
// assertSafeBaseUrl helper throws on reject — we let it propagate
|
|
323
|
+
// rather than silently continue with an insecure URL.
|
|
324
|
+
if (baseUrl) {
|
|
325
|
+
baseUrl = assertSafeBaseUrl(baseUrl, { context: 'resolveCredentials' });
|
|
326
|
+
}
|
|
327
|
+
return { apiKey, baseUrl };
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error types for the cards402 SDK.
|
|
3
|
+
*
|
|
4
|
+
* Catch by type so your agent can handle each case without string-parsing:
|
|
5
|
+
*
|
|
6
|
+
* try {
|
|
7
|
+
* const card = await client.createOrder({ amount_usdc: '10.00' });
|
|
8
|
+
* } catch (err) {
|
|
9
|
+
* if (err instanceof SpendLimitError) { ... }
|
|
10
|
+
* if (err instanceof ServiceUnavailableError) { ... }
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
/** Base class — all cards402 errors extend this. */
|
|
14
|
+
export declare class Cards402Error extends Error {
|
|
15
|
+
readonly code: string;
|
|
16
|
+
readonly status: number;
|
|
17
|
+
readonly raw?: unknown | undefined;
|
|
18
|
+
constructor(message: string, code: string, status: number, raw?: unknown | undefined);
|
|
19
|
+
}
|
|
20
|
+
/** The API key's spend limit has been reached. */
|
|
21
|
+
export declare class SpendLimitError extends Cards402Error {
|
|
22
|
+
readonly limit: string;
|
|
23
|
+
readonly spent: string;
|
|
24
|
+
constructor(limit: string, spent: string);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Rate limit hit. The backend returns `rate_limit_exceeded` for two
|
|
28
|
+
* different limits — 60 orders/hour on POST /v1/orders, and 10
|
|
29
|
+
* requests/second (600/minute) on GET /v1/orders/:id status polling.
|
|
30
|
+
* Which one fired is in the server's `message` field, not in the
|
|
31
|
+
* error code, so we forward the message verbatim instead of hardcoding
|
|
32
|
+
* the wrong explanation.
|
|
33
|
+
*/
|
|
34
|
+
export declare class RateLimitError extends Cards402Error {
|
|
35
|
+
constructor(message?: string);
|
|
36
|
+
}
|
|
37
|
+
/** Service is temporarily suspended (fulfillment circuit breaker tripped). */
|
|
38
|
+
export declare class ServiceUnavailableError extends Cards402Error {
|
|
39
|
+
constructor(message?: string);
|
|
40
|
+
}
|
|
41
|
+
/** XLM price feed is unavailable — retry or use USDC. */
|
|
42
|
+
export declare class PriceUnavailableError extends Cards402Error {
|
|
43
|
+
constructor(message?: string);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* amount_usdc was missing, zero, non-numeric, or outside the bounds
|
|
47
|
+
* [0.01, 10000]. The message defaults to the full-range explanation
|
|
48
|
+
* but is overridden by whatever the backend sent so a call with e.g.
|
|
49
|
+
* "9.99999999" (too many decimals) gets the specific reason instead
|
|
50
|
+
* of the generic bounds message.
|
|
51
|
+
*/
|
|
52
|
+
export declare class InvalidAmountError extends Cards402Error {
|
|
53
|
+
constructor(message?: string);
|
|
54
|
+
}
|
|
55
|
+
/** The API key is missing or invalid. */
|
|
56
|
+
export declare class AuthError extends Cards402Error {
|
|
57
|
+
constructor();
|
|
58
|
+
}
|
|
59
|
+
/** Order failed during fulfillment. A refund may be in progress. */
|
|
60
|
+
export declare class OrderFailedError extends Cards402Error {
|
|
61
|
+
readonly orderId: string;
|
|
62
|
+
readonly refund?: {
|
|
63
|
+
stellar_txid: string;
|
|
64
|
+
} | undefined;
|
|
65
|
+
constructor(orderId: string, reason: string, refund?: {
|
|
66
|
+
stellar_txid: string;
|
|
67
|
+
} | undefined);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown by purchaseCardOWS when the order was created (and possibly paid)
|
|
71
|
+
* but the flow couldn't finish — e.g. Soroban RPC finalization timed out,
|
|
72
|
+
* or waitForCard hit its deadline. Always carries the orderId so the caller
|
|
73
|
+
* can resume via `cards402 purchase --resume <order-id>` without minting a
|
|
74
|
+
* new order (which would strand the original one until it expires).
|
|
75
|
+
*
|
|
76
|
+
* `txHash` is present when the payment was submitted onto the ledger (even
|
|
77
|
+
* if finalization wasn't confirmed client-side) — the backend watcher can
|
|
78
|
+
* still credit the order after the fact.
|
|
79
|
+
*
|
|
80
|
+
* `phase: 'paid'` means payViaContractOWS returned successfully, so resume
|
|
81
|
+
* should skip straight to waitForCard. `phase: 'unpaid'` means the payment
|
|
82
|
+
* never went out and resume may need to retry the Soroban submit.
|
|
83
|
+
*/
|
|
84
|
+
export declare class ResumableError extends Cards402Error {
|
|
85
|
+
readonly orderId: string;
|
|
86
|
+
readonly phase: 'unpaid' | 'paid';
|
|
87
|
+
readonly txHash?: string | undefined;
|
|
88
|
+
readonly cause?: unknown | undefined;
|
|
89
|
+
constructor(orderId: string, reason: string, phase: 'unpaid' | 'paid', txHash?: string | undefined, cause?: unknown | undefined);
|
|
90
|
+
}
|
|
91
|
+
/** Waiting for a card timed out — order may still be processing. */
|
|
92
|
+
export declare class WaitTimeoutError extends Cards402Error {
|
|
93
|
+
readonly orderId: string;
|
|
94
|
+
constructor(orderId: string, timeoutMs: number);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parse a raw API error response into the appropriate typed error.
|
|
98
|
+
* Falls back to generic Cards402Error for unknown codes.
|
|
99
|
+
*/
|
|
100
|
+
export declare function parseApiError(status: number, body: Record<string, unknown>): Cards402Error;
|
|
101
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,oDAAoD;AACpD,qBAAa,aAAc,SAAQ,KAAK;aAGpB,IAAI,EAAE,MAAM;aACZ,MAAM,EAAE,MAAM;aACd,GAAG,CAAC,EAAE,OAAO;gBAH7B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,OAAO,YAAA;CAMhC;AAED,kDAAkD;AAClD,qBAAa,eAAgB,SAAQ,aAAa;aAE9B,KAAK,EAAE,MAAM;aACb,KAAK,EAAE,MAAM;gBADb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM;CAUhC;AAED;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,aAAa;gBACnC,OAAO,SAA6C;CAKjE;AAED,8EAA8E;AAC9E,qBAAa,uBAAwB,SAAQ,aAAa;gBAC5C,OAAO,SAAuE;CAK3F;AAED,yDAAyD;AACzD,qBAAa,qBAAsB,SAAQ,aAAa;gBAEpD,OAAO,SAAuF;CAMjG;AAED;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,aAAa;gBAEjD,OAAO,SAAiG;CAM3G;AAED,yCAAyC;AACzC,qBAAa,SAAU,SAAQ,aAAa;;CAU3C;AAED,oEAAoE;AACpE,qBAAa,gBAAiB,SAAQ,aAAa;aAE/B,OAAO,EAAE,MAAM;aAEf,MAAM,CAAC,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE;gBAFjC,OAAO,EAAE,MAAM,EAC/B,MAAM,EAAE,MAAM,EACE,MAAM,CAAC,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,YAAA;CAapD;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,cAAe,SAAQ,aAAa;aAE7B,OAAO,EAAE,MAAM;aAEf,KAAK,EAAE,QAAQ,GAAG,MAAM;aACxB,MAAM,CAAC,EAAE,MAAM;aACf,KAAK,CAAC,EAAE,OAAO;gBAJf,OAAO,EAAE,MAAM,EAC/B,MAAM,EAAE,MAAM,EACE,KAAK,EAAE,QAAQ,GAAG,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,KAAK,CAAC,EAAE,OAAO,YAAA;CAalC;AAED,oEAAoE;AACpE,qBAAa,gBAAiB,SAAQ,aAAa;aAE/B,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM,EAC/B,SAAS,EAAE,MAAM;CAWpB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,CAyB1F"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Structured error types for the cards402 SDK.
|
|
4
|
+
*
|
|
5
|
+
* Catch by type so your agent can handle each case without string-parsing:
|
|
6
|
+
*
|
|
7
|
+
* try {
|
|
8
|
+
* const card = await client.createOrder({ amount_usdc: '10.00' });
|
|
9
|
+
* } catch (err) {
|
|
10
|
+
* if (err instanceof SpendLimitError) { ... }
|
|
11
|
+
* if (err instanceof ServiceUnavailableError) { ... }
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.WaitTimeoutError = exports.ResumableError = exports.OrderFailedError = exports.AuthError = exports.InvalidAmountError = exports.PriceUnavailableError = exports.ServiceUnavailableError = exports.RateLimitError = exports.SpendLimitError = exports.Cards402Error = void 0;
|
|
16
|
+
exports.parseApiError = parseApiError;
|
|
17
|
+
/** Base class — all cards402 errors extend this. */
|
|
18
|
+
class Cards402Error extends Error {
|
|
19
|
+
code;
|
|
20
|
+
status;
|
|
21
|
+
raw;
|
|
22
|
+
constructor(message, code, status, raw) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.status = status;
|
|
26
|
+
this.raw = raw;
|
|
27
|
+
this.name = 'Cards402Error';
|
|
28
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.Cards402Error = Cards402Error;
|
|
32
|
+
/** The API key's spend limit has been reached. */
|
|
33
|
+
class SpendLimitError extends Cards402Error {
|
|
34
|
+
limit;
|
|
35
|
+
spent;
|
|
36
|
+
constructor(limit, spent) {
|
|
37
|
+
super(`Spend limit exceeded: $${spent} spent of $${limit} limit. Ask your operator to raise the limit or wait for the next reset period.`, 'spend_limit_exceeded', 403);
|
|
38
|
+
this.limit = limit;
|
|
39
|
+
this.spent = spent;
|
|
40
|
+
this.name = 'SpendLimitError';
|
|
41
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.SpendLimitError = SpendLimitError;
|
|
45
|
+
/**
|
|
46
|
+
* Rate limit hit. The backend returns `rate_limit_exceeded` for two
|
|
47
|
+
* different limits — 60 orders/hour on POST /v1/orders, and 10
|
|
48
|
+
* requests/second (600/minute) on GET /v1/orders/:id status polling.
|
|
49
|
+
* Which one fired is in the server's `message` field, not in the
|
|
50
|
+
* error code, so we forward the message verbatim instead of hardcoding
|
|
51
|
+
* the wrong explanation.
|
|
52
|
+
*/
|
|
53
|
+
class RateLimitError extends Cards402Error {
|
|
54
|
+
constructor(message = 'Rate limit exceeded. Back off and retry.') {
|
|
55
|
+
super(message, 'rate_limit_exceeded', 429);
|
|
56
|
+
this.name = 'RateLimitError';
|
|
57
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.RateLimitError = RateLimitError;
|
|
61
|
+
/** Service is temporarily suspended (fulfillment circuit breaker tripped). */
|
|
62
|
+
class ServiceUnavailableError extends Cards402Error {
|
|
63
|
+
constructor(message = 'Card fulfillment is temporarily suspended. Retry in a few minutes.') {
|
|
64
|
+
super(message, 'service_temporarily_unavailable', 503);
|
|
65
|
+
this.name = 'ServiceUnavailableError';
|
|
66
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.ServiceUnavailableError = ServiceUnavailableError;
|
|
70
|
+
/** XLM price feed is unavailable — retry or use USDC. */
|
|
71
|
+
class PriceUnavailableError extends Cards402Error {
|
|
72
|
+
constructor(message = 'XLM price is temporarily unavailable. Retry shortly, or use payment_asset: "usdc".') {
|
|
73
|
+
super(message, 'price_unavailable', 503);
|
|
74
|
+
this.name = 'PriceUnavailableError';
|
|
75
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.PriceUnavailableError = PriceUnavailableError;
|
|
79
|
+
/**
|
|
80
|
+
* amount_usdc was missing, zero, non-numeric, or outside the bounds
|
|
81
|
+
* [0.01, 10000]. The message defaults to the full-range explanation
|
|
82
|
+
* but is overridden by whatever the backend sent so a call with e.g.
|
|
83
|
+
* "9.99999999" (too many decimals) gets the specific reason instead
|
|
84
|
+
* of the generic bounds message.
|
|
85
|
+
*/
|
|
86
|
+
class InvalidAmountError extends Cards402Error {
|
|
87
|
+
constructor(message = 'Invalid amount_usdc — must be a decimal string between "0.01" and "10000.00" (e.g. "10.00").') {
|
|
88
|
+
super(message, 'invalid_amount', 400);
|
|
89
|
+
this.name = 'InvalidAmountError';
|
|
90
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.InvalidAmountError = InvalidAmountError;
|
|
94
|
+
/** The API key is missing or invalid. */
|
|
95
|
+
class AuthError extends Cards402Error {
|
|
96
|
+
constructor() {
|
|
97
|
+
super('Invalid or missing API key. Pass it as the X-Api-Key header, or set CARDS402_API_KEY.', 'invalid_api_key', 401);
|
|
98
|
+
this.name = 'AuthError';
|
|
99
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.AuthError = AuthError;
|
|
103
|
+
/** Order failed during fulfillment. A refund may be in progress. */
|
|
104
|
+
class OrderFailedError extends Cards402Error {
|
|
105
|
+
orderId;
|
|
106
|
+
refund;
|
|
107
|
+
constructor(orderId, reason, refund) {
|
|
108
|
+
const refundNote = refund
|
|
109
|
+
? ` Your payment is being refunded (txid: ${refund.stellar_txid}).`
|
|
110
|
+
: ' A refund will be processed if payment was received.';
|
|
111
|
+
super(`Order ${orderId} failed: ${reason}.${refundNote}`, 'order_failed', 200, {
|
|
112
|
+
orderId,
|
|
113
|
+
reason,
|
|
114
|
+
refund,
|
|
115
|
+
});
|
|
116
|
+
this.orderId = orderId;
|
|
117
|
+
this.refund = refund;
|
|
118
|
+
this.name = 'OrderFailedError';
|
|
119
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.OrderFailedError = OrderFailedError;
|
|
123
|
+
/**
|
|
124
|
+
* Thrown by purchaseCardOWS when the order was created (and possibly paid)
|
|
125
|
+
* but the flow couldn't finish — e.g. Soroban RPC finalization timed out,
|
|
126
|
+
* or waitForCard hit its deadline. Always carries the orderId so the caller
|
|
127
|
+
* can resume via `cards402 purchase --resume <order-id>` without minting a
|
|
128
|
+
* new order (which would strand the original one until it expires).
|
|
129
|
+
*
|
|
130
|
+
* `txHash` is present when the payment was submitted onto the ledger (even
|
|
131
|
+
* if finalization wasn't confirmed client-side) — the backend watcher can
|
|
132
|
+
* still credit the order after the fact.
|
|
133
|
+
*
|
|
134
|
+
* `phase: 'paid'` means payViaContractOWS returned successfully, so resume
|
|
135
|
+
* should skip straight to waitForCard. `phase: 'unpaid'` means the payment
|
|
136
|
+
* never went out and resume may need to retry the Soroban submit.
|
|
137
|
+
*/
|
|
138
|
+
class ResumableError extends Cards402Error {
|
|
139
|
+
orderId;
|
|
140
|
+
phase;
|
|
141
|
+
txHash;
|
|
142
|
+
cause;
|
|
143
|
+
constructor(orderId, reason, phase, txHash, cause) {
|
|
144
|
+
const hashNote = txHash ? ` (tx: ${txHash})` : '';
|
|
145
|
+
super(`Purchase could not finish for order ${orderId}: ${reason}${hashNote}. ` +
|
|
146
|
+
`Resume with: cards402 purchase --resume ${orderId}`, 'resumable', 0, { orderId, reason, phase, txHash });
|
|
147
|
+
this.orderId = orderId;
|
|
148
|
+
this.phase = phase;
|
|
149
|
+
this.txHash = txHash;
|
|
150
|
+
this.cause = cause;
|
|
151
|
+
this.name = 'ResumableError';
|
|
152
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
exports.ResumableError = ResumableError;
|
|
156
|
+
/** Waiting for a card timed out — order may still be processing. */
|
|
157
|
+
class WaitTimeoutError extends Cards402Error {
|
|
158
|
+
orderId;
|
|
159
|
+
constructor(orderId, timeoutMs) {
|
|
160
|
+
super(`Timed out waiting for card after ${timeoutMs / 1000}s (order: ${orderId}). ` +
|
|
161
|
+
'Poll GET /v1/orders/:id to check status — it may still complete.', 'wait_timeout', 408);
|
|
162
|
+
this.orderId = orderId;
|
|
163
|
+
this.name = 'WaitTimeoutError';
|
|
164
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.WaitTimeoutError = WaitTimeoutError;
|
|
168
|
+
/**
|
|
169
|
+
* Parse a raw API error response into the appropriate typed error.
|
|
170
|
+
* Falls back to generic Cards402Error for unknown codes.
|
|
171
|
+
*/
|
|
172
|
+
function parseApiError(status, body) {
|
|
173
|
+
const code = String(body.error ?? 'unknown');
|
|
174
|
+
const message = String(body.message ?? body.error ?? 'Unknown error');
|
|
175
|
+
switch (code) {
|
|
176
|
+
case 'spend_limit_exceeded':
|
|
177
|
+
return new SpendLimitError(String(body.limit ?? '?'), String(body.spent ?? '?'));
|
|
178
|
+
case 'rate_limit_exceeded':
|
|
179
|
+
// Forward the backend message verbatim — the code is shared
|
|
180
|
+
// between order-creation and polling rate limits, and the
|
|
181
|
+
// message is the only way to tell them apart.
|
|
182
|
+
return new RateLimitError(message);
|
|
183
|
+
case 'service_temporarily_unavailable':
|
|
184
|
+
return new ServiceUnavailableError(message);
|
|
185
|
+
case 'price_unavailable':
|
|
186
|
+
case 'xlm_price_unavailable':
|
|
187
|
+
return new PriceUnavailableError(message);
|
|
188
|
+
case 'invalid_amount':
|
|
189
|
+
return new InvalidAmountError(message);
|
|
190
|
+
case 'missing_api_key':
|
|
191
|
+
case 'invalid_api_key':
|
|
192
|
+
return new AuthError();
|
|
193
|
+
default:
|
|
194
|
+
return new Cards402Error(message, code, status, body);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Cards402Client, CSPR402Client } from './client';
|
|
2
|
+
export type { OrderOptions, OrderResponse, OrderStatus, OrderListItem, OrderPhase, CardDetails, PaymentInstructions, SorobanPaymentInstructions, CasperCSPRPaymentInstructions, MockUsdcCep18PaymentInstructions, CasperCSPRPaymentReceipt, CasperPaymentReceipt, MockUsdcReceipt, VerifyCasperPaymentResponse, Budget, UsageSummary, } from './client';
|
|
3
|
+
export { Cards402Error, SpendLimitError, RateLimitError, ServiceUnavailableError, PriceUnavailableError, InvalidAmountError, AuthError, OrderFailedError, WaitTimeoutError, ResumableError, } from './errors';
|
|
4
|
+
export { loadCards402Config, saveCards402Config, resolveCredentials } from './config';
|
|
5
|
+
export type { Cards402Config } from './config';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,gCAAgC,EAChC,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,2BAA2B,EAC3B,MAAM,EACN,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,qBAAqB,EACrB,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACtF,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
|