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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +91 -7
  2. package/ai-exported/AGENTS.md +99 -0
  3. package/ai-exported/integration/README.md +41 -0
  4. package/ai-exported/integration/ai-rules.md +75 -0
  5. package/ai-exported/integration/architecture.md +519 -0
  6. package/ai-exported/integration/chain-specifics.md +189 -0
  7. package/ai-exported/integration/features/README.md +19 -0
  8. package/ai-exported/integration/features/auxiliary-services.md +189 -0
  9. package/ai-exported/integration/features/bridge.md +136 -0
  10. package/ai-exported/integration/features/dex.md +182 -0
  11. package/ai-exported/integration/features/icx-bnusd-baln.md +181 -0
  12. package/ai-exported/integration/features/money-market.md +198 -0
  13. package/ai-exported/integration/features/staking.md +166 -0
  14. package/ai-exported/integration/features/swap.md +207 -0
  15. package/ai-exported/integration/quickstart.md +213 -0
  16. package/ai-exported/integration/recipes/README.md +21 -0
  17. package/ai-exported/integration/recipes/backend-server-init.md +69 -0
  18. package/ai-exported/integration/recipes/chain-key-narrowing.md +65 -0
  19. package/ai-exported/integration/recipes/gas-estimation.md +33 -0
  20. package/ai-exported/integration/recipes/initialize-sodax.md +53 -0
  21. package/ai-exported/integration/recipes/raw-tx-flow.md +71 -0
  22. package/ai-exported/integration/recipes/result-and-errors.md +104 -0
  23. package/ai-exported/integration/recipes/signed-tx-flow.md +46 -0
  24. package/ai-exported/integration/recipes/testing.md +101 -0
  25. package/ai-exported/integration/reference/README.md +18 -0
  26. package/ai-exported/integration/reference/chain-keys.md +67 -0
  27. package/ai-exported/integration/reference/error-codes.md +165 -0
  28. package/ai-exported/integration/reference/glossary.md +32 -0
  29. package/ai-exported/integration/reference/public-api.md +138 -0
  30. package/ai-exported/integration/reference/wallet-providers.md +62 -0
  31. package/ai-exported/migration/README.md +58 -0
  32. package/ai-exported/migration/ai-rules.md +80 -0
  33. package/ai-exported/migration/breaking-changes/architecture.md +342 -0
  34. package/ai-exported/migration/breaking-changes/result-and-errors.md +363 -0
  35. package/ai-exported/migration/breaking-changes/type-system.md +321 -0
  36. package/ai-exported/migration/checklist.md +61 -0
  37. package/ai-exported/migration/features/README.md +35 -0
  38. package/ai-exported/migration/features/auxiliary-services.md +156 -0
  39. package/ai-exported/migration/features/bridge.md +125 -0
  40. package/ai-exported/migration/features/dex.md +143 -0
  41. package/ai-exported/migration/features/icx-bnusd-baln.md +151 -0
  42. package/ai-exported/migration/features/money-market.md +214 -0
  43. package/ai-exported/migration/features/staking.md +138 -0
  44. package/ai-exported/migration/features/swap.md +198 -0
  45. package/ai-exported/migration/recipes.md +288 -0
  46. package/ai-exported/migration/reference/README.md +18 -0
  47. package/ai-exported/migration/reference/deleted-exports.md +126 -0
  48. package/ai-exported/migration/reference/error-code-crosswalk.md +104 -0
  49. package/ai-exported/migration/reference/return-shapes.md +49 -0
  50. package/ai-exported/migration/reference/sodax-config.md +52 -0
  51. package/dist/index.cjs +32076 -31544
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +8604 -7136
  54. package/dist/index.d.ts +8604 -7136
  55. package/dist/index.mjs +31893 -31402
  56. package/dist/index.mjs.map +1 -1
  57. package/package.json +20 -12
