@sodax/sdk 2.0.0-rc.1 → 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
 
@@ -103,12 +103,21 @@ If your project's v1 wrapper code instantiated `*SpokeProvider` classes, don't t
103
103
  |---|---|
104
104
  | `hubAssets[chainId][address]` (vault lookup) | `token.vault` directly (added to `XToken` — see [`type-system.md`](type-system.md) § 4) |
105
105
  | `hubAssets[chainId][address]` (hub-asset address) | `token.hubAsset` directly |
106
- | `moneyMarketSupportedTokens[chainId]` | `sodax.moneyMarket.getSupportedTokensByChainId(chainKey)` |
107
- | `Object.entries(moneyMarketSupportedTokens)` | `sodax.moneyMarket.getSupportedTokens()` (returns `Record<SpokeChainKey, XToken[]>`) |
108
- | `SodaTokens` registry (vault-validation) | `sodax.config.getMoneyMarketReserveAssets()` or `sodax.moneyMarket.getSupportedReserves()` |
109
- | `solverSupportedTokens[chainId]` | `sodax.config.getSupportedSwapTokensByChainId(chainKey)` |
110
106
  | `baseChainInfo[chain].id` | `baseChainInfo[chain].key` (field renamed) |
111
107
 
108
+ ### Static defaults — still exported, prefer the service API
109
+
110
+ These v1 names still ship through `@sodax/sdk` (re-exported from `@sodax/types`), so a v1 `import { ... } from '@sodax/sdk'` will not break. Once you call `await sodax.config.initialize()`, the dynamic config from the backend supersedes the packaged defaults — read through the service API to see runtime updates (new tokens, fee parameters). Without `initialize()`, both paths return the same packaged values.
111
+
112
+ | v1 export | v2 status | Preferred v2 API |
113
+ |---|---|---|
114
+ | `moneyMarketSupportedTokens[chainId]` | Still exported (`packages/types/src/moneyMarket/moneyMarket.ts`) | `sodax.moneyMarket.getSupportedTokensByChainId(chainKey)` |
115
+ | `Object.entries(moneyMarketSupportedTokens)` (walk pattern) | Const still exported | `sodax.moneyMarket.getSupportedTokens()` (returns `Record<SpokeChainKey, XToken[]>`) |
116
+ | `SodaTokens` registry (vault-validation) | Still exported (`packages/types/src/chains/tokens.ts`) | `sodax.config.getMoneyMarketReserveAssets()` or `sodax.moneyMarket.getSupportedReserves()` |
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 |
120
+
112
121
  ### What replaces them
113
122
 
114
123
  `ConfigService` is a stateful service owned by `Sodax`. It loads chain/token config from the backend API on `await sodax.config.initialize()` and falls back to packaged defaults from `@sodax/types` if the backend is unreachable. After init, every lookup goes through the service:
@@ -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
 
@@ -1,6 +1,12 @@
1
- # Deleted exports inventory
1
+ # Deleted / replaced exports inventory
2
2
 
3
- Every v1 export removed from `@sodax/sdk` and `@sodax/types`, with its v2 replacement. If you see `error TS2305: Module '"@sodax/sdk"' has no exported member '<X>'`, find `<X>` in the left column.
3
+ Every v1 export that's gone or repurposed in v2 — and its v2 successor. If you see `error TS2305: Module '"@sodax/sdk"' has no exported member '<X>'`, find `<X>` in the left column.
4
+
5
+ Two categories worth distinguishing:
6
+ - **Truly deleted** — the v1 symbol is gone; an import will fail compile (`TS2305`).
7
+ - **Name preserved, shape replaced** — a v1 `import` still compiles, but the runtime shape changed. Silent breakage if you read v1-only fields.
8
+
9
+ > Scope note: This doc only lists symbols that **don't import anymore** or **silently changed shape**. For v1 static constants that are still exported in v2 but should yield to the dynamic service API, see [`../breaking-changes/architecture.md`](../breaking-changes/architecture.md) § 2.
4
10
 
5
11
  ### Spoke-provider classes + guards
6
12
 
@@ -24,11 +30,9 @@ Every v1 export removed from `@sodax/sdk` and `@sodax/types`, with its v2 replac
24
30
  | v1 export | v2 replacement |
25
31
  |---|---|
26
32
  | `hubAssets` | `XToken.vault` / `XToken.hubAsset` baked in; or `sodax.config.getOriginalAssetAddress(...)`. See [`../breaking-changes/architecture.md`](../breaking-changes/architecture.md) § 2. |
27
- | `moneyMarketSupportedTokens` | `sodax.moneyMarket.getSupportedTokensByChainId(chainKey)` / `getSupportedTokens()`. |
28
- | `solverSupportedTokens` | `sodax.config.getSupportedSwapTokensByChainId(chainKey)`. |
29
- | `SodaTokens` | `sodax.config.getMoneyMarketReserveAssets()` / `sodax.moneyMarket.getSupportedReserves()`. |
30
- | `getHubChainConfig()` | `sodax.config.*` lookups; specific chain configs are loaded by `ConfigService.initialize()`. |
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". |
31
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. |
32
36
 
