@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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holding selection algorithm for USDCx transfers.
|
|
3
|
+
*
|
|
4
|
+
* Canton uses a UTXO-like model: a party can have multiple Holding contracts.
|
|
5
|
+
* When transferring, we need to select holdings that cover the required amount.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { addAmounts, compareAmounts } from "./amount.js";
|
|
9
|
+
|
|
10
|
+
export interface USDCxHolding {
|
|
11
|
+
contractId: string;
|
|
12
|
+
owner: string;
|
|
13
|
+
amount: string;
|
|
14
|
+
templateId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface HoldingSelection {
|
|
18
|
+
type: "single" | "merge-then-transfer";
|
|
19
|
+
contractIds: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class InsufficientBalanceError extends Error {
|
|
23
|
+
readonly available: string;
|
|
24
|
+
readonly required: string;
|
|
25
|
+
|
|
26
|
+
constructor(available: string, required: string) {
|
|
27
|
+
super(`Insufficient balance: have ${available}, need ${required}`);
|
|
28
|
+
this.name = "InsufficientBalanceError";
|
|
29
|
+
this.available = available;
|
|
30
|
+
this.required = required;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Select holdings that cover the required amount.
|
|
36
|
+
*
|
|
37
|
+
* Strategy:
|
|
38
|
+
* 1. Sort holdings by amount descending.
|
|
39
|
+
* 2. Look for a single holding >= required (prefer smallest sufficient one).
|
|
40
|
+
* 3. If none, accumulate multiple holdings until we cover the amount.
|
|
41
|
+
* 4. Throw InsufficientBalanceError if total < required.
|
|
42
|
+
*/
|
|
43
|
+
export function selectHoldings(
|
|
44
|
+
holdings: USDCxHolding[],
|
|
45
|
+
requiredAmount: string,
|
|
46
|
+
): HoldingSelection {
|
|
47
|
+
if (holdings.length === 0) {
|
|
48
|
+
throw new InsufficientBalanceError("0", requiredAmount);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Sort descending by amount
|
|
52
|
+
const sorted = [...holdings].sort((a, b) => compareAmounts(b.amount, a.amount));
|
|
53
|
+
|
|
54
|
+
// 1. Try to find a single holding that covers the amount.
|
|
55
|
+
// Among all sufficient holdings, pick the smallest (best fit).
|
|
56
|
+
let bestSingle: USDCxHolding | null = null;
|
|
57
|
+
for (const h of sorted) {
|
|
58
|
+
if (compareAmounts(h.amount, requiredAmount) >= 0) {
|
|
59
|
+
// This one is sufficient — it might be the best fit
|
|
60
|
+
// since we iterate descending, each subsequent one is smaller but still sufficient
|
|
61
|
+
bestSingle = h;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (bestSingle) {
|
|
66
|
+
return {
|
|
67
|
+
type: "single",
|
|
68
|
+
contractIds: [bestSingle.contractId],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Accumulate multiple holdings (greedy — take largest first)
|
|
73
|
+
let accumulated = "0";
|
|
74
|
+
const selected: string[] = [];
|
|
75
|
+
|
|
76
|
+
for (const h of sorted) {
|
|
77
|
+
selected.push(h.contractId);
|
|
78
|
+
accumulated = addAmounts(accumulated, h.amount);
|
|
79
|
+
|
|
80
|
+
if (compareAmounts(accumulated, requiredAmount) >= 0) {
|
|
81
|
+
return {
|
|
82
|
+
type: "merge-then-transfer",
|
|
83
|
+
contractIds: selected,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Not enough
|
|
89
|
+
throw new InsufficientBalanceError(accumulated, requiredAmount);
|
|
90
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export { CantonClient } from "./client.js";
|
|
2
|
+
export { CantonApiError, CantonAuthError, CantonTimeoutError } from "./errors.js";
|
|
3
|
+
export type {
|
|
4
|
+
ActiveContract,
|
|
5
|
+
ActiveContractsRequest,
|
|
6
|
+
AnyPartyFilter,
|
|
7
|
+
ArchivedEvent,
|
|
8
|
+
CantonClientConfig,
|
|
9
|
+
CantonErrorCode,
|
|
10
|
+
Command,
|
|
11
|
+
CreateCommand,
|
|
12
|
+
CreatedEvent,
|
|
13
|
+
EventFormat,
|
|
14
|
+
ExerciseCommand,
|
|
15
|
+
ExercisedEvent,
|
|
16
|
+
FlatTransaction,
|
|
17
|
+
IdentifierFilter,
|
|
18
|
+
LedgerEndResponse,
|
|
19
|
+
LedgerError,
|
|
20
|
+
PartyDetails,
|
|
21
|
+
PartyFilter,
|
|
22
|
+
PartyLocalMetadata,
|
|
23
|
+
QueryActiveContractsParams,
|
|
24
|
+
SubmitAndWaitRequest,
|
|
25
|
+
SubmitAndWaitResponse,
|
|
26
|
+
SubmitParams,
|
|
27
|
+
TransactionResponse,
|
|
28
|
+
TransactionTree,
|
|
29
|
+
TransactionTreeEvent,
|
|
30
|
+
} from "./types.js";
|
|
31
|
+
export {
|
|
32
|
+
addAmounts,
|
|
33
|
+
compareAmounts,
|
|
34
|
+
isValidAmount,
|
|
35
|
+
subtractAmounts,
|
|
36
|
+
toCantonAmount,
|
|
37
|
+
} from "./amount.js";
|
|
38
|
+
export {
|
|
39
|
+
InsufficientBalanceError,
|
|
40
|
+
selectHoldings,
|
|
41
|
+
type HoldingSelection,
|
|
42
|
+
type USDCxHolding,
|
|
43
|
+
} from "./holdings.js";
|
|
44
|
+
export {
|
|
45
|
+
USDCxService,
|
|
46
|
+
USDCX_HOLDING_TEMPLATE_ID,
|
|
47
|
+
USDCX_INSTRUMENT_ID,
|
|
48
|
+
TRANSFER_FACTORY_TEMPLATE_ID,
|
|
49
|
+
type TransferParams,
|
|
50
|
+
type TransferResult,
|
|
51
|
+
} from "./usdcx.js";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canton JSON Ledger API v2 — Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Verified against: docs.digitalasset.com/build/3.5, Canton OpenAPI spec v3.3.0
|
|
5
|
+
* Default port: 7575
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Party
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export interface PartyLocalMetadata {
|
|
13
|
+
resourceVersion: string;
|
|
14
|
+
annotations: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PartyDetails {
|
|
18
|
+
party: string; // e.g., "Alice::122084768362d0ce21f1ffec870e55e365a292cdf8f54c5c38ad7775b9bdd462e141"
|
|
19
|
+
isLocal: boolean;
|
|
20
|
+
localMetadata: PartyLocalMetadata;
|
|
21
|
+
identityProviderId: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Commands
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export interface CreateCommand {
|
|
29
|
+
CreateCommand: {
|
|
30
|
+
templateId: string; // "#package-name:Module:Template" or "packagehash:Module:Template"
|
|
31
|
+
createArguments: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ExerciseCommand {
|
|
36
|
+
ExerciseCommand: {
|
|
37
|
+
templateId: string;
|
|
38
|
+
contractId: string;
|
|
39
|
+
choice: string;
|
|
40
|
+
choiceArgument: Record<string, unknown>;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type Command = CreateCommand | ExerciseCommand;
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Submit and Wait
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
export interface SubmitAndWaitRequest {
|
|
51
|
+
commands: Command[];
|
|
52
|
+
userId: string;
|
|
53
|
+
commandId: string;
|
|
54
|
+
actAs: string[];
|
|
55
|
+
readAs?: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SubmitAndWaitResponse {
|
|
59
|
+
updateId: string;
|
|
60
|
+
completionOffset: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Events
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
export interface CreatedEvent {
|
|
68
|
+
contractId: string;
|
|
69
|
+
templateId: string;
|
|
70
|
+
createArgument: Record<string, unknown>;
|
|
71
|
+
witnessParties: string[];
|
|
72
|
+
signatories: string[];
|
|
73
|
+
observers: string[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ArchivedEvent {
|
|
77
|
+
contractId: string;
|
|
78
|
+
templateId: string;
|
|
79
|
+
witnessParties: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ExercisedEvent {
|
|
83
|
+
contractId: string;
|
|
84
|
+
templateId: string;
|
|
85
|
+
choice: string;
|
|
86
|
+
choiceArgument: Record<string, unknown>;
|
|
87
|
+
exerciseResult: unknown;
|
|
88
|
+
actingParties: string[];
|
|
89
|
+
childEvents: TransactionTreeEvent[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type TransactionTreeEvent =
|
|
93
|
+
| { createdEvent: CreatedEvent }
|
|
94
|
+
| { exercisedEvent: ExercisedEvent };
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Transactions
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
export interface TransactionTree {
|
|
101
|
+
updateId: string;
|
|
102
|
+
commandId: string;
|
|
103
|
+
effectiveAt: string;
|
|
104
|
+
offset: number;
|
|
105
|
+
eventsById: Record<string, TransactionTreeEvent>;
|
|
106
|
+
rootEventIds: string[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface FlatTransaction {
|
|
110
|
+
updateId: string;
|
|
111
|
+
commandId: string;
|
|
112
|
+
effectiveAt: string;
|
|
113
|
+
offset: number;
|
|
114
|
+
events: Array<{ createdEvent?: CreatedEvent; archivedEvent?: ArchivedEvent }>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TransactionResponse {
|
|
118
|
+
transaction: FlatTransaction;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Active Contracts
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
export interface IdentifierFilter {
|
|
126
|
+
identifierFilter:
|
|
127
|
+
| { TemplateFilter: { value: { templateId: string } } }
|
|
128
|
+
| { WildcardFilter: { value: { includeCreatedEventBlob?: boolean } } };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface PartyFilter {
|
|
132
|
+
cumulative: IdentifierFilter[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface AnyPartyFilter {
|
|
136
|
+
cumulative: IdentifierFilter[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface EventFormat {
|
|
140
|
+
filtersByParty?: Record<string, PartyFilter>;
|
|
141
|
+
filtersForAnyParty?: AnyPartyFilter;
|
|
142
|
+
verbose?: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface ActiveContractsRequest {
|
|
146
|
+
eventFormat: EventFormat;
|
|
147
|
+
activeAtOffset: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface ActiveContract {
|
|
151
|
+
contractId: string;
|
|
152
|
+
templateId: string;
|
|
153
|
+
createArgument: Record<string, unknown>;
|
|
154
|
+
createdAt: string;
|
|
155
|
+
signatories: string[];
|
|
156
|
+
observers: string[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Ledger State
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
export interface LedgerEndResponse {
|
|
164
|
+
offset: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Errors
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
export type CantonErrorCode =
|
|
172
|
+
| "INVALID_ARGUMENT"
|
|
173
|
+
| "NOT_FOUND"
|
|
174
|
+
| "PERMISSION_DENIED"
|
|
175
|
+
| "ALREADY_EXISTS"
|
|
176
|
+
| "FAILED_PRECONDITION"
|
|
177
|
+
| "UNAVAILABLE"
|
|
178
|
+
| string;
|
|
179
|
+
|
|
180
|
+
export interface LedgerError {
|
|
181
|
+
cause: string;
|
|
182
|
+
code: CantonErrorCode;
|
|
183
|
+
context: Record<string, string>;
|
|
184
|
+
errorCategory: number;
|
|
185
|
+
grpcCodeValue: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Client Config
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
export interface CantonClientConfig {
|
|
193
|
+
ledgerUrl: string; // e.g., "http://localhost:7575"
|
|
194
|
+
token: string; // JWT bearer token
|
|
195
|
+
userId: string; // Ledger API user ID
|
|
196
|
+
timeout?: number; // Request timeout in ms (default: 30000)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// Client Method Params (excluding userId, which comes from config)
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
export interface SubmitParams {
|
|
204
|
+
commands: Command[];
|
|
205
|
+
commandId: string;
|
|
206
|
+
actAs: string[];
|
|
207
|
+
readAs?: string[];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface QueryActiveContractsParams {
|
|
211
|
+
filtersByParty?: Record<string, PartyFilter>;
|
|
212
|
+
filtersForAnyParty?: AnyPartyFilter;
|
|
213
|
+
activeAtOffset: number;
|
|
214
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USDCx Operations — Query holdings, transfer, merge.
|
|
3
|
+
*
|
|
4
|
+
* Uses CIP-56 token standard:
|
|
5
|
+
* - Splice.Api.Token.HoldingV1:Holding for balance queries
|
|
6
|
+
* - TransferFactory_Transfer for 1-step transfers (requires TransferPreapproval)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { addAmounts, toCantonAmount } from "./amount.js";
|
|
10
|
+
import type { CantonClient } from "./client.js";
|
|
11
|
+
import { selectHoldings, type HoldingSelection, type USDCxHolding } from "./holdings.js";
|
|
12
|
+
import type { SubmitAndWaitResponse } from "./types.js";
|
|
13
|
+
|
|
14
|
+
/** CIP-56 Holding template ID — used for active contract queries */
|
|
15
|
+
export const USDCX_HOLDING_TEMPLATE_ID = "Splice.Api.Token.HoldingV1:Holding";
|
|
16
|
+
|
|
17
|
+
/** TransferFactory template ID for 1-step transfers */
|
|
18
|
+
export const TRANSFER_FACTORY_TEMPLATE_ID = "Splice.Api.Token.TransferFactoryV1:TransferFactory";
|
|
19
|
+
|
|
20
|
+
/** USDCx instrument identifier */
|
|
21
|
+
export const USDCX_INSTRUMENT_ID = "USDCx";
|
|
22
|
+
|
|
23
|
+
export interface TransferResult {
|
|
24
|
+
updateId: string;
|
|
25
|
+
completionOffset: number;
|
|
26
|
+
commandId: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TransferParams {
|
|
30
|
+
recipient: string;
|
|
31
|
+
amount: string;
|
|
32
|
+
commandId?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class USDCxService {
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly client: CantonClient,
|
|
38
|
+
private readonly partyId: string,
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Query all USDCx Holding contracts for this party.
|
|
43
|
+
*/
|
|
44
|
+
async getHoldings(): Promise<USDCxHolding[]> {
|
|
45
|
+
const offset = await this.client.getLedgerEnd();
|
|
46
|
+
|
|
47
|
+
const contracts = await this.client.queryActiveContracts({
|
|
48
|
+
filtersByParty: {
|
|
49
|
+
[this.partyId]: {
|
|
50
|
+
cumulative: [
|
|
51
|
+
{
|
|
52
|
+
identifierFilter: {
|
|
53
|
+
TemplateFilter: {
|
|
54
|
+
value: { templateId: USDCX_HOLDING_TEMPLATE_ID },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
activeAtOffset: offset,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return contracts.map((c) => ({
|
|
65
|
+
contractId: c.contractId,
|
|
66
|
+
owner: (c.createArgument.owner as string) ?? this.partyId,
|
|
67
|
+
amount: c.createArgument.amount as string,
|
|
68
|
+
templateId: c.templateId,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Calculate total USDCx balance by summing all holding amounts.
|
|
74
|
+
* Returns a string with up to 10 decimal places.
|
|
75
|
+
*/
|
|
76
|
+
async getBalance(): Promise<string> {
|
|
77
|
+
const holdings = await this.getHoldings();
|
|
78
|
+
|
|
79
|
+
if (holdings.length === 0) {
|
|
80
|
+
return "0";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let total = "0";
|
|
84
|
+
for (const h of holdings) {
|
|
85
|
+
total = addAmounts(total, h.amount);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return total;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Transfer USDCx using TransferFactory_Transfer (1-step).
|
|
93
|
+
* Requires the recipient to have an active TransferPreapproval.
|
|
94
|
+
*/
|
|
95
|
+
async transfer(params: TransferParams): Promise<TransferResult> {
|
|
96
|
+
const commandId = params.commandId ?? crypto.randomUUID();
|
|
97
|
+
const amount = toCantonAmount(params.amount);
|
|
98
|
+
|
|
99
|
+
// 1. Query holdings
|
|
100
|
+
const holdings = await this.getHoldings();
|
|
101
|
+
|
|
102
|
+
// 2. Select holdings covering the amount (throws InsufficientBalanceError if not enough)
|
|
103
|
+
const selection: HoldingSelection = selectHoldings(holdings, params.amount);
|
|
104
|
+
|
|
105
|
+
// 3. Build and submit ExerciseCommand for TransferFactory_Transfer
|
|
106
|
+
const result: SubmitAndWaitResponse = await this.client.submitAndWait({
|
|
107
|
+
commands: [
|
|
108
|
+
{
|
|
109
|
+
ExerciseCommand: {
|
|
110
|
+
templateId: TRANSFER_FACTORY_TEMPLATE_ID,
|
|
111
|
+
contractId: selection.contractIds[0],
|
|
112
|
+
choice: "TransferFactory_Transfer",
|
|
113
|
+
choiceArgument: {
|
|
114
|
+
sender: this.partyId,
|
|
115
|
+
receiver: params.recipient,
|
|
116
|
+
amount,
|
|
117
|
+
instrumentId: USDCX_INSTRUMENT_ID,
|
|
118
|
+
inputHoldingCids: selection.contractIds,
|
|
119
|
+
meta: {},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
commandId,
|
|
125
|
+
actAs: [this.partyId],
|
|
126
|
+
readAs: [this.partyId],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
updateId: result.updateId,
|
|
131
|
+
completionOffset: result.completionOffset,
|
|
132
|
+
commandId,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Merge multiple holdings into fewer UTXOs.
|
|
138
|
+
* Returns the commandId of the merge transaction.
|
|
139
|
+
*/
|
|
140
|
+
async mergeHoldings(holdingCids: string[]): Promise<string> {
|
|
141
|
+
if (holdingCids.length < 2) {
|
|
142
|
+
throw new Error("Need at least 2 holdings to merge");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const commandId = crypto.randomUUID();
|
|
146
|
+
|
|
147
|
+
await this.client.submitAndWait({
|
|
148
|
+
commands: [
|
|
149
|
+
{
|
|
150
|
+
ExerciseCommand: {
|
|
151
|
+
templateId: USDCX_HOLDING_TEMPLATE_ID,
|
|
152
|
+
contractId: holdingCids[0],
|
|
153
|
+
choice: "Merge",
|
|
154
|
+
choiceArgument: {
|
|
155
|
+
holdingCids: holdingCids.slice(1),
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
commandId,
|
|
161
|
+
actAs: [this.partyId],
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return commandId;
|
|
165
|
+
}
|
|
166
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @caypo/canton-sdk — Core SDK for Canton Network.
|
|
3
|
+
* JSON Ledger API v2 client, USDCx operations, agent accounts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { MPP_CANTON_VERSION } from "@caypo/mpp-canton";
|
|
7
|
+
|
|
8
|
+
export const CANTON_SDK_VERSION = "0.1.0";
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_LEDGER_PORT = 7575;
|
|
11
|
+
|
|
12
|
+
// Canton JSON Ledger API v2 client
|
|
13
|
+
export { CantonClient } from "./canton/client.js";
|
|
14
|
+
export { CantonApiError, CantonAuthError, CantonTimeoutError } from "./canton/errors.js";
|
|
15
|
+
export type {
|
|
16
|
+
ActiveContract,
|
|
17
|
+
ActiveContractsRequest,
|
|
18
|
+
AnyPartyFilter,
|
|
19
|
+
ArchivedEvent,
|
|
20
|
+
CantonClientConfig,
|
|
21
|
+
CantonErrorCode,
|
|
22
|
+
Command,
|
|
23
|
+
CreateCommand,
|
|
24
|
+
CreatedEvent,
|
|
25
|
+
EventFormat,
|
|
26
|
+
ExerciseCommand,
|
|
27
|
+
ExercisedEvent,
|
|
28
|
+
FlatTransaction,
|
|
29
|
+
IdentifierFilter,
|
|
30
|
+
LedgerEndResponse,
|
|
31
|
+
LedgerError,
|
|
32
|
+
PartyDetails,
|
|
33
|
+
PartyFilter,
|
|
34
|
+
PartyLocalMetadata,
|
|
35
|
+
QueryActiveContractsParams,
|
|
36
|
+
SubmitAndWaitRequest,
|
|
37
|
+
SubmitAndWaitResponse,
|
|
38
|
+
SubmitParams,
|
|
39
|
+
TransactionResponse,
|
|
40
|
+
TransactionTree,
|
|
41
|
+
TransactionTreeEvent,
|
|
42
|
+
} from "./canton/types.js";
|
|
43
|
+
|
|
44
|
+
// USDCx operations
|
|
45
|
+
export {
|
|
46
|
+
USDCxService,
|
|
47
|
+
USDCX_HOLDING_TEMPLATE_ID,
|
|
48
|
+
USDCX_INSTRUMENT_ID,
|
|
49
|
+
TRANSFER_FACTORY_TEMPLATE_ID,
|
|
50
|
+
} from "./canton/usdcx.js";
|
|
51
|
+
export type { TransferParams, TransferResult } from "./canton/usdcx.js";
|
|
52
|
+
|
|
53
|
+
// Amount utilities (string-based decimal arithmetic)
|
|
54
|
+
export {
|
|
55
|
+
addAmounts,
|
|
56
|
+
compareAmounts,
|
|
57
|
+
isValidAmount,
|
|
58
|
+
subtractAmounts,
|
|
59
|
+
toCantonAmount,
|
|
60
|
+
} from "./canton/amount.js";
|
|
61
|
+
|
|
62
|
+
// Holding selection
|
|
63
|
+
export { InsufficientBalanceError, selectHoldings } from "./canton/holdings.js";
|
|
64
|
+
export type { HoldingSelection, USDCxHolding } from "./canton/holdings.js";
|
|
65
|
+
|
|
66
|
+
// Wallet keystore
|
|
67
|
+
export { Keystore } from "./wallet/keystore.js";
|
|
68
|
+
export type { WalletData } from "./wallet/keystore.js";
|
|
69
|
+
|
|
70
|
+
// Agent configuration
|
|
71
|
+
export { loadConfig, saveConfig, DEFAULT_CONFIG } from "./wallet/config.js";
|
|
72
|
+
export type {
|
|
73
|
+
AgentConfig,
|
|
74
|
+
TrafficConfig,
|
|
75
|
+
SafeguardsConfig,
|
|
76
|
+
MppConfig,
|
|
77
|
+
} from "./wallet/config.js";
|
|
78
|
+
|
|
79
|
+
// Safeguards
|
|
80
|
+
export { SafeguardManager } from "./safeguards/manager.js";
|
|
81
|
+
export type { SafeguardConfig, CheckResult } from "./safeguards/manager.js";
|
|
82
|
+
|
|
83
|
+
// High-level agent
|
|
84
|
+
export { CantonAgent } from "./agent.js";
|
|
85
|
+
export type { CantonAgentConfig, WalletInfo } from "./agent.js";
|
|
86
|
+
|
|
87
|
+
// Checking account
|
|
88
|
+
export { CheckingAccount } from "./accounts/checking.js";
|
|
89
|
+
export type { SendOptions, TransactionRecord } from "./accounts/checking.js";
|
|
90
|
+
|
|
91
|
+
// Traffic manager
|
|
92
|
+
export { TrafficManager } from "./traffic/manager.js";
|
|
93
|
+
export type { TrafficBalance, AutoPurchaseConfig } from "./traffic/manager.js";
|
|
94
|
+
|
|
95
|
+
// MPP pay client
|
|
96
|
+
export { MppPayClient, parseWwwAuthenticate } from "./mpp/pay-client.js";
|
|
97
|
+
export type { PayOptions, PayResult, PaymentChallenge } from "./mpp/pay-client.js";
|