Skip to content

Commit 6724a45

Browse files
committed
feat: update modules
1 parent 14a1a16 commit 6724a45

File tree

2 files changed

+248
-7
lines changed

2 files changed

+248
-7
lines changed

packages/evolution/src/sdk/client/Client.ts

Lines changed: 204 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { Data, type Effect } from "effect"
55

6+
import type * as Transaction from "../../core/Transaction.js"
67
import type * as Address from "../Address.js"
78
import type { ReadOnlyTransactionBuilder, ReadOnlyTransactionBuilderEffect } from "../builders/index.js"
89
import type * as Delegation from "../Delegation.js"
@@ -12,7 +13,7 @@ import type * as RewardAddress from "../RewardAddress.js"
1213
import type { EffectToPromiseAPI } from "../Type.js"
1314
import type * as UTxO from "../UTxO.js"
1415
// Type-only imports to avoid runtime circular dependency
15-
import type { ReadOnlyWallet, SigningWallet, SigningWalletEffect } from "../wallet/WalletNew.js"
16+
import type { ApiWallet,ReadOnlyWallet, SigningWallet, SigningWalletEffect, WalletApi } from "../wallet/WalletNew.js"
1617

1718
// ============================================================================
1819
// Error Types
@@ -29,6 +30,43 @@ export class ProviderError extends Data.TaggedError("ProviderError")<{
2930
cause?: unknown
3031
}> {}
3132

33+
/**
34+
* Error class for multi-provider failover operations.
35+
*
36+
* @since 2.0.0
37+
* @category errors
38+
*/
39+
export class MultiProviderError extends Data.TaggedError("MultiProviderError")<{
40+
message?: string
41+
cause?: unknown
42+
failedProviders?: ReadonlyArray<{
43+
provider: string
44+
error: unknown
45+
}>
46+
allProvidersFailed?: boolean
47+
}> {}
48+
49+
// ============================================================================
50+
// Provider Health Types
51+
// ============================================================================
52+
53+
export interface ProviderHealthStatus {
54+
readonly healthy: boolean
55+
readonly latency: number
56+
readonly lastCheck: Date
57+
readonly consecutiveFailures: number
58+
readonly lastError?: unknown
59+
}
60+
61+
export interface MultiProviderState {
62+
readonly currentProvider: number
63+
readonly providers: ReadonlyArray<{
64+
config: KupmiosProviderConfig | BlockfrostProviderConfig
65+
health: ProviderHealthStatus
66+
}>
67+
readonly failoverStrategy: "round-robin" | "priority" | "random"
68+
}
69+
3270
// ============================================================================
3371
// Shared Types
3472
// ============================================================================
@@ -63,6 +101,14 @@ export interface ClientEffect extends ReadOnlyClientEffect, SigningWalletEffect
63101

64102
export interface MinimalClientEffect {}
65103

