@sodax/sdk 1.5.7-beta → 2.0.0-rc.2

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.
Files changed (57) hide show
  1. package/README.md +91 -7
  2. package/ai-exported/AGENTS.md +99 -0
  3. package/ai-exported/integration/README.md +41 -0
  4. package/ai-exported/integration/ai-rules.md +75 -0
  5. package/ai-exported/integration/architecture.md +519 -0
  6. package/ai-exported/integration/chain-specifics.md +189 -0
  7. package/ai-exported/integration/features/README.md +19 -0
  8. package/ai-exported/integration/features/auxiliary-services.md +189 -0
  9. package/ai-exported/integration/features/bridge.md +136 -0
  10. package/ai-exported/integration/features/dex.md +182 -0
  11. package/ai-exported/integration/features/icx-bnusd-baln.md +181 -0
  12. package/ai-exported/integration/features/money-market.md +198 -0
  13. package/ai-exported/integration/features/staking.md +166 -0
  14. package/ai-exported/integration/features/swap.md +207 -0
  15. package/ai-exported/integration/quickstart.md +213 -0
  16. package/ai-exported/integration/recipes/README.md +21 -0
  17. package/ai-exported/integration/recipes/backend-server-init.md +69 -0
  18. package/ai-exported/integration/recipes/chain-key-narrowing.md +65 -0
  19. package/ai-exported/integration/recipes/gas-estimation.md +33 -0
  20. package/ai-exported/integration/recipes/initialize-sodax.md +53 -0
  21. package/ai-exported/integration/recipes/raw-tx-flow.md +71 -0
  22. package/ai-exported/integration/recipes/result-and-errors.md +104 -0
  23. package/ai-exported/integration/recipes/signed-tx-flow.md +46 -0
  24. package/ai-exported/integration/recipes/testing.md +101 -0
  25. package/ai-exported/integration/reference/README.md +18 -0
  26. package/ai-exported/integration/reference/chain-keys.md +67 -0
  27. package/ai-exported/integration/reference/error-codes.md +165 -0
  28. package/ai-exported/integration/reference/glossary.md +32 -0
  29. package/ai-exported/integration/reference/public-api.md +138 -0
  30. package/ai-exported/integration/reference/wallet-providers.md +62 -0
  31. package/ai-exported/migration/README.md +58 -0
  32. package/ai-exported/migration/ai-rules.md +80 -0
  33. package/ai-exported/migration/breaking-changes/architecture.md +342 -0
  34. package/ai-exported/migration/breaking-changes/result-and-errors.md +363 -0
  35. package/ai-exported/migration/breaking-changes/type-system.md +321 -0
  36. package/ai-exported/migration/checklist.md +61 -0
  37. package/ai-exported/migration/features/README.md +35 -0
  38. package/ai-exported/migration/features/auxiliary-services.md +156 -0
  39. package/ai-exported/migration/features/bridge.md +125 -0
  40. package/ai-exported/migration/features/dex.md +143 -0
  41. package/ai-exported/migration/features/icx-bnusd-baln.md +151 -0
  42. package/ai-exported/migration/features/money-market.md +214 -0
  43. package/ai-exported/migration/features/staking.md +138 -0
  44. package/ai-exported/migration/features/swap.md +198 -0
  45. package/ai-exported/migration/recipes.md +288 -0
  46. package/ai-exported/migration/reference/README.md +18 -0
  47. package/ai-exported/migration/reference/deleted-exports.md +126 -0
  48. package/ai-exported/migration/reference/error-code-crosswalk.md +104 -0
  49. package/ai-exported/migration/reference/return-shapes.md +49 -0
  50. package/ai-exported/migration/reference/sodax-config.md +52 -0
  51. package/dist/index.cjs +32076 -31544
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +8604 -7136
  54. package/dist/index.d.ts +8604 -7136
  55. package/dist/index.mjs +31893 -31402
  56. package/dist/index.mjs.map +1 -1
  57. package/package.json +20 -12
