@sodax/dapp-kit 1.5.6-beta → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -422
- package/ai-exported/AGENTS.md +134 -0
- package/ai-exported/integration/README.md +49 -0
- package/ai-exported/integration/ai-rules.md +79 -0
- package/ai-exported/integration/architecture.md +274 -0
- package/ai-exported/integration/features/README.md +29 -0
- package/ai-exported/integration/features/auxiliary-services.md +169 -0
- package/ai-exported/integration/features/bitcoin.md +87 -0
- package/ai-exported/integration/features/bridge.md +91 -0
- package/ai-exported/integration/features/dex.md +152 -0
- package/ai-exported/integration/features/migration.md +118 -0
- package/ai-exported/integration/features/money-market.md +116 -0
- package/ai-exported/integration/features/staking.md +123 -0
- package/ai-exported/integration/features/swap.md +101 -0
- package/ai-exported/integration/quickstart.md +187 -0
- package/ai-exported/integration/recipes/README.md +136 -0
- package/ai-exported/integration/recipes/backend-queries.md +157 -0
- package/ai-exported/integration/recipes/bitcoin.md +193 -0
- package/ai-exported/integration/recipes/bridge.md +174 -0
- package/ai-exported/integration/recipes/dex.md +204 -0
- package/ai-exported/integration/recipes/invalidations.md +115 -0
- package/ai-exported/integration/recipes/migration.md +212 -0
- package/ai-exported/integration/recipes/money-market.md +206 -0
- package/ai-exported/integration/recipes/mutation-error-handling.md +118 -0
- package/ai-exported/integration/recipes/observability.md +93 -0
- package/ai-exported/integration/recipes/setup.md +144 -0
- package/ai-exported/integration/recipes/staking.md +202 -0
- package/ai-exported/integration/recipes/swap.md +272 -0
- package/ai-exported/integration/recipes/wallet-connectivity.md +101 -0
- package/ai-exported/integration/reference/README.md +12 -0
- package/ai-exported/integration/reference/glossary.md +188 -0
- package/ai-exported/integration/reference/hooks-index.md +194 -0
- package/ai-exported/integration/reference/public-api.md +110 -0
- package/ai-exported/integration/reference/querykey-conventions.md +179 -0
- package/ai-exported/migration/README.md +60 -0
- package/ai-exported/migration/ai-rules.md +81 -0
- package/ai-exported/migration/breaking-changes/hook-signatures.md +233 -0
- package/ai-exported/migration/breaking-changes/querykey-conventions.md +108 -0
- package/ai-exported/migration/breaking-changes/result-handling.md +211 -0
- package/ai-exported/migration/breaking-changes/sdk-leakage.md +165 -0
- package/ai-exported/migration/checklist.md +89 -0
- package/ai-exported/migration/features/README.md +34 -0
- package/ai-exported/migration/features/auxiliary-services.md +114 -0
- package/ai-exported/migration/features/bitcoin.md +88 -0
- package/ai-exported/migration/features/bridge.md +123 -0
- package/ai-exported/migration/features/dex.md +101 -0
- package/ai-exported/migration/features/migration.md +120 -0
- package/ai-exported/migration/features/money-market.md +97 -0
- package/ai-exported/migration/features/staking.md +109 -0
- package/ai-exported/migration/features/swap.md +118 -0
- package/ai-exported/migration/recipes.md +188 -0
- package/ai-exported/migration/reference/README.md +15 -0
- package/ai-exported/migration/reference/deleted-hooks.md +110 -0
- package/ai-exported/migration/reference/error-shape-crosswalk.md +144 -0
- package/ai-exported/migration/reference/renamed-hooks.md +66 -0
- package/dist/index.cjs +2642 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1550 -0
- package/dist/index.d.ts +1020 -2051
- package/dist/index.mjs +1594 -1532
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -10
- package/src/contexts/index.ts +0 -3
- package/src/hooks/_mutationContract.test.ts +99 -0
- package/src/hooks/backend/README.md +2 -2
- package/src/hooks/backend/index.ts +13 -13
- package/src/hooks/backend/unwrapResult.ts +1 -0
- package/src/hooks/backend/useBackendAllMoneyMarketAssets.ts +13 -45
- package/src/hooks/backend/useBackendAllMoneyMarketBorrowers.ts +29 -59
- package/src/hooks/backend/useBackendIntentByHash.ts +21 -47
- package/src/hooks/backend/useBackendIntentByTxHash.ts +23 -50
- package/src/hooks/backend/useBackendMoneyMarketAsset.ts +21 -54
- package/src/hooks/backend/useBackendMoneyMarketAssetBorrowers.ts +30 -57
- package/src/hooks/backend/useBackendMoneyMarketAssetSuppliers.ts +31 -58
- package/src/hooks/backend/useBackendMoneyMarketPosition.ts +22 -38
- package/src/hooks/backend/useBackendOrderbook.ts +27 -49
- package/src/hooks/backend/useBackendSubmitSwapTx.ts +30 -36
- package/src/hooks/backend/useBackendSubmitSwapTxStatus.ts +38 -58
- package/src/hooks/backend/useBackendUserIntents.ts +25 -63
- package/src/hooks/bitcoin/index.ts +9 -8
- package/src/hooks/bitcoin/useBitcoinBalance.ts +20 -5
- package/src/hooks/bitcoin/useExpiredUtxos.ts +26 -16
- package/src/hooks/bitcoin/useFundTradingWallet.ts +33 -30
- package/src/hooks/bitcoin/useRadfiAuth.ts +43 -40
- package/src/hooks/bitcoin/useRadfiSession.ts +53 -59
- package/src/hooks/bitcoin/useRadfiWithdraw.ts +35 -53
- package/src/hooks/bitcoin/useRenewUtxos.ts +30 -50
- package/src/hooks/bitcoin/useTradingWallet.ts +1 -1
- package/src/hooks/bitcoin/useTradingWalletBalance.ts +25 -14
- package/src/hooks/bridge/index.ts +5 -5
- package/src/hooks/bridge/useBridge.ts +29 -55
- package/src/hooks/bridge/useBridgeAllowance.ts +38 -38
- package/src/hooks/bridge/useBridgeApprove.ts +32 -57
- package/src/hooks/bridge/useGetBridgeableAmount.ts +23 -37
- package/src/hooks/bridge/useGetBridgeableTokens.ts +27 -50
- package/src/hooks/dex/index.ts +16 -16
- package/src/hooks/dex/useClaimRewards.ts +35 -54
- package/src/hooks/dex/useCreateDecreaseLiquidityParams.ts +7 -20
- package/src/hooks/dex/useCreateDepositParams.ts +7 -21
- package/src/hooks/dex/useCreateSupplyLiquidityParams.ts +13 -28
- package/src/hooks/dex/useCreateWithdrawParams.ts +7 -20
- package/src/hooks/dex/useDecreaseLiquidity.ts +40 -66
- package/src/hooks/dex/useDexAllowance.ts +29 -75
- package/src/hooks/dex/useDexApprove.ts +32 -43
- package/src/hooks/dex/useDexDeposit.ts +42 -49
- package/src/hooks/dex/useDexWithdraw.ts +32 -43
- package/src/hooks/dex/useLiquidityAmounts.ts +27 -84
- package/src/hooks/dex/usePoolBalances.ts +50 -72
- package/src/hooks/dex/usePoolData.ts +17 -43
- package/src/hooks/dex/usePools.ts +11 -38
- package/src/hooks/dex/usePositionInfo.ts +27 -62
- package/src/hooks/dex/useSupplyLiquidity.ts +80 -75
- package/src/hooks/index.ts +12 -10
- package/src/hooks/migrate/index.ts +13 -4
- package/src/hooks/migrate/useMigrateBaln.ts +42 -0
- package/src/hooks/migrate/useMigrateIcxToSoda.ts +44 -0
- package/src/hooks/migrate/useMigratebnUSD.ts +47 -0
- package/src/hooks/migrate/useMigrationAllowance.ts +76 -0
- package/src/hooks/migrate/useMigrationApprove.ts +66 -0
- package/src/hooks/migrate/useRevertMigrateSodaToIcx.ts +39 -0
- package/src/hooks/mm/index.ts +14 -12
- package/src/hooks/mm/useAToken.ts +25 -41
- package/src/hooks/mm/useATokensBalances.ts +29 -60
- package/src/hooks/mm/useBorrow.ts +38 -56
- package/src/hooks/mm/useMMAllowance.ts +37 -73
- package/src/hooks/mm/useMMApprove.ts +36 -43
- package/src/hooks/mm/useRepay.ts +33 -53
- package/src/hooks/mm/useReservesData.ts +12 -38
- package/src/hooks/mm/useReservesHumanized.ts +12 -31
- package/src/hooks/mm/useReservesList.ts +11 -31
- package/src/hooks/mm/useReservesUsdFormat.ts +15 -35
- package/src/hooks/mm/useSupply.ts +45 -51
- package/src/hooks/mm/useUserFormattedSummary.ts +32 -84
- package/src/hooks/mm/useUserReservesData.ts +27 -77
- package/src/hooks/mm/useWithdraw.ts +38 -54
- package/src/hooks/partner/index.ts +6 -0
- package/src/hooks/partner/useApproveToken.ts +42 -0
- package/src/hooks/partner/useFeeClaimSwap.ts +38 -0
- package/src/hooks/partner/useFetchAssetsBalances.ts +37 -0
- package/src/hooks/partner/useGetAutoSwapPreferences.ts +37 -0
- package/src/hooks/partner/useIsTokenApproved.ts +39 -0
- package/src/hooks/partner/useSetSwapPreference.ts +50 -0
- package/src/hooks/provider/index.ts +1 -2
- package/src/hooks/provider/useHubProvider.ts +1 -1
- package/src/hooks/recovery/index.ts +2 -0
- package/src/hooks/recovery/useHubAssetBalances.ts +43 -0
- package/src/hooks/recovery/useWithdrawHubAsset.ts +48 -0
- package/src/hooks/shared/index.ts +10 -6
- package/src/hooks/shared/types.ts +77 -0
- package/src/hooks/shared/unwrapResult.ts +19 -0
- package/src/hooks/shared/useDeriveUserWalletAddress.ts +22 -40
- package/src/hooks/shared/useEstimateGas.ts +18 -15
- package/src/hooks/shared/useGetUserHubWalletAddress.ts +25 -26
- package/src/hooks/shared/useRequestTrustline.ts +28 -61
- package/src/hooks/shared/useSafeMutation.test.ts +43 -0
- package/src/hooks/shared/useSafeMutation.ts +68 -0
- package/src/hooks/shared/useSodaxContext.ts +1 -1
- package/src/hooks/shared/useStellarTrustlineCheck.ts +30 -64
- package/src/hooks/shared/useXBalances.test.ts +113 -0
- package/src/hooks/shared/useXBalances.ts +61 -0
- package/src/hooks/staking/index.ts +18 -18
- package/src/hooks/staking/useCancelUnstake.ts +30 -41
- package/src/hooks/staking/useClaim.ts +27 -36
- package/src/hooks/staking/useConvertedAssets.ts +24 -34
- package/src/hooks/staking/useInstantUnstake.ts +33 -40
- package/src/hooks/staking/useInstantUnstakeAllowance.ts +37 -45
- package/src/hooks/staking/useInstantUnstakeApprove.ts +42 -42
- package/src/hooks/staking/useInstantUnstakeRatio.ts +24 -41
- package/src/hooks/staking/useStake.ts +32 -37
- package/src/hooks/staking/useStakeAllowance.ts +30 -43
- package/src/hooks/staking/useStakeApprove.ts +40 -40
- package/src/hooks/staking/useStakeRatio.ts +24 -40
- package/src/hooks/staking/useStakingConfig.ts +14 -27
- package/src/hooks/staking/useStakingInfo.ts +30 -38
- package/src/hooks/staking/useUnstake.ts +29 -43
- package/src/hooks/staking/useUnstakeAllowance.ts +37 -44
- package/src/hooks/staking/useUnstakeApprove.ts +40 -43
- package/src/hooks/staking/useUnstakingInfo.ts +29 -41
- package/src/hooks/staking/useUnstakingInfoWithPenalty.ts +31 -47
- package/src/hooks/swap/index.ts +8 -8
- package/src/hooks/swap/useCancelLimitOrder.ts +24 -41
- package/src/hooks/swap/useCancelSwap.ts +24 -33
- package/src/hooks/swap/useCreateLimitOrder.ts +29 -62
- package/src/hooks/swap/useQuote.ts +17 -43
- package/src/hooks/swap/useStatus.ts +22 -29
- package/src/hooks/swap/useSwap.ts +31 -49
- package/src/hooks/swap/useSwapAllowance.ts +38 -35
- package/src/hooks/swap/useSwapApprove.ts +48 -57
- package/src/index.ts +5 -3
- package/src/providers/SodaxProvider.tsx +17 -11
- package/src/providers/createSodaxQueryClient.ts +96 -0
- package/src/providers/index.ts +2 -1
- package/src/utils/dex-utils.ts +27 -5
- package/src/utils/index.ts +1 -1
- package/dist/index.d.mts +0 -2581
- package/dist/index.js +0 -2562
- package/dist/index.js.map +0 -1
- package/src/hooks/migrate/types.ts +0 -15
- package/src/hooks/migrate/useMigrate.tsx +0 -110
- package/src/hooks/migrate/useMigrationAllowance.tsx +0 -79
- package/src/hooks/migrate/useMigrationApprove.tsx +0 -129
- package/src/hooks/provider/useSpokeProvider.ts +0 -172
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Recipe: Money Market
|
|
2
|
+
|
|
3
|
+
Cross-chain lending (supply) and borrowing.
|
|
4
|
+
|
|
5
|
+
**Depends on:** [setup.md](setup.md), [wallet-connectivity.md](wallet-connectivity.md)
|
|
6
|
+
|
|
7
|
+
## Hooks
|
|
8
|
+
|
|
9
|
+
| Hook | Type | Purpose |
|
|
10
|
+
|------|------|---------|
|
|
11
|
+
| `useSupply` | Mutation | Supply tokens as collateral |
|
|
12
|
+
| `useBorrow` | Mutation | Borrow against collateral |
|
|
13
|
+
| `useWithdraw` | Mutation | Withdraw supplied tokens |
|
|
14
|
+
| `useRepay` | Mutation | Repay borrowed tokens |
|
|
15
|
+
| `useMMAllowance` | Query | Check approval (auto-skips for borrow/withdraw) |
|
|
16
|
+
| `useMMApprove` | Mutation | Approve tokens |
|
|
17
|
+
| `useReservesData` | Query | All reserve data |
|
|
18
|
+
| `useReservesHumanized` | Query | Reserves in human-readable (decimal-normalized) format |
|
|
19
|
+
| `useReservesList` | Query | List of reserve asset addresses |
|
|
20
|
+
| `useReservesUsdFormat` | Query | Reserves with USD values |
|
|
21
|
+
| `useUserFormattedSummary` | Query | User portfolio summary (health factor, collateral, debt) |
|
|
22
|
+
| `useUserReservesData` | Query | User reserve positions |
|
|
23
|
+
| `useAToken` | Query | aToken metadata |
|
|
24
|
+
| `useATokensBalances` | Query | aToken balances |
|
|
25
|
+
|
|
26
|
+
## Hook shape
|
|
27
|
+
|
|
28
|
+
All mutation hooks follow the **zero-domain-param** policy — the hook itself takes only an optional `mutationOptions` slot; ALL domain inputs (`params`, `walletProvider`, etc.) flow through `mutate(vars)`:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// @ai-snippets-skip
|
|
32
|
+
const { mutateAsync: supply } = useSupply();
|
|
33
|
+
await supply({ params: { srcChainKey, srcAddress, token, amount, action: 'supply' }, walletProvider });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
On SDK failure, `mutationFn` throws — `mutation.error` and `onError` engage natively. Use `mutateAsyncSafe` to get `Promise<Result<T>>` that never rejects.
|
|
37
|
+
|
|
38
|
+
## Display Reserves
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { useReservesData } from '@sodax/dapp-kit';
|
|
42
|
+
|
|
43
|
+
function ReservesList() {
|
|
44
|
+
const { data, isLoading } = useReservesData();
|
|
45
|
+
if (isLoading || !data) return <div>Loading...</div>;
|
|
46
|
+
const [reserves] = data;
|
|
47
|
+
return (
|
|
48
|
+
<table>
|
|
49
|
+
<thead><tr><th>Asset</th><th>Supply Rate</th><th>Borrow Rate</th></tr></thead>
|
|
50
|
+
<tbody>
|
|
51
|
+
{reserves.map((r) => (
|
|
52
|
+
<tr key={r.underlyingAsset}>
|
|
53
|
+
<td>{r.symbol}</td>
|
|
54
|
+
<td>{r.liquidityRate.toString()}</td>
|
|
55
|
+
<td>{r.variableBorrowRate.toString()}</td>
|
|
56
|
+
</tr>
|
|
57
|
+
))}
|
|
58
|
+
</tbody>
|
|
59
|
+
</table>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## User Position
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { useUserFormattedSummary } from '@sodax/dapp-kit';
|
|
68
|
+
import type { SpokeChainKey } from '@sodax/sdk';
|
|
69
|
+
|
|
70
|
+
function UserPosition({ spokeChainKey, userAddress }: { spokeChainKey: SpokeChainKey; userAddress: string }) {
|
|
71
|
+
const { data: summary } = useUserFormattedSummary({ params: { spokeChainKey, userAddress } });
|
|
72
|
+
if (!summary) return null;
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<p>Collateral: ${summary.totalCollateralUSD}</p>
|
|
76
|
+
<p>Debt: ${summary.totalBorrowsUSD}</p>
|
|
77
|
+
<p>Health Factor: {summary.healthFactor}</p>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Check Allowance + Approve
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { useMMAllowance, useMMApprove } from '@sodax/dapp-kit';
|
|
87
|
+
import { useWalletProvider } from '@sodax/wallet-sdk-react';
|
|
88
|
+
import { ChainKeys, type MoneyMarketSupplyParams } from '@sodax/sdk';
|
|
89
|
+
|
|
90
|
+
function MMApproval({ params }: { params: MoneyMarketSupplyParams<typeof ChainKeys.BASE_MAINNET> }) {
|
|
91
|
+
const walletProvider = useWalletProvider({ xChainId: ChainKeys.BASE_MAINNET });
|
|
92
|
+
// useMMAllowance wraps the request under params.payload.
|
|
93
|
+
// Auto-returns true for borrow/withdraw — no unnecessary RPC calls.
|
|
94
|
+
const { data: isApproved } = useMMAllowance({ params: { payload: params } });
|
|
95
|
+
const { mutateAsync: approve, isPending } = useMMApprove();
|
|
96
|
+
|
|
97
|
+
if (isApproved) return null;
|
|
98
|
+
return (
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => walletProvider && approve({ params, walletProvider })}
|
|
101
|
+
disabled={isPending || !walletProvider}
|
|
102
|
+
>
|
|
103
|
+
{isPending ? 'Approving...' : 'Approve'}
|
|
104
|
+
</button>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Supply
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { useSupply } from '@sodax/dapp-kit';
|
|
113
|
+
import { useWalletProvider } from '@sodax/wallet-sdk-react';
|
|
114
|
+
import { ChainKeys } from '@sodax/sdk';
|
|
115
|
+
|
|
116
|
+
function SupplyButton({ srcAddress }: { srcAddress: string }) {
|
|
117
|
+
const chainKey = ChainKeys.BASE_MAINNET;
|
|
118
|
+
const walletProvider = useWalletProvider({ xChainId: chainKey });
|
|
119
|
+
const { mutateAsync: supply, isPending } = useSupply();
|
|
120
|
+
|
|
121
|
+
const handleSupply = async () => {
|
|
122
|
+
if (!walletProvider) return;
|
|
123
|
+
try {
|
|
124
|
+
const txHashPair = await supply({
|
|
125
|
+
params: { srcChainKey: chainKey, srcAddress, token: '0x...', amount: 1_000_000n, action: 'supply' },
|
|
126
|
+
walletProvider,
|
|
127
|
+
});
|
|
128
|
+
console.log('Supplied (spoke, hub):', txHashPair);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error(e);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<button onClick={handleSupply} disabled={isPending || !walletProvider}>
|
|
136
|
+
{isPending ? 'Supplying...' : 'Supply'}
|
|
137
|
+
</button>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Borrow / Withdraw / Repay
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { useBorrow, useWithdraw, useRepay } from '@sodax/dapp-kit';
|
|
146
|
+
import { useWalletProvider } from '@sodax/wallet-sdk-react';
|
|
147
|
+
import { ChainKeys } from '@sodax/sdk';
|
|
148
|
+
|
|
149
|
+
function MMActions({ srcAddress }: { srcAddress: `0x${string}` }) {
|
|
150
|
+
const chainKey = ChainKeys.BASE_MAINNET;
|
|
151
|
+
const walletProvider = useWalletProvider({ xChainId: chainKey });
|
|
152
|
+
|
|
153
|
+
const { mutateAsync: borrow } = useBorrow();
|
|
154
|
+
const { mutateAsync: withdraw } = useWithdraw();
|
|
155
|
+
const { mutateAsync: repay } = useRepay();
|
|
156
|
+
|
|
157
|
+
const handleBorrow = async () => {
|
|
158
|
+
if (!walletProvider) return;
|
|
159
|
+
await borrow({
|
|
160
|
+
params: { srcChainKey: chainKey, srcAddress, token: '0x0000000000000000000000000000000000000000', amount: 500_000n, action: 'borrow' },
|
|
161
|
+
walletProvider,
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleWithdraw = async () => {
|
|
166
|
+
if (!walletProvider) return;
|
|
167
|
+
await withdraw({
|
|
168
|
+
params: { srcChainKey: chainKey, srcAddress, token: '0x0000000000000000000000000000000000000000', amount: 1_000_000n, action: 'withdraw' },
|
|
169
|
+
walletProvider,
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const handleRepay = async () => {
|
|
174
|
+
if (!walletProvider) return;
|
|
175
|
+
await repay({
|
|
176
|
+
params: { srcChainKey: chainKey, srcAddress, token: '0x0000000000000000000000000000000000000000', amount: 500_000n, action: 'repay' },
|
|
177
|
+
walletProvider,
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Types
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
type MoneyMarketSupplyParams<K extends SpokeChainKey = SpokeChainKey> = {
|
|
189
|
+
srcChainKey: K;
|
|
190
|
+
srcAddress: string;
|
|
191
|
+
token: string;
|
|
192
|
+
amount: bigint;
|
|
193
|
+
action: 'supply';
|
|
194
|
+
toChainId?: SpokeChainKey;
|
|
195
|
+
toAddress?: string;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Borrow / Withdraw / Repay follow the same shape with their respective `action` literal.
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Notes
|
|
202
|
+
|
|
203
|
+
- **Borrow/withdraw skip approval** — `useMMAllowance` returns `true` automatically for these actions.
|
|
204
|
+
- **Health factor < 1.0** means liquidation risk.
|
|
205
|
+
- All operations support optional `toChainId` / `toAddress` (and `fromChainId` / `fromAddress` on borrow) for cross-chain delivery.
|
|
206
|
+
- Mutations throw on SDK failure — use `mutateAsyncSafe` for `Result<T>` ergonomics without `try/catch`.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Recipe: Mutation error handling
|
|
2
|
+
|
|
3
|
+
Every dapp-kit mutation hook returns three ways to invoke the mutation. Pick by call shape — they all share the same React Query state under the hood (`isError`, `error`, `data`, devtools all work).
|
|
4
|
+
|
|
5
|
+
## The three call shapes
|
|
6
|
+
|
|
7
|
+
| Method | Returns | Rejects on `!ok`? | When to use |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `mutate(vars)` | `void` (fire-and-forget) | Never | Button-click handlers where you read `isPending` / `isError` / `error` from the hook in render. |
|
|
10
|
+
| `mutateAsync(vars)` | `Promise<TData>` | **Yes** | Imperative chains where you only need the success value. **MUST be wrapped in `try/catch`** or you'll leak unhandled rejections on user-rejects. |
|
|
11
|
+
| `mutateAsyncSafe(vars)` | `Promise<Result<TData>>` | **Never** | Imperative chains where you want explicit branching without exception flow. |
|
|
12
|
+
|
|
13
|
+
`mutateAsyncSafe` is the **recommended default** for sequenced flows like `if (!hasAllowance) await approve(); await action();` — the user-reject case is the modal failure mode in dApps, not exceptional, and `Result<T>`-style branching reads cleaner than exception flow.
|
|
14
|
+
|
|
15
|
+
## fire-and-forget — `mutate`
|
|
16
|
+
|
|
17
|
+
Best for buttons that just kick off a mutation and let render reflect state.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { useSwap } from '@sodax/dapp-kit';
|
|
21
|
+
|
|
22
|
+
function SwapButton({ params, walletProvider }) {
|
|
23
|
+
const m = useSwap();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<button onClick={() => m.mutate({ params, walletProvider })} disabled={m.isPending}>
|
|
28
|
+
{m.isPending ? 'Swapping...' : 'Swap'}
|
|
29
|
+
</button>
|
|
30
|
+
{m.isError && <p>Error: {m.error.message}</p>}
|
|
31
|
+
{m.isSuccess && <p>Done!</p>}
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## throws — `mutateAsync`
|
|
38
|
+
|
|
39
|
+
Use when you want exception flow control. **Always wrap in `try/catch`.**
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { useSwap } from '@sodax/dapp-kit';
|
|
43
|
+
|
|
44
|
+
function MyFlow({ params, walletProvider }) {
|
|
45
|
+
const { mutateAsync: swap } = useSwap();
|
|
46
|
+
|
|
47
|
+
const handleClick = async () => {
|
|
48
|
+
try {
|
|
49
|
+
const result = await swap({ params, walletProvider });
|
|
50
|
+
navigate('/done');
|
|
51
|
+
} catch (e) {
|
|
52
|
+
toast.error(e instanceof Error ? e.message : 'Swap failed');
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you forget `try/catch`, an unhandled rejection lands in the global handler — most apps treat that as a fatal error.
|
|
59
|
+
|
|
60
|
+
## safe — `mutateAsyncSafe`
|
|
61
|
+
|
|
62
|
+
Recommended. Branches on `Result<T>`. Never rejects.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// @ai-snippets-skip — Intent.intentHash placeholder
|
|
66
|
+
import { useSwap } from '@sodax/dapp-kit';
|
|
67
|
+
|
|
68
|
+
function MyFlow({ params, walletProvider }) {
|
|
69
|
+
const { mutateAsyncSafe: swap } = useSwap();
|
|
70
|
+
|
|
71
|
+
const handleClick = async () => {
|
|
72
|
+
const result = await swap({ params, walletProvider });
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
toast.error(result.error instanceof Error ? result.error.message : 'Swap failed');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const { intent, intentDeliveryInfo } = result.value;
|
|
78
|
+
navigate(`/done?intent=${intent.intentHash}`);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Pairs well with sequenced flows:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// @ai-snippets-skip — illustrative sequenced-flow pattern; `approve`, `swap`, `hasAllowance`
|
|
87
|
+
// are assumed from enclosing context (see recipes/swap.md for the full flow).
|
|
88
|
+
const handleSwap = async () => {
|
|
89
|
+
if (!hasAllowance) {
|
|
90
|
+
const a = await approve({ params, walletProvider });
|
|
91
|
+
if (!a.ok) { toast.error('Approve failed'); return; }
|
|
92
|
+
}
|
|
93
|
+
const r = await swap({ params, walletProvider });
|
|
94
|
+
if (!r.ok) { toast.error('Swap failed'); return; }
|
|
95
|
+
// success
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Why `mutationFn` throws on SDK `!ok`
|
|
100
|
+
|
|
101
|
+
Inside the hook, `mutationFn` calls `unwrapResult` on the SDK's `Result<T>`:
|
|
102
|
+
|
|
103
|
+
- On `Result.ok === true` → returns the unwrapped `value` (TData).
|
|
104
|
+
- On `Result.ok === false` → throws `result.error`.
|
|
105
|
+
|
|
106
|
+
Why throw? Because React Query's error model (`isError`, `error`, `onError`, `retry`, `throwOnError`, devtools) keys off `mutationFn` throwing. With `Result<T>` returned as success, none of those engaged on SDK failure. Throwing makes them work out of the box.
|
|
107
|
+
|
|
108
|
+
The `mutateAsyncSafe` shim re-packs the throw back into a `Result<T>` so you can have it both ways: React Query's native error machinery in render + `Result<T>` ergonomics imperatively.
|
|
109
|
+
|
|
110
|
+
## Common pitfall — never call `useMutation` directly
|
|
111
|
+
|
|
112
|
+
Every dapp-kit mutation hook calls `useSafeMutation` (a thin wrapper). When you write your own wrapper hooks around dapp-kit mutations, do the same — call dapp-kit's hooks, not React Query's `useMutation`. Otherwise consumers won't get `mutateAsyncSafe`.
|
|
113
|
+
|
|
114
|
+
## Cross-references
|
|
115
|
+
|
|
116
|
+
- [`observability.md`](observability.md) — global `onMutationError` for logging/Sentry.
|
|
117
|
+
- [`invalidations.md`](invalidations.md) — composing your own `onSuccess` after dapp-kit's hook-owned invalidations.
|
|
118
|
+
- [`../architecture.md`](../architecture.md) — full design rationale for `useSafeMutation` / `unwrapResult`.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Recipe: Observability — global mutation error hook
|
|
2
|
+
|
|
3
|
+
`createSodaxQueryClient` returns a `QueryClient` pre-wired with a `MutationCache.onError` hook that gives you a single observability seam for every mutation failure across the app. Optional opt-in — if you construct your own `QueryClient`, nothing changes.
|
|
4
|
+
|
|
5
|
+
## Default behavior
|
|
6
|
+
|
|
7
|
+
Logs every mutation failure to console as `[sodax] Mutation error: <error>`:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { createSodaxQueryClient } from '@sodax/dapp-kit';
|
|
11
|
+
|
|
12
|
+
const queryClient = createSodaxQueryClient();
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Wire to your own logger
|
|
16
|
+
|
|
17
|
+
Sentry, Datadog, Pino — any error sink:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// @ai-snippets-skip — Sentry 3rd-party import
|
|
21
|
+
import { createSodaxQueryClient } from '@sodax/dapp-kit';
|
|
22
|
+
import * as Sentry from '@sentry/react';
|
|
23
|
+
|
|
24
|
+
const queryClient = createSodaxQueryClient({
|
|
25
|
+
onMutationError: (error) => Sentry.captureException(error),
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Disable the default
|
|
30
|
+
|
|
31
|
+
If you wire per-hook `onError` callbacks plus a React error boundary, you may not want a duplicated console log:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { createSodaxQueryClient } from '@sodax/dapp-kit';
|
|
35
|
+
|
|
36
|
+
const queryClient = createSodaxQueryClient({ onMutationError: () => {} });
|
|
37
|
+
console.log(queryClient);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Per-mutation opt-out — `meta.silent`
|
|
41
|
+
|
|
42
|
+
When a single mutation handles its error locally (e.g. its own toast in `onError`) and you don't want a duplicate `[sodax] Mutation error:` log, pass `meta: { silent: true }` on that mutation:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { useSwap } from '@sodax/dapp-kit';
|
|
46
|
+
|
|
47
|
+
const swap = useSwap({
|
|
48
|
+
mutationOptions: {
|
|
49
|
+
meta: { silent: true },
|
|
50
|
+
onError: (e) => toast.error(e.message),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
console.log(swap);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The global `onMutationError` skips this mutation; everything else still fires through it.
|
|
57
|
+
|
|
58
|
+
## Bring your own `MutationCache`
|
|
59
|
+
|
|
60
|
+
If you pass `config.mutationCache`, the factory keeps your cache instance (preserving any `onError` you set on it) and *additionally* subscribes to its event stream to dispatch `onMutationError`. Both handlers fire — neither replaces the other. `meta.silent` is honored in both branches.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { MutationCache } from '@tanstack/react-query';
|
|
64
|
+
import { createSodaxQueryClient } from '@sodax/dapp-kit';
|
|
65
|
+
|
|
66
|
+
const myCache = new MutationCache({ onError: myOwnErrorHandler });
|
|
67
|
+
const queryClient = createSodaxQueryClient({ config: { mutationCache: myCache } });
|
|
68
|
+
// myOwnErrorHandler runs; sodax onMutationError ALSO runs (unless meta.silent).
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Important: this is observability, not prevention
|
|
72
|
+
|
|
73
|
+
The global hook fires for **every** mutation failure regardless of:
|
|
74
|
+
- Whether the consumer caught the rejection with `try/catch`
|
|
75
|
+
- Whether the consumer branched on `mutateAsyncSafe`'s `Result.ok`
|
|
76
|
+
- Whether the consumer registered a per-hook `onError`
|
|
77
|
+
|
|
78
|
+
It does **not** detect "unhandled" rejections. If you want to prevent unhandled rejections at the call site, use `mutateAsyncSafe` (see [`mutation-error-handling.md`](mutation-error-handling.md)).
|
|
79
|
+
|
|
80
|
+
## When to use
|
|
81
|
+
|
|
82
|
+
- **Sentry/Datadog integration** — single point of capture for all SDK mutation failures.
|
|
83
|
+
- **Global error toast** — render-side error toast on top of per-hook handling. Simpler than per-hook `onError`.
|
|
84
|
+
- **Debug console mode** — keep the default during local dev for quick visibility.
|
|
85
|
+
|
|
86
|
+
## When NOT to use
|
|
87
|
+
|
|
88
|
+
- **You only handle errors per-hook.** The global hook adds noise (duplicate logs). Either disable it entirely or use `meta.silent`.
|
|
89
|
+
|
|
90
|
+
## Cross-references
|
|
91
|
+
|
|
92
|
+
- [`mutation-error-handling.md`](mutation-error-handling.md) — call-site error handling (preventing unhandled rejections).
|
|
93
|
+
- [`../architecture.md`](../architecture.md) — full design notes on `createSodaxQueryClient`.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Recipe: Setup
|
|
2
|
+
|
|
3
|
+
Install and wire `@sodax/dapp-kit` into a React project.
|
|
4
|
+
|
|
5
|
+
**Depends on:** None
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Required
|
|
11
|
+
pnpm add @sodax/dapp-kit @tanstack/react-query
|
|
12
|
+
|
|
13
|
+
# Optional (only if you want built-in wallet connectivity hooks + providers)
|
|
14
|
+
pnpm add @sodax/wallet-sdk-react
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Wire Providers
|
|
18
|
+
|
|
19
|
+
RPC URLs are injected via `config.chains` — each chain entry takes `{ rpcUrl: string }`.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// providers.tsx
|
|
23
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
24
|
+
import { SodaxProvider, createSodaxQueryClient } from '@sodax/dapp-kit';
|
|
25
|
+
import { ChainKeys, type DeepPartial, type SodaxConfig } from '@sodax/sdk';
|
|
26
|
+
|
|
27
|
+
const queryClient = createSodaxQueryClient();
|
|
28
|
+
|
|
29
|
+
const sodaxConfig: DeepPartial<SodaxConfig> = {
|
|
30
|
+
chains: {
|
|
31
|
+
[ChainKeys.ARBITRUM_MAINNET]: { rpcUrl: 'https://arb1.arbitrum.io/rpc' },
|
|
32
|
+
[ChainKeys.BASE_MAINNET]: { rpcUrl: 'https://mainnet.base.org' },
|
|
33
|
+
[ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
|
|
34
|
+
[ChainKeys.POLYGON_MAINNET]: { rpcUrl: 'https://polygon-rpc.com' },
|
|
35
|
+
// Add chains your dApp needs
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<SodaxProvider config={sodaxConfig}>
|
|
42
|
+
<QueryClientProvider client={queryClient}>
|
|
43
|
+
{children}
|
|
44
|
+
</QueryClientProvider>
|
|
45
|
+
</SodaxProvider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Optional: Add Wallet Provider
|
|
51
|
+
|
|
52
|
+
If you want to use `@sodax/wallet-sdk-react` for wallet connectivity, wrap `SodaxWalletProvider` inside `QueryClientProvider`:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// @ai-snippets-skip
|
|
56
|
+
import { SodaxWalletProvider, type SodaxWalletConfig } from '@sodax/wallet-sdk-react';
|
|
57
|
+
|
|
58
|
+
const walletConfig: SodaxWalletConfig = {
|
|
59
|
+
EVM: {
|
|
60
|
+
chains: {
|
|
61
|
+
[ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
|
|
62
|
+
[ChainKeys.BASE_MAINNET]: { rpcUrl: 'https://mainnet.base.org' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
68
|
+
return (
|
|
69
|
+
<SodaxProvider config={sodaxConfig}>
|
|
70
|
+
<QueryClientProvider client={queryClient}>
|
|
71
|
+
<SodaxWalletProvider config={walletConfig}>{children}</SodaxWalletProvider>
|
|
72
|
+
</QueryClientProvider>
|
|
73
|
+
</SodaxProvider>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## `createSodaxQueryClient` (optional)
|
|
79
|
+
|
|
80
|
+
`createSodaxQueryClient` returns a `QueryClient` pre-wired with a `MutationCache.onError` hook for global mutation observability. Use it instead of `new QueryClient()`:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// @ai-snippets-skip — illustrative — multiple createSodaxQueryClient variations
|
|
84
|
+
import { createSodaxQueryClient } from '@sodax/dapp-kit';
|
|
85
|
+
|
|
86
|
+
// Default: logs every mutation failure as `[sodax] Mutation error: <error>`
|
|
87
|
+
const queryClient = createSodaxQueryClient();
|
|
88
|
+
|
|
89
|
+
// Wire to your own logger
|
|
90
|
+
const queryClient = createSodaxQueryClient({
|
|
91
|
+
onMutationError: (e) => Sentry.captureException(e),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Silence a specific mutation locally via meta.silent
|
|
95
|
+
const swap = useSwap({
|
|
96
|
+
mutationOptions: { meta: { silent: true }, onError: (e) => toast.error(e.message) },
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Initialize SDK (Optional)
|
|
101
|
+
|
|
102
|
+
For dynamic config (latest tokens/chains from backend API):
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { useEffect } from 'react';
|
|
106
|
+
import { useSodaxContext } from '@sodax/dapp-kit';
|
|
107
|
+
|
|
108
|
+
export function useInitializeSodax() {
|
|
109
|
+
const { sodax } = useSodaxContext();
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
sodax.initialize().then((result) => {
|
|
113
|
+
if (!result.ok) console.error('Failed to initialize Sodax:', result.error);
|
|
114
|
+
});
|
|
115
|
+
}, [sodax]);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Chain Key Constants
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { ChainKeys } from '@sodax/sdk';
|
|
123
|
+
|
|
124
|
+
// Examples (full list lives in @sodax/sdk's reference):
|
|
125
|
+
ChainKeys.SONIC_MAINNET; // 'sonic' (hub)
|
|
126
|
+
ChainKeys.ARBITRUM_MAINNET; // '0xa4b1.arbitrum'
|
|
127
|
+
ChainKeys.BASE_MAINNET; // '0x2105.base'
|
|
128
|
+
ChainKeys.BSC_MAINNET; // '0x38.bsc'
|
|
129
|
+
ChainKeys.ETHEREUM_MAINNET; // '0x1.ethereum'
|
|
130
|
+
ChainKeys.POLYGON_MAINNET; // '0x89.polygon'
|
|
131
|
+
ChainKeys.OPTIMISM_MAINNET; // '0xa.optimism'
|
|
132
|
+
ChainKeys.AVALANCHE_MAINNET; // '0xa86a.avax'
|
|
133
|
+
ChainKeys.SUI_MAINNET; // 'sui'
|
|
134
|
+
ChainKeys.STELLAR_MAINNET; // 'stellar'
|
|
135
|
+
ChainKeys.SOLANA_MAINNET; // 'solana'
|
|
136
|
+
ChainKeys.ICON_MAINNET; // '0x1.icon'
|
|
137
|
+
ChainKeys.INJECTIVE_MAINNET; // 'injective-1'
|
|
138
|
+
ChainKeys.NEAR_MAINNET; // 'near'
|
|
139
|
+
ChainKeys.STACKS_MAINNET; // 'stacks'
|
|
140
|
+
ChainKeys.BITCOIN_MAINNET; // 'bitcoin'
|
|
141
|
+
// HyperEVM, Lightlink, Redbelly, Kaia also available.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**v1 → v2:** the legacy `*_MAINNET_CHAIN_ID` constants (e.g. `BSC_MAINNET_CHAIN_ID`) are gone. Use `ChainKeys.X_MAINNET` namespace access.
|