@sodax/skills 2.0.0-rc.12 → 2.0.0-rc.13

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.
@@ -7,6 +7,7 @@
7
7
  "./skills/sodax-sdk/bridge",
8
8
  "./skills/sodax-sdk/staking",
9
9
  "./skills/sodax-sdk/dex",
10
+ "./skills/sodax-sdk/leverage-yield",
10
11
  "./skills/sodax-sdk/migration",
11
12
  "./skills/sodax-sdk/partner",
12
13
  "./skills/sodax-sdk/recovery",
@@ -34,6 +35,7 @@
34
35
  "./skills/sodax-dapp-kit/staking",
35
36
  "./skills/sodax-dapp-kit/bridge",
36
37
  "./skills/sodax-dapp-kit/dex",
38
+ "./skills/sodax-dapp-kit/leverage-yield",
37
39
  "./skills/sodax-dapp-kit/migration",
38
40
  "./skills/sodax-dapp-kit/bitcoin",
39
41
  "./skills/sodax-dapp-kit/auxiliary-services"
package/AGENTS.md CHANGED
@@ -8,10 +8,10 @@ This package ships **four mode-gated broad skills** (`skills/<name>/SKILL.md`)
8
8
 
9
9
  | SDK package | Broad skill | Granular per-feature skills |
10
10
  |---|---|---|
11
- | `@sodax/sdk` | `sodax-sdk` | `sodax-sdk/swap`, `sodax-sdk/money-market`, `sodax-sdk/bridge`, `sodax-sdk/staking`, `sodax-sdk/dex`, `sodax-sdk/migration`, `sodax-sdk/partner`, `sodax-sdk/recovery`, `sodax-sdk/backend-api` |
11
+ | `@sodax/sdk` | `sodax-sdk` | `sodax-sdk/swap`, `sodax-sdk/money-market`, `sodax-sdk/bridge`, `sodax-sdk/staking`, `sodax-sdk/dex`, `sodax-sdk/leverage-yield`, `sodax-sdk/migration`, `sodax-sdk/partner`, `sodax-sdk/recovery`, `sodax-sdk/backend-api` |
12
12
  | `@sodax/wallet-sdk-core` | `sodax-wallet-sdk-core` | `sodax-wallet-sdk-core/evm`, `sodax-wallet-sdk-core/solana`, `sodax-wallet-sdk-core/sui`, `sodax-wallet-sdk-core/bitcoin`, `sodax-wallet-sdk-core/stellar`, `sodax-wallet-sdk-core/icon`, `sodax-wallet-sdk-core/injective`, `sodax-wallet-sdk-core/near`, `sodax-wallet-sdk-core/stacks` |
13
13
  | `@sodax/wallet-sdk-react` | `sodax-wallet-sdk-react` | `sodax-wallet-sdk-react/connect`, `sodax-wallet-sdk-react/wallet-modal`, `sodax-wallet-sdk-react/bridge-to-sdk`, `sodax-wallet-sdk-react/switch-chain`, `sodax-wallet-sdk-react/sign-message`, `sodax-wallet-sdk-react/walletconnect` |
14
- | `@sodax/dapp-kit` | `sodax-dapp-kit` | `sodax-dapp-kit/swap`, `sodax-dapp-kit/money-market`, `sodax-dapp-kit/staking`, `sodax-dapp-kit/bridge`, `sodax-dapp-kit/dex`, `sodax-dapp-kit/migration`, `sodax-dapp-kit/bitcoin`, `sodax-dapp-kit/auxiliary-services` |
14
+ | `@sodax/dapp-kit` | `sodax-dapp-kit` | `sodax-dapp-kit/swap`, `sodax-dapp-kit/money-market`, `sodax-dapp-kit/staking`, `sodax-dapp-kit/bridge`, `sodax-dapp-kit/dex`, `sodax-dapp-kit/leverage-yield`, `sodax-dapp-kit/migration`, `sodax-dapp-kit/bitcoin`, `sodax-dapp-kit/auxiliary-services` |
15
15
 
16
16
  **When to prefer a granular skill:** if the consumer has already picked a sub-domain (e.g. *"swap with Sodax"*, *"supply on money market"*, *"useSwap hook"*, *"instantiate EvmWalletProvider"*, *"add a connect button"*), load the matching granular skill — it's ~3 KB vs the broad skill's ~13 KB and points at exactly the knowledge files needed. For a React dapp that has settled on one feature, load `sodax-dapp-kit/<feature>`; for raw SDK / backend work, load `sodax-sdk/<feature>`; for a known chain in a backend/Node wallet flow, load `sodax-wallet-sdk-core/<chain>`; for a known React wallet concern (connect, wallet-modal, bridge-to-sdk, switch-chain, sign-message, walletconnect), load `sodax-wallet-sdk-react/<concern>`. Load the broad skill when the sub-domain is undecided, the task spans several, or the consumer is porting a full v1 codebase.
17
17
 
@@ -23,8 +23,8 @@ Pick the consumer's situation, load the listed skills in order. Each entry names
23
23
 
24
24
  | Consumer is… | Load skills (mode) |
25
25
  |---|---|
26
- | **Scaffolding ONE specific Core SDK feature** (swap, money-market, bridge, staking, dex, migration, partner, recovery, backend-api) | `sodax-sdk/<feature>` (granular, covers both modes via internal links). Add `sodax-wallet-sdk-core` (integration) if it signs and lives outside React. Skip the broad `sodax-sdk` skill |
27
- | **Scaffolding ONE specific dapp-kit feature in React** (swap, money-market, staking, bridge, dex, migration, bitcoin, auxiliary-services) | `sodax-wallet-sdk-react` (integration) → `sodax-dapp-kit/<feature>` (granular, covers both modes via internal links). Skip the broad `sodax-dapp-kit` skill. If the wallet concern is also settled (just a connect button, modal, bridge, etc.), narrow to `sodax-wallet-sdk-react/<concern>` too |
26
+ | **Scaffolding ONE specific Core SDK feature** (swap, money-market, bridge, staking, dex, leverage-yield, migration, partner, recovery, backend-api) | `sodax-sdk/<feature>` (granular, covers both modes via internal links). Add `sodax-wallet-sdk-core` (integration) if it signs and lives outside React. Skip the broad `sodax-sdk` skill |
27
+ | **Scaffolding ONE specific dapp-kit feature in React** (swap, money-market, staking, bridge, dex, leverage-yield, migration, bitcoin, auxiliary-services) | `sodax-wallet-sdk-react` (integration) → `sodax-dapp-kit/<feature>` (granular, covers both modes via internal links). Skip the broad `sodax-dapp-kit` skill. If the wallet concern is also settled (just a connect button, modal, bridge, etc.), narrow to `sodax-wallet-sdk-react/<concern>` too |
28
28
  | **Scaffolding ONE chain's wallet provider** (backend/Node/non-React: evm, solana, sui, bitcoin, stellar, icon, injective, near, stacks) | `sodax-wallet-sdk-core/<chain>` (granular, covers both modes) → `sodax-sdk/<feature>` for the operation it signs. Skip the broad `sodax-wallet-sdk-core` skill |
29
29
  | **Scaffolding ONE React wallet concern** (connect, wallet-modal, bridge-to-sdk, switch-chain, sign-message, walletconnect) | `sodax-wallet-sdk-react/<concern>` (granular, covers both modes). Skip the broad `sodax-wallet-sdk-react` skill |
30
30
  | **Building a NEW React dapp** | `sodax-wallet-sdk-react` (integration) → `sodax-dapp-kit` (integration) → (`sodax-sdk` (integration) only if dropping below dapp-kit) |
@@ -58,8 +58,8 @@ packages/skills/
58
58
  ├── .claude-plugin/plugin.json # Skill registry (broad + nested granular paths)
59
59
  └── skills/ # Each broad skill is mode-gated; some have nested granular children
60
60
  ├── sodax-sdk/ {SKILL.md, integration/knowledge/, migration-v1-to-v2/knowledge/,
61
- │ <feature>/SKILL.md ×9 — swap, money-market, bridge, staking, dex,
62
- │ migration, partner, recovery, backend-api}
61
+ │ <feature>/SKILL.md ×10 — swap, money-market, bridge, staking, dex,
62
+ leverage-yield, migration, partner, recovery, backend-api}
63
63
  ├── sodax-wallet-sdk-core/ {SKILL.md, integration/knowledge/, migration-v1-to-v2/knowledge/,
64
64
  │ <chain>/SKILL.md ×9 — evm, solana, sui, bitcoin, stellar, icon,
65
65
  │ injective, near, stacks}
@@ -68,8 +68,8 @@ packages/skills/
68
68
  │ <concern>/SKILL.md ×6 — connect, wallet-modal, bridge-to-sdk,