33
37
  ### Type aliases
34
38
 
@@ -38,10 +42,10 @@ Every v1 export removed from `@sodax/sdk` and `@sodax/types`, with its v2 replac
38
42
  | `SpokeChainId` (type) | `SpokeChainKey`. |
39
43
  | `EvmChainId` (type) | `EvmChainKey` (subset of `SpokeChainKey`). |
40
44
  | `HubChainId` (type) | `HubChainKey` (literal `'sonic'`). |
41
- | `Token` (type) | `XToken`. See [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 4. |
42
- | `AddressType` (type) | `BtcAddressType`. See [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 7. |
43
- | `BtcWalletAddressType` (type) | `BtcAddressType` (cleaned-up name). |
44
- | `Payload` (type) | None internal `IntentRelayApiService` shape that v1 leaked publicly. Consumers calling the relay layer directly should use `relayTxAndWaitPacket` / `submitTransaction` (which take typed inputs). |
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. |
46
+ | `AddressType` (type — `'P2PKH' \| 'P2SH' \| 'P2WPKH' \| 'P2TR'`) | `BtcAddressType` (renamed; same shape). See [`../breaking-changes/type-system.md`](../breaking-changes/type-system.md) § 7. |
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.
45
49
 
46
50
  ### Constants
47
51
 
@@ -53,24 +57,46 @@ Every v1 export removed from `@sodax/sdk` and `@sodax/types`, with its v2 replac
53
57
 
54
58
  | v1 export | v2 replacement |
55
59
  |---|---|
56
- | `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. |
57
61
 
58
62
  ### Error types and guards
59
63
 
64
+ #### Error types — name preserved, shape replaced
65
+
66
+ The following v1 error types were **plain object literals** `{ code: T; data: GetXxxError<T> }`. v2 keeps the same export names but redefines them as type aliases for the canonical `SodaxError<NarrowCode>` class instance. **A v1 `import { MoneyMarketError } from '@sodax/sdk'` still compiles** — but reading `err.data` will silently fail at runtime because the v2 shape is `{ name, code, feature, message, stack, context, cause }`. Treat these as "shape replaced" rather than deleted.
67
+
68
+ | v1 shape | v2 shape | What to read |
69
+ |---|---|---|
70
+ | `MoneyMarketError<MoneyMarketErrorCode> = { code, data }` | `MoneyMarketError = SodaxError<MoneyMarketErrorCode>` (class instance) | `err.code`, `err.feature === 'moneyMarket'`, `err.context`, `err.cause`. See [`error-code-crosswalk.md`](error-code-crosswalk.md) for code crosswalk. |
71
+ | `BridgeError<BridgeErrorCode> = { code, data }` | `BridgeError = SodaxError<BridgeErrorCode>` | Same pattern; `err.feature === 'bridge'`. |
72
+ | `StakingError<StakingErrorCode> = { code, data }` | `StakingError = SodaxError<StakingErrorCode>` | Same pattern; `err.feature === 'staking'`. |
73
+ | `MigrationError<MigrationErrorCode> = { code, data }` | `MigrationError = SodaxError<MigrationErrorCode>` | Same pattern; `err.feature === 'migration'`. |
74
+
75
+ #### Error types — fully deleted
76
+
60
77
  | v1 export | v2 replacement |
61
78
  |---|---|
62
- | `MoneyMarketError<MoneyMarketErrorCode>`, plus `MoneyMarketErrorCode` | `SodaxError<C>` with `feature: 'moneyMarket'`. See [`error-code-crosswalk.md`](error-code-crosswalk.md) for code crosswalk. |
63
- | `IntentError<IntentErrorCode>`, plus the union | `SodaxError<C>` with `feature: 'swap'`. |
64
- | `StakingError<StakingErrorCode>`, plus the union | `SodaxError<C>` with `feature: 'staking'`. |
65
- | `BridgeError<BridgeErrorCode>`, plus the union | `SodaxError<C>` with `feature: 'bridge'`. |
66
- | `MigrationError<MigrationErrorCode>`, plus the union | `SodaxError<C>` with `feature: 'migration'`. |
67
- | `AssetServiceError<AssetServiceErrorCode>`, plus the union | `SodaxError<C>` with `feature: 'dex'`. |
68
- | `ConcentratedLiquidityError<ConcentratedLiquidityErrorCode>`, plus the union | `SodaxError<C>` with `feature: 'dex'`. |
69
- | `RelayError<RelayErrorCode>`, plus the union | `SodaxError<C>` with relay code on `error.context.relayCode`. |
70
- | `PartnerFeeClaimError<...>` (5 partner errors) | `SodaxError<C>` with `feature: 'partner'`. |
71
- | `isMoneyMarketError`, `isIntentError`, `isStakingError`, `isBridgeError`, `isMigrationError`, `isAssetServiceError`, `isConcentratedLiquidityError`, `isRelayError` (type-guards) | `isSodaxError(e)` + check `e.feature === '<feature>'`, or use `isFeatureError('<feature>')` to build a guard. See [`../breaking-changes/result-and-errors.md`](../breaking-changes/result-and-errors.md) § 6 for migration patterns. |
72
- | `isIntentPostExecutionFailedError(e)` | `isSodaxError(e) && e.feature === 'swap' && e.code === 'EXECUTION_FAILED' && e.context?.phase === 'postExecution'`. |
79
+ | `IntentError<IntentErrorCode>`, plus `IntentErrorCode`, `IntentErrorData` | `SwapError = SodaxError<SwapErrorCode>` (renamed). `feature: 'swap'`. |
80
+ | `AssetServiceError<AssetServiceErrorCode>`, plus the union | `DexError = SodaxError<DexErrorCode>`. `feature: 'dex'`. |
81
+ | `ConcentratedLiquidityError<ConcentratedLiquidityErrorCode>`, plus the union | `DexError = SodaxError<DexErrorCode>` (asset + CL collapsed into one feature). |
82
+ | `RelayError<RelayErrorCode>`, plus the union | `SodaxError<C>` with the lower-level relay code on `error.context.relayCode`. |
83
+ | `SetSwapPreferenceError`, `CreateIntentAutoSwapError`, `WaitIntentAutoSwapError`, `UnknownIntentAutoSwapError`, `ExecuteIntentAutoSwapError` (5 distinct partner error types in `PartnerFeeClaimService.ts`) | `PartnerError = SodaxError<PartnerErrorCode>`. `feature: 'partner'`. |
84
+
85
+ #### Type guards deleted
86
+
87
+ v1 only exposed **specific per-failure-mode guards**. v2 deleted all of these and instead ships **feature-level guards** + helper builders (`isFeatureError('<feature>')`, `isCodeMember(codeSet)`).
88
+
89
+ | v1 deleted guard | v2 replacement |
90
+ |---|---|
91
+ | `isIntentCreationFailedError(e)` | `isSwapCreateIntentError(e)` or `isSodaxError(e) && e.code === 'INTENT_CREATION_FAILED' && e.feature === 'swap'`. |
73
92
  | `isIntentSubmitTxFailedError(e)` | `isSodaxError(e) && e.code === 'TX_SUBMIT_FAILED'`. |
93
+ | `isIntentPostExecutionFailedError(e)` | `isSodaxError(e) && e.feature === 'swap' && e.code === 'EXECUTION_FAILED' && e.context?.phase === 'postExecution'`. |
94
+ | `isWaitUntilIntentExecutedFailed(e)` | `isSodaxError(e) && e.feature === 'swap' && e.code === 'RELAY_TIMEOUT'`. The v1 guard fired when the destination packet never reached `executed`; in v2 that surfaces as the unified `RELAY_TIMEOUT` code (with the underlying relay code on `error.context.relayCode`). |
95
+ | `isIntentCreationUnknownError(e)` | `isSodaxError(e) && e.code === 'UNKNOWN' && e.feature === 'swap'`. |
96
+ | `isMoneyMarketSubmitTxFailedError`, `isMoneyMarketRelayTimeoutError`, `isMoneyMarketCreate{Supply,Borrow,Withdraw,Repay}IntentFailedError`, `isMoneyMarket{Supply,Borrow,Withdraw,Repay}UnknownError` (10 specific guards) | `isMoneyMarketError(e)` (new in v2) for the feature-level check, then narrow on `e.code` / `e.context.action`. |
97
+ | `isCreateIntentAutoSwapError`, `isWaitIntentAutoSwapError`, `isUnknownIntentAutoSwapError`, `isSetSwapPreferenceError` (4 partner guards) | `isPartnerError(e)` (new in v2) for the feature-level check, then narrow on `e.code` / `e.context.action`. |
98
+
99
+ > Note: `isMoneyMarketError`, `isBridgeError`, `isStakingError`, `isMigrationError`, `isSwapError`, `isDexError`, `isPartnerError`, `isRecoveryError` did **not** exist in v1 — v2 added them as new feature-level guards alongside `isSodaxError` and the `isFeatureError('<feature>')` factory. See [`../breaking-changes/result-and-errors.md`](../breaking-changes/result-and-errors.md) § 6 for migration patterns.
74
100
 
75
101
  ### Per-feature param shape
76
102
 
@@ -98,3 +124,4 @@ The [`error TS1360`](https://www.typescriptlang.org/docs/handbook/release-notes/
98
124
  - [`README.md`](README.md) — migration reference index.
99
125
  - [`../README.md`](../README.md) — migration overview.
100
126
  - [`../checklist.md`](../checklist.md) — top-level migration checklist.
127
+ - [`../breaking-changes/architecture.md`](../breaking-changes/architecture.md) § 2 — guidance for v1 static constants that are still exported in v2.