@sodax/sdk 2.0.0-rc.2 → 2.0.0-rc.3

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.
@@ -63,7 +63,7 @@ ai-exported/
63
63
  2. **Every async public method returns `Result<T>`.** Branch on `result.ok`. No throws across service boundaries. Sub-Result forwarding is the default: `if (!sub.ok) return sub`.
64
64
  3. **Errors are `SodaxError<C>`.** A single class with a closed 13-code reason vocabulary (`VALIDATION_FAILED`, `RELAY_TIMEOUT`, `EXECUTION_FAILED`, …) plus a `feature` field (`'swap' | 'moneyMarket' | …`). The pair `(feature, code)` is your discriminator. Use `isSodaxError(e)` (not bare `instanceof`).
65
65
  4. **Signed vs raw is a discriminated union.** `WalletProviderSlot<K, Raw>` enforces at compile time: `{ raw: false, walletProvider: <chain-narrowed> }` for signing, `{ raw: true }` for unsigned-tx building. Mixing them is a TypeScript error.
66
- 5. **Config is dynamic.** `await sodax.config.initialize()` fetches current chain/token config from the backend, with a safe fallback to packaged defaults. Lookups go through `sodax.config.*` there is no `hubAssets`, no `moneyMarketSupportedTokens` global map, no static registry to import.
66
+ 5. **Config is dynamic; overrides only land on `sodax.config`.** Once a `Sodax` instance exists, always read via `sodax.config.*` (e.g. `sodax.config.spokeChainConfig[chainKey]`) direct imports of `spokeChainConfig` / `sodaxConfig` from `@sodax/types` / `@sodax/sdk` are packaged-default snapshots and silently miss both `await sodax.config.initialize()` updates and `new Sodax(config)` overrides. Full instance-scope-reader table + module-scope rules: [`integration/recipes/initialize-sodax.md`](integration/recipes/initialize-sodax.md) § *Module-scope reads*. Per-symbol status of v1 globals: [`migration/breaking-changes/architecture.md`](migration/breaking-changes/architecture.md) § 2.
67
67
 
68
68
  ## Top 5 v1 → v2 traps
69
69
 
@@ -132,14 +132,20 @@ import { Sodax, type SodaxConfig, type DeepPartial } from '@sodax/sdk';
132
132
  new Sodax(config?: DeepPartial<SodaxConfig>): Sodax;
