|
| 1 | +# Evolution SDK Client Module |
| 2 | + |
| 3 | +A specification for the client architecture and behavior. |
| 4 | + |
| 5 | +## Quick Overview |
| 6 | + |
| 7 | +The Evolution SDK provides different types of clients that you can progressively enhance: |
| 8 | + |
| 9 | +```mermaid |
| 10 | +graph TD |
| 11 | + A[MinimalClient] -->|Add Provider| B[ProviderOnlyClient] |
| 12 | + A -->|Add Signing Wallet| C[SigningWalletClient] |
| 13 | + A -->|Add ReadOnly Wallet| D[ReadOnlyWalletClient] |
| 14 | + A -->|Add API Wallet| E[ApiWalletClient] |
| 15 | + B -->|Add Signing Wallet| F[SigningClient] |
| 16 | + B -->|Add ReadOnly Wallet| G[ReadOnlyClient] |
| 17 | + C -->|Add Provider| F |
| 18 | + D -->|Add Provider| G |
| 19 | + E -->|Add Provider| F |
| 20 | + |
| 21 | + classDef minimal fill:#3b82f6,stroke:#1e3a8a,stroke-width:3px,color:#ffffff,font-weight:bold,font-size:14px |
| 22 | + classDef provider fill:#8b5cf6,stroke:#4c1d95,stroke-width:3px,color:#ffffff,font-weight:bold,font-size:14px |
| 23 | + classDef signingWallet fill:#f59e0b,stroke:#92400e,stroke-width:3px,color:#ffffff,font-weight:bold,font-size:14px |
| 24 | + classDef readOnlyWallet fill:#10b981,stroke:#065f46,stroke-width:3px,color:#ffffff,font-weight:bold,font-size:14px |
| 25 | + classDef apiWallet fill:#f97316,stroke:#9a3412,stroke-width:3px,color:#ffffff,font-weight:bold,font-size:14px |
| 26 | + classDef readOnlyClient fill:#06b6d4,stroke:#0e7490,stroke-width:3px,color:#ffffff,font-weight:bold,font-size:14px |
| 27 | + classDef signingClient fill:#ef4444,stroke:#991b1b,stroke-width:3px,color:#ffffff,font-weight:bold,font-size:14px |
| 28 | + |
| 29 | + class A minimal |
| 30 | + class B provider |
| 31 | + class C signingWallet |
| 32 | + class D readOnlyWallet |
| 33 | + class E apiWallet |
| 34 | + class F signingClient |
| 35 | + class G readOnlyClient |
| 36 | +``` |
| 37 | + |
| 38 | +## Client Types |
| 39 | + |
| 40 | +| Client | Can Query Blockchain | Can Sign | Can Submit | Use Case | |
| 41 | +|--------|---------------------|----------|------------|----------| |
| 42 | +| **MinimalClient** | ❌ | ❌ | ❌ | Starting point | |
| 43 | +| **ProviderOnlyClient** | ✅ | ❌ | ✅ | Read blockchain data | |
| 44 | +| **SigningWalletClient** | ❌ | ✅ | ❌ | Sign-only (seed/private key) | |
| 45 | +| **ReadOnlyWalletClient** | ❌ | ❌ | ❌ | Wallet-only (address monitoring) | |
| 46 | +| **ApiWalletClient** | ❌ | ✅ | ✅ | Browser wallets (CIP-30) | |
| 47 | +| **ReadOnlyClient** | ✅ | ❌ | ✅ | Monitor addresses | |
| 48 | +| **SigningClient** | ✅ | ✅ | ✅ | Full functionality | |
| 49 | + |
| 50 | +> **Type Safety**: Separate interfaces ensure compile-time guarantees about submission capabilities. |
| 51 | +
|
| 52 | +## Detailed Capabilities Matrix |
| 53 | + |
| 54 | +### Core Methods Available |
| 55 | + |
| 56 | +| Method/Capability | MinimalClient | ProviderOnlyClient | SigningWalletClient | ReadOnlyWalletClient | ApiWalletClient | ReadOnlyClient | SigningClient | |
| 57 | +|-------------------|---------------|--------------------|---------------------|----------------------|-----------------|----------------|---------------| |
| 58 | +| **Network Access** | |
| 59 | +| `networkId` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| 60 | +| **Provider Operations** | |
| 61 | +| `getProtocolParameters()` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 62 | +| `getUtxos(address)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 63 | +| `getUtxosWithUnit(address, unit)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 64 | +| `getUtxoByUnit(unit)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 65 | +| `getUtxosByOutRef(outRefs)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 66 | +| `getDelegation(rewardAddress)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 67 | +| `getDatum(datumHash)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 68 | +| `awaitTx(txHash)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 69 | +| `evaluateTx(tx)` | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 70 | +| `submitTx(tx)` | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | |
| 71 | +| **Wallet Operations** | |
| 72 | +| `address()` | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| 73 | +| `rewardAddress()` | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| 74 | +| `getWalletUtxos()` | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 75 | +| `getWalletDelegation()` | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 76 | +| `signTx(tx)` | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | |
| 77 | +| `signMessage(address, payload)` | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | |
| 78 | +| **Transaction Building** | |
| 79 | +| `newTx()` | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | |
| 80 | +| **Client Composition** | |
| 81 | +| `attachProvider()` | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | |
| 82 | +| `attachWallet()` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | |
| 83 | +| `attach(provider, wallet)` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | |
| 84 | + |
| 85 | +### Transaction Builder Capabilities |
| 86 | + |
| 87 | +| Builder Method | ReadOnlyClient | SigningClient | Notes | |
| 88 | +|----------------|----------------|---------------|--------| |
| 89 | +| `build()` | ✅ → `Transaction` | ✅ → `SignBuilder` | ReadOnlyClient returns unsigned transaction, SigningClient returns builder with signing capabilities | |
| 90 | + |
| 91 | +> **Note**: Transaction building requires protocol parameters from a provider. Only `ReadOnlyClient` and `SigningClient` have provider access and can build transactions. `ApiWalletClient` cannot build transactions directly - it must be upgraded to `SigningClient` by attaching a provider first. |
| 92 | +
|
| 93 | +### Provider Support |
| 94 | + |
| 95 | +| Provider Type | Description | Supported Operations | |
| 96 | +|---------------|-------------|---------------------| |
| 97 | +| **Blockfrost** | API-based provider | All provider operations | |
| 98 | +| **Kupmios** | Kupo + Ogmios | All provider operations | |
| 99 | +| **Maestro** | Maestro API | All provider operations | |
| 100 | +| **Koios** | Koios API | All provider operations | |
| 101 | +| **Multi-Provider** | Failover support | All provider operations with redundancy (see [Provider Failover Specification](./provider-failover.md)) | |
| 102 | + |
| 103 | +### Wallet Support |
| 104 | + |
| 105 | +| Wallet Type | Client Types | Description | Capabilities | |
| 106 | +|-------------|-------------|-------------|--------------| |
| 107 | +| **Seed Wallet** | SigningWalletClient, SigningClient | HD wallet from mnemonic | Sign only (no submit without provider) | |
| 108 | +| **Private Key** | SigningWalletClient, SigningClient | Single key wallet | Sign only (no submit without provider) | |
| 109 | +| **Read-Only** | ReadOnlyWalletClient, ReadOnlyClient | Address monitoring | Query only, no signing | |
| 110 | +| **API Wallet (CIP-30)** | ApiWalletClient, SigningClient | Browser extension | Sign + submit via extension | |
| 111 | + |
| 112 | +### Error Handling |
| 113 | + |
| 114 | +| Client Type | Error Types | Effect Support | |
| 115 | +|-------------|-------------|----------------| |
| 116 | +| All clients | `ProviderError`, `WalletError` | ✅ Retry, timeout, fallback | |
| 117 | +| Multi-Provider | `MultiProviderError` | ✅ Automatic failover | |
| 118 | +| Transaction Builder | `TransactionBuilderError` | ✅ Validation errors | |
| 119 | + |
| 120 | +### Upgrade Paths |
| 121 | + |
| 122 | +#### Creation Methods |
| 123 | + |
| 124 | +**Progressive Enhancement (starting from MinimalClient):** |
| 125 | +- `createClient()` → `MinimalClient` → `attachProvider()` → `attachWallet()` |
| 126 | + |
| 127 | +**Direct Creation (bypassing MinimalClient):** |
| 128 | +- `createClient({ network, provider })` → `ProviderOnlyClient` |
| 129 | +- `createClient({ network, wallet: seedWallet })` → `SigningWalletClient` |
| 130 | +- `createClient({ network, wallet: apiWallet })` → `ApiWalletClient` |
| 131 | +- `createClient({ network, provider, wallet })` → `ReadOnlyClient` or `SigningClient` |
| 132 | + |
| 133 | +## Creating Clients |
| 134 | + |
| 135 | +### Simple Creation |
| 136 | +```typescript |
| 137 | +// Start with minimal client |
| 138 | +const client = createClient() |
| 139 | + |
| 140 | +// Add provider for blockchain access |
| 141 | +const providerClient = client.attachProvider({ |
| 142 | + type: "blockfrost", |
| 143 | + apiKey: "your-key" |
| 144 | +}) |
| 145 | + |
| 146 | +// Add wallet for signing |
| 147 | +const signingClient = providerClient.attachWallet({ |
| 148 | + type: "seed", |
| 149 | + mnemonic: "your mnemonic" |
| 150 | +}) |
| 151 | +``` |
| 152 | + |
| 153 | +### Direct Creation |
| 154 | +```typescript |
| 155 | +// Create fully configured client directly |
| 156 | +const client = createClient({ |
| 157 | + network: "mainnet", |
| 158 | + provider: { type: "blockfrost", apiKey: "your-key" }, |
| 159 | + wallet: { type: "seed", mnemonic: "your mnemonic" } |
| 160 | +}) |
| 161 | +``` |
| 162 | + |
| 163 | +### Browser Wallet (CIP-30) |
| 164 | +```typescript |
| 165 | +// API wallet without provider (limited) |
| 166 | +const apiClient = createClient({ |
| 167 | + network: "mainnet", |
| 168 | + wallet: { type: "api", api: window.cardano.nami } |
| 169 | +}) |
| 170 | + |
| 171 | +// Upgrade to full client by adding provider |
| 172 | +const fullClient = apiClient.attachProvider({ |
| 173 | + type: "blockfrost", |
| 174 | + apiKey: "your-key" |
| 175 | +}) |
| 176 | +``` |
| 177 | + |
| 178 | +## Architecture |
| 179 | + |
| 180 | +The SDK uses Effect-TS for complex operations and provides Promise APIs for convenience. See the [Effect-Promise Architecture Guide](./effect-promise-architecture.md) for detailed information. |
| 181 | + |
| 182 | +### Two Ways to Use |
| 183 | + |
| 184 | +**Simple (Promise API):** |
| 185 | +```typescript |
| 186 | +// Familiar async/await |
| 187 | +const result = await client.signTx(transaction) |
| 188 | +``` |
| 189 | + |
| 190 | +**Advanced (Effect API):** |
| 191 | +```typescript |
| 192 | +// When you need retries, timeouts, etc. |
| 193 | +const program = client.Effect.signTx(transaction).pipe( |
| 194 | + Effect.retry({ times: 3 }), |
| 195 | + Effect.timeout(30000) |
| 196 | +) |
| 197 | +const result = await Effect.runPromise(program) |
| 198 | +``` |
| 199 | + |
| 200 | +## Multi-Provider Support |
| 201 | + |
| 202 | +For production apps, use multiple providers for reliability. See the [Provider Failover Specification](./provider-failover.md) for detailed failover strategies and error handling. |
| 203 | + |
| 204 | +```typescript |
| 205 | +const client = createClient({ |
| 206 | + network: "mainnet", |
| 207 | + provider: { |
| 208 | + type: "multi", |
| 209 | + strategy: "priority", // try providers in order |
| 210 | + providers: [ |
| 211 | + { |
| 212 | + type: "kupmios", |
| 213 | + kupoUrl: "wss://ogmios.example.com", |
| 214 | + ogmiosUrl: "https://kupo.example.com", |
| 215 | + retryPolicy: { |
| 216 | + maxRetries: 3, |
| 217 | + retryDelayMs: 1000, |
| 218 | + backoffMultiplier: 2, |
| 219 | + maxRetryDelayMs: 30000 |
| 220 | + } |
| 221 | + }, |
| 222 | + { |
| 223 | + type: "blockfrost", |
| 224 | + apiKey: "backup-key", |
| 225 | + baseUrl: "https://cardano-mainnet.blockfrost.io/api/v0", |
| 226 | + retryPolicy: { |
| 227 | + maxRetries: 2, |
| 228 | + retryDelayMs: 500, |
| 229 | + backoffMultiplier: 1.5, |
| 230 | + maxRetryDelayMs: 10000 |
| 231 | + } |
| 232 | + } |
| 233 | + ] |
| 234 | + }, |
| 235 | + wallet: { type: "seed", mnemonic: "your mnemonic" } |
| 236 | +}) |
| 237 | +``` |
| 238 | + |
| 239 | +## Common Patterns |
| 240 | + |
| 241 | +### Signing-Only Wallet (Seed/Private Key) |
| 242 | +```typescript |
| 243 | +// Create signing wallet client for offline signing |
| 244 | +const signingWallet = createClient({ |
| 245 | + network: "mainnet", |
| 246 | + wallet: { type: "seed", mnemonic: "your mnemonic" } |
| 247 | +}) |
| 248 | + |
| 249 | +// Get wallet address |
| 250 | +const address = await signingWallet.address() |
| 251 | + |
| 252 | +// Sign a transaction that was built elsewhere |
| 253 | +const signedTx = await signingWallet.signTx(preBuiltTransaction) |
| 254 | + |
| 255 | +// Sign a message |
| 256 | +const signature = await signingWallet.signMessage(address, "Hello World") |
| 257 | + |
| 258 | +// ❌ Cannot submit - no submitTx method available |
| 259 | +// signingWallet.submitTx() // TypeScript error! |
| 260 | +``` |
| 261 | + |
| 262 | +### API Wallet (CIP-30) |
| 263 | +```typescript |
| 264 | +// Create API wallet client for browser wallet |
| 265 | +const apiWallet = createClient({ |
| 266 | + network: "mainnet", |
| 267 | + wallet: { type: "api", api: window.cardano.nami } |
| 268 | +}) |
| 269 | + |
| 270 | +// Can sign AND submit |
| 271 | +const signedTx = await apiWallet.signTx(preBuiltTransaction) |
| 272 | +const txId = await apiWallet.submitTx(signedTx) // ✅ Available for API wallets |
| 273 | +``` |
| 274 | + |
| 275 | +### Read-Only Wallet (Address Only) |
| 276 | +```typescript |
| 277 | +// Create read-only wallet client for address-only operations |
| 278 | +const readOnlyWallet = createClient({ |
| 279 | + network: "mainnet", |
| 280 | + wallet: { type: "read-only", address: "addr1..." } |
| 281 | +}) |
| 282 | + |
| 283 | +// Get wallet address and reward address |
| 284 | +const address = await readOnlyWallet.address() |
| 285 | +const rewardAddress = await readOnlyWallet.rewardAddress() |
| 286 | + |
| 287 | +// ❌ Cannot query blockchain - no provider |
| 288 | +// readOnlyWallet.getWalletUtxos() // TypeScript error! |
| 289 | + |
| 290 | +// ❌ Cannot sign - read-only wallet |
| 291 | +// readOnlyWallet.signTx() // TypeScript error! |
| 292 | + |
| 293 | +// ❌ Cannot submit - no provider |
| 294 | +// readOnlyWallet.submitTx() // TypeScript error! |
| 295 | + |
| 296 | +// ✅ Can upgrade to ReadOnlyClient by attaching provider |
| 297 | +const readOnlyClient = readOnlyWallet.attachProvider({ |
| 298 | + type: "blockfrost", |
| 299 | + apiKey: "your-key" |
| 300 | +}) |
| 301 | + |
| 302 | +// Now can query blockchain with the address |
| 303 | +const utxos = await readOnlyClient.getWalletUtxos() |
| 304 | +``` |
| 305 | + |
| 306 | +### Monitor an Address |
| 307 | +```typescript |
| 308 | +const client = createClient({ |
| 309 | + network: "mainnet", |
| 310 | + provider: { type: "blockfrost", apiKey: "key" }, |
| 311 | + wallet: { type: "read-only", address: "addr1..." } |
| 312 | +}) |
| 313 | + |
| 314 | +const utxos = await client.getWalletUtxos() |
| 315 | +``` |
| 316 | + |
| 317 | +### Browser dApp |
| 318 | +```typescript |
| 319 | +// Connect to user's wallet |
| 320 | +const client = createClient({ |
| 321 | + network: "mainnet", |
| 322 | + wallet: { type: "api", api: window.cardano.nami } |
| 323 | +}) |
| 324 | + |
| 325 | +// Sign and submit (no provider needed) |
| 326 | +const txId = await client.submitTx(transaction) |
| 327 | +``` |
| 328 | + |
| 329 | +### Server Application |
| 330 | +```typescript |
| 331 | +const client = createClient({ |
| 332 | + network: "mainnet", |
| 333 | + provider: { type: "kupmios", kupoUrl: "...", ogmiosUrl: "..." }, |
| 334 | + wallet: { type: "seed", mnemonic: process.env.MNEMONIC } |
| 335 | +}) |
| 336 | + |
| 337 | +const tx = await client.newTx() |
| 338 | + .payToAddress("addr1...", { lovelace: 1000000n }) |
| 339 | + .complete() |
| 340 | + |
| 341 | +const signed = await client.signTx(tx) |
| 342 | +const txId = await client.submitTx(signed) |
| 343 | +``` |
| 344 | + |
| 345 | +## Key Concepts |
| 346 | + |
| 347 | +- **MinimalClient**: Starting point, just knows about network |
| 348 | +- **Providers**: Connect to Cardano blockchain (Blockfrost, Kupmios, etc.) |
| 349 | +- **Wallets**: Handle signing (seed phrase, private key, or browser extension) |
| 350 | +- **API Wallets**: Browser extensions like Nami, Eternl (CIP-30 standard) |
| 351 | +- **Effect**: Advanced features like retries and timeouts |
| 352 | +- **Promise**: Simple async/await for basic usage |
0 commit comments