69
69
  │ switch-chain, sign-message, walletconnect}
70
70
  └── sodax-dapp-kit/ {SKILL.md, integration/knowledge/, migration-v1-to-v2/knowledge/,
71
- <feature>/SKILL.md ×8 — swap, money-market, staking, bridge, dex,
72
- migration, bitcoin, auxiliary-services}
71
+ <feature>/SKILL.md ×9 — swap, money-market, staking, bridge, dex,
72
+ leverage-yield, migration, bitcoin, auxiliary-services}
73
73
  ```
74
74
 
75
75
  Each `<mode>/knowledge/` subtree contains `ai-rules.md`, `quickstart.md`, `architecture.md`, `features/`, `recipes/`, `reference/`, and `chain-specifics.md` / `breaking-changes/` where applicable.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "2.0.0-rc.12",
7
+ "version": "2.0.0-rc.13",
8
8
  "license": "MIT",
9
9
  "description": "AI-agent skills and knowledge for consumers of @sodax/* SDKs (Claude Code, Codex, Cursor, …)",
10
10
  "keywords": [
@@ -9,6 +9,7 @@ Per-feature reference docs. Each file documents the hooks, params types, return
9
9
  | [`staking.md`](staking.md) | SODA → xSODA staking: `useStake`, `useUnstake`, `useInstantUnstake`, `useClaim`, `useCancelUnstake`, allowance/approve, info/ratio reads. |
10
10
  | [`bridge.md`](bridge.md) | Cross-chain token bridging: `useBridge`, allowance/approve, bridgeable amount/tokens. |
11
11
  | [`dex.md`](dex.md) | Concentrated liquidity DEX: assets in/out, liquidity supply/decrease, claim rewards, position info, pool reads, param builders. |
12
+ | [`leverage-yield.md`](leverage-yield.md) | Leveraged-yield ERC-4626 vaults: `useLeverageYieldDeposit`/`Withdraw` (build) + `useLeverageYieldVaultSwap` (execute), effective APR / position / TVL / share-balance reads. |
12
13
  | [`migration.md`](migration.md) | Token migration: `useMigrateIcxToSoda`, `useRevertMigrateSodaToIcx`, `useMigratebnUSD`, `useMigrateBaln`, allowance/approve. |
13
14
  | [`bitcoin.md`](bitcoin.md) | Bound Exchange (dapp-kit-unique): session, trading wallet, fund/withdraw, UTXOs. |
14
15
  | [`auxiliary-services.md`](auxiliary-services.md) | Partner fee claiming, recovery, backend queries (intent tracking, orderbook, MM data), shared utilities (xBalances, gas, trustlines). |
@@ -20,7 +21,7 @@ Per-feature reference docs. Each file documents the hooks, params types, return
20
21
 
21
22
  ## Pair-completeness
22
23
 
23
- Every file in this directory has a sibling in [`features/`](../../../migration-v1-to-v2/knowledge/features/) with the same filename — the v1→v2 porting playbook for that feature. When you're deep in one, the other is one path-swap away.
24
+ Every file in this directory has a sibling in [`features/`](../../../migration-v1-to-v2/knowledge/features/) with the same filename — the v1→v2 porting playbook for that feature. When you're deep in one, the other is one path-swap away. **Exception:** features introduced in v2 with no v1 equivalent (`leverage-yield.md`) have no migration sibling — there is nothing to port.
24
25
 
25
26
  ## Cross-references
26
27
 
@@ -0,0 +1,112 @@
1
+ # Leverage Yield — `@sodax/dapp-kit`
2
+
3
+ Leveraged-yield ERC-4626 vaults on the Sonic hub. Deposit any token → `lsoda*` vault shares, withdraw shares → any token; plus position / APR / TVL / share-balance reads. New in v2 (no v1 equivalent).
4
+
5
+ ## Hook surface
6
+
7
+ ```ts
8
+ // @ai-snippets-skip
9
+ // Mutations
10
+ useLeverageYieldDeposit({ mutationOptions }); // build a deposit payload (any token → lsoda*)
11
+ useLeverageYieldWithdraw({ mutationOptions }); // build a withdraw payload (lsoda* → any token; hubWalletSwap)
12
+ useLeverageYieldVaultSwap({ mutationOptions }); // EXECUTE the built payload end-to-end
13
+ useLeverageYieldNotifySolver({ mutationOptions }); // manual-flow: notify the solver after a self-driven relay
14
+
15
+ // Reads
16
+ useLeverageYieldEffectiveApr({ params, queryOptions }); // AAVE + LSD effective net APR (60s)
17
+ useLeverageYieldPosition({ params, queryOptions }); // collateral/debt/ltv/healthFactor/idle (30s)
18
+ useLeverageYieldTotalAssets({ params, queryOptions }); // vault TVL, 18-dp bigint (60s)
19
+ useLeverageYieldPreviewRedeem({ params, queryOptions }); // assets for N shares; price-per-share (60s)
20
+ useLeverageYieldShareBalances({ params, queryOptions }); // per-chain share balances via useQueries (15s)
21
+ ```
22
+
23
+ `deposit` / `withdraw` are **builders** — they assemble a `LeverageYieldSwapPayload`, they do NOT broadcast. Spread the built payload into `useLeverageYieldVaultSwap`'s `mutate`, adding the `walletProvider`. There is no dedicated leverage-yield approve hook: the swap-style deposit approves the spoke-side asset manager, so reuse `useSwapApprove` / `useSwapAllowance` (see Approval pattern).
24
+
25
+ ## Mutation TVars
26
+
27
+ ```ts
28
+ // @ai-snippets-skip
29
+ // deposit — vars are the SDK's deposit params (any token → lsoda* on the hub wallet)
30
+ type UseLeverageYieldDepositVars = LeverageYieldSwapDepositParams;
31
+ // { vault: Address; srcChainKey; srcAddress; inputToken; inputAmount: bigint;
32
+ // minOutputAmount: bigint; deadline?: bigint; solver?: Address; partnerFee? }
33
+
34
+ // withdraw — lsoda* shares → any token on any chain (hub-wallet sourced)
35
+ type UseLeverageYieldWithdrawVars = LeverageYieldSwapWithdrawParams;
36
+ // { vault: Address; srcChainKey; srcAddress; dstChainKey; outputToken;
37
+ // inputAmount: bigint; minOutputAmount: bigint; recipient?; deadline?; solver? }
38
+
39
+ // vaultSwap — the executor. Spread the built payload + walletProvider.
40
+ type UseLeverageYieldVaultSwapVars<K> = Omit<VaultSwapActionParams<K, false>, 'raw'>;
41
+ // = SpokeExecActionParams<K, false, CreateIntentParams<K>> & { hubWalletSwap?; partnerFee? }
42
+ // i.e. { params: CreateIntentParams<K>; walletProvider; hubWalletSwap?; partnerFee?; timeout? }
43
+
44
+ // notifySolver — manual-flow notify step (vaultSwap already notifies internally)
45
+ type UseLeverageYieldNotifySolverVars = SolverExecutionRequest;
46
+ // { intent_tx_hash: Hex } — the hub-side tx hash where the intent landed
47
+ ```
48
+
49
+ ## Read shapes (key picks)
50
+
51
+ ```ts
52
+ // @ai-snippets-skip
53
+ // useLeverageYieldEffectiveApr — AAVE rates + LSD staking yield, leverage re-applied (data unwrapped)
54
+ useLeverageYieldEffectiveApr({ params: { vault } })
55
+ // → UseQueryResult<LeverageYieldEffectiveApr>
56
+ // LeverageYieldEffectiveApr = LeverageYieldApr & { lsdApr, effectiveSupplyAprRay, effectiveNetAprRay }
57
+ // LeverageYieldApr = { supplyAprRay, borrowAprRay, targetLtvBps, leverageMultiplierWad, netAprRay } (RAY = 1e27)
58
+
59
+ // useLeverageYieldPosition — live position snapshot
60
+ useLeverageYieldPosition({ params: { vault } })
61
+ // → UseQueryResult<LeverageYieldPosition>
62
+ // LeverageYieldPosition = { collateral, debt, ltv, healthFactor, idleAsset } (all bigint)
63
+
64
+ // useLeverageYieldPreviewRedeem — assets per shares; pass 1e18 for price-per-share
65
+ useLeverageYieldPreviewRedeem({ params: { vault, shares: 10n ** 18n } })
66
+ // → UseQueryResult<bigint>
67
+
68
+ // useLeverageYieldShareBalances — returns an ARRAY (one query per holder), NOT a single result
69
+ useLeverageYieldShareBalances({ params: { vault, holders } })
70
+ // → UseQueryResult<LeverageYieldShareHolding>[]
71
+ // LeverageYieldShareHolding = { chainKey: SpokeChainKey; holder: Address; shares: bigint }
72
+ // holders: { chainKey: SpokeChainKey; address: string }[] — one row per chain the user may hold under
73
+ ```
74
+
75
+ ## Approval pattern
76
+
77
+ Leverage-yield has **no dedicated approve hook**. A deposit is a swap-style intent that bridges `inputToken` from the spoke chain, so it approves the spoke-side asset manager exactly like a swap — reuse the swap hooks. A withdraw needs no spoke-side approval: the payload carries `hubWalletSwap: true` and `vaultSwap` authorises the hub wallet to spend the `lsoda*` shares via a `Connection.sendMessage` the user signs on `srcChainKey`.
78
+
79
+ | Flow | Token approved | Hook |
80
+ |---|---|---|
81
+ | `deposit` | spoke `inputToken` → asset manager | `useSwapApprove`, `useSwapAllowance` (swap domain) |
82
+ | `withdraw` | none (hub-wallet `sendMessage` authorises the spend) | — |
83
+
84
+ ## Return shapes
85
+
86
+ All read hooks here are **already unwrapped** — they throw on SDK `!ok` so `isError` / `error` / `retry` engage. Read `data` directly; do NOT branch on `data.ok`.
87
+
88
+ | Hook | Returns |
89
+ |---|---|
90
+ | `useLeverageYieldDeposit` / `useLeverageYieldWithdraw` | `SafeUseMutationResult<LeverageYieldSwapPayload, Error, …>` (builder — `data` is the payload to spread into `useLeverageYieldVaultSwap`) |
91
+ | `useLeverageYieldVaultSwap` | `SafeUseMutationResult<VaultSwapResponse, Error, …>` (`{ solverExecutionResponse, intent, intentDeliveryInfo }`) |
92
+ | `useLeverageYieldNotifySolver` | `SafeUseMutationResult<SolverExecutionResponse, Error, …>` (`{ answer: 'OK', intent_hash }`) |
93
+ | `useLeverageYieldEffectiveApr` | `UseQueryResult<LeverageYieldEffectiveApr, Error>` |
94
+ | `useLeverageYieldPosition` | `UseQueryResult<LeverageYieldPosition, Error>` |
95
+ | `useLeverageYieldTotalAssets` | `UseQueryResult<bigint, Error>` |
96
+ | `useLeverageYieldPreviewRedeem` | `UseQueryResult<bigint, Error>` |
97
+ | `useLeverageYieldShareBalances` | `UseQueryResult<LeverageYieldShareHolding, Error>[]` (array — one entry per holder) |
98
+
99
+ ## Gotchas
100
+
101
+ 1. **`deposit` / `withdraw` are builders, not executors.** Their `data` is a `LeverageYieldSwapPayload` — spread it into `useLeverageYieldVaultSwap`'s `mutate` with a `walletProvider` to actually run the swap. Calling them does not broadcast anything.
102
+ 2. **`useLeverageYieldShareBalances` returns an ARRAY, not a single query.** It fans out one `useQueries` row per holder. Aggregate yourself: `balances.reduce((acc, q) => acc + (q.data?.shares ?? 0n), 0n)`. `queryOptions` is spread into every per-holder query (no top-level options slot on `useQueries`).
103
+ 3. **The share-balance key segment is singular: `shareBalance`** (`['leverageYield', 'shareBalance', vault, chainKey, address]`) — one per holder, even though the hook name is plural.
104
+ 4. **Withdraw needs no spoke approval.** Don't gate it on `useSwapAllowance` — the hub-wallet `sendMessage` path approves the share spend internally. Only `deposit` needs the swap-domain allowance/approve pair.
105
+ 5. **Deposit's per-intent `partnerFee` must be reflected in the quote.** If you charge a deposit-only fee (e.g. 1%), `vaultSwap`'s underlying `createVaultIntent` deducts it from `inputAmount` before the swap — quote on the post-fee amount, or `minOutputAmount` becomes unfillable and the intent never settles.
106
+ 6. **`useLeverageYieldVaultSwap` invalidates xBalances on both chains** (`['shared', 'xBalances', srcChainKey]` and `dstChainKey`) on success. Compose your own `onSuccess` after the hook's — it runs first.
107
+ 7. **`useLeverageYieldNotifySolver` is for the manual flow only.** `useLeverageYieldVaultSwap` already notifies the solver internally — only reach for the standalone notify hook when you built the intent with `sodax.leverageYield.createVaultIntent` and relayed it yourself. It does NOT invalidate any queries: its only var is `{ intent_tx_hash }` (no chain context), and the fill lands asynchronously afterward.
108
+
109
+ ## Cross-references
110
+
111
+ - [`../recipes/leverage-yield.md`](../recipes/leverage-yield.md) — full worked deposit / withdraw / reads examples.
112
+ - For the underlying SDK leverage-yield surface (`LeverageYieldService`, `createVaultIntent`, `notifySolver`, APR math), load the `sodax-sdk` skill (integration mode) — its `features/leverage-yield.md`.
@@ -35,6 +35,7 @@ Copy-paste patterns for adding SODAX features to a React app. Each recipe is sel
35
35
  | [`staking.md`](staking.md) | `useStake`, `useUnstake`, `useClaim`, staking info, ratios |
36
36
  | [`migration.md`](migration.md) | `useMigrateIcxToSoda`, `useRevertMigrateSodaToIcx`, `useMigratebnUSD`, `useMigrateBaln` |
37
37
  | [`dex.md`](dex.md) | `useDexDeposit`, `useSupplyLiquidity`, positions, pools |
38
+ | [`leverage-yield.md`](leverage-yield.md) | `useLeverageYieldDeposit`, `useLeverageYieldWithdraw`, `useLeverageYieldVaultSwap`, APR/position/TVL/share-balance reads |
38
39
  | [`bitcoin.md`](bitcoin.md) | `useRadfiSession`, `useFundTradingWallet`, `useRadfiWithdraw`, UTXO management |
39
40
  | [`backend-queries.md`](backend-queries.md) | Intent tracking, orderbook, money market position queries (read-only, no wallet) |
40
41
 
@@ -0,0 +1,168 @@
1
+ # Recipe: Leverage Yield
2
+
3
+ Leveraged-yield ERC-4626 vaults on Sonic. Deposit any token → `lsoda*` shares, withdraw shares → any token, and read position / APR / TVL / balances.
4
+
5
+ **Depends on:** [setup.md](setup.md), [wallet-connectivity.md](wallet-connectivity.md)
6
+
7
+ ## Hooks
8
+
9
+ ### Mutations
10
+
11
+ | Hook | Purpose |
12
+ |------|---------|
13
+ | `useLeverageYieldDeposit` | Build a deposit payload (any token → `lsoda*`) |
14
+ | `useLeverageYieldWithdraw` | Build a withdraw payload (`lsoda*` → any token) |
15
+ | `useLeverageYieldVaultSwap` | Execute a built payload end-to-end (create → relay → notify solver) |
16
+ | `useSwapApprove` | Approve the spoke `inputToken` (deposit only — swap-domain hook) |
17
+
18
+ ### Queries
19
+
20
+ | Hook | Purpose |
21
+ |------|---------|
22
+ | `useSwapAllowance` | Check spoke `inputToken` approval (deposit only — swap-domain hook) |
23
+ | `useLeverageYieldEffectiveApr` | AAVE + LSD effective net APR |
24
+ | `useLeverageYieldPosition` | Live position (collateral, debt, LTV, health factor, idle) |
25
+ | `useLeverageYieldTotalAssets` | Vault TVL (18-dp bigint) |
26
+ | `useLeverageYieldPreviewRedeem` | Assets for N shares (pass `1e18` for price-per-share) |
27
+ | `useLeverageYieldShareBalances` | Per-chain share balances (array via `useQueries`) |
28
+
29
+ > A deposit is a swap-style intent, so it approves the **spoke asset manager** via the swap-domain hooks — there is no leverage-yield-specific approve hook. A withdraw carries `hubWalletSwap: true` and needs no spoke approval.
30
+
31
+ ## Vault stats + position
32
+
33
+ ```tsx
34
+ import { useLeverageYieldEffectiveApr, useLeverageYieldTotalAssets, useLeverageYieldPreviewRedeem, useLeverageYieldPosition } from '@sodax/dapp-kit';
35
+ import type { Address } from '@sodax/sdk';
36
+ import { formatUnits } from 'viem';
37
+
38
+ const RAY = 10n ** 27n;
39
+
40
+ function VaultStats({ vault }: { vault: Address }) {
41
+ const { data: apr } = useLeverageYieldEffectiveApr({ params: { vault } }); // 60s refresh
42
+ const { data: tvl } = useLeverageYieldTotalAssets({ params: { vault } }); // 60s refresh
43
+ const { data: position } = useLeverageYieldPosition({ params: { vault } }); // 30s refresh
44
+ const { data: sharePrice } = useLeverageYieldPreviewRedeem({ // 60s refresh
45
+ params: { vault, shares: 10n ** 18n },
46
+ });
47
+
48
+ // RAY (1e27) rates → percent. effectiveNetAprRay folds the LSD staking yield in.
49
+ const netAprPct = apr ? Number((apr.effectiveNetAprRay * 10000n) / RAY) / 100 : undefined;
50
+
51
+ return (
52
+ <div>
53
+ {netAprPct !== undefined && <p>Net APR: {netAprPct.toFixed(2)}%</p>}
54
+ {tvl !== undefined && <p>TVL: {formatUnits(tvl, 18)}</p>}
55
+ {sharePrice !== undefined && <p>1 share = {formatUnits(sharePrice, 18)} assets</p>}
56
+ {position && <p>Health factor: {formatUnits(position.healthFactor, 18)}</p>}
57
+ </div>
58
+ );
59
+ }
60
+ ```
61
+
62
+ ## Share balances across chains
63
+
64
+ `useLeverageYieldShareBalances` returns an **array** — one query per holder. Build `holders` from the chains the user has connected, then aggregate.
65
+
66
+ ```tsx
67
+ import { useLeverageYieldShareBalances } from '@sodax/dapp-kit';
68
+ import type { Address, SpokeChainKey } from '@sodax/sdk';
69
+ import { formatUnits } from 'viem';
70
+
71
+ function ShareTotal({ vault, holders }: { vault: Address; holders: { chainKey: SpokeChainKey; address: string }[] }) {
72
+ const balances = useLeverageYieldShareBalances({ params: { vault, holders } }); // 15s refresh per query
73
+ const total = balances.reduce((acc, q) => acc + (q.data?.shares ?? 0n), 0n);
74
+ return <p>Total shares: {formatUnits(total, 18)}</p>;
75
+ }
76
+ ```
77
+
78
+ ## Deposit (any token → `lsoda*`)
79
+
80
+ Build → approve-if-needed → execute. The built payload is spread straight into `vaultSwap`.
81
+
82
+ ```tsx
83
+ // @ai-snippets-skip — illustrative end-to-end flow wiring the builder, the swap-domain
84
+ // allowance/approve hooks, and the executor across a broad walletProvider union. Real
85
+ // call shapes per hook are in features/leverage-yield.md.
86
+ import { useState } from 'react';
87
+ import {
88
+ useLeverageYieldDeposit, useLeverageYieldVaultSwap, useSwapAllowance, useSwapApprove,
89
+ } from '@sodax/dapp-kit';
90
+ import { useWalletProvider } from '@sodax/wallet-sdk-react';
91
+ import { ChainKeys, type Address, type PartnerFee } from '@sodax/sdk';
92
+ import { parseUnits } from 'viem';
93
+
94
+ const DEPOSIT_PARTNER_FEE: PartnerFee = { address: '0xYourFeeReceiver…', percentage: 100 }; // 1%
95
+
96
+ function DepositForm({ vault, srcAddress, inputToken }: { vault: Address; srcAddress: string; inputToken: string }) {
97
+ const [amount, setAmount] = useState('');
98
+ const chainKey = ChainKeys.ARBITRUM_MAINNET;
99
+ const walletProvider = useWalletProvider({ xChainId: chainKey });
100
+
101
+ const { mutateAsyncSafe: buildDeposit } = useLeverageYieldDeposit();
102
+ const { mutateAsyncSafe: approve } = useSwapApprove();
103
+ const { mutateAsync: vaultSwap, isPending } = useLeverageYieldVaultSwap();
104
+
105
+ const handleDeposit = async () => {
106
+ if (!walletProvider) return;
107
+ const built = await buildDeposit({
108
+ vault, srcChainKey: chainKey, srcAddress, inputToken,
109
+ inputAmount: parseUnits(amount, 18),
110
+ minOutputAmount: 0n, // quote via sodax.swaps.getQuote (token_dst = vault), then subtract slippage
111
+ partnerFee: DEPOSIT_PARTNER_FEE, // per-intent fee — must match the quote's post-fee amount
112
+ });
113
+ if (!built.ok) return;
114
+
115
+ // Deposit approves the spoke asset manager (swap-style). Gate on useSwapAllowance in render.
116
+ await approve({ params: built.value.params, walletProvider });
117
+ await vaultSwap({ ...built.value, walletProvider }); // spread the payload; lsoda* lands in the hub wallet
118
+ };
119
+
120
+ return (
121
+ <div>
122
+ <input value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="amount" />
123
+ <button onClick={handleDeposit} disabled={isPending || !walletProvider}>Deposit</button>
124
+ </div>
125
+ );
126
+ }
127
+ ```
128
+
129
+ ## Withdraw (`lsoda*` → any token)
130
+
131
+ No approval step — `hubWalletSwap: true` authorises the share spend via a `sendMessage`.
132
+
133
+ ```tsx
134
+ // @ai-snippets-skip — illustrative end-to-end flow (builder + executor) across a broad
135
+ // walletProvider union; see features/leverage-yield.md for exact per-hook shapes.
136
+ import { useLeverageYieldWithdraw, useLeverageYieldVaultSwap } from '@sodax/dapp-kit';
137
+ import { useWalletProvider } from '@sodax/wallet-sdk-react';
138
+ import { ChainKeys, type Address } from '@sodax/sdk';
139
+
140
+ function WithdrawButton({ vault, srcAddress, outputToken, shares }: { vault: Address; srcAddress: string; outputToken: string; shares: bigint }) {
141
+ const chainKey = ChainKeys.ARBITRUM_MAINNET;
142
+ const walletProvider = useWalletProvider({ xChainId: chainKey });
143
+ const { mutateAsyncSafe: buildWithdraw } = useLeverageYieldWithdraw();
144
+ const { mutateAsync: vaultSwap, isPending } = useLeverageYieldVaultSwap();
145
+
146
+ const handleWithdraw = async () => {
147
+ if (!walletProvider) return;
148
+ const built = await buildWithdraw({
149
+ vault, srcChainKey: chainKey, srcAddress,
150
+ dstChainKey: chainKey, outputToken,
151
+ inputAmount: shares, // lsoda* shares to burn
152
+ minOutputAmount: 0n, // quote via sodax.swaps.getQuote (token_src = vault), then subtract slippage
153
+ });
154
+ if (!built.ok) return;
155
+ await vaultSwap({ ...built.value, walletProvider }); // built.value.hubWalletSwap === true
156
+ };
157
+
158
+ return <button onClick={handleWithdraw} disabled={isPending || !walletProvider}>Withdraw</button>;
159
+ }
160
+ ```
161
+
162
+ ## Notes
163
+
164
+ - **Two roles:** `deposit` / `withdraw` *build* a `LeverageYieldSwapPayload`; `useLeverageYieldVaultSwap` *executes* it. Always spread the built payload into the executor with a `walletProvider`.
165
+ - **Quotes:** size `minOutputAmount` with `sodax.swaps.getQuote` — vault address as `token_dst` (deposit) or `token_src` (withdraw), since `lsoda*` shares are solver-tradeable. Subtract your slippage tolerance.
166
+ - **Deposit fee:** a per-intent `partnerFee` is deducted from `inputAmount` before the swap; quote on the post-fee amount or the intent won't fill.
167
+ - **Withdraw:** no spoke approval — the hub wallet authorises the share spend via `Connection.sendMessage`. Output lands at `recipient` (defaults to `srcAddress`) on `dstChainKey`.
168
+ - **Reads** (`useLeverageYieldEffectiveApr`, `Position`, `TotalAssets`, `PreviewRedeem`) are already unwrapped — read `data` directly. `useLeverageYieldShareBalances` returns an array; aggregate the `shares` yourself.
@@ -1,6 +1,6 @@
1
1
  # Hooks index — `@sodax/dapp-kit` v2
2
2
 
3
- Comprehensive hook table across 11 feature domains. Use this when you know the feature you're building but don't remember the exact hook name.
3
+ Comprehensive hook table across 12 feature domains. Use this when you know the feature you're building but don't remember the exact hook name.
4
4
 
5
5
  ## Provider + context
6
6
 
@@ -97,6 +97,20 @@ Comprehensive hook table across 11 feature domains. Use this when you know the f
97
97
  | `useCreateSupplyLiquidityParams` | Param builder | Build tick-range + liquidity params |
98
98
  | `useCreateDecreaseLiquidityParams` | Param builder | Build decrease params from position state |
99
99
 
100
+ ## Leverage Yield
101
+
102
+ | Hook | Type | Purpose |
103
+ |---|---|---|
104
+ | `useLeverageYieldDeposit` | Mutation | Build a deposit payload (any token → `lsoda*` shares) — spread into `useLeverageYieldVaultSwap` |
105
+ | `useLeverageYieldWithdraw` | Mutation | Build a withdraw payload (`lsoda*` → any token; `hubWalletSwap`) |
106
+ | `useLeverageYieldVaultSwap` | Mutation | Execute a built payload end-to-end (create → verify → relay → notify solver) |
107
+ | `useLeverageYieldNotifySolver` | Mutation | Manual-flow notify step (after a self-driven `createVaultIntent` + relay) |
108
+ | `useLeverageYieldEffectiveApr` | Query | AAVE + LSD effective net APR (60s) |
109
+ | `useLeverageYieldPosition` | Query | Live position: collateral, debt, LTV, health factor, idle (30s) |
110
+ | `useLeverageYieldTotalAssets` | Query | Vault TVL (18-dp bigint, 60s) |
111
+ | `useLeverageYieldPreviewRedeem` | Query | Assets for N shares; pass `1e18` for price-per-share (60s) |
112
+ | `useLeverageYieldShareBalances` | Query | Per-chain `lsoda*` balances via `useQueries` — returns an array (15s) |
113
+
100
114
  ## Migration
101
115
 
102
116
  | Hook | Type | Purpose |
@@ -12,6 +12,7 @@ Mandatory shape rules for every `queryKey` and `mutationKey`. Mechanically enfor
12
12
  | `bitcoin/` | `'bitcoin'` |
13
13
  | `bridge/` | `'bridge'` |
14
14
  | `dex/` | `'dex'` |
15
+ | `leverageYield/` | `'leverageYield'` |
15
16
  | `mm/` | `'mm'` |
16
17
  | `partner/` | `'partner'` |
17
18
  | `recovery/` | `'recovery'` |
@@ -149,6 +150,20 @@ Skip if you're writing a feature hook for the first time and want to align with
149
150
  | `['staking', 'cancelUnstake']` | `useCancelUnstake` |
150
151
  | `['staking', 'approve', 'stake' \| 'unstake' \| 'instantUnstake']` | `useStakeApprove` / `useUnstakeApprove` / `useInstantUnstakeApprove` |
151
152
 
153
+ ### Leverage Yield
154
+
155
+ | Key | Hook |
156
+ |---|---|
157
+ | `['leverageYield', 'effectiveApr', vault]` | `useLeverageYieldEffectiveApr` |
158
+ | `['leverageYield', 'position', vault]` | `useLeverageYieldPosition` |
159
+ | `['leverageYield', 'totalAssets', vault]` | `useLeverageYieldTotalAssets` |
160
+ | `['leverageYield', 'previewRedeem', vault, shares.toString()]` | `useLeverageYieldPreviewRedeem` |
161
+ | `['leverageYield', 'shareBalance', vault, chainKey, address]` | `useLeverageYieldShareBalances` (singular `shareBalance`; one query per holder) |
162
+ | `['leverageYield', 'deposit']` | `useLeverageYieldDeposit` mutation |
163
+ | `['leverageYield', 'withdraw']` | `useLeverageYieldWithdraw` mutation |
164
+ | `['leverageYield', 'vaultSwap']` | `useLeverageYieldVaultSwap` mutation |
165
+ | `['leverageYield', 'notifySolver']` | `useLeverageYieldNotifySolver` mutation |
166
+
152
167
  ### Bridge / DEX / Migration / Bitcoin / etc.
153
168
 
154
169
  Follow the same shape. See the source hook files (`packages/dapp-kit/src/hooks/<feature>/`) for current keys.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: sodax-dapp-kit-leverage-yield
3
+ description: 'Granular skill for the @sodax/dapp-kit v2 leverage-yield feature only — React Query hooks for leveraged-yield ERC-4626 vaults on Sonic: useLeverageYieldDeposit and useLeverageYieldWithdraw (build a swap payload), useLeverageYieldVaultSwap (execute it end-to-end), plus reads useLeverageYieldEffectiveApr, useLeverageYieldPosition, useLeverageYieldTotalAssets, useLeverageYieldPreviewRedeem, useLeverageYieldShareBalances. Use when a React dapp task is leverage-yield vaults (e.g. "deposit into a leverage vault with dapp-kit", "useLeverageYieldVaultSwap hook", "render vault APR / position / TVL", "lsoda share balances across chains", "withdraw from leverage vault"). New in v2 — integration only, no v1 migration path. Links into the parent sodax-dapp-kit knowledge tree. For backend/Node, use the sodax-sdk skill.'
4
+ ---
5
+
6
+ # Leverage Yield (dapp-kit granular skill)
7
+
8
+ Granular skill for the leverage-yield hooks of `@sodax/dapp-kit` v2. queryKey/mutationKey first segment: `leverageYield`. React-only — backend uses `@sodax/sdk` directly. **New in v2; no v1 migration path.**
9
+
10
+ ## Step 1 — Clarify with user before coding
11
+
12
+ 1. **Deposit or withdraw?** Deposit = any token → `lsoda*` shares (lands in the hub wallet). Withdraw = `lsoda*` shares → any token on any chain.
13
+ 2. **Build vs execute.** `useLeverageYieldDeposit` / `useLeverageYieldWithdraw` only *build* a `LeverageYieldSwapPayload`; `useLeverageYieldVaultSwap` *executes* it (create → relay → notify solver). You always need both.
14
+ 3. **Approval?** Deposit approves the spoke `inputToken` via the swap-domain `useSwapApprove` / `useSwapAllowance` (no leverage-yield-specific approve hook). Withdraw needs no approval (`hubWalletSwap: true`).
15
+ 4. **Which reads?** `useLeverageYieldEffectiveApr` (headline APR), `useLeverageYieldPosition` (LTV/health), `useLeverageYieldTotalAssets` (TVL), `useLeverageYieldPreviewRedeem` (price-per-share), `useLeverageYieldShareBalances` (per-chain balances — returns an array).
16
+
17
+ ## Integration workflow (new v2 code)
18
+
19
+ 1. [`../integration/knowledge/ai-rules.md`](../integration/knowledge/ai-rules.md) — DO / DO NOT (read first).
20
+ 2. [`../integration/knowledge/architecture.md`](../integration/knowledge/architecture.md) — hook shapes, `mutateAsyncSafe`, `unwrapResult`, queryKey conventions.
21
+ 3. [`../integration/knowledge/features/leverage-yield.md`](../integration/knowledge/features/leverage-yield.md) — full hook surface, mutation TVars, read shapes, approval pattern, gotchas.
22
+ 4. [`../integration/knowledge/recipes/leverage-yield.md`](../integration/knowledge/recipes/leverage-yield.md) — full worked examples (deposit, withdraw, stats, share balances).
23
+ 5. Call-shape choice → [`../integration/knowledge/recipes/mutation-error-handling.md`](../integration/knowledge/recipes/mutation-error-handling.md).
24
+
25
+ ### Leverage-yield-specific anti-patterns (dapp-kit)
26
+
27
+ - **Calling `useLeverageYieldDeposit` / `useLeverageYieldWithdraw` and expecting a tx.** They are builders — their `data` is a `LeverageYieldSwapPayload`. Spread it into `useLeverageYieldVaultSwap`'s `mutate` with a `walletProvider` to broadcast.
28
+ - **Treating `useLeverageYieldShareBalances` as a single query.** It returns an **array** (one `useQueries` row per holder). Aggregate with `reduce`; the key segment is singular `shareBalance`.
29
+ - **Gating withdraw on an allowance check.** Withdraw carries `hubWalletSwap: true` — the hub wallet authorises the share spend via `sendMessage`. Only `deposit` uses `useSwapAllowance` / `useSwapApprove`.
30
+ - **Reaching for a `useLeverageYieldApprove` hook.** It doesn't exist — the deposit approves the spoke asset manager, so use the swap-domain hooks.
31
+ - **Quoting on the pre-fee amount.** A deposit's per-intent `partnerFee` is deducted from `inputAmount` before the swap — quote on the post-fee amount or `minOutputAmount` is unfillable.
32
+
33
+ ## Verification
34
+
35
+ 1. `pnpm tsc --noEmit` clean.
36
+ 2. Deposit/withdraw build a payload, then `useLeverageYieldVaultSwap` executes it (`{ ...payload, walletProvider }`).
37
+ 3. `useLeverageYieldShareBalances` consumers treat `data` as an array and aggregate.
38
+ 4. Withdraw flows do not gate on `useSwapAllowance`.
39
+ 5. Mutation flows use `mutateAsyncSafe` and branch on `result.ok`; reads read `data` directly (no `.ok`/`.value`).
40
+
41
+ ## Related granular skills (same family)
42
+
43
+ - [`../swap/SKILL.md`](../swap/SKILL.md) — `useSwapApprove` / `useSwapAllowance` (spoke-side deposit approval) and `useQuote` (size `minOutputAmount`) live here.
44
+ - [`../auxiliary-services/SKILL.md`](../auxiliary-services/SKILL.md) — `useXBalances` / gas-estimation utilities used alongside leverage-yield UI.
45
+
46
+ For multi-feature tasks, load the broad [`sodax-dapp-kit` skill](../SKILL.md).
47
+
48
+ ## Wallet connectivity (different SDK package family)
49
+
50
+ `walletProvider` flows through `mutate(vars)`. **Also load the `sodax-wallet-sdk-react` skill (integration mode)** to wire wallets and get a typed `walletProvider` via `useWalletProvider({ xChainId: chainKey })`.
@@ -9,6 +9,7 @@ One file per feature service. Each file documents the v2 API surface, common cal
9
9
  | [`staking.md`](staking.md) | `StakingService` | SODA → xSoda staking via ERC-4626 vault. Stake, unstake (with penalty curve), instant unstake (slippage), claim, cancel. |
10
10
  | [`bridge.md`](bridge.md) | `BridgeService` | Cross-chain token transfer via vault. `bridge` returns `TxHashPair = { srcChainTxHash, dstChainTxHash }`. Bridgeable-amount queries respect vault deposit limits. |
11
11
  | [`dex.md`](dex.md) | `ClService` + `AssetService` | Uniswap-V3-style concentrated liquidity positions. Asset deposit/withdraw. Increase/decrease/claim flows. |
12
+ | [`leverage-yield.md`](leverage-yield.md) | `LeverageYieldService` | Leveraged-yield ERC-4626 vaults on Sonic. Deposit/withdraw as solver-tradeable `lsoda*` swaps; effective APR (AAVE + LSD), position, share-balance reads. |
12
13
  | [`migration.md`](migration.md) | `MigrationService` (the SDK module — not v1→v2 porting) | Legacy ICON ecosystem token migration. ICX ↔ SODA, legacy bnUSD ↔ new bnUSD, BALN → SODA with lockup multipliers. |
13
14
  | [`partner.md`](partner.md) | `PartnerService` | Partner-fee handling: token approval, auto-swap preferences, fee-claim flows. |
14
15
  | [`recovery.md`](recovery.md) | `RecoveryService` | Withdraw stuck hub-wallet assets back to a spoke chain. |
@@ -18,4 +19,4 @@ All feature services are constructed and wired by the `Sodax` facade. You don't
18
19
 
19
20
  ## Cross-references to migration
20
21
 
21
- For the v1 → v2 port playbook on each feature, see the matching file in [`features/`](../../../migration-v1-to-v2/knowledge/features/) — same filename, different angle.
22
+ For the v1 → v2 port playbook on each feature, see the matching file in [`features/`](../../../migration-v1-to-v2/knowledge/features/) — same filename, different angle. **Exception:** features introduced in v2 with no v1 equivalent (`leverage-yield.md`) have no migration sibling — there is nothing to port.
@@ -0,0 +1,175 @@
1
+ # Leverage Yield — `LeverageYieldService`
2
+
3
+ Leveraged-yield ERC-4626 vaults on the Sonic hub. A vault loops supply → borrow → swap → re-supply to a target LTV, producing a leveraged long on the `asset` / `borrowToken` peg. The vault's share token (`lsoda*`) is treated as a **solver-tradeable token**, so deposits and withdrawals are intent-based swaps the service executes itself. New in v2 — no v1 equivalent.
4
+
5
+ Access: `sodax.leverageYield`. Service class: `LeverageYieldService`. Feature tag for errors: `'leverageYield'`.
6
+
7
+ ## How it works
8
+
9
+ - A vault holds `asset` (a Sodax vault token like sodaWEETH) as collateral, borrows `borrowToken` (e.g. sodaETH) from the Sodax-forked AAVE pool, swaps it back into the asset, and re-supplies — to a `targetLTV`.
10
+ - The ERC-4626 **share token is the vault proxy address itself** (`lsoda*`). Holding shares = holding the leveraged position.
11
+ - **Deposit** = swap any spoke token → `lsoda*`, delivered to the user's **hub wallet** on Sonic. **Withdraw** = swap `lsoda*` (held in the hub wallet) → any token on any chain.
12
+ - **Steady-state APR**: `netAprRay = supplyAprRay + leverageMultiplier × (supplyAprRay − borrowAprRay)`, where `leverageMultiplier = targetLTV / (1 − targetLTV)`. Rates are RAY (`1e27`); the multiplier is WAD (`1e18`). `netAprRay` goes **negative** when the borrow rate exceeds supply — for LSD-backed vaults the LSD's native staking yield (folded in by `getEffectiveApr`) is the real alpha.
13
+
14
+ ## Public methods
15
+
16
+ ```ts
17
+ // Builders — assemble a LeverageYieldSwapPayload (spread into vaultSwap). Do NOT broadcast.
18
+ sodax.leverageYield.deposit(params: LeverageYieldSwapDepositParams): Promise<Result<LeverageYieldSwapPayload, SodaxError>>;
19
+ sodax.leverageYield.withdraw(params: LeverageYieldSwapWithdrawParams): Promise<Result<LeverageYieldSwapPayload, SodaxError>>;
20
+ // withdraw sets hubWalletSwap: true; both default `deadline` from the hub block timestamp (so withdraw is async)
21
+
22
+ // Intent create + end-to-end execute (leverage-yield copies of swap's createIntent / swap)
23
+ sodax.leverageYield.createVaultIntent<K, Raw>(params: VaultSwapActionParams<K, Raw>): Promise<Result<CreateVaultIntentResult<K, Raw>, SodaxError>>;
24
+ sodax.leverageYield.vaultSwap<K>(params: VaultSwapActionParams<K, false>): Promise<Result<VaultSwapResponse, SodaxError>>;
25
+ sodax.leverageYield.notifySolver(request: { intent_tx_hash: string }): Promise<Result<SolverExecutionResponse, SodaxError>>;
26
+ // notifySolver is PUBLIC — call it to finish a manual createVaultIntent → relay → notify flow
27
+
28
+ // Sonic-direct allowance for the vault's underlying asset (the swap-style deposit handles its own approvals)
29
+ sodax.leverageYield.approve<R>(params: LeverageYieldApproveParams<R>): Promise<Result<TxReturnType<HubChainKey, R>, SodaxError>>;
30
+ sodax.leverageYield.isAllowanceValid(params: LeverageYieldAllowanceParams): Promise<Result<boolean, SodaxError>>;
31
+
32
+ // Reads (all Result<…, SodaxError> with LOOKUP_FAILED on failure)
33
+ sodax.leverageYield.getApr(vault): Promise<Result<LeverageYieldApr, SodaxError>>; // AAVE-only steady-state
34
+ sodax.leverageYield.getEffectiveApr(vault): Promise<Result<LeverageYieldEffectiveApr, SodaxError>>; // + LSD staking yield (headline)
35
+ sodax.leverageYield.getLsdApr(vault): Promise<Result<LeverageYieldLsdApr, SodaxError>>; // off-chain DefiLlama; always ok for a known vault
36
+ sodax.leverageYield.getPosition(vault): Promise<Result<LeverageYieldPosition, SodaxError>>;
37
+ sodax.leverageYield.getTotalAssets(vault): Promise<Result<bigint, SodaxError>>; // TVL
38
+ sodax.leverageYield.previewDeposit(vault, assets): Promise<Result<bigint, SodaxError>>;
39
+ sodax.leverageYield.previewWithdraw(vault, assets): Promise<Result<bigint, SodaxError>>;
40
+ sodax.leverageYield.previewRedeem(vault, shares): Promise<Result<bigint, SodaxError>>;
41
+ sodax.leverageYield.getMaxWithdraw(vault, owner): Promise<Result<bigint, SodaxError>>;
42
+ sodax.leverageYield.getMaxWithdrawForUser(vault, srcChainKey, srcAddress): Promise<Result<bigint, SodaxError>>; // dust-buffered
43
+ sodax.leverageYield.getShareBalance(vault, owner): Promise<Result<bigint, SodaxError>>;
44
+ sodax.leverageYield.getShareBalanceForUser(vault, srcChainKey, srcAddress): Promise<Result<bigint, SodaxError>>;
45
+ sodax.leverageYield.getAsset(vault): Promise<Result<Address, SodaxError>>;
46
+
47
+ // Registry (synchronous)
48
+ sodax.leverageYield.listVaults(): readonly LeverageYieldVault[];
49
+ sodax.leverageYield.getVault(name): LeverageYieldVault | undefined;
50
+ sodax.leverageYield.getVaultByAddress(address): LeverageYieldVault | undefined;
51
+ ```
52
+
53
+ ## Action params shape
54
+
55
+ ```ts
56
+ type LeverageYieldSwapDepositParams = {
57
+ vault: Address; // the lsoda* vault proxy
58
+ srcChainKey: SpokeChainKey; // chain the user holds inputToken on & signs from
59
+ srcAddress: string;
60
+ inputToken: string; // spoke-side token paid in
61
+ inputAmount: bigint;
62
+ minOutputAmount: bigint; // min lsoda* (18 dp), slippage applied
63
+ deadline?: bigint; // defaults to hub block timestamp + 5 min
64
+ solver?: Address; // 0x0 = any solver
65
+ partnerFee?: PartnerFee; // per-intent override; deducted from inputAmount before the swap
66
+ };
67
+
68
+ type LeverageYieldSwapWithdrawParams = {
69
+ vault: Address;
70
+ srcChainKey: SpokeChainKey; // chain the user signs the sendMessage on
71
+ srcAddress: string;
72
+ dstChainKey: SpokeChainKey; // where the swapped-back token is delivered
73
+ outputToken: string;
74
+ inputAmount: bigint; // lsoda* shares to burn (18 dp)
75
+ minOutputAmount: bigint;
76
+ recipient?: string; // defaults to srcAddress
77
+ deadline?: bigint;
78
+ solver?: Address;
79
+ };
80
+
81
+ // The execute-mode wrapper (createVaultIntent / vaultSwap). The two vault execution modifiers
82
+ // live HERE, never on the generic swap surface:
83
+ type VaultSwapActionParams<K, Raw> = SpokeExecActionParams<K, Raw, CreateIntentParams<K>> & {
84
+ hubWalletSwap?: boolean; // withdraw: inputToken is hub-wallet lsoda*, authorise via Connection.sendMessage
85
+ partnerFee?: PartnerFee; // beats config.swaps.partnerFee for this intent only
86
+ };
87
+ ```
88
+
89
+ ## Common call shapes
90
+
91
+ ### Deposit (any token → `lsoda*`)
92
+
93
+ ```ts
94
+ const built = await sodax.leverageYield.deposit({
95
+ vault: vault.vault,
96
+ srcChainKey: ChainKeys.ARBITRUM_MAINNET,
97
+ srcAddress: '0x…',
98
+ inputToken: '0x…weETHonArbitrum',
99
+ inputAmount: parseUnits('1', 18),
100
+ minOutputAmount: 0n, // quote via sodax.swaps.getQuote (token_dst = vault), then apply slippage
101
+ partnerFee: { address: '0x…', percentage: 100 }, // optional 1% per-intent fee
102
+ });
103
+ if (!built.ok) return;
104
+
105
+ const result = await sodax.leverageYield.vaultSwap({ ...built.value, walletProvider });
106
+ if (!result.ok) return;
107
+ const { solverExecutionResponse, intent, intentDeliveryInfo } = result.value;
108
+ ```
109
+
110
+ ### Withdraw (`lsoda*` → any token)
111
+
112
+ ```ts
113
+ const built = await sodax.leverageYield.withdraw({
114
+ vault: vault.vault,
115
+ srcChainKey: ChainKeys.ARBITRUM_MAINNET, // user signs the sendMessage here
116
+ srcAddress: '0x…',
117
+ dstChainKey: ChainKeys.ARBITRUM_MAINNET, // token delivered here
118
+ outputToken: '0x…weETHonArbitrum',
119
+ inputAmount: shareBalance, // lsoda* to burn
120
+ minOutputAmount: 0n, // quote via sodax.swaps.getQuote (token_src = vault)
121
+ });
122
+ if (!built.ok) return;
123
+ // built.value.hubWalletSwap === true — no spoke approval; the hub wallet authorises the spend
124
+ await sodax.leverageYield.vaultSwap({ ...built.value, walletProvider });
125
+ ```
126
+
127
+ ### Manual create → relay → notify
128
+
129
+ ```ts
130
+ const created = await sodax.leverageYield.createVaultIntent({ ...built.value, raw: false, walletProvider });
131
+ if (!created.ok) return;
132
+ const { tx, relayData } = created.value;
133
+ // relay `tx` with the shared relayTxAndWaitPacket helper using relayData, then:
134
+ await sodax.leverageYield.notifySolver({ intent_tx_hash: hubIntentTxHash });
135
+ ```
136
+
137
+ ## Return shapes
138
+
139
+ | Method | Success type |
140
+ |---|---|
141
+ | `deposit`, `withdraw` | `LeverageYieldSwapPayload` (`{ params: CreateIntentParams; hubWalletSwap?: true; partnerFee? }`) |
142
+ | `createVaultIntent` | `CreateVaultIntentResult<K, Raw>` (`{ tx, intent & feeAmount, relayData }`) |
143
+ | `vaultSwap` | `VaultSwapResponse` (`{ solverExecutionResponse, intent, intentDeliveryInfo }`) |
144
+ | `notifySolver` | `SolverExecutionResponse` (`{ answer, intent_hash }`) |
145
+ | `approve` | `TxReturnType<HubChainKey, R>` |
146
+ | `isAllowanceValid` | `boolean` |
147
+ | `getApr` | `LeverageYieldApr` (`{ supplyAprRay, borrowAprRay, targetLtvBps, leverageMultiplierWad, netAprRay }`, RAY/WAD) |
148
+ | `getEffectiveApr` | `LeverageYieldEffectiveApr` (= `LeverageYieldApr & { lsdApr, effectiveSupplyAprRay, effectiveNetAprRay }`) |
149
+ | `getLsdApr` | `LeverageYieldLsdApr` (`{ aprRay, label, stale }`) |
150
+ | `getPosition` | `LeverageYieldPosition` (`{ collateral, debt, ltv, healthFactor, idleAsset }`) |
151
+ | `getTotalAssets`, `preview*`, `getMaxWithdraw*`, `getShareBalance*` | `bigint` |
152
+ | `getAsset` | `Address` |
153
+ | `listVaults` / `getVault` / `getVaultByAddress` | `LeverageYieldVault[]` / `LeverageYieldVault \| undefined` (synchronous) |
154
+
155
+ ## Error codes
156
+
157
+ `feature: 'leverageYield'`. Action discriminator on `context.action`: `'deposit' | 'withdraw' | 'approve' | 'allowanceCheck' | 'vaultSwap'`. Read methods partition on `context.method`.
158
+
159
+ | Method | Narrow code union |
160
+ |---|---|
161
+ | `deposit`, `withdraw` | `VALIDATION_FAILED \| INTENT_CREATION_FAILED \| LOOKUP_FAILED \| UNKNOWN` (create-intent subset + `LOOKUP_FAILED` with `method: 'resolveDeadline'` when the default-deadline hub-block read fails) |
162
+ | `createVaultIntent` | `VALIDATION_FAILED \| INTENT_CREATION_FAILED \| UNKNOWN` (create-intent subset) |
163
+ | `vaultSwap` | `VALIDATION_FAILED \| INTENT_CREATION_FAILED \| TX_VERIFICATION_FAILED \| TX_SUBMIT_FAILED \| RELAY_TIMEOUT \| RELAY_FAILED \| EXECUTION_FAILED \| EXTERNAL_API_ERROR \| UNKNOWN` |
164
+ | `notifySolver` | `EXECUTION_FAILED \| EXTERNAL_API_ERROR \| UNKNOWN` (with `phase: 'postExecution'`) |
165
+ | `approve` | `VALIDATION_FAILED \| APPROVE_FAILED \| UNKNOWN` |
166
+ | `isAllowanceValid` | `VALIDATION_FAILED \| ALLOWANCE_CHECK_FAILED \| UNKNOWN` (action `'allowanceCheck'`) |
167
+ | Read methods | `VALIDATION_FAILED \| LOOKUP_FAILED \| UNKNOWN` (with `method` discriminator) |
168
+
169
+ Relay/tx-verification codes appear **only** on `vaultSwap` — `createVaultIntent` alone stays within the create-intent subset. `notifySolver` (public, for manual orchestration) emits the post-execution subset, which `vaultSwap` also surfaces.
170
+
171
+ ## Cross-references
172
+
173
+ - ERC-4626 share-as-token model, APR math, and the deliberate swap-domain duplication: SDK source `packages/sdk/docs/LEVERAGE_YIELD.md`.
174
+ - For React Query hooks over this surface, load the `sodax-dapp-kit` skill (integration mode) — its `features/leverage-yield.md`.
175
+ - v2-only feature — no migration sibling.
@@ -29,7 +29,8 @@ type SodaxFeature =
29
29
  | 'migration'
30
30
  | 'dex'
31
31
  | 'partner'
32
- | 'recovery';
32
+ | 'recovery'
33
+ | 'leverageYield';
33
34
  ```