133
133
  ```
134
134
 
135
- `SodaxConfig` carries (all optional via `DeepPartial`):
135
+ `SodaxConfig` has exactly **10 fields** (all required at the type level, but `DeepPartial` makes every leaf optional):
136
136
 
137
- - `solver` — `{ intentsContract, solverApiEndpoint, protocolIntentsContract }` (endpoints).
138
- - `swaps` — `SwapsConfig` (supported solver tokens per chain).
139
- - `moneyMarket`, `bridge`, `staking`, `dex`, `migration`, `partner`, `recovery` feature-specific config (contract addresses, etc.).
140
- - `rpcConfig` — mapped type keyed by `ChainKey` values; `BitcoinRpcConfig` for `BITCOIN_MAINNET`, `StellarRpcConfig` for `STELLAR_MAINNET`, RPC URL strings for everything else.
141
- - `hubConfig` — hub provider config (consumed by `EvmHubProvider`).
142
- - `backendApi` — `{ url, api?: IConfigApi }` for custom backend / sandbox endpoints.
137
+ - `fee: PartnerFee | undefined` — global partner fee, applied unless a feature-level config overrides.
138
+ - `chains: Record<SpokeChainKey, SpokeChainConfig>` per-spoke-chain config. Each entry carries `rpcUrl`, polling config, and chain-family-specific extras (`BitcoinSpokeChainConfig`, `StellarSpokeChainConfig`, etc.).
139
+ - `swaps: SwapsConfig` — supported solver tokens per chain.
140
+ - `moneyMarket: MoneyMarketConfig` — money market contracts + supported tokens.
141
+ - `bridge: BridgeConfig` — bridge `{ partnerFee }` override.
142
+ - `dex: DexConfig` — DEX pool/asset config.
143
+ - `hub: HubConfig` — hub-chain (Sonic) full address map + RPC URL + polling config.
144
+ - `api: ApiConfig` — backend API endpoint (`{ baseURL, timeout, headers }`).
145
+ - `solver: SolverConfig` — `{ intentsContract, solverApiEndpoint, protocolIntentsContract }`.
146
+ - `relay: RelayConfig` — intent relay endpoint + chain-id map.
147
+
148
+ > **Not config slots** — `staking`, `migration`, `partner`/`partners`, `recovery` are services on the `Sodax` instance (`sodax.staking`, etc.) but they are **not** configurable via `SodaxConfig`. They run on packaged defaults; per-call params handle customization.
143
149
 
144
150
  In production, the packaged defaults are sufficient — pass nothing and call `await sodax.config.initialize()` to load fresh data from the backend.
145
151
 
@@ -174,7 +180,15 @@ Chain configs (vault addresses, supported tokens, fee parameters) change between
174
180
 
175
181
  ### Custom backend
176
182
 
177
- Inject a custom `IConfigApi` for testing or sandbox via `SodaxConfig.backendApi.api`. The contract: every method on `IConfigApi` returns `Promise<Result<T>>`.
183
+ Point at a custom backend URL via `SodaxConfig.api.baseURL`:
184
+
185
+ ```ts
186
+ const sodax = new Sodax({
187
+ api: { baseURL: 'https://sandbox-api.example.com' },
188
+ });
189
+ ```
190
+
191
+ `SodaxConfig.api` is `ApiConfig` (`{ baseURL, timeout, headers }`) — pass any subset via `DeepPartial`. v2 does not provide a typed slot to inject a custom `IConfigApi` implementation at construction; if you need to mock the backend for tests, point `baseURL` at a local mock server, or construct your own `BackendApiService`-compatible mock and inject it where you control the `Sodax` instance (e.g. dependency-injected in your app layer).
178
192
 
179
193
  ---
180
194
 
@@ -196,7 +196,7 @@ The errors most likely to hit on a fresh install or first port.
196
196
 
197
197
  ### `sodax.config.initialize()` hangs / errors
198
198
 
199
- - The backend API is unreachable. The SDK should fall back to packaged defaults silently — check your network, then check that `SodaxConfig.backendApi.url` is correct (or omit it for the default).
199
+ - The backend API is unreachable. The SDK should fall back to packaged defaults silently — check your network, then check that `SodaxConfig.api.baseURL` is correct (or omit it for the default).
200
200
 
201
201
  ### Stellar bridge / swap fails with `'Trustline missing'`
202
202
 
@@ -45,6 +45,31 @@ await sodax.config.initialize();
45
45
 
46
46
  `initialize()` is the only initialization step. Don't `await` it inside every feature call — call it once at app startup. If you skip it entirely, feature services fall back to packaged defaults, which may be stale relative to the latest backend config (new tokens, new chains, fee parameter changes).
47
47
 
48
+ ## Module-scope reads (no Sodax instance needed)
49
+
50
+ Some code runs at **module-load time** — constants files, utility modules, framework-provider configs — before any `Sodax` instance exists. For those, import the packaged-default constants directly from `@sodax/sdk` (re-exported from `@sodax/types`):
51
+
52
+ ```ts
53
+ import { sodaxConfig, hubConfig } from '@sodax/sdk';
54
+
55
+ // Hub address constants
56
+ export const HUB_WALLET = hubConfig.addresses.hubWallet;
57
+ export const STAKING_ROUTER = hubConfig.addresses.stakingRouter;
58
+
59
+ // Full default config (every SodaxConfig field with packaged defaults)
60
+ export const DEFAULT_SOLVER_ENDPOINT = sodaxConfig.solver.solverApiEndpoint;
61
+ export const SUPPORTED_TOKENS_PER_CHAIN = sodaxConfig.swaps.supportedTokens;
62
+ ```
63
+
64
+ | Need | Module-scope import (defaults only) | Instance-scope read (with overrides) |
65
+ |---|---|---|
66
+ | Hub contract addresses (assetManager, hubWallet, stakingRouter, etc.) | `hubConfig.addresses.*` | `sodax.config.getHubChainConfig().addresses.*` |
67
+ | Full default SodaxConfig (read-only snapshot) | `sodaxConfig.*` (e.g. `sodaxConfig.hub`, `sodaxConfig.moneyMarket`) | `sodax.config.sodaxConfig` |
68
+ | Per-chain spoke config (rpcUrl, nativeToken, addresses, supportedTokens, polling) | `spokeChainConfig[ChainKeys.X_MAINNET]` (from `@sodax/types` / `@sodax/sdk`) | `sodax.config.spokeChainConfig[ChainKeys.X_MAINNET]` *or* `sodax.config.getChainConfig(ChainKeys.X_MAINNET)` |
69
+ | Money market reserve assets | `sodaxConfig.moneyMarket.supportedReserveAssets` | `sodax.config.getMoneyMarketReserveAssets()` |
70
+
71
+ > **Static vs dynamic — and the override-gap consequence.** `sodaxConfig` / `hubConfig` / `spokeChainConfig` are **packaged-default snapshots** frozen at SDK release time. They are safe at module scope but: (a) won't reflect backend-driven config updates loaded by `sodax.config.initialize()`, and (b) **won't reflect overrides passed to `new Sodax(config)`** — those merge into `sodax.config` (the `ConfigService`) but never mutate the static imports. So once a `Sodax` instance exists, prefer the instance-scope readers in the right column above — particularly `sodax.config.spokeChainConfig` over the same-named static import — or you will silently fall back to the packaged defaults for any chain you customized.
72
+
48
73
 
49
74
  ## Cross-references
50
75
 
@@ -10,7 +10,7 @@ v2 was a deep architectural reshape, not a feature release. Five orthogonal chan
10
10
  2. **`Result<T>` everywhere.** Every async public method on every service returns `Promise<Result<T, SodaxError<C>>>`. v1 throw-on-error patterns are gone.
11
11
  3. **One canonical error class.** `SodaxError<C>` with a closed 13-code vocabulary plus `feature: 'swap' | 'moneyMarket' | …`. The per-module typed error unions (`MoneyMarketError<Code>`, `IntentError<Code>`, `StakingError<Code>`, `BridgeError<Code>`, `MigrationError<Code>`, `AssetServiceError<Code>`, `ConcentratedLiquidityError<Code>`, partner errors, …) are deleted.
12
12
  4. **`WalletProviderSlot<K, Raw>` is the discriminated union.** Every signed-execution method takes `{ raw: false, walletProvider }` (chain-narrowed via `GetWalletProviderType<K>`); every raw-tx-building method takes `{ raw: true }` (no wallet provider). Compile-time enforced.
13
- 5. **`ConfigService` replaces static lookups.** Globals like `hubAssets`, `moneyMarketSupportedTokens`, `SodaTokens`, and the `*_MAINNET_CHAIN_ID` constants are gone. Lookups go through `sodax.config.*` (which loads from the backend API with a packaged-defaults fallback).
13
+ 5. **`ConfigService` is the preferred lookup surface.** `hubAssets` and `*_MAINNET_CHAIN_ID` constants **are deleted** (won't compile). `moneyMarketSupportedTokens`, `SodaTokens`, `supportedSpokeChains` **are still exported** as packaged-default constants (so v1 imports keep compiling), but prefer `sodax.config.*` / `sodax.moneyMarket.getSupportedTokens*()` those reflect backend-driven runtime updates after `await sodax.config.initialize()`. See [`breaking-changes/architecture.md`](breaking-changes/architecture.md) § 2 for the per-symbol table.
14
14
 
15
15
  The remainder is per-feature (return shape diffs, field renames, new required params like `srcAddress`).
16
16
 
@@ -40,7 +40,7 @@ Same word, different concept across versions. Skim before reading the breaking-c
40
40
  | **raw tx** | An ad-hoc method on each spoke provider, sometimes named `executeXxx`, returning a chain-specific payload. | The discriminator `{ raw: true }` on the standard SDK call shape. Return type narrows via `TxReturnType<K, true>` (`EvmRawTransaction`, `SolanaRawTransaction`, …). |
41
41
  | **config** | A `SodaxConfig` object passed at construction with hard-coded chain/token tables. Solver endpoints lived under `SodaxConfig.swaps`. | A `Sodax` instance owns a `ConfigService` that loads from the backend API, with packaged defaults as fallback. `sodax.config.*` is the lookup surface. **Solver endpoints moved to `SodaxConfig.solver`** (not `swaps`); `swaps` is `SwapsConfig` (supported tokens). |
42
42
  | **`xChainId` field on tokens** | Field name on the `Token` type. | Renamed: `XToken.chainKey`. Type also renamed: `Token` → `XToken`. |
43
- | **`hubAssets` / `moneyMarketSupportedTokens`** | Static `Record` global maps imported and walked. | Gone. Use `sodax.config.*` and `sodax.moneyMarket.getSupportedTokens*()`. Each `XToken` now carries `vault` and `hubAsset` directly. |
43
+ | **`hubAssets` / `moneyMarketSupportedTokens`** | Static `Record` global maps imported and walked. | `hubAssets` **deleted** — won't compile. `moneyMarketSupportedTokens` **still exported** as a packaged default, but prefer `sodax.config.*` and `sodax.moneyMarket.getSupportedTokens*()` so backend updates take effect. Each `XToken` now carries `vault` and `hubAsset` directly. |
44
44
  | **`SubmitSwapTxRequest.srcChainId`** | Numeric chain id field on the backend submit-swap request. | Renamed: `srcChainKey: SpokeChainKey`. |
45
45
  | **`Intent.srcChain` / `Intent.dstChain`** | Read shape: `IntentRelayChainId` (bigint). | **Unchanged.** This is the relay chain id, not a spoke chain key. A blanket grep-replace `srcChain`→`srcChainKey` will break this. |
46
46
  | **`AddressType`** | Bitcoin-specific address-type union. | Renamed: `BtcAddressType`. (Generic name freed up.) |
@@ -5,7 +5,7 @@ The structural changes that reshaped the SDK at the service / runtime layer. Rea
5
5
  The four load-bearing shifts:
6
6
 
7
7
  1. **Per-chain `*SpokeProvider` classes are deleted.** Routing is by chain key, dispatched internally by `SpokeService`.
8
- 2. **Static lookup tables (`hubAssets`, `moneyMarketSupportedTokens`, `SodaTokens`) are deleted.** Lookups go through `sodax.config.*`.
8
+ 2. **Static lookup tables `hubAssets` deleted; `moneyMarketSupportedTokens`, `SodaTokens`, `supportedSpokeChains` still exported as packaged defaults but should yield to `sodax.config.*` so backend updates take effect.** See § 2 below for the per-symbol status table.
9
9
  3. **Relay flow is centralised.** Two functions (`relayTxAndWaitPacket`, `submitTransaction`) and one mapper (`mapRelayFailure`) replace per-feature relay helpers.
10
10
  4. **Invariants and guards are unified.** `sodaxInvariant` + per-feature aliases (`swapInvariant`, `mmInvariant`, etc.) and `isSodaxError` / `isFeatureError` replace ad-hoc throws and module-specific type guards.
11
11
 
@@ -115,6 +115,8 @@ These v1 names still ship through `@sodax/sdk` (re-exported from `@sodax/types`)
115
115
  | `Object.entries(moneyMarketSupportedTokens)` (walk pattern) | Const still exported | `sodax.moneyMarket.getSupportedTokens()` (returns `Record<SpokeChainKey, XToken[]>`) |
116
116
  | `SodaTokens` registry (vault-validation) | Still exported (`packages/types/src/chains/tokens.ts`) | `sodax.config.getMoneyMarketReserveAssets()` or `sodax.moneyMarket.getSupportedReserves()` |
117
117
  | `swapSupportedTokens[chainId]` (v1 had no `solverSupportedTokens`) | Still exported (`packages/types/src/swap/swap.ts`) | `sodax.config.getSupportedSwapTokensByChainId(chainKey)` |
118
+ | `getSupportedSolverTokens(chainId)` (free function) | Still exported (`packages/types/src/swap/swap.ts`) | `sodax.config.getSupportedSwapTokensByChainId(chainKey)` |
119
+ | `supportedSpokeChains` (`SpokeChainKey[]`) | Still exported (`packages/types/src/chains/chains.ts`) | `sodax.config.isValidSpokeChainKey(chainKey)` for validation, or read `sodaxConfig.chains` at module scope |
118
120
 
119
121
  ### What replaces them
120
122
 
@@ -194,6 +194,26 @@ v1 consumers reached into a global `hubAssets[chainId][address]` map to get the
194
194
 
195
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
196
 
197
+ ### Exception — partner module read shapes DID rename
198
+
199
+ The above "read shapes keep `srcChain`/`dstChain`" rule has one exception: **partner module** read shapes also renamed `dstChain` → `dstChainKey`. Specifically:
200
+
201
+ - `AutoSwapPreferences` (returned by `sodax.partners.getAutoSwapPreferences(queryAddress)`) — field `dstChain` → **`dstChainKey: SpokeChainKey | 'not configured'`**.
202
+ - `SetSwapPreferenceParams` (request type for `setSwapPreference`) — `dstChain` → `dstChainKey: SpokeChainKey`.
203
+
204
+ ```diff
205
+ - // v1
206
+ - const prefs = await partnerFeeClaimService.getAutoSwapPreferences(addr);
207
+ - const destChain: SpokeChainId = prefs.dstChain;
208
+
209
+ + // v2
210
+ + const result = await sodax.partners.getAutoSwapPreferences(addr);
211
+ + if (!result.ok) return;
212
+ + const destChain: SpokeChainKey | 'not configured' = result.value.dstChainKey;
213
+ ```
214
+
215
+ A blanket grep `dstChain` → `dstChainKey` is safe in the partner module **but** still unsafe in `Intent` / `IntentResponse` reads. Scope grep replacements per file or per import. The general rule for `srcChain` (Intent read field) is unchanged: it remains the relay chain id.
216
+
197
217
  ---
198
218
 
199
219
  ## 5. `RpcConfig` reshape
@@ -20,6 +20,11 @@ Work this top-down. Each step is independent enough to land as its own commit; t
20
20
  [ ] 7. Add { raw: false } discriminator to every signed call shape that
21
21
  previously took a positional spoke provider:
22
22
  { intentParams, spokeProvider } → { params, raw: false, walletProvider }
23
+ [ ] 7b. Cross-cutting rename: every payload field named `spokeProvider:` (incl.
24
+ approve/allowance/utility methods that aren't signed-execution shapes)
25
+ → `walletProvider:`. Mechanical for the OBJECT-LITERAL field key; don't
26
+ blindly rename variable names of the same spelling. See
27
+ [`recipes.md`](recipes.md) § 1 "What's not safe to grep-replace".
23
28
  [ ] 8. Add srcChainKey + srcAddress to every action params object that didn't
24
29
  carry them in v1 (most MM, staking, dex, bridge, migration param shapes).
25
30
  [ ] 9. Convert every await sodax.<service>.<method>(...) call site that previously
@@ -28,9 +33,10 @@ Work this top-down. Each step is independent enough to land as its own commit; t
28
33
  [ ] 10. Delete imports of MoneyMarketError, IntentError, StakingError, BridgeError,
29
34
  MigrationError, AssetServiceError, ConcentratedLiquidityError, RelayError,
30
35
  plus the five Partner error types and their type-guard helpers.
31
- [ ] 11. Replace any walked global lookup (hubAssets[chainId][address],
32
- moneyMarketSupportedTokens[chainId], SodaTokens[...]) with the equivalent
33
- sodax.config.* / sodax.moneyMarket.getSupportedTokens*() calls.
36
+ [ ] 11. Replace any walked global lookup (hubAssets, moneyMarketSupportedTokens,
37
+ SodaTokens, supportedSpokeChains) with the equivalent sodax.config.* /
38
+ sodax.moneyMarket.getSupportedTokens*() call. Per-symbol deleted-vs-
39
+ still-exported status: see breaking-changes/architecture.md § 2.
34
40
  [ ] 12. Initialize ConfigService at app startup: await sodax.config.initialize().
35
41
  Falls back to packaged defaults if the backend is unreachable.
36
42
  [ ] 13. Add { raw: true } to any read-only allowance check that previously took
@@ -19,7 +19,7 @@ Pair: [`../../integration/features/bridge.md`](../../integration/features/bridge
19
19
 
20
20
  | Type | v1 shape | v2 shape | Notes |
21
21
  |---|---|---|---|
22
- | `CreateBridgeParams` | `{ srcAsset, amount, dstChainId, dstAddress, dstAsset }` | `{ srcChainKey, srcAddress, srcAsset, amount, dstChainKey, dstAddress, dstAsset }` | Now generic `<K>`. `srcChainId`/`dstChainId` (where they appeared) → `srcChainKey`/`dstChainKey`. |
22
+ | `CreateBridgeIntentParams` | `{ srcChainId, srcAsset, amount, dstChainId, dstAsset, recipient }` | `{ srcChainKey, srcAddress, srcToken, amount, dstChainKey, dstToken, recipient }` | Now generic `<K>`. Renames: `srcChainId`/`dstChainId` `srcChainKey`/`dstChainKey`; `srcAsset`/`dstAsset` → `srcToken`/`dstToken`. `recipient` is **unchanged**. NEW required: `srcAddress` (user's spoke-side sender, distinct from `recipient` which is the destination receiver). |
23
23
  | Bridge action wrapper | `{ params, spokeProvider }` | `{ params, raw: false, walletProvider }` | Same as every feature. |
24
24
  | `bridge` return | `Promise<string>` (tx hash, throws on error) | `Promise<Result<TxHashPair, SodaxError>>` | Tx-pair + Result. |
25
25
  | `getBridgeableAmount` | `Promise<bigint>` | `Promise<Result<BridgeLimit, SodaxError>>` where `BridgeLimit = { amount, decimals, type }` | Result-wrapped + richer return shape. Now takes `(from: XToken, to: XToken)` (was `(srcChainId, srcToken, dstChainId, dstToken)`). |
@@ -44,21 +44,24 @@ Pair: [`../../integration/features/bridge.md`](../../integration/features/bridge
44
44
 
45
45
  ```diff