104+
// ============================================================================
105+
// Wallet-Only Client (for API wallets without provider)
106+
// ============================================================================
107+
108+
export interface WalletAPIClientEffect extends SigningWalletEffect {
109+
readonly newTx: (utxos?: ReadonlyArray<UTxO.UTxO>) => ReadOnlyTransactionBuilderEffect
110+
}
111+
66112
// ============================================================================
67113
// Promise-based Client Interfaces
68114
// ============================================================================
@@ -92,15 +138,38 @@ export type SigningClient = EffectToPromiseAPI<ClientEffect> & {
92138

93139
export type Client = SigningClient
94140

141+
export type WalletAPIClient = EffectToPromiseAPI<WalletAPIClientEffect> & {
142+
readonly address: () => Promise<Address.Address>
143+
readonly rewardAddress: () => Promise<RewardAddress.RewardAddress | null>
144+
readonly newTx: (utxos?: ReadonlyArray<UTxO.UTxO>) => ReadOnlyTransactionBuilder
145+
readonly submitTx: (tx: Transaction.Transaction | string) => Promise<string>
146+
readonly wallet: ApiWallet
147+
readonly attachProvider: {
148+
(provider: Provider.Provider): SigningClient
149+
(config: ProviderConfig): SigningClient
150+
}
151+
readonly Effect: WalletAPIClientEffect
152+
}
153+
95154
export type ProviderOnlyClient = EffectToPromiseAPI<ProviderOnlyClientEffect> & {
96155
readonly attachWallet: {
97156
(wallet: SigningWallet): SigningClient
98157
(wallet: ReadOnlyWallet): ReadOnlyClient
99158
(config: SeedWalletConfig): SigningClient
100159
(config: ReadOnlyWalletConfig): ReadOnlyClient
160+
(config: ApiWalletConfig): SigningClient
161+
(api: WalletApi): SigningClient
101162
}
102163
readonly Effect: ProviderOnlyClientEffect
103164
readonly provider: Provider.Provider
165+
readonly isMultiProvider: boolean
166+
readonly getActiveProvider?: () => Provider.Provider
167+
readonly getProviderHealth?: () => Promise<ReadonlyArray<{
168+
provider: Provider.Provider
169+
healthy: boolean
170+
latency: number
171+
lastCheck: Date
172+
}>>
104173
}
105174

106175
export interface MinimalClient {
@@ -109,15 +178,23 @@ export interface MinimalClient {
109178
(provider: Provider.Provider): ProviderOnlyClient
110179
(config: ProviderConfig): ProviderOnlyClient
111180
}
181+
readonly attachMultiProvider: {
182+
(config: MultiProviderConfig): ProviderOnlyClient
183+
}
112184
readonly attach: {
113185
(provider: Provider.Provider, wallet: SigningWallet): SigningClient
114186
(provider: Provider.Provider, wallet: ReadOnlyWallet): ReadOnlyClient
115187
(provider: Provider.Provider, wallet: SeedWalletConfig): SigningClient
116188
(provider: Provider.Provider, wallet: ReadOnlyWalletConfig): ReadOnlyClient
189+
(provider: Provider.Provider, wallet: ApiWalletConfig): SigningClient
117190
(config: ProviderConfig, wallet: SigningWallet): SigningClient
118191
(config: ProviderConfig, wallet: ReadOnlyWallet): ReadOnlyClient
119192
(config: ProviderConfig, wallet: SeedWalletConfig): SigningClient
120193
(config: ProviderConfig, wallet: ReadOnlyWalletConfig): ReadOnlyClient
194+
(config: ProviderConfig, wallet: ApiWalletConfig): SigningClient
195+
// API wallet can work without a separate provider (uses CIP-30 submitTx)
196+
(wallet: ApiWalletConfig): WalletAPIClient
197+
(api: WalletApi): WalletAPIClient
121198
}
122199
readonly Effect: MinimalClientEffect
123200
}
@@ -134,15 +211,33 @@ export interface KupmiosProviderConfig {
134211
readonly apiKey: string
135212
readonly ogmiosUrl?: string
136213
readonly kupoUrl?: string
214+
readonly priority?: number
137215
}
138216

139217
export interface BlockfrostProviderConfig {
140218
readonly type: "blockfrost"
141219
readonly apiKey: string
142220
readonly url?: string
221+
readonly priority?: number
222+
}
223+
224+
export interface MultiProviderConfig {
225+
readonly type: "multi"
226+
readonly providers: ReadonlyArray<KupmiosProviderConfig | BlockfrostProviderConfig>
227+
readonly failoverStrategy?: "round-robin" | "priority" | "random"
228+
readonly healthCheck?: {
229+
readonly enabled?: boolean
230+
readonly intervalMs?: number
231+
readonly timeoutMs?: number
232+
}
233+
readonly retryConfig?: {
234+
readonly maxRetries?: number
235+
readonly retryDelayMs?: number
236+
readonly backoffMultiplier?: number
237+
}
143238
}
144239

145-
export type ProviderConfig = KupmiosProviderConfig | BlockfrostProviderConfig
240+
export type ProviderConfig = KupmiosProviderConfig | BlockfrostProviderConfig | MultiProviderConfig
146241

147242
// Wallet Configs
148243
export interface SeedWalletConfig {
@@ -159,7 +254,15 @@ export interface ReadOnlyWalletConfig {
159254
readonly rewardAddress?: string
160255
}
161256

162-
export type WalletConfig = SeedWalletConfig | ReadOnlyWalletConfig
257+
export interface ApiWalletConfig {
258+
readonly type: "api"
259+
readonly api: WalletApi // CIP-30 wallet API interface
260+
// API wallets handle submission internally - no provider needed for transactions
261+
// Optional provider only for enhanced blockchain queries if needed
262+
readonly provider?: ProviderConfig
263+
}
264+
265+
export type WalletConfig = SeedWalletConfig | ReadOnlyWalletConfig | ApiWalletConfig
163266

164267
export type CreateClientConfig =
165268
| { network: NetworkId }
@@ -178,17 +281,112 @@ export declare function createClient(config: { network: NetworkId }): MinimalCli
178281
export declare function createClient(config: { network: NetworkId; provider: ProviderConfig }): ProviderOnlyClient
179282
export declare function createClient(config: { network: NetworkId; provider: ProviderConfig; wallet: SeedWalletConfig }): SigningClient
180283
export declare function createClient(config: { network: NetworkId; provider: ProviderConfig; wallet: ReadOnlyWalletConfig }): ReadOnlyClient
284+
export declare function createClient(config: { network: NetworkId; provider: ProviderConfig; wallet: ApiWalletConfig }): SigningClient
285+
export declare function createClient(config: { network: NetworkId; wallet: ApiWalletConfig }): WalletAPIClient // API wallet without separate provider
181286
export declare function createClient(
182287
config?: CreateClientConfig
183-
): MinimalClient | ProviderOnlyClient | SigningClient | ReadOnlyClient
288+
): MinimalClient | ProviderOnlyClient | SigningClient | ReadOnlyClient | WalletAPIClient
184289

185290
// Helper factory declarations (not yet implemented in this module)
186291
export declare function providerFromConfig(config: ProviderConfig, network: NetworkId): Provider.Provider
292+
export declare function multiProviderFromConfig(config: MultiProviderConfig, network: NetworkId): Provider.Provider
187293
export declare function seedWalletFromConfig(config: SeedWalletConfig, network: NetworkId): SigningWallet
188294
export declare function readOnlyWalletFromConfig(config: ReadOnlyWalletConfig, network: NetworkId): ReadOnlyWallet
295+
export declare function apiWalletFromConfig(config: ApiWalletConfig, network: NetworkId): ApiWallet
296+
export declare function walletFromCip30Api(api: WalletApi): ApiWallet
189297

190298
// Example (non-executable) usage:
191299
const minimalClient = createClient()
192300
const providerOnlyClient = minimalClient.attachProvider({ type: "blockfrost", apiKey: "xxx" })
193-
const readOnlyClient = providerOnlyClient.attachWallet({ type: "read-only", address: "test" })
194-
const signingClient = providerOnlyClient.attachWallet({ type: "seed", mnemonic: "..." })
301+
302+
// API wallet client upgrade path:
303+
const apiWalletClient = createClient({ network: "mainnet", wallet: { type: "api", api: {} as WalletApi } })
304+
// apiWalletClient: WalletAPIClient (can sign/submit, but no blockchain queries)
305+
306+
const _fullSigningClient = apiWalletClient.attachProvider({ type: "blockfrost", apiKey: "xxx" })
307+
// _fullSigningClient: SigningClient (full capabilities: query + sign + submit)
308+
const _readOnlyClient = providerOnlyClient.attachWallet({ type: "read-only", address: "test" })
309+
const _signingClient = providerOnlyClient.attachWallet({ type: "seed", mnemonic: "..." })
310+
311+
// Multi-provider examples:
312+
const _multiProviderClient = createClient({
313+
network: "mainnet",
314+
provider: {
315+
type: "multi",
316+
providers: [
317+
{
318+
type: "kupmios",
319+
apiKey: "primary-key",
320+
priority: 1
321+
},
322+
{
323+
type: "blockfrost",
324+
apiKey: "fallback-key",
325+
priority: 2
326+
}
327+
],
328+
failoverStrategy: "priority",
329+
healthCheck: {
330+
enabled: true,
331+
intervalMs: 30000,
332+
timeoutMs: 5000
333+
},
334+
retryConfig: {
335+
maxRetries: 3,
336+
retryDelayMs: 1000,
337+
backoffMultiplier: 2
338+
}
339+
}
340+
})
341+
342+
const _roundRobinClient = createClient({
343+
network: "mainnet",
344+
provider: {
345+
type: "multi",
346+
providers: [
347+
{ type: "kupmios", apiKey: "key1" },
348+
{ type: "kupmios", apiKey: "key2" },
349+
{ type: "blockfrost", apiKey: "key3" }
350+
],
351+
failoverStrategy: "round-robin"
352+
}
353+
})
354+
355+
// Advanced multi-provider with wallet attachment:
356+
const _fullClient = createClient({
357+
network: "mainnet",
358+
provider: {
359+
type: "multi",
360+
providers: [
361+
{
362+
type: "kupmios",
363+
apiKey: "primary-key",
364+
ogmiosUrl: "wss://ogmios.example.com",
365+
kupoUrl: "https://kupo.example.com",
366+
priority: 1
367+
},
368+
{
369+
type: "blockfrost",
370+
apiKey: "backup-key",
371+
url: "https://blockfrost.example.com",
372+
priority: 2
373+
}
374+
],
375+
failoverStrategy: "priority",
376+
healthCheck: {
377+
enabled: true,
378+
intervalMs: 15000, // Check every 15 seconds
379+
timeoutMs: 3000 // 3 second timeout
380+
},
381+
retryConfig: {
382+
maxRetries: 2,
383+
retryDelayMs: 500,
384+
backoffMultiplier: 1.5
385+
}
386+
},
387+
wallet: {
388+
type: "seed",
389+
mnemonic: "abandon abandon abandon...",
390+
accountIndex: 0
391+
}
392+
})

packages/evolution/src/sdk/wallet/WalletNew.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface ReadOnlyWalletEffect {
5656

5757
export interface ReadOnlyWallet extends EffectToPromiseAPI<ReadOnlyWalletEffect> {
5858
readonly Effect: ReadOnlyWalletEffect
59+
readonly type: "read-only" // Read-only wallet
5960
}
6061

6162
/**
@@ -83,6 +84,48 @@ export interface SigningWalletEffect extends ReadOnlyWalletEffect {
8384

8485
export interface SigningWallet extends EffectToPromiseAPI<SigningWalletEffect> {
8586
readonly Effect: SigningWalletEffect
87+
readonly type: "signing" // Local signing wallet (seed/private key)
88+
}
89+
90+
// ============================================================================
91+
// API Wallet Types (CIP-30 Compatible)
92+
// ============================================================================
93+
94+
/**
95+
* API Wallet Effect interface for CIP-30 compatible wallets.
96+
* API wallets handle both signing and submission through the wallet extension,
97+
* eliminating the need for a separate provider in browser environments.
98+
*
99+
* @since 2.0.0
100+
* @category interfaces
101+
*/
102+
export interface ApiWalletEffect extends ReadOnlyWalletEffect {
103+
readonly signTx: (
104+
tx: Transaction.Transaction | string,
105+
context?: { utxos?: ReadonlyArray<UTxO.UTxO> }
106+
) => Effect.Effect<TransactionWitnessSet.TransactionWitnessSet, WalletError>
107+
readonly signMessage: (
108+
address: Address.Address | RewardAddress.RewardAddress,
109+
payload: Payload
110+
) => Effect.Effect<SignedMessage, WalletError>
111+
/**
112+
* Submit transaction directly through the wallet API.
113+
* API wallets can submit without requiring a separate provider.
114+
*/
115+
readonly submitTx: (tx: Transaction.Transaction | string) => Effect.Effect<string, WalletError>
116+
}
117+
118+
/**
119+
* API Wallet interface for CIP-30 compatible wallets.
120+
* These wallets handle signing and submission internally through the browser extension.
121+
*
122+
* @since 2.0.0
123+
* @category interfaces
124+
*/
125+
export interface ApiWallet extends EffectToPromiseAPI<ApiWalletEffect> {
126+
readonly Effect: ApiWalletEffect
127+
readonly api: WalletApi
128+
readonly type: "api" // CIP-30 API wallet
86129
}
87130

88131
// Transaction builder interfaces moved to sdk/builders module (Phase 1)
@@ -157,7 +200,7 @@ export declare function makeWalletFromPrivateKey(
157200
privateKeyBech32: string
158201
): SigningWallet
159202

160-
export declare function makeWalletFromAPI(api: WalletApi): SigningWallet
203+
export declare function makeWalletFromAPI(api: WalletApi): ApiWallet
161204

162205
export declare function makeWalletFromAddress(
163206
network: Network,

0 commit comments

Comments
 (0)