@@ -0,0 +1,363 @@
1
+ # Result and error model breaking changes — v1 → v2
2
+
3
+ The runtime contract of every async method changed. v1 threw on failure; v2 resolves `{ ok: false, error }`. v1 had per-module typed-error unions; v2 has a single canonical `SodaxError<C>` with a closed 13-code vocabulary.
4
+
5
+ This is the largest behavioral change in v2. A consumer that ignores it will compile against v2 (with help from the type system) but silently swallow failures — a v1-style `try { await sodax.<method>(...) } catch` does **not** catch SDK-level failures, because they don't throw.
6
+
7
+ Read after [`type-system.md`](type-system.md) and [`architecture.md`](architecture.md).
8
+
9
+ ## Section index
10
+
11
+ 1. [`Result<T>` — the new return contract](#1-resultt--the-new-return-contract)
12
+ 2. [`SodaxError<C>` — the canonical error class](#2-sodaxerrorc--the-canonical-error-class)
13
+ 3. [The 13-code vocabulary](#3-the-13-code-vocabulary)
14
+ 4. [Reference tables — moved](#4-reference-tables--moved) (v1 ↔ v2 code crosswalk and per-method return-shape diffs)
15
+ 5. [Carve-out: `BalnSwapService` still throws](#5-carve-out-balnswapservice-still-throws)
16
+ 6. [Migration patterns](#6-migration-patterns)
17
+
18
+ ---
19
+
20
+ ## 1. `Result<T>` — the new return contract
21
+
22
+ ### Shape
23
+
24
+ `Result<T, E = Error | unknown>` is defined in `@sodax/types` (re-exported from `@sodax/sdk`):
25
+
26
+ ```ts
27
+ type Result<T, E = Error | unknown> =
28
+ | { ok: true; value: T }
29
+ | { ok: false; error: E };
30
+ ```
31
+
32
+ Every async public method on every feature service in v2 returns `Promise<Result<T, SodaxError<C>>>`. There is no `throw` across a service boundary.
33
+
34
+ ### Where it applies
35
+
36
+ The **complete list** of services whose public methods return `Result<T>`:
37
+
38
+ - `SwapService` (every async method)
39
+ - `MoneyMarketService` (every async method)
40
+ - `BridgeService` (every async method)
41
+ - `StakingService` (every async method except `StakingLogic.*` static helpers — see § 5)
42
+ - `MigrationService` (every async method except `BalnSwapService` lock-management methods — see § 5)
43
+ - `DexService` / `ClService` / `AssetService` (every async method)
44
+ - `PartnerService`
45
+ - `RecoveryService`
46
+ - `BackendApiService`
47
+ - `SpokeService` (router-level helpers)
48
+ - `IConfigApi` implementations (every method)
49
+
50
+ Private helpers may still throw; the outer `try/catch` at each public method's boundary absorbs those and converts them to `{ ok: false, error }`.
51
+
52
+ ### Propagation pattern
53
+
54
+ The canonical pattern across the SDK:
55
+
56
+ ```ts
57
+ // Forward a sub-Result without re-wrapping.
58
+ // (TypeScript narrows the error union — narrower codes are structurally
59
+ // assignable to wider ones, so this typechecks even if `sub` returns a smaller
60
+ // code union than the outer method.)
61
+ const sub = await this.subOperation();
62
+ if (!sub.ok) return sub;
63
+
64
+ // Success
65
+ return { ok: true, value: /* … */ };
66
+
67
+ // Outer catch at every public method's boundary
68
+ catch (error) {
69
+ if (isMethodError(error)) return { ok: false, error };
70
+ return {
71
+ ok: false,
72
+ error: new SodaxError('EXECUTION_FAILED', '<short message>', {
73
+ feature: 'swap',
74
+ cause: error,
75
+ context: { action: 'createIntent', phase: 'execution' },
76
+ }),
77
+ };
78
+ }
79
+ ```
80
+
81
+ There is **no** `toResult` / `tryCatch` / `safeCall` helper. Explicit `try/catch` at each method boundary is the deliberate convention.
82
+
83
+ ### Branching
84
+
85
+ ```ts
86
+ const result = await sodax.swaps.createIntent({ params, raw: false, walletProvider });
87
+ if (!result.ok) {
88
+ if (isSodaxError(result.error)) {
89
+ if (result.error.code === 'RELAY_TIMEOUT') { /* retry */ }
90
+ if (result.error.code === 'INTENT_CREATION_FAILED') { /* show input error */ }
91
+ }
92
+ return;
93
+ }
94
+ const { tx, intent, relayData } = result.value;
95
+ ```
96
+
97
+ ### Migration mechanics
98
+
99
+ ```diff
100
+ - try {
101
+ - const result = await sodax.swaps.createIntent({ intentParams, spokeProvider });
102
+ - const [spokeTxHash, intent, relayData] = result;
103
+ - /* … */
104
+ - } catch (e) {
105
+ - if (e instanceof IntentError && e.code === 'CREATE_INTENT_FAILED') { /* … */ }
106
+ - }
107
+
108
+ + const result = await sodax.swaps.createIntent({ params, raw: false, walletProvider });
109
+ + if (!result.ok) {
110
+ + if (isSodaxError(result.error) && result.error.code === 'INTENT_CREATION_FAILED') { /* … */ }
111
+ + return;
112
+ + }
113
+ + const { tx: spokeTxHash, intent, relayData } = result.value; // object, not tuple — see ../reference/return-shapes.md
114
+ ```
115
+
116
+ ### Pitfall
117
+
118
+ A `try { await sodax.<method>(...) } catch` block in v2 only catches **exceptions** thrown from inside the call (e.g. a bug, a missing argument, a synchronous validation thrown from the wrapper). It does **not** catch SDK-level failures like `RELAY_TIMEOUT` — those resolve to `{ ok: false, error }` and skip your `catch` entirely. The legacy `try/catch` can stay as defense-in-depth, but you **must** also branch on `result.ok`.
119
+
120
+ ---
121
+
122
+ ## 2. `SodaxError<C>` — the canonical error class
123
+
124
+ ### Shape
125
+
126
+ ```ts
127
+ class SodaxError<C extends SodaxErrorCode = SodaxErrorCode> extends Error {
128
+ readonly code: C; // closed 13-code reason union
129
+ readonly feature: SodaxFeature; // 'swap' | 'moneyMarket' | 'bridge' | 'staking' | 'migration' | 'dex' | 'partner' | 'recovery'
130
+ readonly cause?: unknown;
131
+ readonly context?: SodaxErrorContext;
132
+
133
+ toJSON(): SodaxErrorJSON<C>; // canonical logger surface (Sentry/Pino/Datadog)
134
+ }
135
+ ```
136
+
137
+ ### Discrimination contract
138
+
139
+ The `(feature, code)` pair is the canonical discriminator. Loggers emit it as a tag pair; consumer switch statements branch on it. **Do not** string-match on `error.message` — messages are short prose and may change between releases. Codes are closed and stable.
140
+
141
+ ### `isSodaxError` over `instanceof`
142
+
143
+ ```ts
144
+ import { isSodaxError } from '@sodax/sdk';
145
+
146
+ if (isSodaxError(e)) {
147
+ // e: SodaxError<SodaxErrorCode>
148
+ }
149
+ ```
150
+
151
+ `isSodaxError` walks `e.name === 'SodaxError'` + a `code: string` + `feature: string` shape check. Prefer it over bare `instanceof SodaxError` — `instanceof` returns `false` when `@sodax/sdk` is loaded twice in the same bundle (a real-world hazard with monorepos and dual ESM/CJS, especially in apps with mixed package resolution).
152
+
153
+ ### `error.context`
154
+
155
+ A free-form metadata bag with reserved fields:
156
+
157
+ ```ts
158
+ type SodaxErrorContext = {
159
+ action?: string; // feature operation (e.g. 'supply', 'stake', 'migrateBaln')
160
+ phase?: SodaxPhase; // 'validate' | 'intentCreation' | 'verify' | 'submit' | 'relay' | 'destinationExecution' | 'execution' | 'postExecution' | 'approve' | 'allowanceCheck' | 'gasEstimation' | 'lookup'
161
+ srcChainKey?: string;
162
+ dstChainKey?: string;
163
+ relayCode?: 'SUBMIT_TX_FAILED' | 'RELAY_TIMEOUT' | 'RELAY_POLLING_FAILED' | 'UNKNOWN';
164
+ api?: 'solver' | 'backend';
165
+ method?: string; // names the read-only method on LOOKUP_FAILED
166
+ direction?: 'forward' | 'reverse'; // migration's bnUSD-only
167
+ field?: string; // VALIDATION_FAILED specifics
168
+ reason?: string;
169
+ [key: string]: unknown; // open at the index signature
170
+ };
171
+ ```
172
+
173
+ ### `error.toJSON()` for logging
174
+
175
+ `JSON.stringify(error)` invokes `toJSON()` automatically. The serializer:
176
+
177
+ - Coerces `bigint` to string anywhere in `context`.
178
+ - Walks `cause` chains up to depth 3.
179
+ - Stringifies `Date`, `Map`, `Set`, `Error`, and class instances safely.
180
+ - Handles cycles (depth-bounded at 5).
181
+
182
+ Consumer-side logging integration:
183
+
184
+ ```ts
185
+ // Sentry
186
+ Sentry.captureException(error, { tags: { feature: error.feature, code: error.code, action: error.context?.action } });
187
+
188
+ // Pino / Winston
189
+ logger.error({ err: error }, 'sodax operation failed');
190
+ ```
191
+
192
+ ---
193
+
194
+ ## 3. The 13-code vocabulary
195
+
196
+ | Code | Meaning | Common context fields |
197
+ |---|---|---|
198
+ | `VALIDATION_FAILED` | Pre-flight invariant tripped (input shape, unsupported chain, etc.). | `field`, `reason`, `phase: 'validate'` |
199
+ | `INTENT_CREATION_FAILED` | Building the intent / payload failed. | `phase: 'intentCreation'` |
200
+ | `EXECUTION_FAILED` | Orchestrator-level catch-all (post-relay business logic). | `action`, `phase: 'execution'` or `'postExecution'` |
201
+ | `TX_VERIFICATION_FAILED` | Spoke-side `verifyTxHash` returned false / threw. | `phase: 'verify'`, `srcChainKey` |
202
+ | `TX_SUBMIT_FAILED` | Spoke tx landed; relay POST submit failed. | `phase: 'submit'`, `relayCode: 'SUBMIT_TX_FAILED'` |
203
+ | `RELAY_TIMEOUT` | Destination packet didn't reach `executed` within timeout. | `phase: 'relay'`, `srcChainKey`, `dstChainKey`, `relayCode: 'RELAY_TIMEOUT'` |
204
+ | `RELAY_FAILED` | Relay polling outage / unrecognised relay error. | `phase: 'relay'`, `relayCode` |
205
+ | `APPROVE_FAILED` | Token approval call failed. | `phase: 'approve'` |
206
+ | `ALLOWANCE_CHECK_FAILED` | Reading on-chain allowance failed. (Distinct from `LOOKUP_FAILED` for retry semantics.) | `phase: 'allowanceCheck'` |
207
+ | `GAS_ESTIMATION_FAILED` | Gas estimation returned an error. (Distinct for retry semantics.) | `phase: 'gasEstimation'` |
208
+ | `LOOKUP_FAILED` | Read-only on-chain query / off-chain config fetch. | `method`, `phase: 'lookup'` |
209
+ | `EXTERNAL_API_ERROR` | Upstream API call failed (solver, backend). | `api: 'solver' \| 'backend'`, plus `solverCode`/`solverDetail` for solver |
210
+ | `UNKNOWN` | Last-resort catch in an outer `try`. Should be rare in production. | (none guaranteed) |
211
+
212
+ ### Per-method narrow unions
213
+
214
+ Every public method declares a `<MethodName>ErrorCode` narrow union built via `Extract<SodaxErrorCode, ...>`. Switch exhaustively over the narrow union when you know the method:
215
+
216
+ ```ts
217
+ type CreateSupplyIntentErrorCode = Extract<
218
+ SodaxErrorCode,
219
+ 'VALIDATION_FAILED' | 'INTENT_CREATION_FAILED' | 'UNKNOWN'
220
+ >;
221
+
222
+ // In the queryFn or call site:
223
+ const result = await sodax.moneyMarket.createSupplyIntent({ params, raw: true });
224
+ if (!result.ok) {
225
+ switch (result.error.code) {
226
+ case 'VALIDATION_FAILED': /* show input error */ break;
227
+ case 'INTENT_CREATION_FAILED': /* show "couldn't build supply intent" */ break;
228
+ case 'UNKNOWN': /* fallback */ break;
229
+ }
230
+ }
231
+ ```
232
+
233
+ The narrow unions are exported from each feature's `errors.ts` (e.g. `@sodax/sdk` re-exports `SupplyErrorCode`, `BorrowErrorCode`, `BridgeErrorCode`, `StakeErrorCode`, etc.). See [`../../integration/reference/`](../../integration/reference/) § "Error codes" for the full catalogue.
234
+
235
+ ### Read-method partition
236
+
237
+ `LOOKUP_FAILED` is the catch-all for read-only methods. Its partition lives on `error.context.method`:
238
+
239
+ ```ts
240
+ if (result.error.code === 'LOOKUP_FAILED') {
241
+ switch (result.error.context?.method) {
242
+ case 'getStakingInfo': /* … */ break;
243
+ case 'getBridgeableAmount': /* … */ break;
244
+ case 'getUnstakingInfoWithPenalty': /* … */ break;
245
+ }
246
+ }
247
+ ```
248
+
249
+ This avoids inflating the global code count (`getStakingInfoFailed`, `getBridgeableAmountFailed`, …) while keeping per-method retry semantics inspectable.
250
+
251
+ ---
252
+
253
+ ## 4. Reference tables — moved
254
+
255
+ The two reference-grade tables that used to live in this file (the **v1 ↔ v2 code crosswalk** and the **per-method return-shape diffs**) have moved into `../reference/`:
256
+
257
+ - [`../reference/error-code-crosswalk.md`](../reference/error-code-crosswalk.md) — every v1 module-error code → v2 `(feature, code, context)` mapping, per feature.
258
+ - [`../reference/return-shapes.md`](../reference/return-shapes.md) — per-method return-shape diffs (`CreateIntentResult` tuple → object, every cross-chain mutation now returns `TxHashPair`, `getBridgeableAmount` returns `BridgeLimit`, `getStakeRatio` returns a tuple, etc.).
259
+
260
+ The prose on shape semantics stays here; the lookup tables live in `../reference/`.
261
+
262
+ ## 5. Carve-out: `BalnSwapService` still throws
263
+
264
+ The lock-management methods on `BalnSwapService` (a sub-service of `MigrationService`) **do not** return `Result<T>` in v2. They keep the v1 throw-on-error contract:
265
+
266
+ - `BalnSwapService.claim()`
267
+ - `BalnSwapService.claimUnstaked()`
268
+ - `BalnSwapService.stake()`
269
+ - `BalnSwapService.unstake()`
270
+ - `BalnSwapService.cancelUnstake()`
271
+ - `BalnSwapService.getDetailedUserLocks()`
272
+
273
+ This is **technical debt**, marked for cleanup in a future release. Until then, your code must `try/catch` these specific calls. Every other public async method on every other service in v2 returns `Result<T>`.
274
+
275
+ ---
276
+
277
+ ## 6. Migration patterns
278
+
279
+ ### Convert one call site (the typical pattern)
280
+
281
+ ```diff
282
+ try {
283
+ - const result = await sodax.moneyMarket.supply({ params: supplyParams, spokeProvider });
284
+ - const txHash = result;
285
+ + const result = await sodax.moneyMarket.supply({ params: supplyParams, raw: false, walletProvider });
286
+ + if (!result.ok) {
287
+ + setError(getMmErrorText(result.error));
288
+ + return;
289
+ + }
290
+ + const { srcChainTxHash } = result.value;
291
+ + /* … */
292
+ } catch (e) {
293
+ /* keep as defense-in-depth net for unexpected throws */
294
+ }
295
+ ```
296
+
297
+ ### Adapt a `getXxxErrorText` helper
298
+
299
+ If your v1 code has a helper that branches on `error.code`, the minimal change is to map v2 shape onto the v1 shape at the boundary:
300
+
301
+ ```ts
302
+ // Module-scope helper
303
+ function adaptToV1Shape(error: unknown): { code?: string; message?: string; data?: { error?: unknown } } | null {
304
+ if (!error) return null;
305
+ if (isSodaxError(error)) {
306
+ return {
307
+ code: error.code,
308
+ message: error.message,
309
+ data: { error: error.cause },
310
+ };
311
+ }
312
+ if (error instanceof Error) return { code: error.message, message: error.message };
313
+ if (typeof error === 'object') return error as { code?: string; message?: string; data?: { error?: unknown } };
314
+ return null;
315
+ }
316
+
317
+ // Existing branches (sdkError.code === 'SUPPLY_FAILED') keep working — until you migrate
318
+ // them properly to (feature, code) tuples per ../reference/error-code-crosswalk.md.
319
+ ```
320
+
321
+ ### Use the per-feature guard factory for routing
322
+
323
+ ```ts
324
+ const isSwapError = isFeatureError('swap');
325
+ const isMmError = isFeatureError('moneyMarket');
326
+ const isBridgeError = isFeatureError('bridge');
327
+
328
+ if (!result.ok) {
329
+ if (isSwapError(result.error)) router.push('/swap-error');
330
+ else if (isMmError(result.error)) router.push('/loan-error');
331
+ else if (isBridgeError(result.error)) router.push('/bridge-error');
332
+ else router.push('/error');
333
+ }
334
+ ```
335
+
336
+ ### A `throwIfError` shim for incremental migration
337
+
338
+ If you can't refactor every call site in one pass:
339
+
340
+ ```ts
341
+ // Drop into a shared lib
342
+ export function throwIfError<T>(result: Result<T, unknown>): T {
343
+ if (!result.ok) throw result.error;
344
+ return result.value;
345
+ }
346
+
347
+ // Use at call sites where you haven't migrated the surrounding error handling yet:
348
+ const { tx, intent } = throwIfError(
349
+ await sodax.swaps.createIntent({ params, raw: false, walletProvider }),
350
+ );
351
+ ```
352
+
353
+ This is **transitional**. Once your error-handling tree is updated, remove `throwIfError` and branch on `result.ok` directly. See [`../recipes.md`](../recipes.md) § "Result adapter" for a fuller version.
354
+
355
+ ---
356
+
357
+ ## Cross-references
358
+
359
+ - Type-level renames (deleted error types, return-type renames): [`type-system.md`](type-system.md) §§ 6, 10.
360
+ - Architecture reshape (relay layer, mapRelayFailure, sodaxInvariant, isSodaxError): [`architecture.md`](architecture.md) §§ 3, 4.
361
+ - v2 design context (Result propagation, error model): [`../../integration/architecture.md`](../../integration/architecture.md) and [`../../integration/recipes/`](../../integration/recipes/) § "Result handling".
362
+ - Per-feature narrow code unions: [`../../integration/reference/`](../../integration/reference/) § "Error codes".
363
+ - Per-feature playbooks (with `getXxxErrorText` adapters in context): [`../features/`](../features/).
@@ -0,0 +1,321 @@
1
+ # Type-system breaking changes — v1 → v2
2
+
3
+ Every type-level rename and shape change introduced by v2. Fix these first — once your imports compile, every other migration step is tractable. The errors you'll see during this phase live in three buckets: **import resolution** (a symbol moved or was deleted), **field access** (a field renamed), and **shape mismatch** (a generic added a required parameter, a return type changed).
4
+
5
+ > v1 source the comparisons below cite: `github.com/icon-project/sodax-frontend` (branch `sdk-v1-main`), `packages/sdk` and `packages/types`. v2 source: this package's `src/`.
6
+
7
+ ## Section index
8
+
9
+ 1. [Chain identifiers](#1-chain-identifiers) — `*_MAINNET_CHAIN_ID` → `ChainKeys.*`. The single biggest mechanical migration.
10
+ 2. [`@sodax/types` package surface](#2-sodaxtypes-package-surface) — what to import, what got renamed, what got deleted.
11
+ 3. [Wallet-provider typing](#3-wallet-provider-typing) — `GetWalletProviderType<K>` and `WalletProviderSlot<K, Raw>` replace ad-hoc spoke-provider classes.
12
+ 4. [`Token` / `XToken` field renames](#4-token--xtoken-field-renames) — `xChainId` → `chainKey`; `Token` → `XToken`.
13
+ 5. [`RpcConfig` reshape](#5-rpcconfig-reshape) — now keyed by `ChainKey` values; chain-family-specific shapes for Bitcoin and Stellar.
14
+ 6. [`IConfigApi` Result-wrapping](#6-iconfigapi-result-wrapping) — every method returns `Promise<Result<T>>`.
15
+ 7. [Address-type rename](#7-address-type-rename) — `AddressType` → `BtcAddressType`.
16
+ 8. [Wallet-provider `chainType` discriminant](#8-wallet-provider-chaintype-discriminant) — every `I*WalletProvider` declares a literal field.
17
+ 9. [`ChainId` / `SpokeChainId` → `SpokeChainKey`](#9-chainid--spokechainid--spokechainkey) — type alias rename.
18
+ 10. [Deleted module-error types](#10-deleted-module-error-types) — covered structurally here, semantics in [`result-and-errors.md`](result-and-errors.md).
19
+
20
+ ---
21
+
22
+ ## 1. Chain identifiers
23
+
24
+ v1 exported one constant per chain (`SONIC_MAINNET_CHAIN_ID`, `ARBITRUM_MAINNET_CHAIN_ID`, …). v2 collapses them into a single `ChainKeys` object whose values are the same string union exposed as the `ChainKey` type. The constants and their old union (`SpokeChainId` / `ChainId`) are deleted.
25
+
26
+ ### Import migration
27
+
28
+ ```diff
29
+ - import {
30
+ - SONIC_MAINNET_CHAIN_ID,
31
+ - ARBITRUM_MAINNET_CHAIN_ID,
32
+ - AVALANCHE_MAINNET_CHAIN_ID,
33
+ - // ...
34
+ - } from '@sodax/types';
35
+ + import { ChainKeys } from '@sodax/sdk'; // re-exported from @sodax/types
36
+ ```
37
+
38
+ > Prefer importing `ChainKeys` from `@sodax/sdk` — see [§2](#2-sodaxtypes-package-surface). Importing from `@sodax/types` still works but adds an unnecessary peer dependency.
39
+
40
+ ### Full mapping table
41
+
42
+ | v1 constant | v2 `ChainKeys.*` | String value |
43
+ |---|---|---|
44
+ | `SONIC_MAINNET_CHAIN_ID` | `ChainKeys.SONIC_MAINNET` | `'sonic'` |
45
+ | `ARBITRUM_MAINNET_CHAIN_ID` | `ChainKeys.ARBITRUM_MAINNET` | `'0xa4b1.arbitrum'` |
46
+ | `AVALANCHE_MAINNET_CHAIN_ID` | `ChainKeys.AVALANCHE_MAINNET` | `'0xa86a.avax'` |
47
+ | `BASE_MAINNET_CHAIN_ID` | `ChainKeys.BASE_MAINNET` | `'0x2105.base'` |
48
+ | `BSC_MAINNET_CHAIN_ID` | `ChainKeys.BSC_MAINNET` | `'0x38.bsc'` |
49
+ | `ETHEREUM_MAINNET_CHAIN_ID` | `ChainKeys.ETHEREUM_MAINNET` | `'ethereum'` |
50
+ | `HYPEREVM_MAINNET_CHAIN_ID` | `ChainKeys.HYPEREVM_MAINNET` | `'hyper'` |
51
+ | `ICON_MAINNET_CHAIN_ID` | `ChainKeys.ICON_MAINNET` | `'0x1.icon'` |
52
+ | `INJECTIVE_MAINNET_CHAIN_ID` | `ChainKeys.INJECTIVE_MAINNET` | `'injective-1'` |
53
+ | `KAIA_MAINNET_CHAIN_ID` | `ChainKeys.KAIA_MAINNET` | `'0x2019.kaia'` |
54
+ | `LIGHTLINK_MAINNET_CHAIN_ID` | `ChainKeys.LIGHTLINK_MAINNET` | `'lightlink'` |
55
+ | `NEAR_MAINNET_CHAIN_ID` | `ChainKeys.NEAR_MAINNET` | `'near'` |
56
+ | `OPTIMISM_MAINNET_CHAIN_ID` | `ChainKeys.OPTIMISM_MAINNET` | `'0xa.optimism'` |
57
+ | `POLYGON_MAINNET_CHAIN_ID` | `ChainKeys.POLYGON_MAINNET` | `'0x89.polygon'` |
58
+ | `REDBELLY_MAINNET_CHAIN_ID` | `ChainKeys.REDBELLY_MAINNET` | `'redbelly'` |
59
+ | `SOLANA_MAINNET_CHAIN_ID` | `ChainKeys.SOLANA_MAINNET` | `'solana'` |
60
+ | `STACKS_MAINNET_CHAIN_ID` | `ChainKeys.STACKS_MAINNET` | `'stacks'` |
61
+ | `STELLAR_MAINNET_CHAIN_ID` | `ChainKeys.STELLAR_MAINNET` | `'stellar'` |
62
+ | `SUI_MAINNET_CHAIN_ID` | `ChainKeys.SUI_MAINNET` | `'sui'` |
63
+ | `BITCOIN_MAINNET_CHAIN_ID` | `ChainKeys.BITCOIN_MAINNET` | `'bitcoin'` |
64
+
65
+ ### Bulk codemod
66
+
67
+ ```bash
68
+ # Find — preserves capture group
69
+ grep -rE '(\w+)_MAINNET_CHAIN_ID' src/
70
+
71
+ # Sed (one-shot)
72
+ find src -type f -name '*.ts' -o -name '*.tsx' | xargs sed -i '' -E 's/([A-Z_]+)_MAINNET_CHAIN_ID/ChainKeys.\1_MAINNET/g'
73
+ ```
74
+
75
+ After replacement, fix import statements: remove the individual constants, add `ChainKeys`. See [`../recipes.md`](../recipes.md) § "Codemod patterns" for a more robust `ts-morph` variant.
76
+
77
+ ### Pitfalls
78
+
79
+ 1. **`ChainKeys.ICON_MAINNET` is a string `'0x1.icon'`, not the legacy numeric ID.** Anywhere v1 did `Number(chainId)` for ICON, the v2 result is `NaN`. Use string equality (`chainKey === ChainKeys.ICON_MAINNET`) and audit numeric coercions.
80
+ 2. **`SONIC_MAINNET` is `'sonic'`, not `'0x92.sonic'`** — it's the simple string `'sonic'` because Sonic is the hub chain and treated specially by routing.
81
+ 3. **Don't confuse `ChainKey` with relay chain IDs.** Read shapes like `Intent.srcChain` and `Intent.dstChain` are still `IntentRelayChainId` (bigint) — those did **not** rename. A blanket `srcChain` → `srcChainKey` grep-replace will break Intent reads. Use `sodax.config.getSpokeChainKeyFromIntentRelayChainId(...)` to convert.
82
+
83
+ ---
84
+
85
+ ## 2. `@sodax/types` package surface
86
+
87
+ ### Export reorganization
88
+
89
+ v1 had a single `packages/types/src/constants/index.ts` barrel exporting all chain IDs and ad-hoc tables. That file is **deleted**. Symbols now live in domain-organized modules: `chains/`, `swap/`, `wallet/`, `bitcoin/`, etc.
90
+
91
+ | v1 import path | v2 home |
92
+ |---|---|
93
+ | `@sodax/types/btc/...` | `@sodax/types/bitcoin` (path renamed) |
94
+ | `*_MAINNET_CHAIN_ID` from `@sodax/types` constants index | `ChainKeys.*` (single barrel) |
95
+ | `Token` | `XToken` (renamed; see [§4](#4-token--xtoken-field-renames)) |
96
+ | `SpokeChainId` / `ChainId` | `SpokeChainKey` (see [§9](#9-chainid--spokechainid--spokechainkey)) |
97
+
98
+ ### Re-export from `@sodax/sdk`
99
+
100
+ `@sodax/sdk` v2 barrel re-exports the entire `@sodax/types` surface (`export * from '@sodax/types'` from `src/index.ts`). For consumers, this means:
101
+
102
+ - **Recommended:** import everything from `@sodax/sdk`. You don't need a separate `@sodax/types` dependency.
103
+ - **Tolerated:** keep importing from `@sodax/types` directly. v2 guarantees type identity (since SDK bundles `@sodax/types` via `noExternal`), but you'll pin two versions instead of one.
104
+
105
+ ### Pitfall
106
+
107
+ If your `package.json` lists `@sodax/types` as a direct dependency in v2, **remove it**. Letting it float independent of `@sodax/sdk` invites silent version skew on the next minor bump.
108
+
109
+ ---
110
+
111
+ ## 3. Wallet-provider typing
112
+
113
+ v1 modeled "the wallet to use for chain X" as a class instance: `EvmSpokeProvider`, `SolanaSpokeProvider`, etc. Consumers constructed one and passed it positionally to every SDK call. v2 deletes these classes (see [`architecture.md`](architecture.md) § "Spoke-provider deletion") and replaces the typing with two parameterised aliases:
114
+
115
+ ```ts
116
+ GetChainType<K extends ChainKey> // 'EVM' | 'BITCOIN' | 'SOLANA' | …
117
+ GetWalletProviderType<K extends ChainKey> // IEvmWalletProvider | IBitcoinWalletProvider | …
118
+ ```
119
+
120
+ When the caller passes a literal chain key (e.g. `ChainKeys.ETHEREUM_MAINNET`), TypeScript preserves the literal in the generic `K`. From that one literal:
121
+
122
+ - `GetChainType<K>` resolves to `'EVM'`.
123
+ - `GetWalletProviderType<K>` resolves to `IEvmWalletProvider` (the exact interface, not a broad union).
124
+
125
+ This is what allows v2 to demand the chain-correct wallet provider at compile time without a runtime check.
126
+
127
+ ### `WalletProviderSlot<K, Raw>` — the discriminator
128
+
129
+ ```ts
130
+ // Conceptual; lives in @sodax/types/common
131
+ export type WalletProviderSlot<K extends ChainKey, Raw extends boolean> =
132
+ Raw extends true
133
+ ? { raw: true; walletProvider?: never }
134
+ : { raw: false; walletProvider: GetWalletProviderType<K> };
135
+ ```
136
+
137
+ Three rules enforced at compile time:
138
+
139
+ 1. **`raw: true`** — `walletProvider` is **forbidden** (`?: never` rejects any value). The method returns a raw, unsigned tx payload. Use for sign-elsewhere flows.
140
+ 2. **`raw: false`** — `walletProvider` is **required** and chain-narrowed. The method signs and broadcasts; returns a tx hash.
141
+ 3. **No overlap** — TypeScript can't pick a branch unless the discriminator field is present. Forgetting `raw: false` is the #1 v2 typecheck error after migration.
142
+
143
+ ### Migration mechanics
144
+
145
+ ```diff
146
+ await sodax.swaps.createIntent({
147
+ - intentParams,
148
+ - spokeProvider: sourceProvider,
149
+ + params: intentParams,
150
+ + raw: false,
151
+ + walletProvider: sourceWalletProvider,
152
+ });
153
+ ```
154
+
155
+ For raw-tx building:
156
+
157
+ ```diff
158
+ - // v1 had a separate executeXxx method per chain
159
+ - const tx = await sourceProvider.executeCreateIntent(intentParams);
160
+ + const result = await sodax.swaps.createIntent({ params: intentParams, raw: true });
161
+ + // result.value: { tx: EvmRawTransaction | SolanaRawTransaction | ..., intent, relayData }
162
+ ```
163
+
164
+ ### Pitfall
165
+
166
+ If your wallet provider variable is typed as the broad `IWalletProvider | undefined` union (the typical case when the variable is keyed by a runtime chain-key value rather than a literal), v2 still accepts it — `K` defaults to the broad `SpokeChainKey` union, so `GetWalletProviderType<K>` resolves to the `IWalletProvider` union. For tighter narrowing on a literal chain branch, see [`../recipes.md`](../recipes.md) § "Cast-at-boundary".
167
+
168
+ ---
169
+
170
+ ## 4. `Token` / `XToken` field renames
171
+
172
+ | v1 | v2 |
173
+ |---|---|
174
+ | `Token` (type name) | `XToken` |
175
+ | `Token.xChainId` | `XToken.chainKey` |
176
+ | `Token.symbol`, `decimals`, `address`, `name` | unchanged |
177
+ | (n/a) | `XToken.vault` — added; the hub-side ERC4626 vault for this token |
178
+ | (n/a) | `XToken.hubAsset` — added; the hub-side wrapped/unified asset address |
179
+
180
+ Migration:
181
+
182
+ ```diff
183
+ - import type { Token } from '@sodax/types';
184
+ + import type { XToken } from '@sodax/sdk';
185
+ - token.xChainId
186
+ + token.chainKey
187
+ ```
188
+
189
+ ### Why `vault` / `hubAsset` matter
190
+
191
+ v1 consumers reached into a global `hubAssets[chainId][address]` map to get the vault address for a token. **v2 deletes that global** and bakes the data directly into every supported `XToken`. Anywhere v1 walked `hubAssets`, v2 reads `token.vault` or `token.hubAsset` directly. See [`architecture.md`](architecture.md) § "ConfigService replaces static lookups" for the full lookup migration.
192
+
193
+ ### Pitfall
194
+
195
+ Read shapes like `Intent` and `IntentResponse` from the backend keep `srcChain` / `dstChain` as the **relay** chain id (numeric, `IntentRelayChainId`). They are **not** chain keys and were **not** renamed to `srcChainKey`/`dstChainKey`. Only **request** types (`CreateIntentParams`, `CreateLimitOrderParams`, `SubmitSwapTxRequest`) gained the `*ChainKey` field names.
196
+
197
+ ---
198
+
199
+ ## 5. `RpcConfig` reshape
200
+
201
+ v1 modeled `RpcConfig` as a flat object with one optional URL field per chain. v2 makes it a mapped type keyed by `ChainKey` values, with chain-family-specific shapes:
202
+
203
+ ```ts
204
+ type RpcConfig = {
205
+ [K in ChainKey]:
206
+ K extends typeof ChainKeys.BITCOIN_MAINNET ? BitcoinRpcConfig :
207
+ K extends typeof ChainKeys.STELLAR_MAINNET ? StellarRpcConfig :
208
+ string // RPC URL for every other chain
209
+ };
210
+ ```
211
+
212
+ Migration:
213
+
214
+ ```diff
215
+ - rpcConfig.sonic
216
+ + rpcConfig[ChainKeys.SONIC_MAINNET]
217
+ - rpcConfig.btc
218
+ + rpcConfig[ChainKeys.BITCOIN_MAINNET] // BitcoinRpcConfig (shape, not string)
219
+ ```
220
+
221
+ ### Pitfall
222
+
223
+ Bitcoin and Stellar have richer RPC needs than other chains (multiple endpoints, network params). Their entries in `RpcConfig` are objects, not strings. If your config builder did `rpcConfig.btc = 'https://…'` in v1, that's a type error in v2 — you need the full `BitcoinRpcConfig` shape.
224
+
225
+ ---
226
+
227
+ ## 6. `IConfigApi` Result-wrapping
228
+
229
+ Every method on the `IConfigApi` contract changed signature in v2. v1 returned plain `Promise<T>` and threw on failure; v2 returns `Promise<Result<T>>` and never throws.
230
+
231
+ | Method | v1 return | v2 return |
232
+ |---|---|---|
233
+ | `getChains` | `Promise<ChainConfig[]>` | `Promise<Result<GetChainsApiResponse>>` |
234
+ | `getSwapTokens` | `Promise<SwapTokenConfig>` | `Promise<Result<GetSwapTokensApiResponse>>` |
235
+ | `getSwapTokensByChainId` | `Promise<XToken[]>` | `Promise<Result<XToken[]>>` |
236
+ | `getMoneyMarketTokens` | `Promise<MMTokenConfig>` | `Promise<Result<GetMoneyMarketTokensApiResponse>>` |
237
+ | `getMoneyMarketTokensByChainId` | `Promise<XToken[]>` | `Promise<Result<XToken[]>>` |
238
+ | `getRelayChainIdMap` | (n/a in v1) | `Promise<Result<GetRelayChainIdMapApiResponse>>` (v2-new) |
239
+
240
+ If you implemented a custom `IConfigApi` (e.g. for a sandbox or test fixture), update every method signature. If you only consumed the default implementation through `Sodax.config`, the SDK already uses `Result` internally — your consumer-side code doesn't see the wrapping.
241
+
242
+ ---
243
+
244
+ ## 7. Address-type rename
245
+
246
+ The Bitcoin-specific address-type union changed name to free up the generic `AddressType` identifier:
247
+
248
+ ```diff
249
+ - import type { AddressType } from '@sodax/types';
250
+ + import type { BtcAddressType } from '@sodax/sdk';
251
+ ```
252
+
253
+ Value union is unchanged: `'P2PKH' | 'P2SH' | 'P2WPKH' | 'P2TR'`. Custom Bitcoin wallet provider implementations must update the import.
254
+
255
+ ---
256
+
257
+ ## 8. Wallet-provider `chainType` discriminant
258
+
259
+ Every `I*WalletProvider` interface in v2 declares a `readonly chainType: '<CHAIN>'` literal field. Custom implementations must add the field; consumers can use it for runtime narrowing without `instanceof`:
260
+
261
+ ```ts
262
+ if (walletProvider.chainType === 'EVM') {
263
+ // walletProvider: IEvmWalletProvider
264
+ }
265
+ if (walletProvider.chainType === 'SOLANA') {
266
+ // walletProvider: ISolanaWalletProvider
267
+ }
268
+ ```
269
+
270
+ Supported values: `'EVM'`, `'BITCOIN'`, `'SOLANA'`, `'STELLAR'`, `'SUI'`, `'ICON'`, `'INJECTIVE'`, `'STACKS'`, `'NEAR'`.
271
+
272
+ This replaces v1's `provider instanceof EvmSpokeProvider` discrimination. Cross-bundle `instanceof` is fragile (different `@sodax/sdk` copies in dual ESM/CJS bundles can return false); the literal `chainType` field works regardless.
273
+
274
+ ---
275
+
276
+ ## 9. `ChainId` / `SpokeChainId` → `SpokeChainKey`
277
+
278
+ Type alias rename. Same value union (the chain-key strings).
279
+
280
+ ```diff
281
+ - import type { ChainId, SpokeChainId } from '@sodax/types';
282
+ + import type { SpokeChainKey } from '@sodax/sdk';
283
+ - function pickChain(id: ChainId): boolean { ... }
284
+ + function pickChain(key: SpokeChainKey): boolean { ... }
285
+ ```
286
+
287
+ Function bodies that compared `chainId === SONIC_MAINNET_CHAIN_ID` need [§1](#1-chain-identifiers)'s constant rename in addition to the type rename.
288
+
289
+ ---
290
+
291
+ ## 10. Deleted module-error types
292
+
293
+ These types were exported from v1 but are deleted in v2. Replace with `SodaxError<C>` (see [`result-and-errors.md`](result-and-errors.md) for full semantics):
294
+
295
+ - `MoneyMarketError<MoneyMarketErrorCode>`
296
+ - `IntentError<IntentErrorCode>`
297
+ - `StakingError<StakingErrorCode>`
298
+ - `BridgeError<BridgeErrorCode>`
299
+ - `MigrationError<MigrationErrorCode>`
300
+ - `AssetServiceError<AssetServiceErrorCode>`
301
+ - `ConcentratedLiquidityError<ConcentratedLiquidityErrorCode>`
302
+ - `RelayError<RelayErrorCode>`
303
+ - 5 partner error types (`PartnerFeeClaimError<...>`, etc.) and their `is<Module>Error()` type-guard helpers.
304
+
305
+ The replacement contract:
306
+
307
+ ```diff
308
+ - if (error instanceof MoneyMarketError && error.code === 'CREATE_SUPPLY_INTENT_FAILED') { … }
309
+ + if (isSodaxError(error) && error.feature === 'moneyMarket' && error.code === 'INTENT_CREATION_FAILED') { … }
310
+ ```
311
+
312
+ The full v1 → v2 code crosswalk lives in [`result-and-errors.md`](result-and-errors.md) § "v1 ↔ v2 code crosswalk".
313
+
314
+ ---
315
+
316
+ ## Cross-references
317
+
318
+ - Architectural changes (spoke-provider deletion, ConfigService, relay flow): [`architecture.md`](architecture.md).
319
+ - Result/error model details (propagation patterns, code crosswalk, return shapes): [`result-and-errors.md`](result-and-errors.md).
320
+ - v2 design context (what to use instead of each deleted symbol): [`../../integration/architecture.md`](../../integration/architecture.md).
321
+ - Lookup tables (full chain-key list, `I*WalletProvider` interfaces, public API surface): [`../../integration/reference/`](../../integration/reference/).