46
46
  - const txHash: string = await sodax.bridge.bridge({
47
- - params: { srcAsset, amount, dstChainId, dstAddress, dstAsset },
47
+ - params: { srcAsset, amount, dstChainId, dstAsset, recipient },
48
48
  - spokeProvider,
49
49
  - });
50
50
  + const result = await sodax.bridge.bridge({
51
51
  + params: {
52
52
  + srcChainKey: ChainKeys.ARBITRUM_MAINNET,
53
- + srcAddress: '0x…',
54
- + srcAsset, amount,
53
+ + srcAddress: '0x…', // NEW: required (your spoke-side sender)
54
+ + srcToken, // RENAMED from `srcAsset`
55
+ + amount,
55
56
  + dstChainKey: ChainKeys.STELLAR_MAINNET,
56
- + dstAddress: 'G…',
57
- + dstAsset,
57
+ + dstToken, // RENAMED from `dstAsset`
58
+ + recipient: 'G…', // UNCHANGED — destination receiver
58
59
  + },
59
60
  + raw: false,
60
61
  + walletProvider,
61
62
  + });
63
+ + // Type: Result<TxHashPair, BridgeOrchestrationError>
64
+ + // where TxHashPair = { srcChainTxHash: string; dstChainTxHash: string }
62
65
  + if (!result.ok) return;