34
35
 
35
36
  The `(feature, code)` pair is the canonical discriminator. Loggers tag both fields; switch statements branch on both.
@@ -156,6 +157,21 @@ type LookupErrorCode = 'VALIDATION_FAILED' | 'LOOKUP_FAILED' | 'UNKNOWN';
156
157
 
157
158
  Both follow the same shape: action methods get the full exec union (`'EXECUTION_FAILED' \| 'INTENT_CREATION_FAILED' \| ...`), read methods get `LookupErrorCode`, approve methods get `ApproveErrorCode`.
158
159
 
160
+ ### Leverage Yield (`feature: 'leverageYield'`)
161
+
162
+ Action discriminator on `context.action`: `'deposit' \| 'withdraw' \| 'approve' \| 'allowanceCheck' \| 'vaultSwap'`.
163
+
164
+ | Method | Narrow code union |
165
+ |---|---|
166
+ | `deposit`, `withdraw`, `createVaultIntent` | `CreateIntentErrorCode` (create-intent subset) |
167
+ | `vaultSwap` | All exec codes: `VALIDATION_FAILED \| INTENT_CREATION_FAILED \| TX_VERIFICATION_FAILED \| TX_SUBMIT_FAILED \| RELAY_TIMEOUT \| RELAY_FAILED \| EXECUTION_FAILED \| EXTERNAL_API_ERROR \| UNKNOWN` |
168
+ | `notifySolver` | `EXECUTION_FAILED \| EXTERNAL_API_ERROR \| UNKNOWN` (with `phase: 'postExecution'`) |
169
+ | `approve` | `ApproveErrorCode` |
170
+ | `isAllowanceValid` | `AllowanceCheckErrorCode` (action `'allowanceCheck'`) |
171
+ | `getApr`, `getEffectiveApr`, `getLsdApr`, `getPosition`, `getTotalAssets`, `previewDeposit`, `previewWithdraw`, `previewRedeem`, `getMaxWithdraw`, `getMaxWithdrawForUser`, `getShareBalance`, `getShareBalanceForUser`, `getAsset` | `LookupErrorCode` (with `method` discriminator) |
172
+
173
+ Relay/tx-verification codes appear only on `vaultSwap`; `createVaultIntent` stays within the create-intent subset. `notifySolver` is public (manual create→relay→notify) and emits the post-execution subset.
174
+
159
175
  ---
