@caypo/canton-sdk 0.1.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/.turbo/turbo-build.log +26 -0
- package/.turbo/turbo-test.log +23 -0
- package/README.md +120 -0
- package/SPEC.md +223 -0
- package/dist/amount-L2SDLRZT.js +15 -0
- package/dist/amount-L2SDLRZT.js.map +1 -0
- package/dist/chunk-GSDB5FKZ.js +110 -0
- package/dist/chunk-GSDB5FKZ.js.map +1 -0
- package/dist/index.cjs +1158 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +673 -0
- package/dist/index.d.ts +673 -0
- package/dist/index.js +986 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
- package/src/__tests__/agent.test.ts +217 -0
- package/src/__tests__/amount.test.ts +202 -0
- package/src/__tests__/client.test.ts +516 -0
- package/src/__tests__/e2e/canton-client.e2e.test.ts +190 -0
- package/src/__tests__/e2e/mpp-flow.e2e.test.ts +346 -0
- package/src/__tests__/e2e/setup.ts +112 -0
- package/src/__tests__/e2e/usdcx.e2e.test.ts +114 -0
- package/src/__tests__/keystore.test.ts +197 -0
- package/src/__tests__/pay-client.test.ts +257 -0
- package/src/__tests__/safeguards.test.ts +333 -0
- package/src/__tests__/usdcx.test.ts +374 -0
- package/src/accounts/checking.ts +118 -0
- package/src/agent.ts +132 -0
- package/src/canton/amount.ts +167 -0
- package/src/canton/client.ts +218 -0
- package/src/canton/errors.ts +45 -0
- package/src/canton/holdings.ts +90 -0
- package/src/canton/index.ts +51 -0
- package/src/canton/types.ts +214 -0
- package/src/canton/usdcx.ts +166 -0
- package/src/index.ts +97 -0
- package/src/mpp/pay-client.ts +170 -0
- package/src/safeguards/manager.ts +183 -0
- package/src/traffic/manager.ts +95 -0
- package/src/wallet/config.ts +88 -0
- package/src/wallet/keystore.ts +164 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +9 -0
- package/vitest.config.ts +7 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addAmounts,
|
|
3
|
+
compareAmounts,
|
|
4
|
+
isValidAmount,
|
|
5
|
+
subtractAmounts,
|
|
6
|
+
toCantonAmount
|
|
7
|
+
} from "./chunk-GSDB5FKZ.js";
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
import { MPP_CANTON_VERSION } from "@caypo/mpp-canton";
|
|
11
|
+
|
|
12
|
+
// src/canton/errors.ts
|
|
13
|
+
var CantonApiError = class extends Error {
|
|
14
|
+
code;
|
|
15
|
+
ledgerCause;
|
|
16
|
+
grpcCodeValue;
|
|
17
|
+
errorCategory;
|
|
18
|
+
context;
|
|
19
|
+
constructor(ledgerError) {
|
|
20
|
+
super(`Canton API error [${ledgerError.code}]: ${ledgerError.cause}`);
|
|
21
|
+
this.name = "CantonApiError";
|
|
22
|
+
this.code = ledgerError.code;
|
|
23
|
+
this.ledgerCause = ledgerError.cause;
|
|
24
|
+
this.grpcCodeValue = ledgerError.grpcCodeValue;
|
|
25
|
+
this.errorCategory = ledgerError.errorCategory;
|
|
26
|
+
this.context = ledgerError.context;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var CantonTimeoutError = class extends Error {
|
|
30
|
+
timeoutMs;
|
|
31
|
+
path;
|
|
32
|
+
constructor(path, timeoutMs) {
|
|
33
|
+
super(`Canton request to ${path} timed out after ${timeoutMs}ms`);
|
|
34
|
+
this.name = "CantonTimeoutError";
|
|
35
|
+
this.timeoutMs = timeoutMs;
|
|
36
|
+
this.path = path;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var CantonAuthError = class extends Error {
|
|
40
|
+
statusCode;
|
|
41
|
+
constructor(statusCode, message) {
|
|
42
|
+
super(message ?? `Canton authentication failed (HTTP ${statusCode})`);
|
|
43
|
+
this.name = "CantonAuthError";
|
|
44
|
+
this.statusCode = statusCode;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/canton/client.ts
|
|
49
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
50
|
+
var CantonClient = class {
|
|
51
|
+
ledgerUrl;
|
|
52
|
+
token;
|
|
53
|
+
userId;
|
|
54
|
+
timeout;
|
|
55
|
+
constructor(config) {
|
|
56
|
+
this.ledgerUrl = config.ledgerUrl.replace(/\/+$/, "");
|
|
57
|
+
this.token = config.token;
|
|
58
|
+
this.userId = config.userId;
|
|
59
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
60
|
+
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Command Submission
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
async submitAndWait(params) {
|
|
65
|
+
return this.request("POST", "/v2/commands/submit-and-wait", {
|
|
66
|
+
commands: params.commands,
|
|
67
|
+
userId: this.userId,
|
|
68
|
+
commandId: params.commandId,
|
|
69
|
+
actAs: params.actAs,
|
|
70
|
+
readAs: params.readAs
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async submitAndWaitForTransaction(params) {
|
|
74
|
+
return this.request(
|
|
75
|
+
"POST",
|
|
76
|
+
"/v2/commands/submit-and-wait-for-transaction",
|
|
77
|
+
{
|
|
78
|
+
commands: params.commands,
|
|
79
|
+
userId: this.userId,
|
|
80
|
+
commandId: params.commandId,
|
|
81
|
+
actAs: params.actAs,
|
|
82
|
+
readAs: params.readAs
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Active Contract Queries
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
async queryActiveContracts(params) {
|
|
90
|
+
const body = {
|
|
91
|
+
eventFormat: {
|
|
92
|
+
filtersByParty: params.filtersByParty,
|
|
93
|
+
filtersForAnyParty: params.filtersForAnyParty,
|
|
94
|
+
verbose: true
|
|
95
|
+
},
|
|
96
|
+
activeAtOffset: params.activeAtOffset
|
|
97
|
+
};
|
|
98
|
+
const response = await this.request("POST", "/v2/state/active-contracts", body);
|
|
99
|
+
if (!response.contractEntry) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return response.contractEntry.filter((entry) => entry.createdEvent != null).map((entry) => {
|
|
103
|
+
const evt = entry.createdEvent;
|
|
104
|
+
return {
|
|
105
|
+
contractId: evt.contractId,
|
|
106
|
+
templateId: evt.templateId,
|
|
107
|
+
createArgument: evt.createArgument,
|
|
108
|
+
createdAt: "",
|
|
109
|
+
signatories: evt.signatories,
|
|
110
|
+
observers: evt.observers
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Transaction Lookup
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
async getTransactionById(updateId) {
|
|
118
|
+
try {
|
|
119
|
+
return await this.request(
|
|
120
|
+
"GET",
|
|
121
|
+
`/v2/updates/transaction-by-id/${encodeURIComponent(updateId)}`
|
|
122
|
+
);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (err instanceof CantonApiError && err.code === "NOT_FOUND") {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Ledger State
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
async getLedgerEnd() {
|
|
134
|
+
const response = await this.request("GET", "/v2/state/ledger-end");
|
|
135
|
+
return response.offset;
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Party Management
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
async allocateParty(hint) {
|
|
141
|
+
const response = await this.request("POST", "/v2/parties", {
|
|
142
|
+
partyIdHint: hint,
|
|
143
|
+
identityProviderId: ""
|
|
144
|
+
});
|
|
145
|
+
return response.partyDetails;
|
|
146
|
+
}
|
|
147
|
+
async listParties() {
|
|
148
|
+
const response = await this.request("GET", "/v2/parties");
|
|
149
|
+
return response.partyDetails;
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Health Check
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
async isHealthy() {
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch(`${this.ledgerUrl}/livez`, {
|
|
157
|
+
method: "GET",
|
|
158
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
159
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
160
|
+
});
|
|
161
|
+
return response.ok;
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Internal
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
async request(method, path, body) {
|
|
170
|
+
const url = `${this.ledgerUrl}${path}`;
|
|
171
|
+
const headers = {
|
|
172
|
+
Authorization: `Bearer ${this.token}`
|
|
173
|
+
};
|
|
174
|
+
if (body !== void 0) {
|
|
175
|
+
headers["Content-Type"] = "application/json";
|
|
176
|
+
}
|
|
177
|
+
let response;
|
|
178
|
+
try {
|
|
179
|
+
response = await fetch(url, {
|
|
180
|
+
method,
|
|
181
|
+
headers,
|
|
182
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
183
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
184
|
+
});
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
187
|
+
throw new CantonTimeoutError(path, this.timeout);
|
|
188
|
+
}
|
|
189
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
190
|
+
throw new CantonTimeoutError(path, this.timeout);
|
|
191
|
+
}
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
if (response.status === 401 || response.status === 403) {
|
|
195
|
+
const text2 = await response.text().catch(() => "");
|
|
196
|
+
throw new CantonAuthError(response.status, text2 || void 0);
|
|
197
|
+
}
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
const errorBody = await response.json().catch(() => null);
|
|
200
|
+
if (errorBody && typeof errorBody === "object" && "code" in errorBody) {
|
|
201
|
+
throw new CantonApiError(errorBody);
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`Canton API error: HTTP ${response.status} on ${method} ${path}`);
|
|
204
|
+
}
|
|
205
|
+
const text = await response.text();
|
|
206
|
+
if (!text) {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
return JSON.parse(text);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/canton/holdings.ts
|
|
214
|
+
var InsufficientBalanceError = class extends Error {
|
|
215
|
+
available;
|
|
216
|
+
required;
|
|
217
|
+
constructor(available, required) {
|
|
218
|
+
super(`Insufficient balance: have ${available}, need ${required}`);
|
|
219
|
+
this.name = "InsufficientBalanceError";
|
|
220
|
+
this.available = available;
|
|
221
|
+
this.required = required;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
function selectHoldings(holdings, requiredAmount) {
|
|
225
|
+
if (holdings.length === 0) {
|
|
226
|
+
throw new InsufficientBalanceError("0", requiredAmount);
|
|
227
|
+
}
|
|
228
|
+
const sorted = [...holdings].sort((a, b) => compareAmounts(b.amount, a.amount));
|
|
229
|
+
let bestSingle = null;
|
|
230
|
+
for (const h of sorted) {
|
|
231
|
+
if (compareAmounts(h.amount, requiredAmount) >= 0) {
|
|
232
|
+
bestSingle = h;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (bestSingle) {
|
|
236
|
+
return {
|
|
237
|
+
type: "single",
|
|
238
|
+
contractIds: [bestSingle.contractId]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
let accumulated = "0";
|
|
242
|
+
const selected = [];
|
|
243
|
+
for (const h of sorted) {
|
|
244
|
+
selected.push(h.contractId);
|
|
245
|
+
accumulated = addAmounts(accumulated, h.amount);
|
|
246
|
+
if (compareAmounts(accumulated, requiredAmount) >= 0) {
|
|
247
|
+
return {
|
|
248
|
+
type: "merge-then-transfer",
|
|
249
|
+
contractIds: selected
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
throw new InsufficientBalanceError(accumulated, requiredAmount);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/canton/usdcx.ts
|
|
257
|
+
var USDCX_HOLDING_TEMPLATE_ID = "Splice.Api.Token.HoldingV1:Holding";
|
|
258
|
+
var TRANSFER_FACTORY_TEMPLATE_ID = "Splice.Api.Token.TransferFactoryV1:TransferFactory";
|
|
259
|
+
var USDCX_INSTRUMENT_ID = "USDCx";
|
|
260
|
+
var USDCxService = class {
|
|
261
|
+
constructor(client, partyId) {
|
|
262
|
+
this.client = client;
|
|
263
|
+
this.partyId = partyId;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Query all USDCx Holding contracts for this party.
|
|
267
|
+
*/
|
|
268
|
+
async getHoldings() {
|
|
269
|
+
const offset = await this.client.getLedgerEnd();
|
|
270
|
+
const contracts = await this.client.queryActiveContracts({
|
|
271
|
+
filtersByParty: {
|
|
272
|
+
[this.partyId]: {
|
|
273
|
+
cumulative: [
|
|
274
|
+
{
|
|
275
|
+
identifierFilter: {
|
|
276
|
+
TemplateFilter: {
|
|
277
|
+
value: { templateId: USDCX_HOLDING_TEMPLATE_ID }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
activeAtOffset: offset
|
|
285
|
+
});
|
|
286
|
+
return contracts.map((c) => ({
|
|
287
|
+
contractId: c.contractId,
|
|
288
|
+
owner: c.createArgument.owner ?? this.partyId,
|
|
289
|
+
amount: c.createArgument.amount,
|
|
290
|
+
templateId: c.templateId
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Calculate total USDCx balance by summing all holding amounts.
|
|
295
|
+
* Returns a string with up to 10 decimal places.
|
|
296
|
+
*/
|
|
297
|
+
async getBalance() {
|
|
298
|
+
const holdings = await this.getHoldings();
|
|
299
|
+
if (holdings.length === 0) {
|
|
300
|
+
return "0";
|
|
301
|
+
}
|
|
302
|
+
let total = "0";
|
|
303
|
+
for (const h of holdings) {
|
|
304
|
+
total = addAmounts(total, h.amount);
|
|
305
|
+
}
|
|
306
|
+
return total;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Transfer USDCx using TransferFactory_Transfer (1-step).
|
|
310
|
+
* Requires the recipient to have an active TransferPreapproval.
|
|
311
|
+
*/
|
|
312
|
+
async transfer(params) {
|
|
313
|
+
const commandId = params.commandId ?? crypto.randomUUID();
|
|
314
|
+
const amount = toCantonAmount(params.amount);
|
|
315
|
+
const holdings = await this.getHoldings();
|
|
316
|
+
const selection = selectHoldings(holdings, params.amount);
|
|
317
|
+
const result = await this.client.submitAndWait({
|
|
318
|
+
commands: [
|
|
319
|
+
{
|
|
320
|
+
ExerciseCommand: {
|
|
321
|
+
templateId: TRANSFER_FACTORY_TEMPLATE_ID,
|
|
322
|
+
contractId: selection.contractIds[0],
|
|
323
|
+
choice: "TransferFactory_Transfer",
|
|
324
|
+
choiceArgument: {
|
|
325
|
+
sender: this.partyId,
|
|
326
|
+
receiver: params.recipient,
|
|
327
|
+
amount,
|
|
328
|
+
instrumentId: USDCX_INSTRUMENT_ID,
|
|
329
|
+
inputHoldingCids: selection.contractIds,
|
|
330
|
+
meta: {}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
],
|
|
335
|
+
commandId,
|
|
336
|
+
actAs: [this.partyId],
|
|
337
|
+
readAs: [this.partyId]
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
updateId: result.updateId,
|
|
341
|
+
completionOffset: result.completionOffset,
|
|
342
|
+
commandId
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Merge multiple holdings into fewer UTXOs.
|
|
347
|
+
* Returns the commandId of the merge transaction.
|
|
348
|
+
*/
|
|
349
|
+
async mergeHoldings(holdingCids) {
|
|
350
|
+
if (holdingCids.length < 2) {
|
|
351
|
+
throw new Error("Need at least 2 holdings to merge");
|
|
352
|
+
}
|
|
353
|
+
const commandId = crypto.randomUUID();
|
|
354
|
+
await this.client.submitAndWait({
|
|
355
|
+
commands: [
|
|
356
|
+
{
|
|
357
|
+
ExerciseCommand: {
|
|
358
|
+
templateId: USDCX_HOLDING_TEMPLATE_ID,
|
|
359
|
+
contractId: holdingCids[0],
|
|
360
|
+
choice: "Merge",
|
|
361
|
+
choiceArgument: {
|
|
362
|
+
holdingCids: holdingCids.slice(1)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
],
|
|
367
|
+
commandId,
|
|
368
|
+
actAs: [this.partyId]
|
|
369
|
+
});
|
|
370
|
+
return commandId;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/wallet/keystore.ts
|
|
375
|
+
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
376
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
377
|
+
import { dirname, join } from "path";
|
|
378
|
+
import { homedir } from "os";
|
|
379
|
+
var PBKDF2_ITERATIONS = 1e5;
|
|
380
|
+
var PBKDF2_KEYLEN = 32;
|
|
381
|
+
var PBKDF2_DIGEST = "sha256";
|
|
382
|
+
var SALT_LEN = 32;
|
|
383
|
+
var IV_LEN = 16;
|
|
384
|
+
var ALGORITHM = "aes-256-gcm";
|
|
385
|
+
var DEFAULT_WALLET_PATH = join(homedir(), ".caypo", "wallet.key");
|
|
386
|
+
function deriveKey(pin, salt) {
|
|
387
|
+
return pbkdf2Sync(pin, salt, PBKDF2_ITERATIONS, PBKDF2_KEYLEN, PBKDF2_DIGEST);
|
|
388
|
+
}
|
|
389
|
+
function encrypt(data, pin) {
|
|
390
|
+
const salt = randomBytes(SALT_LEN);
|
|
391
|
+
const key = deriveKey(pin, salt);
|
|
392
|
+
const iv = randomBytes(IV_LEN);
|
|
393
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
394
|
+
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
395
|
+
const tag = cipher.getAuthTag();
|
|
396
|
+
return {
|
|
397
|
+
iv: iv.toString("base64"),
|
|
398
|
+
salt: salt.toString("base64"),
|
|
399
|
+
encrypted: encrypted.toString("base64"),
|
|
400
|
+
tag: tag.toString("base64")
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function decrypt(file, pin) {
|
|
404
|
+
const salt = Buffer.from(file.salt, "base64");
|
|
405
|
+
const iv = Buffer.from(file.iv, "base64");
|
|
406
|
+
const encrypted = Buffer.from(file.encrypted, "base64");
|
|
407
|
+
const tag = Buffer.from(file.tag, "base64");
|
|
408
|
+
const key = deriveKey(pin, salt);
|
|
409
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
410
|
+
decipher.setAuthTag(tag);
|
|
411
|
+
try {
|
|
412
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
413
|
+
return decrypted.toString("utf8");
|
|
414
|
+
} catch {
|
|
415
|
+
throw new Error("Invalid PIN or corrupted wallet file");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
var Keystore = class _Keystore {
|
|
419
|
+
data;
|
|
420
|
+
filePath;
|
|
421
|
+
constructor(data, filePath) {
|
|
422
|
+
this.data = data;
|
|
423
|
+
this.filePath = filePath;
|
|
424
|
+
}
|
|
425
|
+
/** Party ID of this wallet. */
|
|
426
|
+
get address() {
|
|
427
|
+
return this.data.partyId;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Create a new encrypted wallet.
|
|
431
|
+
* Generates a random 32-byte private key and saves encrypted to disk.
|
|
432
|
+
*/
|
|
433
|
+
static async create(pin, params, path) {
|
|
434
|
+
const filePath = path ?? DEFAULT_WALLET_PATH;
|
|
435
|
+
const walletData = {
|
|
436
|
+
partyId: params.partyId,
|
|
437
|
+
jwt: params.jwt,
|
|
438
|
+
userId: params.userId,
|
|
439
|
+
privateKey: randomBytes(32).toString("hex")
|
|
440
|
+
};
|
|
441
|
+
const encryptedFile = encrypt(JSON.stringify(walletData), pin);
|
|
442
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
443
|
+
await writeFile(filePath, JSON.stringify(encryptedFile), "utf8");
|
|
444
|
+
return new _Keystore(walletData, filePath);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Load and decrypt an existing wallet.
|
|
448
|
+
* Throws if the PIN is wrong or the file is corrupted.
|
|
449
|
+
*/
|
|
450
|
+
static async load(pin, path) {
|
|
451
|
+
const filePath = path ?? DEFAULT_WALLET_PATH;
|
|
452
|
+
const raw = await readFile(filePath, "utf8");
|
|
453
|
+
const encryptedFile = JSON.parse(raw);
|
|
454
|
+
const decrypted = decrypt(encryptedFile, pin);
|
|
455
|
+
const walletData = JSON.parse(decrypted);
|
|
456
|
+
return new _Keystore(walletData, filePath);
|
|
457
|
+
}
|
|
458
|
+
/** Get credentials for Canton Ledger API access. */
|
|
459
|
+
getCredentials() {
|
|
460
|
+
return {
|
|
461
|
+
partyId: this.data.partyId,
|
|
462
|
+
jwt: this.data.jwt,
|
|
463
|
+
userId: this.data.userId
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/** Change the encryption PIN. Re-encrypts wallet data with new PIN. */
|
|
467
|
+
async changePin(oldPin, newPin) {
|
|
468
|
+
const raw = await readFile(this.filePath, "utf8");
|
|
469
|
+
const encryptedFile = JSON.parse(raw);
|
|
470
|
+
decrypt(encryptedFile, oldPin);
|
|
471
|
+
const newEncrypted = encrypt(JSON.stringify(this.data), newPin);
|
|
472
|
+
await writeFile(this.filePath, JSON.stringify(newEncrypted), "utf8");
|
|
473
|
+
}
|
|
474
|
+
/** Export the raw private key. Dangerous — only call with explicit user consent. */
|
|
475
|
+
exportKey(pin) {
|
|
476
|
+
void pin;
|
|
477
|
+
return this.data.privateKey;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// src/wallet/config.ts
|
|
482
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
483
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
484
|
+
import { homedir as homedir2 } from "os";
|
|
485
|
+
var DEFAULT_CONFIG_PATH = join2(homedir2(), ".caypo", "config.json");
|
|
486
|
+
var DEFAULT_CONFIG = {
|
|
487
|
+
version: 2,
|
|
488
|
+
network: "testnet",
|
|
489
|
+
ledgerUrl: "http://localhost:7575",
|
|
490
|
+
partyId: "",
|
|
491
|
+
userId: "ledger-api-user",
|
|
492
|
+
keystorePath: join2(homedir2(), ".caypo", "wallet.key"),
|
|
493
|
+
traffic: {
|
|
494
|
+
autoPurchase: true,
|
|
495
|
+
minBalance: 1e3,
|
|
496
|
+
purchaseAmountCC: "5.0"
|
|
497
|
+
},
|
|
498
|
+
safeguards: {
|
|
499
|
+
txLimit: "100",
|
|
500
|
+
dailyLimit: "1000"
|
|
501
|
+
},
|
|
502
|
+
mpp: {
|
|
503
|
+
gatewayUrl: "https://mpp.cayvox.io",
|
|
504
|
+
maxAutoPayPrice: "1.00"
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
async function loadConfig(path) {
|
|
508
|
+
const filePath = path ?? DEFAULT_CONFIG_PATH;
|
|
509
|
+
try {
|
|
510
|
+
const raw = await readFile2(filePath, "utf8");
|
|
511
|
+
const parsed = JSON.parse(raw);
|
|
512
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
513
|
+
} catch (err) {
|
|
514
|
+
if (err.code === "ENOENT") {
|
|
515
|
+
return { ...DEFAULT_CONFIG };
|
|
516
|
+
}
|
|
517
|
+
throw err;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async function saveConfig(config, path) {
|
|
521
|
+
const filePath = path ?? DEFAULT_CONFIG_PATH;
|
|
522
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
523
|
+
await writeFile2(filePath, JSON.stringify(config, null, 2), "utf8");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/safeguards/manager.ts
|
|
527
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
528
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
529
|
+
import { homedir as homedir3 } from "os";
|
|
530
|
+
var DEFAULT_SAFEGUARDS_PATH = join3(homedir3(), ".caypo", "safeguards.json");
|
|
531
|
+
var DEFAULT_SAFEGUARD_CONFIG = {
|
|
532
|
+
txLimit: "100",
|
|
533
|
+
dailyLimit: "1000",
|
|
534
|
+
locked: false,
|
|
535
|
+
lockedPinHash: "",
|
|
536
|
+
dailySpent: "0",
|
|
537
|
+
lastResetDate: today()
|
|
538
|
+
};
|
|
539
|
+
function today() {
|
|
540
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
541
|
+
}
|
|
542
|
+
function hashPin(pin) {
|
|
543
|
+
let hash = 0;
|
|
544
|
+
for (let i = 0; i < pin.length; i++) {
|
|
545
|
+
const ch = pin.charCodeAt(i);
|
|
546
|
+
hash = (hash << 5) - hash + ch | 0;
|
|
547
|
+
}
|
|
548
|
+
return String(hash);
|
|
549
|
+
}
|
|
550
|
+
var SafeguardManager = class _SafeguardManager {
|
|
551
|
+
config;
|
|
552
|
+
filePath;
|
|
553
|
+
constructor(config, filePath) {
|
|
554
|
+
this.config = config ? { ...config } : { ...DEFAULT_SAFEGUARD_CONFIG };
|
|
555
|
+
this.filePath = filePath ?? DEFAULT_SAFEGUARDS_PATH;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Load safeguards from disk. Returns a new SafeguardManager.
|
|
559
|
+
* If file doesn't exist, uses defaults.
|
|
560
|
+
*/
|
|
561
|
+
static async load(path) {
|
|
562
|
+
const filePath = path ?? DEFAULT_SAFEGUARDS_PATH;
|
|
563
|
+
try {
|
|
564
|
+
const raw = await readFile3(filePath, "utf8");
|
|
565
|
+
const parsed = JSON.parse(raw);
|
|
566
|
+
const config = { ...DEFAULT_SAFEGUARD_CONFIG, ...parsed };
|
|
567
|
+
return new _SafeguardManager(config, filePath);
|
|
568
|
+
} catch (err) {
|
|
569
|
+
if (err.code === "ENOENT") {
|
|
570
|
+
return new _SafeguardManager(void 0, filePath);
|
|
571
|
+
}
|
|
572
|
+
throw err;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/** Get current safeguard settings. */
|
|
576
|
+
settings() {
|
|
577
|
+
return { ...this.config };
|
|
578
|
+
}
|
|
579
|
+
/** Set per-transaction limit. */
|
|
580
|
+
setTxLimit(amount) {
|
|
581
|
+
this.config.txLimit = amount;
|
|
582
|
+
void this.save();
|
|
583
|
+
}
|
|
584
|
+
/** Set daily spending limit. */
|
|
585
|
+
setDailyLimit(amount) {
|
|
586
|
+
this.config.dailyLimit = amount;
|
|
587
|
+
void this.save();
|
|
588
|
+
}
|
|
589
|
+
/** Lock the wallet. All transactions will be rejected until unlocked. */
|
|
590
|
+
lock(pin) {
|
|
591
|
+
this.config.locked = true;
|
|
592
|
+
if (pin) {
|
|
593
|
+
this.config.lockedPinHash = hashPin(pin);
|
|
594
|
+
}
|
|
595
|
+
void this.save();
|
|
596
|
+
}
|
|
597
|
+
/** Unlock the wallet. Requires PIN if one was set during lock. */
|
|
598
|
+
unlock(pin) {
|
|
599
|
+
if (this.config.lockedPinHash && hashPin(pin) !== this.config.lockedPinHash) {
|
|
600
|
+
throw new Error("Invalid PIN");
|
|
601
|
+
}
|
|
602
|
+
this.config.locked = false;
|
|
603
|
+
this.config.lockedPinHash = "";
|
|
604
|
+
void this.save();
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Check if a transaction for the given amount is allowed.
|
|
608
|
+
* Auto-resets daily counter if the date has changed.
|
|
609
|
+
*/
|
|
610
|
+
check(amount) {
|
|
611
|
+
this.autoResetDaily();
|
|
612
|
+
const dailyRemaining = subtractAmounts(this.config.dailyLimit, this.config.dailySpent);
|
|
613
|
+
if (this.config.locked) {
|
|
614
|
+
return { allowed: false, reason: "Wallet is locked", dailyRemaining };
|
|
615
|
+
}
|
|
616
|
+
if (compareAmounts(amount, this.config.txLimit) > 0) {
|
|
617
|
+
return {
|
|
618
|
+
allowed: false,
|
|
619
|
+
reason: `Amount ${amount} exceeds per-transaction limit of ${this.config.txLimit}`,
|
|
620
|
+
dailyRemaining
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
const projectedDaily = addAmounts(this.config.dailySpent, amount);
|
|
624
|
+
if (compareAmounts(projectedDaily, this.config.dailyLimit) > 0) {
|
|
625
|
+
return {
|
|
626
|
+
allowed: false,
|
|
627
|
+
reason: `Amount ${amount} would exceed daily limit of ${this.config.dailyLimit} (spent: ${this.config.dailySpent})`,
|
|
628
|
+
dailyRemaining
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return { allowed: true, dailyRemaining };
|
|
632
|
+
}
|
|
633
|
+
/** Record a completed spend. Call after successful transaction. */
|
|
634
|
+
recordSpend(amount) {
|
|
635
|
+
this.autoResetDaily();
|
|
636
|
+
this.config.dailySpent = addAmounts(this.config.dailySpent, amount);
|
|
637
|
+
void this.save();
|
|
638
|
+
}
|
|
639
|
+
/** Manually reset the daily counter. */
|
|
640
|
+
resetDaily() {
|
|
641
|
+
this.config.dailySpent = "0";
|
|
642
|
+
this.config.lastResetDate = today();
|
|
643
|
+
void this.save();
|
|
644
|
+
}
|
|
645
|
+
autoResetDaily() {
|
|
646
|
+
const currentDate = today();
|
|
647
|
+
if (this.config.lastResetDate !== currentDate) {
|
|
648
|
+
this.config.dailySpent = "0";
|
|
649
|
+
this.config.lastResetDate = currentDate;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async save() {
|
|
653
|
+
try {
|
|
654
|
+
await mkdir3(dirname3(this.filePath), { recursive: true });
|
|
655
|
+
await writeFile3(this.filePath, JSON.stringify(this.config, null, 2), "utf8");
|
|
656
|
+
} catch {
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// src/accounts/checking.ts
|
|
662
|
+
var CheckingAccount = class {
|
|
663
|
+
constructor(usdcx, safeguards, client, partyId) {
|
|
664
|
+
this.usdcx = usdcx;
|
|
665
|
+
this.safeguards = safeguards;
|
|
666
|
+
this.client = client;
|
|
667
|
+
this.partyId = partyId;
|
|
668
|
+
}
|
|
669
|
+
/** Get USDCx balance: total available and number of UTXO holdings. */
|
|
670
|
+
async balance() {
|
|
671
|
+
const holdings = await this.usdcx.getHoldings();
|
|
672
|
+
let available = "0";
|
|
673
|
+
const { addAmounts: addAmounts2 } = await import("./amount-L2SDLRZT.js");
|
|
674
|
+
for (const h of holdings) {
|
|
675
|
+
available = addAmounts2(available, h.amount);
|
|
676
|
+
}
|
|
677
|
+
return { available, holdingCount: holdings.length };
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Send USDCx to a recipient.
|
|
681
|
+
* Checks safeguards before executing the transfer.
|
|
682
|
+
*/
|
|
683
|
+
async send(recipient, amount, opts) {
|
|
684
|
+
const check = this.safeguards.check(amount);
|
|
685
|
+
if (!check.allowed) {
|
|
686
|
+
throw new Error(`Safeguard rejected: ${check.reason}`);
|
|
687
|
+
}
|
|
688
|
+
const result = await this.usdcx.transfer({
|
|
689
|
+
recipient,
|
|
690
|
+
amount,
|
|
691
|
+
commandId: opts?.commandId
|
|
692
|
+
});
|
|
693
|
+
this.safeguards.recordSpend(amount);
|
|
694
|
+
return result;
|
|
695
|
+
}
|
|
696
|
+
/** Party ID for receiving payments. */
|
|
697
|
+
address() {
|
|
698
|
+
return this.partyId;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Query transaction history via /v2/updates/flats.
|
|
702
|
+
* Returns recent flat transactions involving this party.
|
|
703
|
+
*/
|
|
704
|
+
async history(opts) {
|
|
705
|
+
const limit = opts?.limit ?? 20;
|
|
706
|
+
const offset = await this.client.getLedgerEnd();
|
|
707
|
+
const contracts = await this.client.queryActiveContracts({
|
|
708
|
+
filtersByParty: {
|
|
709
|
+
[this.partyId]: {
|
|
710
|
+
cumulative: [
|
|
711
|
+
{
|
|
712
|
+
identifierFilter: {
|
|
713
|
+
WildcardFilter: { value: { includeCreatedEventBlob: false } }
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
]
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
activeAtOffset: offset
|
|
720
|
+
});
|
|
721
|
+
return contracts.slice(0, limit).map((c) => ({
|
|
722
|
+
updateId: "",
|
|
723
|
+
commandId: "",
|
|
724
|
+
effectiveAt: c.createdAt,
|
|
725
|
+
offset: 0,
|
|
726
|
+
type: "unknown",
|
|
727
|
+
amount: typeof c.createArgument.amount === "string" ? c.createArgument.amount : void 0
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// src/mpp/pay-client.ts
|
|
733
|
+
function parseWwwAuthenticate(header) {
|
|
734
|
+
if (!header.startsWith("Payment")) {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
const params = {};
|
|
738
|
+
const re = /(\w+)="([^"]*)"/g;
|
|
739
|
+
let match;
|
|
740
|
+
while ((match = re.exec(header)) !== null) {
|
|
741
|
+
params[match[1]] = match[2];
|
|
742
|
+
}
|
|
743
|
+
if (params.method !== "canton" || !params.amount || !params.recipient || !params.network) {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
amount: params.amount,
|
|
748
|
+
currency: params.currency ?? "USDCx",
|
|
749
|
+
recipient: params.recipient,
|
|
750
|
+
network: params.network,
|
|
751
|
+
description: params.description
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
var MppPayClient = class {
|
|
755
|
+
constructor(usdcx, safeguards, partyId, network) {
|
|
756
|
+
this.usdcx = usdcx;
|
|
757
|
+
this.safeguards = safeguards;
|
|
758
|
+
this.partyId = partyId;
|
|
759
|
+
this.network = network;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Pay for an API call via MPP 402 flow.
|
|
763
|
+
* If the response is not 402, returns it as-is with paid=false.
|
|
764
|
+
*/
|
|
765
|
+
async pay(url, opts) {
|
|
766
|
+
const requestInit = {
|
|
767
|
+
method: opts?.method ?? "GET",
|
|
768
|
+
headers: opts?.headers,
|
|
769
|
+
body: opts?.body
|
|
770
|
+
};
|
|
771
|
+
const firstResponse = await fetch(url, requestInit);
|
|
772
|
+
if (firstResponse.status !== 402) {
|
|
773
|
+
return { response: firstResponse, paid: false };
|
|
774
|
+
}
|
|
775
|
+
const authHeader = firstResponse.headers.get("WWW-Authenticate") ?? "";
|
|
776
|
+
const challenge = parseWwwAuthenticate(authHeader);
|
|
777
|
+
if (!challenge) {
|
|
778
|
+
throw new Error("402 received but no valid Canton payment challenge in WWW-Authenticate");
|
|
779
|
+
}
|
|
780
|
+
if (challenge.network !== this.network) {
|
|
781
|
+
throw new Error(
|
|
782
|
+
`Network mismatch: challenge requires ${challenge.network}, agent on ${this.network}`
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
if (opts?.maxPrice && compareAmounts(challenge.amount, opts.maxPrice) > 0) {
|
|
786
|
+
throw new Error(
|
|
787
|
+
`Price ${challenge.amount} exceeds maxPrice ${opts.maxPrice}`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const check = this.safeguards.check(challenge.amount);
|
|
791
|
+
if (!check.allowed) {
|
|
792
|
+
throw new Error(`Safeguard rejected: ${check.reason}`);
|
|
793
|
+
}
|
|
794
|
+
const transferResult = await this.usdcx.transfer({
|
|
795
|
+
recipient: challenge.recipient,
|
|
796
|
+
amount: challenge.amount
|
|
797
|
+
});
|
|
798
|
+
this.safeguards.recordSpend(challenge.amount);
|
|
799
|
+
const credential = Buffer.from(
|
|
800
|
+
JSON.stringify({
|
|
801
|
+
updateId: transferResult.updateId,
|
|
802
|
+
completionOffset: transferResult.completionOffset,
|
|
803
|
+
sender: this.partyId,
|
|
804
|
+
commandId: transferResult.commandId
|
|
805
|
+
})
|
|
806
|
+
).toString("base64");
|
|
807
|
+
const retryResponse = await fetch(url, {
|
|
808
|
+
...requestInit,
|
|
809
|
+
headers: {
|
|
810
|
+
...opts?.headers,
|
|
811
|
+
Authorization: `Payment ${credential}`
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
return {
|
|
815
|
+
response: retryResponse,
|
|
816
|
+
paid: true,
|
|
817
|
+
receipt: {
|
|
818
|
+
updateId: transferResult.updateId,
|
|
819
|
+
completionOffset: transferResult.completionOffset,
|
|
820
|
+
commandId: transferResult.commandId,
|
|
821
|
+
amount: challenge.amount
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// src/traffic/manager.ts
|
|
828
|
+
var TrafficManager = class {
|
|
829
|
+
constructor(client, partyId) {
|
|
830
|
+
this.client = client;
|
|
831
|
+
this.partyId = partyId;
|
|
832
|
+
}
|
|
833
|
+
autoPurchaseConfig = {
|
|
834
|
+
enabled: false,
|
|
835
|
+
minBalance: 1e3,
|
|
836
|
+
purchaseAmount: "5.0"
|
|
837
|
+
};
|
|
838
|
+
/**
|
|
839
|
+
* Check validator's traffic balance.
|
|
840
|
+
*
|
|
841
|
+
* TODO: Implement using actual validator admin API.
|
|
842
|
+
* The traffic balance is a validator-level concept, not per-party.
|
|
843
|
+
* For now, returns a stub indicating sufficient traffic.
|
|
844
|
+
*/
|
|
845
|
+
async trafficBalance() {
|
|
846
|
+
const healthy = await this.client.isHealthy();
|
|
847
|
+
if (!healthy) {
|
|
848
|
+
return { totalPurchased: 0, consumed: 0, remaining: 0 };
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
totalPurchased: 1e7,
|
|
852
|
+
consumed: 0,
|
|
853
|
+
remaining: 1e7
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Purchase additional traffic by burning Canton Coin (CC).
|
|
858
|
+
*
|
|
859
|
+
* TODO: Implement using actual CC burn mechanism.
|
|
860
|
+
*/
|
|
861
|
+
async purchaseTraffic(ccAmount) {
|
|
862
|
+
void ccAmount;
|
|
863
|
+
throw new Error("Traffic purchase not yet implemented \u2014 requires validator admin API");
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Check if there's sufficient traffic for a standard operation.
|
|
867
|
+
* Returns true if remaining traffic > minimum threshold.
|
|
868
|
+
*/
|
|
869
|
+
async hasSufficientTraffic() {
|
|
870
|
+
const balance = await this.trafficBalance();
|
|
871
|
+
return balance.remaining > this.autoPurchaseConfig.minBalance;
|
|
872
|
+
}
|
|
873
|
+
/** Configure auto-purchase settings. */
|
|
874
|
+
setAutoPurchase(config) {
|
|
875
|
+
this.autoPurchaseConfig = { ...config };
|
|
876
|
+
}
|
|
877
|
+
/** Get current auto-purchase configuration. */
|
|
878
|
+
getAutoPurchaseConfig() {
|
|
879
|
+
return { ...this.autoPurchaseConfig };
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/agent.ts
|
|
884
|
+
var CantonAgent = class _CantonAgent {
|
|
885
|
+
checking;
|
|
886
|
+
safeguards;
|
|
887
|
+
traffic;
|
|
888
|
+
mpp;
|
|
889
|
+
wallet;
|
|
890
|
+
client;
|
|
891
|
+
usdcx;
|
|
892
|
+
constructor(client, usdcx, checking, safeguards, traffic, mpp, wallet) {
|
|
893
|
+
this.client = client;
|
|
894
|
+
this.usdcx = usdcx;
|
|
895
|
+
this.checking = checking;
|
|
896
|
+
this.safeguards = safeguards;
|
|
897
|
+
this.traffic = traffic;
|
|
898
|
+
this.mpp = mpp;
|
|
899
|
+
this.wallet = wallet;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Create a new CantonAgent from config.
|
|
903
|
+
*
|
|
904
|
+
* Loads configuration from ~/.caypo/config.json (or overrides),
|
|
905
|
+
* initializes all sub-services, and wires them together.
|
|
906
|
+
*/
|
|
907
|
+
static async create(config) {
|
|
908
|
+
const fileConfig = await loadConfig(config?.configPath);
|
|
909
|
+
const merged = {
|
|
910
|
+
...fileConfig,
|
|
911
|
+
...config?.ledgerUrl && { ledgerUrl: config.ledgerUrl },
|
|
912
|
+
...config?.partyId && { partyId: config.partyId },
|
|
913
|
+
...config?.userId && { userId: config.userId },
|
|
914
|
+
...config?.network && { network: config.network }
|
|
915
|
+
};
|
|
916
|
+
const token = config?.token ?? "";
|
|
917
|
+
const client = new CantonClient({
|
|
918
|
+
ledgerUrl: merged.ledgerUrl,
|
|
919
|
+
token,
|
|
920
|
+
userId: merged.userId
|
|
921
|
+
});
|
|
922
|
+
const usdcx = new USDCxService(client, merged.partyId);
|
|
923
|
+
const safeguards = await SafeguardManager.load(config?.safeguardsPath);
|
|
924
|
+
const traffic = new TrafficManager(client, merged.partyId);
|
|
925
|
+
const checking = new CheckingAccount(usdcx, safeguards, client, merged.partyId);
|
|
926
|
+
const mpp = new MppPayClient(usdcx, safeguards, merged.partyId, merged.network);
|
|
927
|
+
const wallet = {
|
|
928
|
+
address: merged.partyId,
|
|
929
|
+
partyId: merged.partyId,
|
|
930
|
+
network: merged.network
|
|
931
|
+
};
|
|
932
|
+
return new _CantonAgent(client, usdcx, checking, safeguards, traffic, mpp, wallet);
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Create a CantonAgent from explicit parameters (no file I/O).
|
|
936
|
+
* Useful for testing and programmatic setup.
|
|
937
|
+
*/
|
|
938
|
+
static fromParams(params) {
|
|
939
|
+
const safeguards = params.safeguards ?? new SafeguardManager();
|
|
940
|
+
const usdcx = new USDCxService(params.client, params.partyId);
|
|
941
|
+
const traffic = new TrafficManager(params.client, params.partyId);
|
|
942
|
+
const checking = new CheckingAccount(usdcx, safeguards, params.client, params.partyId);
|
|
943
|
+
const mpp = new MppPayClient(usdcx, safeguards, params.partyId, params.network);
|
|
944
|
+
const wallet = {
|
|
945
|
+
address: params.partyId,
|
|
946
|
+
partyId: params.partyId,
|
|
947
|
+
network: params.network
|
|
948
|
+
};
|
|
949
|
+
return new _CantonAgent(params.client, usdcx, checking, safeguards, traffic, mpp, wallet);
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
// src/index.ts
|
|
954
|
+
var CANTON_SDK_VERSION = "0.1.0";
|
|
955
|
+
var DEFAULT_LEDGER_PORT = 7575;
|
|
956
|
+
export {
|
|
957
|
+
CANTON_SDK_VERSION,
|
|
958
|
+
CantonAgent,
|
|
959
|
+
CantonApiError,
|
|
960
|
+
CantonAuthError,
|
|
961
|
+
CantonClient,
|
|
962
|
+
CantonTimeoutError,
|
|
963
|
+
CheckingAccount,
|
|
964
|
+
DEFAULT_CONFIG,
|
|
965
|
+
DEFAULT_LEDGER_PORT,
|
|
966
|
+
InsufficientBalanceError,
|
|
967
|
+
Keystore,
|
|
968
|
+
MPP_CANTON_VERSION,
|
|
969
|
+
MppPayClient,
|
|
970
|
+
SafeguardManager,
|
|
971
|
+
TRANSFER_FACTORY_TEMPLATE_ID,
|
|
972
|
+
TrafficManager,
|
|
973
|
+
USDCX_HOLDING_TEMPLATE_ID,
|
|
974
|
+
USDCX_INSTRUMENT_ID,
|
|
975
|
+
USDCxService,
|
|
976
|
+
addAmounts,
|
|
977
|
+
compareAmounts,
|
|
978
|
+
isValidAmount,
|
|
979
|
+
loadConfig,
|
|
980
|
+
parseWwwAuthenticate,
|
|
981
|
+
saveConfig,
|
|
982
|
+
selectHoldings,
|
|
983
|
+
subtractAmounts,
|
|
984
|
+
toCantonAmount
|
|
985
|
+
};
|
|
986
|
+
//# sourceMappingURL=index.js.map
|