@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,26 @@
|
|
|
1
|
+
|
|
2
|
+
> @caypo/canton-sdk@0.1.0 build /Users/anil/Desktop/caypo/packages/sdk
|
|
3
|
+
> tsup
|
|
4
|
+
|
|
5
|
+
CLI Building entry: src/index.ts
|
|
6
|
+
CLI Using tsconfig: tsconfig.json
|
|
7
|
+
CLI tsup v8.5.1
|
|
8
|
+
CLI Using tsup config: /Users/anil/Desktop/caypo/packages/sdk/tsup.config.ts
|
|
9
|
+
CLI Target: es2022
|
|
10
|
+
CLI Cleaning output folder
|
|
11
|
+
ESM Build start
|
|
12
|
+
CJS Build start
|
|
13
|
+
CJS dist/index.cjs 35.38 KB
|
|
14
|
+
CJS dist/index.cjs.map 72.40 KB
|
|
15
|
+
CJS ⚡️ Build success in 15ms
|
|
16
|
+
ESM dist/chunk-GSDB5FKZ.js 3.22 KB
|
|
17
|
+
ESM dist/amount-L2SDLRZT.js 264.00 B
|
|
18
|
+
ESM dist/index.js 29.26 KB
|
|
19
|
+
ESM dist/chunk-GSDB5FKZ.js.map 7.90 KB
|
|
20
|
+
ESM dist/amount-L2SDLRZT.js.map 71.00 B
|
|
21
|
+
ESM dist/index.js.map 64.49 KB
|
|
22
|
+
ESM ⚡️ Build success in 15ms
|
|
23
|
+
DTS Build start
|
|
24
|
+
DTS ⚡️ Build success in 519ms
|
|
25
|
+
DTS dist/index.d.ts 20.56 KB
|
|
26
|
+
DTS dist/index.d.cts 20.56 KB
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
> @caypo/canton-sdk@0.1.0 test /Users/anil/Desktop/caypo/packages/sdk
|
|
3
|
+
> vitest run
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RUN v3.2.4 /Users/anil/Desktop/caypo/packages/sdk
|
|
7
|
+
|
|
8
|
+
↓ src/__tests__/e2e/canton-client.e2e.test.ts (13 tests | 13 skipped)
|
|
9
|
+
✓ src/__tests__/amount.test.ts (107 tests) 5ms
|
|
10
|
+
↓ src/__tests__/e2e/mpp-flow.e2e.test.ts (11 tests | 11 skipped)
|
|
11
|
+
✓ src/__tests__/client.test.ts (24 tests) 18ms
|
|
12
|
+
✓ src/__tests__/usdcx.test.ts (21 tests) 11ms
|
|
13
|
+
✓ src/__tests__/pay-client.test.ts (15 tests) 13ms
|
|
14
|
+
✓ src/__tests__/safeguards.test.ts (24 tests) 24ms
|
|
15
|
+
✓ src/__tests__/agent.test.ts (13 tests) 6ms
|
|
16
|
+
↓ src/__tests__/e2e/usdcx.e2e.test.ts (5 tests | 5 skipped)
|
|
17
|
+
✓ src/__tests__/keystore.test.ts (11 tests) 374ms
|
|
18
|
+
|
|
19
|
+
Test Files 7 passed | 3 skipped (10)
|
|
20
|
+
Tests 215 passed | 29 skipped (244)
|
|
21
|
+
Start at 19:11:46
|
|
22
|
+
Duration 636ms (transform 376ms, setup 0ms, collect 632ms, tests 452ms, environment 1ms, prepare 629ms)
|
|
23
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @caypo/canton-sdk
|
|
2
|
+
|
|
3
|
+
**Core SDK for AI agent banking on [Canton Network](https://canton.network)**
|
|
4
|
+
|
|
5
|
+
USDCx transfers, encrypted wallets, safeguards, traffic management, and MPP auto-pay — everything an AI agent needs to operate a bank account on Canton.
|
|
6
|
+
|
|
7
|
+
[](../../LICENSE-APACHE)
|
|
8
|
+
[](../../)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @caypo/canton-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { CantonAgent } from "@caypo/canton-sdk";
|
|
20
|
+
|
|
21
|
+
const agent = await CantonAgent.create({
|
|
22
|
+
ledgerUrl: "http://localhost:7575",
|
|
23
|
+
token: "your-jwt",
|
|
24
|
+
partyId: "Agent::1220...",
|
|
25
|
+
network: "testnet",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Balance
|
|
29
|
+
const { available, holdingCount } = await agent.checking.balance();
|
|
30
|
+
console.log(`${available} USDCx across ${holdingCount} holdings`);
|
|
31
|
+
|
|
32
|
+
// Send USDCx (safeguards checked automatically)
|
|
33
|
+
const tx = await agent.checking.send("Bob::1220...", "10.00");
|
|
34
|
+
console.log(`Sent! Update ID: ${tx.updateId}`);
|
|
35
|
+
|
|
36
|
+
// Pay for API call (handles 402 automatically)
|
|
37
|
+
const result = await agent.mpp.pay("https://mpp.cayvox.io/openai/v1/chat/completions", {
|
|
38
|
+
method: "POST",
|
|
39
|
+
body: JSON.stringify({ model: "gpt-4o", messages: [{ role: "user", content: "Hello" }] }),
|
|
40
|
+
maxPrice: "0.05",
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
### CantonClient — JSON Ledger API v2
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { CantonClient } from "@caypo/canton-sdk";
|
|
50
|
+
|
|
51
|
+
const client = new CantonClient({ ledgerUrl, token, userId });
|
|
52
|
+
await client.submitAndWait({ commands, commandId, actAs });
|
|
53
|
+
await client.queryActiveContracts({ filtersByParty, activeAtOffset });
|
|
54
|
+
await client.getLedgerEnd();
|
|
55
|
+
await client.allocateParty("Alice");
|
|
56
|
+
await client.isHealthy();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### USDCxService — Token Operations
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { USDCxService } from "@caypo/canton-sdk";
|
|
63
|
+
|
|
64
|
+
const usdcx = new USDCxService(client, partyId);
|
|
65
|
+
const holdings = await usdcx.getHoldings(); // USDCx UTXO holdings
|
|
66
|
+
const balance = await usdcx.getBalance(); // Sum as string
|
|
67
|
+
await usdcx.transfer({ recipient, amount }); // TransferFactory_Transfer
|
|
68
|
+
await usdcx.mergeHoldings(holdingCids); // Consolidate UTXOs
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### SafeguardManager — Spending Controls
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { SafeguardManager } from "@caypo/canton-sdk";
|
|
75
|
+
|
|
76
|
+
const safeguards = await SafeguardManager.load();
|
|
77
|
+
safeguards.setTxLimit("100"); // Per-transaction limit
|
|
78
|
+
safeguards.setDailyLimit("1000"); // Daily spending limit
|
|
79
|
+
safeguards.lock("1234"); // Lock wallet
|
|
80
|
+
const { allowed, reason } = safeguards.check("50"); // Pre-tx check
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Keystore — Encrypted Wallet
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { Keystore } from "@caypo/canton-sdk";
|
|
87
|
+
|
|
88
|
+
const ks = await Keystore.create("1234", { partyId, jwt, userId });
|
|
89
|
+
const loaded = await Keystore.load("1234");
|
|
90
|
+
const { partyId, jwt } = loaded.getCredentials();
|
|
91
|
+
await loaded.changePin("1234", "5678");
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Amount Utilities — No Floating Point
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { addAmounts, compareAmounts, subtractAmounts } from "@caypo/canton-sdk";
|
|
98
|
+
|
|
99
|
+
addAmounts("1.5", "2.5"); // "4"
|
|
100
|
+
compareAmounts("10", "9.99"); // 1
|
|
101
|
+
subtractAmounts("100", "0.01"); // "99.99"
|
|
102
|
+
// All amounts are strings — Canton uses Numeric 10 (10 decimal places)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
| Class | Purpose |
|
|
108
|
+
|-------|---------|
|
|
109
|
+
| `CantonAgent` | High-level entry point — wires all services together |
|
|
110
|
+
| `CheckingAccount` | Balance, send, receive, history |
|
|
111
|
+
| `CantonClient` | Low-level JSON Ledger API v2 client |
|
|
112
|
+
| `USDCxService` | Holdings, balance, transfer, merge |
|
|
113
|
+
| `SafeguardManager` | Tx/daily limits, lock/unlock |
|
|
114
|
+
| `TrafficManager` | Validator traffic balance |
|
|
115
|
+
| `MppPayClient` | HTTP 402 auto-pay flow |
|
|
116
|
+
| `Keystore` | AES-256-GCM encrypted wallet |
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
Apache-2.0 OR MIT
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# @cayvox/canton-sdk — Core SDK Specification (Production)
|
|
2
|
+
|
|
3
|
+
## Canton Client (Low-Level)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export class CantonClient {
|
|
7
|
+
constructor(config: {
|
|
8
|
+
ledgerUrl: string; // e.g., "http://localhost:7575"
|
|
9
|
+
token: string; // JWT bearer token
|
|
10
|
+
userId: string; // Ledger API user ID
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// Command submission (verified v2 API)
|
|
14
|
+
async submitAndWait(params: {
|
|
15
|
+
commands: Command[];
|
|
16
|
+
commandId: string;
|
|
17
|
+
actAs: string[];
|
|
18
|
+
readAs?: string[];
|
|
19
|
+
}): Promise<{ updateId: string; completionOffset: number }>
|
|
20
|
+
|
|
21
|
+
async submitAndWaitForTransaction(params: {
|
|
22
|
+
commands: Command[];
|
|
23
|
+
commandId: string;
|
|
24
|
+
actAs: string[];
|
|
25
|
+
readAs?: string[];
|
|
26
|
+
}): Promise<TransactionResponse>
|
|
27
|
+
|
|
28
|
+
// Active contract queries
|
|
29
|
+
async queryActiveContracts(params: {
|
|
30
|
+
filtersByParty?: Record<string, PartyFilter>;
|
|
31
|
+
filtersForAnyParty?: AnyPartyFilter;
|
|
32
|
+
activeAtOffset: number;
|
|
33
|
+
}): Promise<ActiveContract[]>
|
|
34
|
+
|
|
35
|
+
// Transaction lookup
|
|
36
|
+
async getTransactionById(updateId: string): Promise<TransactionTree>
|
|
37
|
+
|
|
38
|
+
// Ledger state
|
|
39
|
+
async getLedgerEnd(): Promise<number>
|
|
40
|
+
|
|
41
|
+
// Party management
|
|
42
|
+
async allocateParty(hint: string): Promise<PartyDetails>
|
|
43
|
+
async listParties(): Promise<PartyDetails[]>
|
|
44
|
+
|
|
45
|
+
// Health check
|
|
46
|
+
async isHealthy(): Promise<boolean>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Command types (verified from Canton OpenAPI)
|
|
50
|
+
type Command =
|
|
51
|
+
| { CreateCommand: { createArguments: any; templateId: string } }
|
|
52
|
+
| { ExerciseCommand: { templateId: string; contractId: string; choice: string; choiceArgument: any } };
|
|
53
|
+
|
|
54
|
+
interface PartyDetails {
|
|
55
|
+
party: string; // e.g., "Alice::1220abcd..."
|
|
56
|
+
isLocal: boolean;
|
|
57
|
+
localMetadata: { resourceVersion: string; annotations: Record<string, string> };
|
|
58
|
+
identityProviderId: string;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## USDCx Operations
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
export class USDCxService {
|
|
66
|
+
constructor(private client: CantonClient, private partyId: string)
|
|
67
|
+
|
|
68
|
+
/** Query all USDCx Holding contracts for this party */
|
|
69
|
+
async getHoldings(): Promise<USDCxHolding[]>
|
|
70
|
+
|
|
71
|
+
/** Calculate total balance from all holdings */
|
|
72
|
+
async getBalance(): Promise<string>
|
|
73
|
+
|
|
74
|
+
/** Transfer USDCx via TransferFactory (requires recipient TransferPreapproval) */
|
|
75
|
+
async transfer(params: {
|
|
76
|
+
recipient: string; // Recipient party ID
|
|
77
|
+
amount: string; // Decimal amount
|
|
78
|
+
commandId?: string; // Optional custom idempotency key
|
|
79
|
+
}): Promise<TransferResult>
|
|
80
|
+
|
|
81
|
+
/** Create a TransferInstruction (2-step, for when no preapproval exists) */
|
|
82
|
+
async createTransferInstruction(params: {
|
|
83
|
+
recipient: string;
|
|
84
|
+
amount: string;
|
|
85
|
+
}): Promise<TransferInstructionResult>
|
|
86
|
+
|
|
87
|
+
/** Merge multiple holdings into fewer UTXOs */
|
|
88
|
+
async mergeHoldings(holdingCids: string[]): Promise<string>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface USDCxHolding {
|
|
92
|
+
contractId: string;
|
|
93
|
+
owner: string; // Party ID
|
|
94
|
+
amount: string; // Decimal amount (up to 10 decimals)
|
|
95
|
+
templateId: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface TransferResult {
|
|
99
|
+
updateId: string;
|
|
100
|
+
completionOffset: number;
|
|
101
|
+
commandId: string;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## CantonAgent (High-Level)
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
export class CantonAgent {
|
|
109
|
+
static async create(config?: Partial<AgentConfig>): Promise<CantonAgent>
|
|
110
|
+
|
|
111
|
+
// Sub-services
|
|
112
|
+
readonly checking: CheckingAccount;
|
|
113
|
+
readonly savings: SavingsAccount; // Phase 3
|
|
114
|
+
readonly credit: CreditAccount; // Phase 3
|
|
115
|
+
readonly exchange: ExchangeAccount; // Phase 3
|
|
116
|
+
readonly invest: InvestmentAccount; // Phase 4
|
|
117
|
+
|
|
118
|
+
readonly safeguards: SafeguardManager;
|
|
119
|
+
readonly traffic: TrafficManager; // NOT "gas" — Canton uses traffic
|
|
120
|
+
readonly mpp: MppPayClient;
|
|
121
|
+
readonly wallet: WalletInfo;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## CheckingAccount
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
export class CheckingAccount {
|
|
129
|
+
/** Get USDCx balance */
|
|
130
|
+
async balance(): Promise<{ available: string; holdingCount: number }>
|
|
131
|
+
|
|
132
|
+
/** Send USDCx — uses TransferFactory_Transfer if preapproval exists,
|
|
133
|
+
falls back to TransferInstruction (2-step) otherwise */
|
|
134
|
+
async send(recipient: string, amount: string, opts?: {
|
|
135
|
+
memo?: string;
|
|
136
|
+
commandId?: string;
|
|
137
|
+
}): Promise<TransferResult>
|
|
138
|
+
|
|
139
|
+
/** Party ID for receiving payments */
|
|
140
|
+
address(): string
|
|
141
|
+
|
|
142
|
+
/** Transaction history via /v2/updates/flats */
|
|
143
|
+
async history(opts?: { limit?: number }): Promise<TransactionRecord[]>
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## TrafficManager (replaces "GasManager")
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
/** Canton uses traffic budgets per validator, NOT per-transaction gas fees */
|
|
151
|
+
export class TrafficManager {
|
|
152
|
+
/** Check validator's traffic balance */
|
|
153
|
+
async trafficBalance(): Promise<{
|
|
154
|
+
totalPurchased: number;
|
|
155
|
+
consumed: number;
|
|
156
|
+
remaining: number;
|
|
157
|
+
}>
|
|
158
|
+
|
|
159
|
+
/** Purchase additional traffic by burning CC */
|
|
160
|
+
async purchaseTraffic(ccAmount: string): Promise<{ txId: string }>
|
|
161
|
+
|
|
162
|
+
/** Check if sufficient traffic for an operation */
|
|
163
|
+
async hasSufficientTraffic(): Promise<boolean>
|
|
164
|
+
|
|
165
|
+
/** Auto-purchase configuration */
|
|
166
|
+
setAutoPurchase(config: { enabled: boolean; minBalance: number; purchaseAmount: string }): void
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## SafeguardManager
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
export class SafeguardManager {
|
|
174
|
+
settings(): SafeguardConfig
|
|
175
|
+
setTxLimit(amount: string): void
|
|
176
|
+
setDailyLimit(amount: string): void
|
|
177
|
+
lock(): void
|
|
178
|
+
unlock(pin: string): void
|
|
179
|
+
check(amount: string): { allowed: boolean; reason?: string }
|
|
180
|
+
recordSpend(amount: string): void
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Storage: ~/.canton-agent/safeguards.json
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Wallet Keystore
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
export class Keystore {
|
|
190
|
+
static async create(pin: string, path?: string): Promise<Keystore>
|
|
191
|
+
static async load(pin: string, path?: string): Promise<Keystore>
|
|
192
|
+
getCredentials(): { partyId: string; jwt: string }
|
|
193
|
+
readonly address: string; // Party ID
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Storage: ~/.canton-agent/wallet.key (AES-256-GCM encrypted)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"version": 2,
|
|
204
|
+
"network": "mainnet",
|
|
205
|
+
"ledgerUrl": "https://canton-node.example.com:7575",
|
|
206
|
+
"partyId": "Agent::1220abcdef...",
|
|
207
|
+
"userId": "ledger-api-user",
|
|
208
|
+
"keystorePath": "~/.canton-agent/wallet.key",
|
|
209
|
+
"traffic": {
|
|
210
|
+
"autoPurchase": true,
|
|
211
|
+
"minBalance": 1000,
|
|
212
|
+
"purchaseAmountCC": "5.0"
|
|
213
|
+
},
|
|
214
|
+
"safeguards": {
|
|
215
|
+
"txLimit": "100",
|
|
216
|
+
"dailyLimit": "1000"
|
|
217
|
+
},
|
|
218
|
+
"mpp": {
|
|
219
|
+
"gatewayUrl": "https://mpp.cayvox.io",
|
|
220
|
+
"maxAutoPayPrice": "1.00"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addAmounts,
|
|
3
|
+
compareAmounts,
|
|
4
|
+
isValidAmount,
|
|
5
|
+
subtractAmounts,
|
|
6
|
+
toCantonAmount
|
|
7
|
+
} from "./chunk-GSDB5FKZ.js";
|
|
8
|
+
export {
|
|
9
|
+
addAmounts,
|
|
10
|
+
compareAmounts,
|
|
11
|
+
isValidAmount,
|
|
12
|
+
subtractAmounts,
|
|
13
|
+
toCantonAmount
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=amount-L2SDLRZT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/canton/amount.ts
|
|
2
|
+
var AMOUNT_RE = /^\d+(\.\d+)?$/;
|
|
3
|
+
function isValidAmount(s) {
|
|
4
|
+
return AMOUNT_RE.test(s);
|
|
5
|
+
}
|
|
6
|
+
function parse(s) {
|
|
7
|
+
const dot = s.indexOf(".");
|
|
8
|
+
const intPart = dot === -1 ? s : s.slice(0, dot);
|
|
9
|
+
const fracPart = dot === -1 ? "" : s.slice(dot + 1);
|
|
10
|
+
return [
|
|
11
|
+
intPart.split("").map(Number),
|
|
12
|
+
fracPart.split("").map(Number)
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
function align(a, b) {
|
|
16
|
+
const [aInt, aFrac] = parse(a);
|
|
17
|
+
const [bInt, bFrac] = parse(b);
|
|
18
|
+
const fracLen = Math.max(aFrac.length, bFrac.length);
|
|
19
|
+
while (aFrac.length < fracLen) aFrac.push(0);
|
|
20
|
+
while (bFrac.length < fracLen) bFrac.push(0);
|
|
21
|
+
const intLen = Math.max(aInt.length, bInt.length);
|
|
22
|
+
while (aInt.length < intLen) aInt.unshift(0);
|
|
23
|
+
while (bInt.length < intLen) bInt.unshift(0);
|
|
24
|
+
return [aInt, aFrac, bInt, bFrac, fracLen];
|
|
25
|
+
}
|
|
26
|
+
function formatResult(intDigits, fracDigits) {
|
|
27
|
+
while (fracDigits.length > 0 && fracDigits[fracDigits.length - 1] === 0) {
|
|
28
|
+
fracDigits.pop();
|
|
29
|
+
}
|
|
30
|
+
while (intDigits.length > 1 && intDigits[0] === 0) {
|
|
31
|
+
intDigits.shift();
|
|
32
|
+
}
|
|
33
|
+
const intStr = intDigits.join("");
|
|
34
|
+
return fracDigits.length > 0 ? `${intStr}.${fracDigits.join("")}` : intStr;
|
|
35
|
+
}
|
|
36
|
+
function compareAmounts(a, b) {
|
|
37
|
+
const [aInt, aFrac, bInt, bFrac] = align(a, b);
|
|
38
|
+
for (let i = 0; i < aInt.length; i++) {
|
|
39
|
+
if (aInt[i] < bInt[i]) return -1;
|
|
40
|
+
if (aInt[i] > bInt[i]) return 1;
|
|
41
|
+
}
|
|
42
|
+
for (let i = 0; i < aFrac.length; i++) {
|
|
43
|
+
if (aFrac[i] < bFrac[i]) return -1;
|
|
44
|
+
if (aFrac[i] > bFrac[i]) return 1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
function addAmounts(a, b) {
|
|
49
|
+
const [aInt, aFrac, bInt, bFrac, fracLen] = align(a, b);
|
|
50
|
+
const aAll = [...aInt, ...aFrac];
|
|
51
|
+
const bAll = [...bInt, ...bFrac];
|
|
52
|
+
const result = new Array(aAll.length).fill(0);
|
|
53
|
+
let carry = 0;
|
|
54
|
+
for (let i = aAll.length - 1; i >= 0; i--) {
|
|
55
|
+
const sum = aAll[i] + bAll[i] + carry;
|
|
56
|
+
result[i] = sum % 10;
|
|
57
|
+
carry = Math.floor(sum / 10);
|
|
58
|
+
}
|
|
59
|
+
if (carry > 0) {
|
|
60
|
+
result.unshift(carry);
|
|
61
|
+
}
|
|
62
|
+
const intDigits = result.slice(0, result.length - fracLen);
|
|
63
|
+
const fracDigits = result.slice(result.length - fracLen);
|
|
64
|
+
return formatResult(
|
|
65
|
+
intDigits.length === 0 ? [0] : intDigits,
|
|
66
|
+
fracDigits
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function subtractAmounts(a, b) {
|
|
70
|
+
if (compareAmounts(a, b) < 0) {
|
|
71
|
+
throw new Error(`Cannot subtract: ${a} < ${b}`);
|
|
72
|
+
}
|
|
73
|
+
const [aInt, aFrac, bInt, bFrac, fracLen] = align(a, b);
|
|
74
|
+
const aAll = [...aInt, ...aFrac];
|
|
75
|
+
const bAll = [...bInt, ...bFrac];
|
|
76
|
+
const result = new Array(aAll.length).fill(0);
|
|
77
|
+
let borrow = 0;
|
|
78
|
+
for (let i = aAll.length - 1; i >= 0; i--) {
|
|
79
|
+
let diff = aAll[i] - bAll[i] - borrow;
|
|
80
|
+
if (diff < 0) {
|
|
81
|
+
diff += 10;
|
|
82
|
+
borrow = 1;
|
|
83
|
+
} else {
|
|
84
|
+
borrow = 0;
|
|
85
|
+
}
|
|
86
|
+
result[i] = diff;
|
|
87
|
+
}
|
|
88
|
+
const intDigits = result.slice(0, result.length - fracLen);
|
|
89
|
+
const fracDigits = result.slice(result.length - fracLen);
|
|
90
|
+
return formatResult(
|
|
91
|
+
intDigits.length === 0 ? [0] : intDigits,
|
|
92
|
+
fracDigits
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function toCantonAmount(s, decimals = 10) {
|
|
96
|
+
const dot = s.indexOf(".");
|
|
97
|
+
const intPart = dot === -1 ? s : s.slice(0, dot);
|
|
98
|
+
const fracPart = dot === -1 ? "" : s.slice(dot + 1);
|
|
99
|
+
const padded = fracPart.padEnd(decimals, "0").slice(0, decimals);
|
|
100
|
+
return `${intPart}.${padded}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
isValidAmount,
|
|
105
|
+
compareAmounts,
|
|
106
|
+
addAmounts,
|
|
107
|
+
subtractAmounts,
|
|
108
|
+
toCantonAmount
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=chunk-GSDB5FKZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/canton/amount.ts"],"sourcesContent":["/**\n * String-based decimal arithmetic for Canton amounts.\n *\n * Canton uses Numeric 10 (up to 10 decimal places). USDCx has 6 meaningful decimals.\n * ALL arithmetic is pure string manipulation — NO floating point.\n */\n\nconst AMOUNT_RE = /^\\d+(\\.\\d+)?$/;\n\n/**\n * Validate that a string is a valid non-negative decimal amount.\n */\nexport function isValidAmount(s: string): boolean {\n return AMOUNT_RE.test(s);\n}\n\n/**\n * Parse an amount string into integer and fractional digit arrays.\n * Returns [intDigits, fracDigits] where each is a number[].\n */\nfunction parse(s: string): [number[], number[]] {\n const dot = s.indexOf(\".\");\n const intPart = dot === -1 ? s : s.slice(0, dot);\n const fracPart = dot === -1 ? \"\" : s.slice(dot + 1);\n return [\n intPart.split(\"\").map(Number),\n fracPart.split(\"\").map(Number),\n ];\n}\n\n/**\n * Normalise two amount strings so their fractional parts have equal length.\n * Returns [aParts, bParts, fracLen].\n */\nfunction align(a: string, b: string): [number[], number[], number[], number[], number] {\n const [aInt, aFrac] = parse(a);\n const [bInt, bFrac] = parse(b);\n const fracLen = Math.max(aFrac.length, bFrac.length);\n while (aFrac.length < fracLen) aFrac.push(0);\n while (bFrac.length < fracLen) bFrac.push(0);\n // Also align integer parts to equal length by prepending zeros\n const intLen = Math.max(aInt.length, bInt.length);\n while (aInt.length < intLen) aInt.unshift(0);\n while (bInt.length < intLen) bInt.unshift(0);\n return [aInt, aFrac, bInt, bFrac, fracLen];\n}\n\n/**\n * Strip trailing zeros from fractional part and leading zeros from integer part.\n */\nfunction formatResult(intDigits: number[], fracDigits: number[]): string {\n // Remove trailing zeros from fraction\n while (fracDigits.length > 0 && fracDigits[fracDigits.length - 1] === 0) {\n fracDigits.pop();\n }\n // Remove leading zeros from integer (keep at least one)\n while (intDigits.length > 1 && intDigits[0] === 0) {\n intDigits.shift();\n }\n const intStr = intDigits.join(\"\");\n return fracDigits.length > 0 ? `${intStr}.${fracDigits.join(\"\")}` : intStr;\n}\n\n/**\n * Compare two decimal amount strings.\n * Returns -1 if a < b, 0 if a === b, 1 if a > b.\n */\nexport function compareAmounts(a: string, b: string): -1 | 0 | 1 {\n const [aInt, aFrac, bInt, bFrac] = align(a, b);\n\n // Compare integer parts digit by digit\n for (let i = 0; i < aInt.length; i++) {\n if (aInt[i] < bInt[i]) return -1;\n if (aInt[i] > bInt[i]) return 1;\n }\n\n // Compare fractional parts digit by digit\n for (let i = 0; i < aFrac.length; i++) {\n if (aFrac[i] < bFrac[i]) return -1;\n if (aFrac[i] > bFrac[i]) return 1;\n }\n\n return 0;\n}\n\n/**\n * Add two decimal amount strings. Returns the sum as a string.\n */\nexport function addAmounts(a: string, b: string): string {\n const [aInt, aFrac, bInt, bFrac, fracLen] = align(a, b);\n\n // Combine into single digit arrays (integer + fractional)\n const aAll = [...aInt, ...aFrac];\n const bAll = [...bInt, ...bFrac];\n\n const result: number[] = new Array(aAll.length).fill(0);\n let carry = 0;\n\n for (let i = aAll.length - 1; i >= 0; i--) {\n const sum = aAll[i] + bAll[i] + carry;\n result[i] = sum % 10;\n carry = Math.floor(sum / 10);\n }\n\n if (carry > 0) {\n result.unshift(carry);\n }\n\n // Split back into integer and fractional parts\n const intDigits = result.slice(0, result.length - fracLen);\n const fracDigits = result.slice(result.length - fracLen);\n\n return formatResult(\n intDigits.length === 0 ? [0] : intDigits,\n fracDigits,\n );\n}\n\n/**\n * Subtract b from a. Both must be valid amounts and a >= b.\n * Throws if a < b.\n */\nexport function subtractAmounts(a: string, b: string): string {\n if (compareAmounts(a, b) < 0) {\n throw new Error(`Cannot subtract: ${a} < ${b}`);\n }\n\n const [aInt, aFrac, bInt, bFrac, fracLen] = align(a, b);\n\n const aAll = [...aInt, ...aFrac];\n const bAll = [...bInt, ...bFrac];\n\n const result: number[] = new Array(aAll.length).fill(0);\n let borrow = 0;\n\n for (let i = aAll.length - 1; i >= 0; i--) {\n let diff = aAll[i] - bAll[i] - borrow;\n if (diff < 0) {\n diff += 10;\n borrow = 1;\n } else {\n borrow = 0;\n }\n result[i] = diff;\n }\n\n const intDigits = result.slice(0, result.length - fracLen);\n const fracDigits = result.slice(result.length - fracLen);\n\n return formatResult(\n intDigits.length === 0 ? [0] : intDigits,\n fracDigits,\n );\n}\n\n/**\n * Pad (or truncate) a decimal string to exactly N decimal places.\n * Default: 10 (Canton Numeric 10).\n */\nexport function toCantonAmount(s: string, decimals = 10): string {\n const dot = s.indexOf(\".\");\n const intPart = dot === -1 ? s : s.slice(0, dot);\n const fracPart = dot === -1 ? \"\" : s.slice(dot + 1);\n\n const padded = fracPart.padEnd(decimals, \"0\").slice(0, decimals);\n return `${intPart}.${padded}`;\n}\n"],"mappings":"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","names":[]}
|