63
66
  + const { srcChainTxHash, dstChainTxHash } = result.value;
64
67
  ```
@@ -1,13 +1,16 @@
1
1
  # Migration recipes — v1 → v2
2
2
 
3
- Practical patterns for porting consumer code without rewriting everything in one pass. Three recipes:
3
+ Practical patterns for porting consumer code without rewriting everything in one pass.
4
4
 
5
5
  1. [Codemod patterns](#1-codemod-patterns) — regex find/replace + a small `ts-morph` script for the renames that grep can't safely handle.
6
- 2. [Error-shape adapter](#2-error-shape-adapter) — adapt v2 `SodaxError` onto v1 `{ code, data }` branches so existing error-formatting helpers keep working.
7
- 3. [Result adapter](#3-result-adapter) — wrap v2 `Result<T>` in a v1-style throw shim for incremental conversion of call sites.
6
+ 2. [Free-function lookups at module scope](#2-free-function-lookups-at-module-scope) — `getHubChainConfig()` / `getMoneyMarketConfig()` migration for constants/util files where no `Sodax` instance exists yet.
7
+ 3. [Error-shape adapter](#3-error-shape-adapter) — adapt v2 `SodaxError` onto v1 `{ code, data }` branches so existing error-formatting helpers keep working.
8
+ 4. [Result adapter](#4-result-adapter) — wrap v2 `Result<T>` in a v1-style throw shim for incremental conversion of call sites.
8
9
 
9
10
  These are migration-only — once the port is complete, delete them. They're not patterns for new code.
10
11
 
12
+ The patterns below describe **what** the rewrite needs to do; pick whichever codemod tool fits your project (regex, `ts-morph`, `jscodeshift`, IDE refactor). The example scripts use `ts-morph` for AST-level rewrites where regex is unsafe.
13
+
11
14
  ---
12
15
 
13
16
  ## 1. Codemod patterns
@@ -16,12 +19,27 @@ These are migration-only — once the port is complete, delete them. They're not
16
19
 
17
20
  | Change | Find | Replace | Notes |
18
21
  |---|---|---|---|
19
- | Chain-id constants | `(\w+)_MAINNET_CHAIN_ID` | `ChainKeys.$1_MAINNET` | Mechanical. After: fix `import` statements. |
22
+ | Chain-id constants | `(\w+)_MAINNET_CHAIN_ID` | `ChainKeys.$1_MAINNET` | Mechanical. **Two-pass see below.** First-pass rewrites usages; second-pass fixes the broken `import { ... }` statements. |
20
23
  | `xChainId` field on `XToken` | `\.xChainId\b` | `.chainKey` | Token field rename. Audit first — some non-token types may use `xChainId` differently. |
21
24
  | `Token` type rename | `\bimport(.*)\bToken\b(.*)\bfrom 'sodax/types'` | `import$1XToken$2from '@sodax/sdk'` | Best done with `ts-morph` to avoid touching unrelated `Token` identifiers. |
22
25
  | `SpokeChainId` → `SpokeChainKey` | `\b(SpokeChainId\|ChainId)\b` (in type positions) | `SpokeChainKey` | Audit — `ChainId` is also used by 3rd-party libs (viem, etc.). Limit to `@sodax/types` imports. |
23
26
  | `AddressType` → `BtcAddressType` | `\bAddressType\b` (in `@sodax/types` import positions) | `BtcAddressType` | |
24
27
 
28
+ ### Two-pass codemod for chain-id constants
29
+
30
+ A one-pass `_MAINNET_CHAIN_ID` → `ChainKeys.*_MAINNET` rewrite produces invalid syntax inside `import { ... }` blocks — `ChainKeys.SONIC_MAINNET` is a member-access expression, not a bare identifier, and cannot live inside a named-import list (TS1109).
31
+
32
+ Split the rewrite into two passes:
33
+
34
+ 1. **Pass 1 — rewrite usages.** Every `<X>_MAINNET_CHAIN_ID` → `ChainKeys.<X>_MAINNET`. Touches expression positions and (incorrectly) named-import positions; the broken imports get fixed in pass 2.
35
+ 2. **Pass 2 — sweep imports.** For each `import { … } from '@sodax/{types,sdk}'`, drop the now-broken `ChainKeys.<X>_MAINNET` entries from the named-imports list and ensure `ChainKeys` itself is imported once.
36
+
37
+ Use the `ts-morph` script from the chain-id section above as a starting point for an AST-aware version, or build the two passes with whatever tooling fits your repo. After pass 2, every chain-id reference compiles.
38
+
39
+ ### Pitfall — duplicate `ChainKeys` imports
40
+
41
+ If a file imports `<X>_MAINNET_CHAIN_ID` from BOTH `@sodax/types` and `@sodax/sdk` (some legacy code split imports across the two), pass 2 may add `ChainKeys` to both import statements → `TS2300 Duplicate identifier 'ChainKeys'`. Either keep `ChainKeys` only in the first import block, or consolidate to one import — `@sodax/sdk` re-exports the entire `@sodax/types` surface, so a single `import … from '@sodax/sdk'` is sufficient.
42
+
25
43
  ### What's not safe to grep-replace
26
44
 
27
45
  - `srcChain` → `srcChainKey` on **request** types only. Read shapes (`Intent.srcChain`, `IntentResponse.srcChain`) keep `srcChain` as the relay chain id. Use a `ts-morph` script keyed by parameter type.
@@ -95,7 +113,51 @@ The `ts-morph` script above edits in-place. Commit your tree before running.
95
113
 
96
114
  ---
97
115
 
98
- ## 2. Error-shape adapter
116
+ ## 2. Free-function lookups at module scope
117
+
118
+ v1 exported free functions like `getHubChainConfig()` and `getMoneyMarketConfig(chainId)` that consumers called at **module-load time** inside constants/util files — *before* any `Sodax` instance exists. The v2 replacement on `Sodax.config.*` is unusable in that context (chicken-and-egg).
119
+
120
+ The real v2 answer: read directly from the packaged-default const `sodaxConfig`, re-exported from `@sodax/sdk` (and from `@sodax/types`):
121
+
122
+ | v1 free function | v2 module-scope equivalent |
123
+ |---|---|
124
+ | `getHubChainConfig()` | `sodaxConfig.hub` |
125
+ | `getMoneyMarketConfig(hubChainId)` | `sodaxConfig.moneyMarket` |
126
+ | `getMoneyMarketConfig(hubChainId).supportedTokens` | `sodaxConfig.moneyMarket.supportedTokens` |
127
+ | `getSolverConfig(SONIC_MAINNET_CHAIN_ID)` (read solver endpoints) | `sodaxConfig.solver` |
128
+ | (none — v1 had no module-scope-safe accessor for full hub object) | `sodaxConfig.hub.addresses.hubWallet`, `.assetManager`, etc. |
129
+
130
+ ```diff
131
+ - // v1 — module-scope constants file (no Sodax instance available yet)
132
+ - import { getHubChainConfig, getMoneyMarketConfig } from '@sodax/sdk';
133
+ - import { SONIC_MAINNET_CHAIN_ID } from '@sodax/types';
134
+ -
135
+ - const hubConfig = { hubRpcUrl, chainConfig: getHubChainConfig() };
136
+ - const moneyMarketConfig = getMoneyMarketConfig(SONIC_MAINNET_CHAIN_ID);
137
+
138
+ + // v2 — read from packaged defaults; no Sodax instance needed
139
+ + import { sodaxConfig } from '@sodax/sdk';
140
+ +
141
+ + const hubAddresses = sodaxConfig.hub.addresses;
142
+ + const moneyMarketTokens = sodaxConfig.moneyMarket.supportedTokens;
143
+ ```
144
+
145
+ > **Once you have a `Sodax` instance**, prefer `sodax.config.*` (`sodax.config.getHubChainConfig()`, `sodax.config.getMoneyMarketReserveAssets()`, etc.). The service-API path reflects backend-driven runtime updates after `await sodax.config.initialize()`; `sodaxConfig` is a packaged-default snapshot frozen at SDK release time.
146
+
147
+ For hub-only module-scope reads, `hubConfig` is also exported directly:
148
+
149
+ ```ts
150
+ import { hubConfig } from '@sodax/sdk';
151
+
152
+ const HUB_WALLET = hubConfig.addresses.hubWallet;
153
+ const STAKING_ROUTER = hubConfig.addresses.stakingRouter;
154
+ ```
155
+
156
+ See [`reference/sodax-config.md`](reference/sodax-config.md) § "Pitfall — module-scope reads" for the same guidance in the SodaxConfig reshape doc.
157
+
158
+ ---
159
+
160
+ ## 3. Error-shape adapter
99
161
 
100
162
  If your consumer code has `getMmErrorText`, `getSwapErrorText`, or similar helpers that branch on a v1 error object's `.code` and `.data.error`, the minimal-change migration is to wrap incoming v2 `SodaxError` instances at the entry point of each helper:
101
163
 
@@ -191,7 +253,7 @@ See [`reference/error-code-crosswalk.md`](reference/error-code-crosswalk.md) for
191
253
 
192
254
  ---
193
255
 
194
- ## 3. Result adapter
256
+ ## 4. Result adapter
195
257
 
196
258
  If converting every call site to branch on `result.ok` in one pass isn't realistic, use a `throwIfError` shim during migration. Then convert call sites at your own pace.
197
259
 
@@ -30,8 +30,9 @@ Two categories worth distinguishing:
30
30
  | v1 export | v2 replacement |
31
31
  |---|---|
32
32
  | `hubAssets` | `XToken.vault` / `XToken.hubAsset` baked in; or `sodax.config.getOriginalAssetAddress(...)`. See [`../breaking-changes/architecture.md`](../breaking-changes/architecture.md) § 2. |
33
- | `getHubChainConfig()` (free function) | `sodax.config.getHubChainConfig()` (now a method on `ConfigService`, accessed via the `Sodax` instance). |
33
+ | `getHubChainConfig()` (free function) | `sodax.config.getHubChainConfig()` (now a method on `ConfigService`, accessed via the `Sodax` instance). For module-scope reads (before a `Sodax` instance exists), see [`sodax-config.md`](sodax-config.md) § "Pitfall — module-scope reads". |
34
34
  | `EvmWalletAbstraction` (class) | `sodax.hubProvider.getUserHubWalletAddress(...)` (the equivalent functionality lives on `EvmHubProvider`, accessed via the `Sodax` instance). |
35
+ | `HubService` (class) + `HubService.getUserHubWalletAddress(spokeAddress, spokeChainId, hubProvider)` (static method) | `sodax.hubProvider.getUserHubWalletAddress(spokeAddress, spokeChainKey)` (instance method on `EvmHubProvider`; doesn't take `hubProvider` arg — `this` is the hub provider). v1 callers in `dex/AssetService.ts`, `mm/useATokensBalances.ts`, `shared/useGetUserHubWalletAddress.ts` all flatten to this single shape. |
35
36
 
36
37
  ### Type aliases
37
38
 
@@ -41,7 +42,7 @@ Two categories worth distinguishing:
41
42
  | `SpokeChainId` (type) | `SpokeChainKey`. |
42
43
  | `EvmChainId` (type) | `EvmChainKey` (subset of `SpokeChainKey`). |
43
44
  | `HubChainId` (type) | `HubChainKey` (literal `'sonic'`). |
44
- | `Token` (type) | `XToken`. See [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 4. |
45
+ | `Token` (type) | `XToken`. **Fully removed — no legacy alias.** `import type { Token } from '@sodax/types'` fails with `TS2305: '"@sodax/types"' has no exported member named 'Token'. Did you mean 'XToken'?`. The shape also changed (added `vault`, `hubAsset`; renamed `xChainId` → `chainKey`) — see [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 4. |
45
46
  | `AddressType` (type — `'P2PKH' \| 'P2SH' \| 'P2WPKH' \| 'P2TR'`) | `BtcAddressType` (renamed; same shape). See [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 7. |
46
47
 
47
48
  > Note: `BtcWalletAddressType` (`'taproot' | 'segwit'`, wallet-UI choice) is preserved in v2 with the same shape — it is **not** the same thing as `BtcAddressType` (on-chain address format). They coexist; do not blindly rename one to the other.
@@ -56,7 +57,7 @@ Two categories worth distinguishing:
56
57
 
57
58
  | v1 export | v2 replacement |
58
59
  |---|---|
59
- | `CustomProvider` (Hana-wallet window typedecl) | None. Window declaration becomes `unknown` or imports directly from the wallet vendor. Low-level Hana-extension helper functions (`requestAddress`, `requestSigning`, `requestJsonRpc`) ship from `@sodax/sdk` for consumers building their own Hana-based `IIconWalletProvider`. |
60
+ | `CustomProvider` (Hana-wallet window typedecl) | **Pick by what you actually use:** (a) **You only declared `window.hanaWallet.ethereum: CustomProvider` for typedecl quietness, never called methods on it** → replace the type with `unknown` (`declare global { interface Window { hanaWallet: { ethereum: unknown } } }`). 1-line fix. (b) **You called `window.hanaWallet.ethereum.request(...)` or built a custom Hana wallet** → import the named helpers `requestAddress`, `requestSigning`, `requestJsonRpc` from `@sodax/sdk` and use them in place of the raw provider calls. These are the same low-level Hana-extension helpers v1 wrapped, now first-class exports. |
60
61
 
61
62
  ### Error types and guards
62
63
 
@@ -1,46 +1,137 @@
1
1
  # `SodaxConfig` constructor reshape
2
2
 
3
- The v2 `Sodax` constructor accepts a `DeepPartial<SodaxConfig>`. Several config fields renamed or moved between v1 and v2; if your project passed a custom config, check these:
3
+ The v2 `Sodax` constructor accepts a `DeepPartial<SodaxConfig>`. Several config fields renamed, moved, or were added between v1 and v2; if your project passed a custom config, check these.
4
4
 
5
- | v1 location | v2 location |
6
- |---|---|
7
- | `SodaxConfig.swaps` (held solver endpoints AND supported tokens) | Split into two: `SodaxConfig.swaps` (now `SwapsConfig` — supported tokens per chain) and `SodaxConfig.solver` (`{ intentsContract, solverApiEndpoint, protocolIntentsContract }`). |
8
- | `SodaxConfig.rpcConfig` (flat object: one URL per chain field) | `SodaxConfig.rpcConfig` (mapped type keyed by `ChainKey` values; chain-family-specific shapes — see [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 5). |
9
- | `SodaxConfig.hubProviderConfig` | Renamed: `SodaxConfig.hubConfig`. |
10
- | `SodaxConfig.configService` (raw `IConfigApi` instance you injected) | Pass via `new Sodax({ configService: <your impl> })` is gone — `ConfigService` is constructed internally. To inject a custom `IConfigApi`, override via `SodaxConfig.backendApi`. |
5
+ ## v2 `SodaxConfig` shape (source of truth)
6
+
7
+ Defined in `@sodax/types` (`packages/types/src/sodax-config/sodax-config.ts`):
8
+
9
+ ```ts
10
+ type SodaxConfig = {
11
+ fee: PartnerFee | undefined; // global partner fee (overridable per-feature)
12
+ chains: Record<SpokeChainKey, SpokeChainConfig>; // per-spoke-chain config (rpcUrl + tx polling + chain-specific shape)
13
+ swaps: SwapsConfig; // supported swap tokens per chain
14
+ moneyMarket: MoneyMarketConfig; // money market service config
15
+ bridge: BridgeConfig; // bridge partner-fee override
16
+ dex: DexConfig; // DEX service config
17
+ hub: HubConfig; // hub-chain (Sonic) provider config
18
+ api: ApiConfig; // backend API endpoint
19
+ solver: SolverConfig; // intent solver endpoint + contracts
20
+ relay: RelayConfig; // intent-relay endpoint
21
+ };
22
+ ```
23
+
24
+ A matching default `sodaxConfig` const is exported from the same module — `new Sodax()` deep-merges your `DeepPartial<SodaxConfig>` over it.
25
+
26
+ ## v1 shape (for reference)
27
+
28
+ ```ts
29
+ // v1 — packages/sdk/src/shared/entities/Sodax.ts
30
+ type SodaxConfig = {
31
+ swaps?: SolverConfigParams; // { intentsContract, solverApiEndpoint, protocolIntentsContract?, partnerFee? }
32
+ moneyMarket?: MoneyMarketConfigParams;
33
+ migration?: MigrationServiceConfig;
34
+ bridge?: BridgeServiceConfig;
35
+ dex?: DexServiceConfig;
36
+ hubProviderConfig?: EvmHubProviderConfig; // { hubRpcUrl, chainConfig }
37
+ relayerApiEndpoint?: HttpUrl; // single URL string
38
+ backendApiConfig?: BackendApiConfig;
39
+ partners?: PartnerServiceConfig;
40
+ sharedConfig?: typeof defaultSharedConfig;
41
+ };
42
+ ```
43
+
44
+ All v1 fields were **optional**. v1 had **no** top-level `rpcConfig` on `SodaxConfig` — RPC URLs were a separate prop accepted by the framework-layer `SodaxProvider` (alongside the `config` prop carrying `SodaxConfig`); the `Sodax` class constructor itself never received `rpcConfig`.
45
+
46
+ ## v1 → v2 field map
47
+
48
+ | v1 location | v2 location | Notes |
49
+ |---|---|---|
50
+ | `SodaxConfig.swaps` (`SolverConfigParams` — `{ intentsContract, solverApiEndpoint, protocolIntentsContract?, partnerFee? }`) | **Split into two:** `SodaxConfig.solver: SolverConfig` (`{ intentsContract, solverApiEndpoint, protocolIntentsContract }`) and `SodaxConfig.swaps: SwapsConfig` (supported tokens per chain — new in v2). | v1 partner-fee inside `SolverConfigParams` moves to the global `SodaxConfig.fee` slot or per-feature configs. |
51
+ | `SodaxConfig.hubProviderConfig` (`EvmHubProviderConfig` — `{ hubRpcUrl, chainConfig }`) | **`SodaxConfig.hub`** (`HubConfig` — full hub addresses + native token + bnUSD + polling + RPC URL). | Field renamed `hubProviderConfig` → `hub`. Shape expanded: v1 just had RPC URL + chain config; v2 ships the full hub-contract address map. |
52
+ | `SodaxConfig.moneyMarket` (`MoneyMarketConfigParams`) | `SodaxConfig.moneyMarket` (`MoneyMarketConfig` — required, shape changed). | Reshape, see `@sodax/types/src/common/common.ts` MoneyMarketConfig. |
53
+ | `SodaxConfig.bridge` (`BridgeServiceConfig`) | `SodaxConfig.bridge` (`BridgeConfig` — `{ partnerFee }`). | Reshape; smaller. |
54
+ | `SodaxConfig.dex` (`DexServiceConfig`) | `SodaxConfig.dex` (`DexConfig`). | Reshape. |
55
+ | `SodaxConfig.relayerApiEndpoint: HttpUrl` (string) | **`SodaxConfig.relay`** (`RelayConfig` — object with relayer URL + chain-id map). | Renamed + reshaped from string to object. |
56
+ | `SodaxConfig.backendApiConfig` (`BackendApiConfig`) | **`SodaxConfig.api`** (`ApiConfig`). | Renamed. |
57
+ | (Separate `rpcConfig` prop on the framework-layer SodaxProvider, NOT a `SodaxConfig` field) | **`SodaxConfig.chains`** (`Record<SpokeChainKey, SpokeChainConfig>`). | v2 absorbs RPC URLs + polling + chain-specific extras into the `SodaxConfig.chains` mapped type. v1's separate `rpcConfig` provider prop is gone. The standalone `RpcConfig` type still ships from `@sodax/types` for utility use (e.g. the demo's `providers.tsx` declares a local `RpcConfig` then maps values into `chains`), but it is not a `SodaxConfig` field. |
58
+ | `SodaxConfig.migration` (`MigrationServiceConfig`) | **Removed.** Migration service runs with hard-coded defaults. | No replacement on `SodaxConfig`. v2 surfaces customization via per-method params on `sodax.migration.*`, not constructor config — see [`../features/icx-bnusd-baln.md`](../features/icx-bnusd-baln.md). |
59
+ | `SodaxConfig.partners` (`PartnerServiceConfig`) | **Removed.** Partner service runs with defaults. | Per-claim partner-fee config now flows via call-level params on `sodax.partners.*` methods. |
60
+ | `SodaxConfig.sharedConfig` (`typeof defaultSharedConfig`) | **Removed.** | Absorbed into `ConfigService` + per-chain `SpokeChainConfig`. Override individual chains via `SodaxConfig.chains[key]`. |
61
+ | (none in v1) | **`SodaxConfig.fee: PartnerFee \| undefined`** (new). | Global partner-fee, applies to all features unless overridden by feature-level config (`bridge.partnerFee`, money market, etc.). |
62
+ | (v1 had no top-level `configService` injection slot on `SodaxConfig` — `ConfigService` was always constructed internally from `backendApiConfig` + `sharedConfig`.) | Same — `ConfigService` is internal. v2 does **not** expose a typed slot to inject a custom `IConfigApi` either. To swap the backend in tests, point `SodaxConfig.api.baseURL` at a mock server. | See Pitfall below. |
11
63
 
12
64
  Migration:
13
65
 
14
66
  ```diff