160
176
 
161
177
 
@@ -67,6 +67,7 @@ import {
67
67
  dexInvariant,
68
68
  partnerInvariant,
69
69
  recoveryInvariant,
70
+ leverageYieldInvariant,
70
71
  mapRelayFailure,
71
72
 
72
73
  // Tokens
@@ -108,6 +109,17 @@ import {
108
109
  type ClClaimRewardsParams,
109
110
  type MigrationParams,
110
111
  type UnifiedBnUSDMigrateParams,
112
+ type LeverageYieldSwapDepositParams,
113
+ type LeverageYieldSwapWithdrawParams,
114
+ type LeverageYieldSwapPayload,
115
+ type VaultSwapActionParams,
116
+ type VaultSwapResponse,
117
+ type CreateVaultIntentResult,
118
+ type LeverageYieldApr,
119
+ type LeverageYieldEffectiveApr,
120
+ type LeverageYieldLsdApr,
121
+ type LeverageYieldPosition,
122
+ type LeverageYieldVault,
111
123
  // …
112
124
 
113
125
  // Backend / relay
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: sodax-sdk-leverage-yield
3
+ description: 'Granular skill for the @sodax/sdk v2 leverage-yield feature only — leveraged-yield ERC-4626 vaults on Sonic via LeverageYieldService. Deposit any token → lsoda* shares, withdraw shares → any token (both as solver-tradeable intent swaps), createVaultIntent/vaultSwap/notifySolver, Sonic-direct approve/isAllowanceValid, and reads (getApr, getEffectiveApr, getPosition, getTotalAssets, preview*, getMaxWithdraw*, getShareBalance*, listVaults). Use when the task is leverage-yield vaults (e.g. "deposit into a leverage vault", "withdraw lsoda shares", "vault APR / effective APR", "leverage vault position / health factor", "lsoda share balance"). New in v2 — integration only, no v1 migration path. Links into the parent sodax-sdk knowledge tree. For React dapps, prefer sodax-dapp-kit.'
4
+ ---
5
+
6
+ # Leverage Yield (Core SDK granular skill)
7
+
8
+ Granular skill for `LeverageYieldService` — `sodax.leverageYield`. Feature tag: `'leverageYield'`. **New in v2; no v1 migration path.**
9
+
10
+ ## Step 1 — Clarify with user before coding
11
+
12
+ 1. **Deposit or withdraw?** Deposit = any spoke token → `lsoda*` shares (delivered to the user's hub wallet). Withdraw = `lsoda*` shares (in the hub wallet) → any token on any chain.
13
+ 2. **Build vs execute.** `deposit` / `withdraw` only *build* a `LeverageYieldSwapPayload`; `vaultSwap` *executes* it end-to-end. Spread the built payload into `vaultSwap({ ...payload, walletProvider })`.
14
+ 3. **End-to-end or manual relay?** `vaultSwap` does create → verify → relay → notify. For backend submit-tx, use `createVaultIntent` then relay yourself and finish with the public `notifySolver`.
15
+ 4. **Reads?** `getEffectiveApr` (headline, AAVE + LSD), `getApr` (AAVE-only — can be negative), `getPosition`, `getTotalAssets`, `previewRedeem`, `getMaxWithdrawForUser` / `getShareBalanceForUser` (resolve the hub wallet from a spoke address internally).
16
+
17
+ ## Integration workflow
18
+
19
+ 1. [`../integration/knowledge/ai-rules.md`](../integration/knowledge/ai-rules.md).
20
+ 2. [`../integration/knowledge/features/leverage-yield.md`](../integration/knowledge/features/leverage-yield.md) — `LeverageYieldService` API, the share-as-token model, deposit/withdraw/vaultSwap flows, APR math.
21
+ 3. Path-specific recipe:
22
+ - Signed → [`../integration/knowledge/recipes/signed-tx-flow.md`](../integration/knowledge/recipes/signed-tx-flow.md)
23
+ - Unsigned → [`../integration/knowledge/recipes/raw-tx-flow.md`](../integration/knowledge/recipes/raw-tx-flow.md)
24
+ 4. Errors (`feature: 'leverageYield'`) → [`../integration/knowledge/reference/error-codes.md`](../integration/knowledge/reference/error-codes.md).
25
+
26
+ ### Leverage-yield-specific anti-patterns
27
+
28
+ - **Expecting `deposit` / `withdraw` to broadcast.** They are builders returning a `LeverageYieldSwapPayload`. Execute via `vaultSwap({ ...payload, walletProvider })`.
29
+ - **Using `getApr` as the headline number.** For LSD-backed vaults the AAVE-only spread is often negative; `getEffectiveApr` folds in the LSD staking yield (the real source of return).
30
+ - **Passing a spoke address to `getShareBalance` / `getMaxWithdraw`.** Those take a hub address. Use the `*ForUser(vault, srcChainKey, srcAddress)` variants to resolve the hub wallet first.
31
+ - **Gating withdraw on `approve` / `isAllowanceValid`.** Those are Sonic-direct allowance helpers for the vault's underlying asset; the swap-style withdraw authorises the share spend via a hub-wallet `Connection.sendMessage` (`hubWalletSwap: true`).
32
+ - **Quoting on the pre-fee amount.** A deposit's per-intent `partnerFee` is deducted from `inputAmount` before the swap — quote (via the swap solver, `token_dst` = vault) on the post-fee amount.
33
+
34
+ ## Verification
35
+
36
+ 1. `pnpm tsc --noEmit` clean.
37
+ 2. Every `await sodax.leverageYield.<method>(...)` has `if (!result.ok)`.
38
+ 3. Deposit/withdraw build a payload that is then run through `vaultSwap` (or `createVaultIntent` + relay + `notifySolver`).
39
+ 4. APR display uses `getEffectiveApr`; share/withdraw sizing from a spoke address uses the `*ForUser` reads.
40
+
41
+ ## Related granular skills (same family)
42
+
43
+ - [`../swap/SKILL.md`](../swap/SKILL.md) — `vaultSwap` is a leverage-yield copy of the swap intent flow; quote `minOutputAmount` via the solver quote there.
44
+ - [`../recovery/SKILL.md`](../recovery/SKILL.md) — recover stuck hub-wallet assets (including `lsoda*` shares) back to a spoke chain.
45
+
46
+ For multi-feature tasks, load the broad [`sodax-sdk` skill](../SKILL.md).