@@ -0,0 +1,151 @@
1
+ # Token migration (ICX/bnUSD/BALN) — v1 → v2
2
+
3
+ Pure-SDK migration playbook for `MigrationService` (the SDK module — not v1→v2 SDK porting).
4
+
5
+ Pair: [`../../integration/features/icx-bnusd-baln.md`](../../integration/features/icx-bnusd-baln.md).
6
+
7
+ ## TL;DR
8
+
9
+ 1. **Drop `spokeProvider`. Pass `walletProvider` directly.**
10
+ 2. **Add `srcChainKey` + `srcAddress` to every action params.** All migration param types (`MigrationParams<K>`, `UnifiedBnUSDMigrateParams<K>`, `IcxToSodaMigrateParams<K>`, `RevertSodaToIcxParams<K>`, `BalnSwapParams<K>`) gained both fields and a `<K>` generic.
11
+ 3. **All 4 orchestrator methods return `Result<TxHashPair>`.** v1 returned a string tx hash and threw on error.
12
+ 4. **`migratebnUSD` carries `direction` on error context** — `'forward'` (legacy → new) or `'reverse'` (new → legacy). The SDK detects direction from `(srcToken, dstToken)` addresses.
13
+ 5. **`BalnSwapService` lock-management methods STILL THROW.** `claim`, `claimUnstaked`, `stake`, `unstake`, `cancelUnstake`, `getDetailedUserLocks` — these 6 methods preserve the v1 throw-on-error contract. Wrap in `try/catch` until a future cleanup release converts them to `Result`.
14
+ 6. **Errors → `SodaxError` + `Result<T>`.** v1's `MigrationError<MigrationErrorCode>` is gone.
15
+
16
+ ## Type / symbol cheat sheet
17
+
18
+ ### Field-level renames
19
+
20
+ | Type | v1 shape | v2 shape | Notes |
21
+ |---|---|---|---|
22
+ | `MigrationParams` | non-generic | `MigrationParams<K extends SpokeChainKey>` with `srcChainKey`, `srcAddress`, `amount`, `dstAddress?` | Generic added. |
23
+ | `UnifiedBnUSDMigrateParams` | non-generic | `UnifiedBnUSDMigrateParams<K>` extends `MigrationParams<K>` with `srcToken`, `dstToken` | Generic added. |
24
+ | `BalnSwapParams` | `{ amount, lockPeriodMonths }` | `MigrationParams<K> & { lockPeriodMonths }` | Adds chain context. |
25
+
26
+ ### Deleted symbols
27
+
28
+ - `MigrationError<MigrationErrorCode>` and `isMigrationError` — replaced by `SodaxError<C>` + `feature: 'migration'`.
29
+ - The 6 v1 separate-method-per-direction names (`migrateBnUSDForward`, `migrateBnUSDReverse`, …) — collapsed into `migratebnUSD` with auto-direction detection on `error.context.direction`.
30
+
31
+ ### v1 → v2 error code crosswalk
32
+
33
+ | v1 `MigrationErrorCode` | v2 code + context |
34
+ |---|---|
35
+ | `MIGRATE_BNUSD_FORWARD_FAILED` | `EXECUTION_FAILED` (`action: 'migratebnUSD'`, `direction: 'forward'`) |
36
+ | `MIGRATE_BNUSD_REVERSE_FAILED` | `EXECUTION_FAILED` (`action: 'migratebnUSD'`, `direction: 'reverse'`) |
37
+ | `MIGRATE_ICX_TO_SODA_FAILED` | `EXECUTION_FAILED` (`action: 'migrateIcxToSoda'`) |
38
+ | `REVERT_MIGRATE_SODA_TO_ICX_FAILED` | `EXECUTION_FAILED` (`action: 'revertMigrateSodaToIcx'`) |
39
+ | `MIGRATE_BALN_FAILED` | `EXECUTION_FAILED` (`action: 'migrateBaln'`) |
40
+ | `GET_AVAILABLE_AMOUNT_FAILED` | `LOOKUP_FAILED` (`method: 'getAvailableAmount'`) |
41
+
42
+ `migratebnUSD` additionally has `phase: 'destinationExecution'` on errors from its secondary `waitUntilIntentExecuted` watcher (after the primary relay completes, bnUSD waits for the destination contract to finalize).
43
+
44
+ ## Per-method delta
45
+
46
+ ### `migratebnUSD` (replaces v1 forward + reverse methods)
47
+
48
+ ```diff
49
+ - // v1 had two methods:
50
+ - await sodax.migration.migrateBnUSDForward({ amount, /* … */ }, spokeProvider);
51
+ - await sodax.migration.migrateBnUSDReverse({ amount, /* … */ }, spokeProvider);
52
+
53
+ + // v2 has one method; SDK detects direction from (srcToken, dstToken):
54
+ + const result = await sodax.migration.migratebnUSD({
55
+ + params: {
56
+ + srcChainKey, srcAddress,
57
+ + srcToken, // legacy or new bnUSD
58
+ + dstToken, // the other one
59
+ + amount,
60
+ + dstAddress,
61
+ + },
62
+ + raw: false,
63
+ + walletProvider,
64
+ + });
65
+ + if (!result.ok) {
66
+ + const dir = result.error.context?.direction; // 'forward' | 'reverse'
67
+ + /* … */
68
+ + }
69
+ ```
70
+
71
+ ### `migrateIcxToSoda` / `revertMigrateSodaToIcx` / `migrateBaln`
72
+
73
+ Standard pattern:
74
+
75
+ ```diff
76
+ - await sodax.migration.migrateIcxToSoda({ amount }, spokeProvider);
77
+ + const result = await sodax.migration.migrateIcxToSoda({
78
+ + params: { srcChainKey: ChainKeys.ICON_MAINNET, srcAddress: 'hx…', amount },
79
+ + raw: false,
80
+ + walletProvider: iconWp,
81
+ + });
82
+ + if (!result.ok) return;
83
+ + const { srcChainTxHash, dstChainTxHash } = result.value;
84
+ ```
85
+
86
+ `migrateBaln` adds `lockPeriodMonths: 0 | 1 | 2 | 3 | 6 | 12 | 18 | 24` to the params. Reward multiplier ranges 0.5x (0 months) – 1.5x (24 months).
87
+
88
+ ### Approve / allowance — action-discriminated
89
+
90
+ ```ts
91
+ await sodax.migration.approve({
92
+ params: { srcChainKey, srcAddress, amount, action: 'migrateBaln' /* or migratebnUSD | migrateIcxToSoda | revertMigrateSodaToIcx */ },
93
+ raw: false,
94
+ walletProvider,
95
+ });
96
+
97
+ const allowed = await sodax.migration.isAllowanceValid({
98
+ params: { srcChainKey, srcAddress, amount, action: 'migrateBaln' },
99
+ raw: true, // read-only
100
+ });
101
+ ```
102
+
103
+ ### `getAvailableAmount` (claimable from partial migration)
104
+
105
+ ```diff
106
+ - const amount: bigint = await sodax.migration.icx.getAvailableAmount(spokeProvider);
107
+ + const result = await sodax.migration.icxMigration.getAvailableAmount(); // sub-service renamed; takes no args in v2
108
+ + if (!result.ok) return 0n;
109
+ + const amount = result.value;
110
+ ```
111
+
112
+ ### BALN lock management — STILL THROWS
113
+
114
+ ```diff
115
+ try {
116
+ - const tx = await sodax.migration.balnSwapService.stake({ amount, lockPeriod }, spokeProvider);
117
+ + const tx = await sodax.migration.balnSwapService.stake({
118
+ + params: { srcChainKey, srcAddress, amount, lockPeriodMonths },
119
+ + raw: false,
120
+ + walletProvider,
121
+ + });
122
+ /* … */
123
+ } catch (e) {
124
+ /* still v1-style: catch the throw */
125
+ }
126
+ ```
127
+
128
+ `claim`, `claimUnstaked`, `unstake`, `cancelUnstake`, `getDetailedUserLocks` follow the same pattern. They preserve the throw-on-error contract for now — future cleanup will Result-wrap them.
129
+
130
+ ## Pitfalls
131
+
132
+ Cross-cutting traps (Result destructuring, error-model migration, srcChain/dstChain renames, etc.) live in [`../ai-rules.md`](../ai-rules.md). The list below is feature-specific — typecheck fingerprints, return-shape diffs, and gotchas unique to this feature.
133
+
134
+ 1. **`migratebnUSD` direction detection.** v1 had explicit forward/reverse methods; v2 detects from `(srcToken, dstToken)`. If both are on the same side (both legacy or both new), the SDK rejects with `VALIDATION_FAILED`. Use the `direction` field on `error.context` to disambiguate in error messaging.
135
+ 2. **BALN lock methods don't return `Result`.** Be careful migrating wrappers — if your wrapper assumes Result-shape, lock methods will produce `undefined.ok` runtime errors. Keep the `try/catch` shape.
136
+ 3. **`lockPeriodMonths` is a literal union, not arbitrary `number`.** TypeScript rejects `lockPeriodMonths: 7`. Allowed values: `0 | 1 | 2 | 3 | 6 | 12 | 18 | 24`.
137
+ 4. **`getAvailableAmount` lives on the sub-service `sodax.migration.icxMigration` and takes no arguments.** v1 expected `(spokeProvider)`; v2 reads on-chain SODA liquidity directly from the hub provider, so the method needs no chain context. Sub-service field names: `sodax.migration.icxMigration`, `sodax.migration.bnUSDMigrationService`, `sodax.migration.balnSwapService`.
138
+ 5. **`destinationExecution` phase on bnUSD errors** — these errors land **after** the relay succeeds. The spoke and hub txs may already exist; the destination-side finalization is what failed. Distinguish from primary relay errors (`phase: 'relay'`) when surfacing UX.
139
+
140
+ ## Verification
141
+
142
+ ```bash
143
+ pnpm -C <your-app-dir> checkTs
144
+
145
+ grep -rE "spokeProvider:\s*\w+|migrateBnUSDForward\b|migrateBnUSDReverse\b|isMigrationError\b|MigrationError\b" src/
146
+ ```
147
+
148
+ ## Cross-references
149
+
150
+ - v2 token migration usage: [`../../integration/features/icx-bnusd-baln.md`](../../integration/features/icx-bnusd-baln.md).
151
+ - Cross-cutting prerequisites listed in [`../README.md`](../README.md).
@@ -0,0 +1,214 @@
1
+ # Money Market migration — v1 → v2
2
+
3
+ Pure-SDK migration playbook for `MoneyMarketService`.
4
+
5
+ Pair: [`../../integration/features/money-market.md`](../../integration/features/money-market.md).
6
+
7
+ ## TL;DR
8
+
9
+ 1. **Drop `spokeProvider` from every params object.** Pass `walletProvider` directly into the SDK call payload alongside `params` and `raw: false`.
10
+ 2. **Add `srcChainKey` + `srcAddress` to every action params object.** v2's `MoneyMarketParams<K>` requires both; v1 didn't have them at all.
11
+ 3. **Mutations resolve `Result<TxHashPair>`, not throw.** v1 mutation methods threw; v2 returns `{ ok: false, error }` on SDK-level failure. Branch on `result.ok`.
12
+ 4. **`MoneyMarketSupplyParams` etc. are now generic** (`MoneyMarketSupplyParams<K extends SpokeChainKey>`). Add a chain-key generic to your params variables, or let TS infer from a literal `srcChainKey`.
13
+ 5. **Replace `moneyMarketSupportedTokens[chainId]` with `sodax.moneyMarket.getSupportedTokensByChainId(chainKey)`.**
14
+ 6. **Replace `hubAssets[chainId][address]?.vault` with `token.vault`** (now baked into `XToken`). Same for `token.hubAsset`.
15
+ 7. **`sodax.moneyMarket.getAToken(...)` returns `Erc20Token & { chainKey }`, not a full `XToken`.** No `hubAsset` / `vault` on the result — look up the full `XToken` via `sodax.config.getMoneyMarketToken(chainKey, address)` if needed.
16
+ 8. **Errors → `SodaxError` + `Result<T>`.** v1's `MoneyMarketError<MoneyMarketErrorCode>` is gone. The CODE moved from `error.code` to `error.message`-style? **No** — it's still on `error.code`, but the union changed (see crosswalk below).
17
+
18
+ ## Type / symbol cheat sheet
19
+
20
+ ### Field-level renames
21
+
22
+ | Type | v1 shape | v2 shape | Notes |
23
+ |---|---|---|---|
24
+ | `MoneyMarketSupplyParams` | `{ token, amount, action }` | `{ srcChainKey, srcAddress, token, amount, action, dstChainKey?, dstAddress? }` | Same template for borrow / withdraw / repay. Now generic in `K extends SpokeChainKey`. |
25
+ | `MoneyMarketBorrowParams` | non-generic | `MoneyMarketBorrowParams<K>` | Optional `dstChainKey`/`dstAddress` for cross-chain delivery. |
26
+ | `MoneyMarketRepayParams` | non-generic | `MoneyMarketRepayParams<K>` | Optional `dstChainKey`/`dstAddress` for paying off debt on a different chain. |
27
+ | `XToken` | `xChainId` | `chainKey` | Type renamed `Token` → `XToken`. New fields `vault`, `hubAsset` baked in. |
28
+
29
+ ### Deleted symbols
30
+
31
+ - `moneyMarketSupportedTokens` — `Record<SpokeChainKey, XToken[]>` global. Use `sodax.moneyMarket.getSupportedTokensByChainId(chainKey)` / `getSupportedTokens()`.
32
+ - `hubAssets` — vault address lookup global. Use `XToken.vault` / `XToken.hubAsset` directly.
33
+ - `SodaTokens` — vault-validation registry. Use `sodax.config.getMoneyMarketReserveAssets()`.
34
+ - `MoneyMarketError<MoneyMarketErrorCode>` and `isMoneyMarketError` — replaced by `SodaxError<C>` + `isSodaxError(e) && e.feature === 'moneyMarket'`.
35
+
36
+ ### v1 → v2 error code crosswalk (money-market-specific)
37
+
38
+ | v1 `MoneyMarketErrorCode` | v2 code + context |
39
+ |---|---|
40
+ | `CREATE_SUPPLY_INTENT_FAILED` | `INTENT_CREATION_FAILED` (`action: 'supply'`) |
41
+ | `CREATE_BORROW_INTENT_FAILED` | `INTENT_CREATION_FAILED` (`action: 'borrow'`) |
42
+ | `CREATE_WITHDRAW_INTENT_FAILED` | `INTENT_CREATION_FAILED` (`action: 'withdraw'`) |
43
+ | `CREATE_REPAY_INTENT_FAILED` | `INTENT_CREATION_FAILED` (`action: 'repay'`) |
44
+ | `SUPPLY_FAILED` | `EXECUTION_FAILED` (`action: 'supply'`) |
45
+ | `BORROW_FAILED` | `EXECUTION_FAILED` (`action: 'borrow'`) |
46
+ | `WITHDRAW_FAILED` | `EXECUTION_FAILED` (`action: 'withdraw'`) |
47
+ | `REPAY_FAILED` | `EXECUTION_FAILED` (`action: 'repay'`) |
48
+ | `ALLOWANCE_CHECK_FAILED` | `ALLOWANCE_CHECK_FAILED` (unchanged) |
49
+ | `APPROVE_FAILED` | `APPROVE_FAILED` (unchanged) |
50
+ | `GAS_ESTIMATION_FAILED` | `GAS_ESTIMATION_FAILED` (unchanged) |
51
+
52
+ ## Per-method delta
53
+
54
+ ### `supply`
55
+
56
+ ```diff
57
+ - const params: MoneyMarketSupplyParams = {
58
+ - token: token.address,
59
+ - amount: parseUnits('100', 6),
60
+ - action: 'supply',
61
+ - };
62
+ - const result = await sodax.moneyMarket.supply({ params, spokeProvider });
63
+ - // result throws on failure, or returns the tx hash
64
+ + const params: MoneyMarketSupplyParams<typeof srcChainKey> = {
65
+ + srcChainKey: ChainKeys.ARBITRUM_MAINNET,
66
+ + srcAddress: '0x…',
67
+ + token: token.address,
68
+ + amount: parseUnits('100', 6),
69
+ + action: 'supply',
70
+ + };
71
+ + const result = await sodax.moneyMarket.supply({ params, raw: false, walletProvider });
72
+ + if (!result.ok) {
73
+ + // result.error: SodaxError with feature: 'moneyMarket'
74
+ + return;
75
+ + }
76
+ + const { srcChainTxHash, dstChainTxHash } = result.value;
77
+ ```
78
+
79
+ ### `borrow` — gain cross-chain delivery
80
+
81
+ If you ported a same-chain borrow, no new fields needed — just `srcChainKey` + `srcAddress`. For cross-chain delivery (which v1 didn't expose this cleanly), add `dstChainKey` and `dstAddress`:
82
+
83
+ ```ts
84
+ await sodax.moneyMarket.borrow({
85
+ params: {
86
+ srcChainKey: ChainKeys.ARBITRUM_MAINNET,
87
+ srcAddress,
88
+ token: USDC.address,
89
+ amount,
90
+ action: 'borrow',
91
+ dstChainKey: ChainKeys.STELLAR_MAINNET, // NEW in v2
92
+ dstAddress: 'G…', // NEW in v2
93
+ },
94
+ raw: false,
95
+ walletProvider,
96
+ });
97
+ ```
98
+
99
+ ### `repay` — pay from a different chain than the debt
100
+
101
+ Similar: v2 lets the spender chain (`srcChainKey`) differ from the debt chain (`dstChainKey`):
102
+
103
+ ```ts
104
+ await sodax.moneyMarket.repay({
105
+ params: {
106
+ srcChainKey: fromChain,
107
+ srcAddress: fromAddress,
108
+ token: tokenOnFromChain.address,
109
+ amount,
110
+ action: 'repay',
111
+ dstChainKey: debtChain,
112
+ dstAddress: debtAddress,
113
+ },
114
+ raw: false,
115
+ walletProvider: walletOnFromChain,
116
+ });
117
+ ```
118
+
119
+ ### `approve` / `isAllowanceValid`
120
+
121
+ ```diff
122
+ - const allowed = await sodax.moneyMarket.isAllowanceValid({ params, spokeProvider });
123
+ + const allowed = await sodax.moneyMarket.isAllowanceValid({
124
+ + params, // includes srcChainKey, srcAddress, action
125
+ + raw: true, // read-only — walletProvider not needed
126
+ + });
127
+ + if (!allowed.ok) return false;
128
+ + if (!allowed.value) await sodax.moneyMarket.approve({ params, raw: false, walletProvider });
129
+ ```
130
+
131
+ The `params.action` field discriminates which token gets approved (relevant for repay where the spent token may differ).
132
+
133
+ ## Replacing the static lookups
134
+
135
+ ```diff
136
+ - import { moneyMarketSupportedTokens, hubAssets } from '@sodax/types';
137
+ - const supplyTokens = moneyMarketSupportedTokens[chainId];
138
+ + const supplyTokens = sodax.moneyMarket.getSupportedTokensByChainId(chainKey);
139
+
140
+ - const allTokens = Object.entries(moneyMarketSupportedTokens)
141
+ - .flatMap(([chainId, tokens]) => tokens.map(t => ({ ...t, xChainId: chainId })));
142
+ + const allTokens = Object.entries(sodax.moneyMarket.getSupportedTokens())
143
+ + .flatMap(([_chainKey, tokens]) => tokens); // tokens already carry chainKey in v2
144
+ ```
145
+
146
+ ```diff
147
+ - const vault = hubAssets[chainId]?.[token.address]?.vault;
148
+ + const vault = token.vault; // baked into XToken
149
+ ```
150
+
151
+ ## Worked example — supply flow
152
+
153
+ A single supply call site, before and after. Shows every change in one place: spoke-provider drop, params shape, return shape, field rename.
154
+
155
+ ```diff
156
+ - const params: MoneyMarketSupplyParams = {
157
+ - token: token.address,
158
+ - amount: parseUnits(amount, token.decimals),
159
+ - action: 'supply',
160
+ - };
161
+ - const txHash = await sodax.moneyMarket.supply({ params, spokeProvider });
162
+ - // throws on failure
163
+ + const params: MoneyMarketSupplyParams<typeof srcChainKey> = {
164
+ + srcChainKey, // NEW: required
165
+ + srcAddress, // NEW: required (GetAddressType<K>)
166
+ + token: token.address,
167
+ + amount: parseUnits(amount, token.decimals),
168
+ + action: 'supply',
169
+ + };
170
+ + const result = await sodax.moneyMarket.supply({
171
+ + params,
172
+ + raw: false, // NEW: discriminator
173
+ + walletProvider, // NEW: was inside spokeProvider in v1
174
+ + });
175
+ + if (!result.ok) {
176
+ + // result.error: SodaxError with feature: 'moneyMarket', context.action: 'supply'
177
+ + return;
178
+ + }
179
+ + const { srcChainTxHash, dstChainTxHash } = result.value; // TxHashPair, not single hash
180
+
181
+ const successData: ActionSuccessData = {
182
+ /* … */
183
+ - destinationChainId: token.xChainId, // OLD field name
184
+ + destinationChainId: token.chainKey, // RENAMED
185
+ txHash: srcChainTxHash,
186
+ };
187
+ ```
188
+
189
+ ## Pitfalls
190
+
191
+ Cross-cutting traps (Result destructuring, error-model migration, srcChain/dstChain renames, etc.) live in [`../ai-rules.md`](../ai-rules.md). The list below is feature-specific — typecheck fingerprints, return-shape diffs, and gotchas unique to this feature.
192
+
193
+ 1. **Forgetting `srcChainKey` + `srcAddress` in params.** TypeScript surfaces this as `error TS1360: Type '{ token, amount, action }' does not satisfy the expected type 'MoneyMarketSupplyParams'`. Add both required fields.
194
+ 2. **Borrow/repay default delivery to source.** Omit `dstChainKey`/`dstAddress` if you want same-chain. Don't pass them as the same value as `srcChainKey` / `srcAddress` — let the default kick in.
195
+ 3. **`sodax.moneyMarket.getAToken(...)` returns a partial token.** `Erc20Token & { chainKey }`, not a full `XToken` — no `vault` / `hubAsset` on the result. Look up the full `XToken` via `sodax.config.getMoneyMarketToken(chainKey, address)` separately if you need those fields.
196
+ 4. **`hubAssets` is gone.** Anything that walked it for vault lookup must use `token.vault` directly.
197
+ 5. **`baseChainInfo[chain].id` is gone — entries have `.key`.** Common in `ChainSelector`-style components.
198
+ 6. **`spokeProvider.chainConfig.chain.type === 'EVM'`** is gone. Use `getChainType(chainKey) === 'EVM'` from `@sodax/sdk`.
199
+ 7. **`Number(chainKey)` returns `NaN` for non-numeric keys.** `ChainKeys.ICON_MAINNET` is `'0x1.icon'`; numeric coercions break.
200
+
201
+ ## Verification
202
+
203
+ ```bash
204
+ pnpm -C <your-app-dir> checkTs
205
+
206
+ # Targeted scans:
207
+ grep -rE "spokeProvider:\s*\w+|moneyMarketSupportedTokens|\bhubAssets\b" src/
208
+ grep -rE "isMoneyMarketError\b|MoneyMarketError\b" src/
209
+ ```
210
+
211
+ ## Cross-references
212
+
213
+ - v2 money market usage: [`../../integration/features/money-market.md`](../../integration/features/money-market.md).
214
+ - Cross-cutting prerequisites listed in [`../README.md`](../README.md).
@@ -0,0 +1,138 @@
1
+ # Staking migration — v1 → v2
2
+
3
+ Pure-SDK migration playbook for `StakingService`.
4
+
5
+ Pair: [`../../integration/features/staking.md`](../../integration/features/staking.md).
6
+
7
+ ## TL;DR
8
+
9
+ 1. **Drop `spokeProvider` from every params object.** Pass `walletProvider` directly into the SDK call.
10
+ 2. **Add `srcChainKey` + `srcAddress` to every `*Params<K>`.** `account` field is renamed to `srcAddress`.
11
+ 3. **All 5 staking actions are cross-chain.** Even though staking writes happen on the hub, every SDK method (`stake`, `unstake`, `instantUnstake`, `claim`, `cancelUnstake`) accepts `srcChainKey: K extends SpokeChainKey` and relays spoke→hub via `relayTxAndWaitPacket`. **Return shape is always `Result<TxHashPair>`.**
12
+ 4. **`approve` is an exception — it returns `Result<TxReturnType<K, false>>` (single hash).** Approve is spoke-only (no relay) — it just spends ERC20 allowance on the source chain.
13
+ 5. **Approve and allowance are action-discriminated.** `staking.approve` and `staking.isAllowanceValid` take a `StakingParamsUnion` discriminated by `params.action`. `'stake'` approves SODA; `'unstake'` and `'instantUnstake'` approve xSoda.
14
+ 6. **Info getter signatures changed.** v1 took `spokeProvider`; v2 takes `(srcAddress, srcChainKey)`. The SDK derives the hub wallet internally.
15
+ 7. **Hub-only / amount-only reads have no chain context.** `getStakingConfig()`, `getStakeRatio(amount)`, `getInstantUnstakeRatio(amount)`, `getConvertedAssets(amount)` — none accept `srcChainKey`. (Take `bigint` amount directly, not an object.) `getStakeRatio` returns `Result<[xSodaAmount, previewDepositAmount]>` (a tuple — both are bigints).
16
+ 8. **Errors → `SodaxError` + `Result<T>`.** v1's `StakingError<StakingErrorCode>` is gone.
17
+
18
+ ## Type / symbol cheat sheet
19
+
20
+ ### Field-level renames
21
+
22
+ | Type | v1 shape | v2 shape | Notes |
23
+ |---|---|---|---|
24
+ | `StakeParams` | `{ amount, account, minReceive, action: 'stake' }` | `{ srcChainKey, srcAddress, amount, minReceive, action: 'stake' }` | Now generic `<K>`. `account` → `srcAddress`. |
25
+ | `UnstakeParams` | `{ amount, account, action: 'unstake' }` | `{ srcChainKey, srcAddress, amount, action: 'unstake' }` | |
26
+ | `InstantUnstakeParams` | `{ amount, minAmount, account, action: 'instantUnstake' }` | `{ srcChainKey, srcAddress, amount, minAmount, action: 'instantUnstake' }` | |
27
+ | `ClaimParams` | `{ requestId, amount, action: 'claim' }` | `{ srcChainKey, srcAddress, requestId, amount, action: 'claim' }` | Adds chain context. |
28
+ | `CancelUnstakeParams` | `{ requestId, action: 'cancelUnstake' }` | `{ srcChainKey, srcAddress, requestId, action: 'cancelUnstake' }` | Adds chain context. |
29
+ | `getStakingInfo` (read) | `(spokeProvider) => Promise<StakingInfo>` | `(srcAddress, srcChainKey) => Promise<Result<StakingInfo>>` | Renamed to `getStakingInfoFromSpoke` (the v1 `getStakingInfo` was hub-only and is not surfaced now). |
30
+ | `getUnstakingInfo` (read) | `(userAddress, spokeProvider)` | `(srcAddress, srcChainKey)` | v1 ignored `userAddress`; v2 reads it for real. |
31
+ | `getUnstakingInfoWithPenalty` (read) | new (v2) | `(srcAddress, srcChainKey) => Promise<Result<UnstakingInfo & { requestsWithPenalty: UnstakeRequestWithPenalty[] }>>` | Wraps `getUnstakingInfo`'s base shape with a penalty-augmented request list. |
32
+
33
+ ### Deleted symbols
34
+
35
+ - `StakingError<StakingErrorCode>` and `isStakingError` — replaced by `SodaxError<C>` + `feature: 'staking'`.
36
+ - v1 `getStakingInfo(hubAddress, …)` — not exposed as a public method in v2. Use `getStakingInfoFromSpoke(srcAddress, srcChainKey)`; the SDK derives the hub wallet via `HubService.getUserHubWalletAddress` internally.
37
+ - `spokeProvider instanceof SonicSpokeProvider` runtime checks — replace with `isHubChainKeyType(chainKey)` from `@sodax/sdk`.
38
+
39
+ ### v1 → v2 error code crosswalk (staking-specific)
40
+
41
+ | v1 `StakingErrorCode` | v2 code + context |
42
+ |---|---|
43
+ | `STAKE_FAILED` | `EXECUTION_FAILED` (`action: 'stake'`) |
44
+ | `UNSTAKE_FAILED` | `EXECUTION_FAILED` (`action: 'unstake'`) |
45
+ | `INSTANT_UNSTAKE_FAILED` | `EXECUTION_FAILED` (`action: 'instantUnstake'`) |
46
+ | `CLAIM_FAILED` | `EXECUTION_FAILED` (`action: 'claim'`) |
47
+ | `CANCEL_UNSTAKE_FAILED` | `EXECUTION_FAILED` (`action: 'cancelUnstake'`) |
48
+ | `GET_STAKING_INFO_FAILED` | `LOOKUP_FAILED` (`method: 'getStakingInfo'` or `'getStakingInfoFromSpoke'`) |
49
+ | `GET_UNSTAKING_INFO_FAILED` | `LOOKUP_FAILED` (`method: 'getUnstakingInfo'`) |
50
+ | `GET_STAKING_CONFIG_FAILED` | `LOOKUP_FAILED` (`method: 'getStakingConfig'`) |
51
+ | `GET_STAKE_RATIO_FAILED` | `LOOKUP_FAILED` (`method: 'getStakeRatio'`) |
52
+
53
+ ## Per-method delta
54
+
55
+ ### `stake`
56
+
57
+ ```diff
58
+ - await sodax.staking.stake({ amount, account, minReceive, action: 'stake' }, spokeProvider);
59
+ + const result = await sodax.staking.stake({
60
+ + params: {
61
+ + srcChainKey: ChainKeys.ARBITRUM_MAINNET,
62
+ + srcAddress: '0x…',
63
+ + amount, minReceive,
64
+ + action: 'stake',
65
+ + },
66
+ + raw: false,
67
+ + walletProvider,
68
+ + });
69
+ + if (!result.ok) return;
70
+ + const { srcChainTxHash, dstChainTxHash } = result.value;
71
+ ```
72
+
73
+ ### `unstake` / `instantUnstake` / `claim` / `cancelUnstake`
74
+
75
+ Same shape as stake. `account` → `srcAddress`. `requestId` (claim, cancelUnstake) is unchanged.
76
+
77
+ ### `approve` / `isAllowanceValid` — action-discriminated
78
+
79
+ ```diff
80
+ - await sodax.staking.approveStake({ amount, account, ... }, spokeProvider);
81
+ + await sodax.staking.approve({
82
+ + params: { srcChainKey, srcAddress, amount, action: 'stake' },
83
+ + raw: false,
84
+ + walletProvider,
85
+ + });
86
+ ```
87
+
88
+ For `isAllowanceValid`:
89
+
90
+ ```ts
91
+ const result = await sodax.staking.isAllowanceValid({
92
+ params: { srcChainKey, srcAddress, amount, action: 'stake' },
93
+ raw: true, // read-only
94
+ });
95
+ ```
96
+
97
+ ### Info reads
98
+
99
+ ```diff
100
+ - const info = await sodax.staking.getStakingInfo(spokeProvider);
101
+ + const result = await sodax.staking.getStakingInfoFromSpoke(srcAddress, srcChainKey);
102
+ + if (!result.ok) return;
103
+ + const info = result.value;
104
+ ```
105
+
106
+ For amount-only reads (no chain context):
107
+
108
+ ```ts
109
+ const result = await sodax.staking.getStakeRatio(parseUnits('100', 18));
110
+ if (result.ok) {
111
+ const [xSodaAmount, previewDepositAmount] = result.value;
112
+ }
113
+ ```
114
+
115
+ ## Pitfalls
116
+
117
+ Cross-cutting traps (Result destructuring, error-model migration, srcChain/dstChain renames, etc.) live in [`../ai-rules.md`](../ai-rules.md). The list below is feature-specific — typecheck fingerprints, return-shape diffs, and gotchas unique to this feature.
118
+
119
+ 1. **Wrong return shape for actions.** Treating `stake/unstake/etc.` as returning `Result<TxReturnType<K, false>>` (single hash) is **wrong** — they return `Result<TxHashPair>` because they always relay spoke→hub. Only `approve` returns a single hash.
120
+ 2. **Forgetting `raw: true` on the allowance query.** TypeScript error: `Property 'walletProvider' is missing`. `isAllowanceValid` requires `WalletProviderSlot<K, Raw>`; `raw: false` would force a wallet provider. Use `raw: true` for read-only.
121
+ 3. **Forgetting to remove the v1 `account` field from params.** v2 uses `srcAddress`. If both are set, TypeScript rejects the literal.
122
+ 4. **`getStakingInfo(hubAddress)` is not a public method in v2.** v1 had it for direct hub queries. v2 has `getStakingInfoFromSpoke(srcAddress, srcChainKey)` which derives the hub wallet internally. Use the spoke variant.
123
+ 5. **`UnstakingInfo` no longer accepts `userAddress` separately.** v1 took both `spokeProvider` and `userAddress` props but ignored `userAddress` inside. v2 takes `srcAddress` and uses it.
124
+
125
+ ## Verification
126
+
127
+ ```bash
128
+ pnpm -C <your-app-dir> checkTs
129
+
130
+ # Targeted scans:
131
+ grep -rE "spokeProvider:\s*\w+|account:\s*[`'][^`']+['\"`]" src/ # leftover v1 patterns
132
+ grep -rE "isStakingError\b|StakingError\b" src/
133
+ ```
134
+
135
+ ## Cross-references
136
+
137
+ - v2 staking usage: [`../../integration/features/staking.md`](../../integration/features/staking.md).
138
+ - Cross-cutting prerequisites listed in [`../README.md`](../README.md).