15
- const sodax = new Sodax({
67
+ - // v1 typical SodaxConfig literal
68
+ - const sodaxConfig = {
69
+ - hubProviderConfig: { hubRpcUrl: 'https://…', chainConfig: getHubChainConfig() },
70
+ - moneyMarket: getMoneyMarketConfig(hubChainId),
16
71
  - swaps: {
17
72
  - intentsContract: '0x…',
18
73
  - solverApiEndpoint: 'https://…',
19
- - supportedTokens: { /* */ },
74
+ - protocolIntentsContract: '0x',
75
+ - partnerFee: { address: '0x…', percentage: 10 },
20
76
  - },
77
+ - relayerApiEndpoint: 'https://relay.example.com',
78
+ - } satisfies SodaxConfig;
79
+ - // RPC URLs were passed as a SEPARATE prop on the framework-layer SodaxProvider
80
+ - // (alongside the sodaxConfig). The Sodax constructor itself never saw rpcConfig.
81
+
82
+ + // v2 — DeepPartial<SodaxConfig> passed directly to new Sodax(...)
83
+ + const sodax = new Sodax({
84
+ + hub: { /* HubConfig — usually omit, default ships full hub addresses */ },
21
85
  + solver: {
22
86
  + intentsContract: '0x…',
23
87
  + solverApiEndpoint: 'https://…',
88
+ + protocolIntentsContract: '0x…',
24
89
  + },
25
90
  + swaps: {
26
91
  + supportedTokens: { /* per-chain table */ },
27
92
  + },
28
- - rpcConfig: { sonic: 'https://…', arbitrum: 'https://…' },
29
- + rpcConfig: {
30
- + [ChainKeys.SONIC_MAINNET]: 'https://…',
31
- + [ChainKeys.ARBITRUM_MAINNET]: 'https://…',
32
- + [ChainKeys.BITCOIN_MAINNET]: { /* BitcoinRpcConfig shape */ },
93
+ + relay: { /* RelayConfig relayer URL + chain-id map */ },
94
+ + fee: { address: '0x…', percentage: 10 }, // global partner fee (moved out of swaps)
95
+ + chains: {
96
+ + [ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://…' },
97
+ + [ChainKeys.ARBITRUM_MAINNET]: { rpcUrl: 'https://…' },
98
+ + [ChainKeys.BITCOIN_MAINNET]: { /* BitcoinSpokeChainConfig shape */ },
33
99
  + // …
34
100
  + },
35
- - hubProviderConfig: { /* … */ },
36
- + hubConfig: { /* … */ },
37
- });
38
- await sodax.config.initialize();
101
+ + });
102
+ + await sodax.config.initialize();
39
103
  ```
40
104
 
105
+ ## Per-chain `SpokeChainConfig` shape
106
+
107
+ `SodaxConfig.chains` is keyed by `SpokeChainKey`; each entry's value type varies by chain family. The user-overridable surface (`rpcUrl`, polling config, chain-specific extras) is the same set of fields `RpcConfig` covers in v1, but nested inside `SpokeChainConfig` rather than flat. Inspect the type at:
108
+
109
+ - `packages/types/src/chains/chains.ts` — `SpokeChainConfig` discriminated union
110
+ - `packages/types/src/common/common.ts` — `BitcoinRpcConfig`, `StellarRpcConfig`, `InjectiveRpcConfig` (used inside the EVM-non-EVM branches)
111
+
112
+ See [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 5 for the chain-family-specific entry shapes.
113
+
41
114
  ### Pitfall
42
115
 
43
- If you previously injected a custom `ConfigService` for testing (a v1 escape hatch), v2 doesn't accept one at the top level. Inject a custom `IConfigApi` via `SodaxConfig.backendApi.api` instead `ConfigService` consumes it internally on `initialize()`.
116
+ If you previously injected a custom `ConfigService` for testing (a v1 escape hatch), v2 doesn't accept one at the top level and unlike what earlier doc versions claimed, **v2 also doesn't expose a typed slot to inject a custom `IConfigApi`**. The realistic options:
117
+
118
+ - Point `SodaxConfig.api.baseURL` at a local mock backend server.
119
+ - Construct your own `BackendApiService`-compatible object in your app/test bootstrap and swap it in where you control the `Sodax` instance.
120
+
121
+ The `SodaxConfig.api` field is `ApiConfig` (`{ baseURL, timeout, headers }`) — there is no `api.api` sub-field for IConfigApi injection.
122
+
123
+ ### Pitfall — module-scope reads
124
+
125
+ If your code reads hub addresses or default configs **before** the `Sodax` instance exists (module-scope constants), use the re-exported defaults from `@sodax/types` (also flow through `@sodax/sdk` since it re-exports the entire types surface):
126
+
127
+ ```ts
128
+ import { hubConfig, sodaxConfig } from '@sodax/sdk';
129
+
130
+ const HUB_WALLET = hubConfig.addresses.hubWallet; // module-scope OK
131
+ const DEFAULT_RELAY = sodaxConfig.relay; // module-scope OK
132
+ ```
133
+
134
+ `hubConfig` / `sodaxConfig` are the **packaged defaults** — the same values `ConfigService` falls back to if the backend is unreachable. Once you have a `Sodax` instance, prefer `sodax.config.getHubChainConfig()` / `sodax.config.*` so backend-driven updates take effect.
44
135
 
45
136
  ---
46
137
 
@@ -50,3 +141,5 @@ If you previously injected a custom `ConfigService` for testing (a v1 escape hat
50
141
  - [`README.md`](README.md) — migration reference index.
51
142
  - [`../README.md`](../README.md) — migration overview.
52
143
  - [`../checklist.md`](../checklist.md) — top-level migration checklist.
144
+ - [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 5 — per-chain config entry shapes.
145
+ - [`deleted-exports.md`](deleted-exports.md) — `getHubChainConfig()` and other deleted symbols.
package/dist/index.cjs CHANGED
@@ -3899,7 +3899,7 @@ function isValidWalletProviderForChainKey(chainKey, walletProvider) {
3899
3899
  }
3900
3900
 
3901
3901
  // ../types/dist/index.js
3902
- var CONFIG_VERSION = 200;
3902
+ var CONFIG_VERSION = 201;
3903
3903
  function isEvmSpokeChainConfig(value) {
3904
3904
  return typeof value === "object" && value !== null && value.chain.type === "EVM" && value.chain.key !== HUB_CHAIN_KEY;
3905
3905
  }