@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.
- package/README.md +91 -7
- package/ai-exported/AGENTS.md +99 -0
- package/ai-exported/integration/README.md +41 -0
- package/ai-exported/integration/ai-rules.md +75 -0
- package/ai-exported/integration/architecture.md +519 -0
- package/ai-exported/integration/chain-specifics.md +189 -0
- package/ai-exported/integration/features/README.md +19 -0
- package/ai-exported/integration/features/auxiliary-services.md +189 -0
- package/ai-exported/integration/features/bridge.md +136 -0
- package/ai-exported/integration/features/dex.md +182 -0
- package/ai-exported/integration/features/icx-bnusd-baln.md +181 -0
- package/ai-exported/integration/features/money-market.md +198 -0
- package/ai-exported/integration/features/staking.md +166 -0
- package/ai-exported/integration/features/swap.md +207 -0
- package/ai-exported/integration/quickstart.md +213 -0
- package/ai-exported/integration/recipes/README.md +21 -0
- package/ai-exported/integration/recipes/backend-server-init.md +69 -0
- package/ai-exported/integration/recipes/chain-key-narrowing.md +65 -0
- package/ai-exported/integration/recipes/gas-estimation.md +33 -0
- package/ai-exported/integration/recipes/initialize-sodax.md +53 -0
- package/ai-exported/integration/recipes/raw-tx-flow.md +71 -0
- package/ai-exported/integration/recipes/result-and-errors.md +104 -0
- package/ai-exported/integration/recipes/signed-tx-flow.md +46 -0
- package/ai-exported/integration/recipes/testing.md +101 -0
- package/ai-exported/integration/reference/README.md +18 -0
- package/ai-exported/integration/reference/chain-keys.md +67 -0
- package/ai-exported/integration/reference/error-codes.md +165 -0
- package/ai-exported/integration/reference/glossary.md +32 -0
- package/ai-exported/integration/reference/public-api.md +138 -0
- package/ai-exported/integration/reference/wallet-providers.md +62 -0
- package/ai-exported/migration/README.md +58 -0
- package/ai-exported/migration/ai-rules.md +80 -0
- package/ai-exported/migration/breaking-changes/architecture.md +342 -0
- package/ai-exported/migration/breaking-changes/result-and-errors.md +363 -0
- package/ai-exported/migration/breaking-changes/type-system.md +321 -0
- package/ai-exported/migration/checklist.md +61 -0
- package/ai-exported/migration/features/README.md +35 -0
- package/ai-exported/migration/features/auxiliary-services.md +156 -0
- package/ai-exported/migration/features/bridge.md +125 -0
- package/ai-exported/migration/features/dex.md +143 -0
- package/ai-exported/migration/features/icx-bnusd-baln.md +151 -0
- package/ai-exported/migration/features/money-market.md +214 -0
- package/ai-exported/migration/features/staking.md +138 -0
- package/ai-exported/migration/features/swap.md +198 -0
- package/ai-exported/migration/recipes.md +288 -0
- package/ai-exported/migration/reference/README.md +18 -0
- package/ai-exported/migration/reference/deleted-exports.md +126 -0
- package/ai-exported/migration/reference/error-code-crosswalk.md +104 -0
- package/ai-exported/migration/reference/return-shapes.md +49 -0
- package/ai-exported/migration/reference/sodax-config.md +52 -0
- package/dist/index.cjs +32076 -31544
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8604 -7136
- package/dist/index.d.ts +8604 -7136
- package/dist/index.mjs +31893 -31402
- package/dist/index.mjs.map +1 -1
- 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/).
|