@sodax/dapp-kit 2.0.0-rc.2 → 2.0.0-rc.4
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 +9 -63
- package/dist/index.d.ts +11 -4
- package/dist/index.mjs +3 -6
- package/package.json +31 -19
- package/src/providers/SodaxProvider.tsx +15 -11
- package/ai-exported/AGENTS.md +0 -134
- package/ai-exported/integration/README.md +0 -49
- package/ai-exported/integration/ai-rules.md +0 -79
- package/ai-exported/integration/architecture.md +0 -274
- package/ai-exported/integration/features/README.md +0 -29
- package/ai-exported/integration/features/auxiliary-services.md +0 -169
- package/ai-exported/integration/features/bitcoin.md +0 -87
- package/ai-exported/integration/features/bridge.md +0 -91
- package/ai-exported/integration/features/dex.md +0 -152
- package/ai-exported/integration/features/migration.md +0 -118
- package/ai-exported/integration/features/money-market.md +0 -116
- package/ai-exported/integration/features/staking.md +0 -123
- package/ai-exported/integration/features/swap.md +0 -101
- package/ai-exported/integration/quickstart.md +0 -187
- package/ai-exported/integration/recipes/README.md +0 -136
- package/ai-exported/integration/recipes/backend-queries.md +0 -157
- package/ai-exported/integration/recipes/bitcoin.md +0 -193
- package/ai-exported/integration/recipes/bridge.md +0 -174
- package/ai-exported/integration/recipes/dex.md +0 -204
- package/ai-exported/integration/recipes/invalidations.md +0 -115
- package/ai-exported/integration/recipes/migration.md +0 -212
- package/ai-exported/integration/recipes/money-market.md +0 -206
- package/ai-exported/integration/recipes/mutation-error-handling.md +0 -118
- package/ai-exported/integration/recipes/observability.md +0 -93
- package/ai-exported/integration/recipes/setup.md +0 -144
- package/ai-exported/integration/recipes/staking.md +0 -202
- package/ai-exported/integration/recipes/swap.md +0 -272
- package/ai-exported/integration/recipes/wallet-connectivity.md +0 -101
- package/ai-exported/integration/reference/README.md +0 -12
- package/ai-exported/integration/reference/glossary.md +0 -188
- package/ai-exported/integration/reference/hooks-index.md +0 -190
- package/ai-exported/integration/reference/public-api.md +0 -110
- package/ai-exported/integration/reference/querykey-conventions.md +0 -179
- package/ai-exported/migration/README.md +0 -60
- package/ai-exported/migration/ai-rules.md +0 -81
- package/ai-exported/migration/breaking-changes/hook-signatures.md +0 -233
- package/ai-exported/migration/breaking-changes/querykey-conventions.md +0 -108
- package/ai-exported/migration/breaking-changes/result-handling.md +0 -211
- package/ai-exported/migration/breaking-changes/sdk-leakage.md +0 -165
- package/ai-exported/migration/checklist.md +0 -89
- package/ai-exported/migration/features/README.md +0 -34
- package/ai-exported/migration/features/auxiliary-services.md +0 -114
- package/ai-exported/migration/features/bitcoin.md +0 -88
- package/ai-exported/migration/features/bridge.md +0 -123
- package/ai-exported/migration/features/dex.md +0 -101
- package/ai-exported/migration/features/migration.md +0 -120
- package/ai-exported/migration/features/money-market.md +0 -97
- package/ai-exported/migration/features/staking.md +0 -109
- package/ai-exported/migration/features/swap.md +0 -118
- package/ai-exported/migration/recipes.md +0 -188
- package/ai-exported/migration/reference/README.md +0 -15
- package/ai-exported/migration/reference/deleted-hooks.md +0 -110
- package/ai-exported/migration/reference/error-shape-crosswalk.md +0 -144
- package/ai-exported/migration/reference/renamed-hooks.md +0 -66
- package/dist/index.cjs +0 -2642
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1550
- package/dist/index.mjs.map +0 -1
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
# Recipe: Migration
|
|
2
|
-
|
|
3
|
-
Legacy token migration (ICX, bnUSD, BALN) from the ICON ecosystem to SODAX.
|
|
4
|
-
|
|
5
|
-
**Depends on:** [setup.md](setup.md), [wallet-connectivity.md](wallet-connectivity.md)
|
|
6
|
-
|
|
7
|
-
## Hooks
|
|
8
|
-
|
|
9
|
-
| Hook | Type | Purpose |
|
|
10
|
-
|------|------|---------|
|
|
11
|
-
| `useMigrateIcxToSoda` | Mutation | ICX/wICX (ICON) → SODA (Sonic) |
|
|
12
|
-
| `useRevertMigrateSodaToIcx` | Mutation | SODA (Sonic) → wICX (ICON) |
|
|
13
|
-
| `useMigratebnUSD` | Mutation | Legacy bnUSD ↔ new bnUSD (bidirectional, any spoke chain) |
|
|
14
|
-
| `useMigrateBaln` | Mutation | BALN (ICON) → SODA (Sonic) with optional lock period |
|
|
15
|
-
| `useMigrationApprove` | Mutation | Approve token spending before migration |
|
|
16
|
-
| `useMigrationAllowance` | Query | Check if approval is needed |
|
|
17
|
-
|
|
18
|
-
## Migration Paths
|
|
19
|
-
|
|
20
|
-
| From | To | Reversible | Approval needed |
|
|
21
|
-
|------|----|-----------|----------------|
|
|
22
|
-
| ICX/wICX (ICON) | SODA (Sonic) | Yes (use `useRevertMigrateSodaToIcx`) | No (ICON has no ERC-20 allowance) |
|
|
23
|
-
| BALN (ICON) | SODA (Sonic) | No | No |
|
|
24
|
-
| Legacy bnUSD (EVM/Stellar/ICON) | New bnUSD | Yes (same hook, swap src/dst) | Yes (EVM/Stellar sources) |
|
|
25
|
-
|
|
26
|
-
## ICX to SODA
|
|
27
|
-
|
|
28
|
-
```tsx
|
|
29
|
-
import { useMigrateIcxToSoda } from '@sodax/dapp-kit';
|
|
30
|
-
import { useWalletProvider } from '@sodax/wallet-sdk-react';
|
|
31
|
-
import { ChainKeys, type IconAddress } from '@sodax/sdk';
|
|
32
|
-
|
|
33
|
-
function IcxMigration({ srcAddress, dstAddress }: { srcAddress: IconAddress; dstAddress: `0x${string}` }) {
|
|
34
|
-
const walletProvider = useWalletProvider({ xChainId: ChainKeys.ICON_MAINNET });
|
|
35
|
-
const { mutateAsync: migrate, isPending } = useMigrateIcxToSoda();
|
|
36
|
-
|
|
37
|
-
const handleMigrate = async () => {
|
|
38
|
-
if (!walletProvider) return;
|
|
39
|
-
try {
|
|
40
|
-
const txHashPair = await migrate({
|
|
41
|
-
params: {
|
|
42
|
-
srcChainKey: ChainKeys.ICON_MAINNET,
|
|
43
|
-
srcAddress,
|
|
44
|
-
// `address` must be the wICX or native-ICX token address — typed as the
|
|
45
|
-
// narrow IcxTokenType union from @sodax/sdk's ICON chain config.
|
|
46
|
-
address: 'cx3975b43d260fb8ec802cef6e60c2f4d07486f11d', // wICX on ICON mainnet
|
|
47
|
-
amount: 1_000_000_000_000_000_000n,
|
|
48
|
-
dstAddress, // Sonic recipient address
|
|
49
|
-
},
|
|
50
|
-
walletProvider,
|
|
51
|
-
});
|
|
52
|
-
console.log('Migrated:', txHashPair);
|
|
53
|
-
} catch (e) {
|
|
54
|
-
console.error(e);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<button onClick={handleMigrate} disabled={isPending || !walletProvider}>
|
|
60
|
-
{isPending ? 'Migrating...' : 'Migrate ICX to SODA'}
|
|
61
|
-
</button>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Revert SODA → wICX
|
|
67
|
-
|
|
68
|
-
Requires approval — SODA is an EVM token on Sonic.
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import { useMigrationAllowance, useMigrationApprove, useRevertMigrateSodaToIcx } from '@sodax/dapp-kit';
|
|
72
|
-
import { useWalletProvider } from '@sodax/wallet-sdk-react';
|
|
73
|
-
import { ChainKeys } from '@sodax/sdk';
|
|
74
|
-
import type { IcxCreateRevertMigrationParams } from '@sodax/sdk';
|
|
75
|
-
|
|
76
|
-
function RevertMigration({ srcAddress }: { srcAddress: `0x${string}` }) {
|
|
77
|
-
const walletProvider = useWalletProvider({ xChainId: ChainKeys.SONIC_MAINNET });
|
|
78
|
-
|
|
79
|
-
const revertParams: IcxCreateRevertMigrationParams = {
|
|
80
|
-
srcChainKey: ChainKeys.SONIC_MAINNET,
|
|
81
|
-
srcAddress,
|
|
82
|
-
amount: 1_000_000_000_000_000_000n,
|
|
83
|
-
dstAddress: 'hx...', // ICON recipient address
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const { data: isApproved } = useMigrationAllowance({
|
|
87
|
-
params: { params: revertParams, action: 'revert' },
|
|
88
|
-
});
|
|
89
|
-
const { mutateAsync: approve, isPending: isApproving } = useMigrationApprove();
|
|
90
|
-
const { mutateAsync: revert, isPending: isReverting } = useRevertMigrateSodaToIcx();
|
|
91
|
-
|
|
92
|
-
const handleRevert = async () => {
|
|
93
|
-
if (!walletProvider) return;
|
|
94
|
-
try {
|
|
95
|
-
if (!isApproved) {
|
|
96
|
-
await approve({ params: revertParams, walletProvider, action: 'revert' });
|
|
97
|
-
}
|
|
98
|
-
const txHashPair = await revert({ params: revertParams, walletProvider });
|
|
99
|
-
console.log('Reverted:', txHashPair);
|
|
100
|
-
} catch (e) {
|
|
101
|
-
console.error(e);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<button onClick={handleRevert} disabled={isReverting || isApproving || !walletProvider}>
|
|
107
|
-
{isApproving ? 'Approving...' : isReverting ? 'Reverting...' : 'Revert to wICX'}
|
|
108
|
-
</button>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## BALN to SODA (with optional lock period)
|
|
114
|
-
|
|
115
|
-
BALN migration does not require approval (ICON chain, no ERC-20 allowance). Longer lock period = higher SODA reward multiplier.
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
import { useMigrateBaln } from '@sodax/dapp-kit';
|
|
119
|
-
import { useWalletProvider } from '@sodax/wallet-sdk-react';
|
|
120
|
-
import { ChainKeys, LockupPeriod, type IconAddress } from '@sodax/sdk';
|
|
121
|
-
|
|
122
|
-
function BalnMigration({ srcAddress, dstAddress }: { srcAddress: IconAddress; dstAddress: `0x${string}` }) {
|
|
123
|
-
const walletProvider = useWalletProvider({ xChainId: ChainKeys.ICON_MAINNET });
|
|
124
|
-
const { mutateAsync: migrateBaln, isPending } = useMigrateBaln();
|
|
125
|
-
|
|
126
|
-
const handleMigrate = async () => {
|
|
127
|
-
if (!walletProvider) return;
|
|
128
|
-
try {
|
|
129
|
-
const txHashPair = await migrateBaln({
|
|
130
|
-
params: {
|
|
131
|
-
srcChainKey: ChainKeys.ICON_MAINNET,
|
|
132
|
-
srcAddress,
|
|
133
|
-
amount: 100_000_000_000_000_000_000n,
|
|
134
|
-
// LockupPeriod is an enum in seconds: NO_LOCKUP (0.5x reward),
|
|
135
|
-
// SIX_MONTHS (0.75x), TWELVE_MONTHS (1.0x), EIGHTEEN_MONTHS (1.25x),
|
|
136
|
-
// TWENTY_FOUR_MONTHS (1.5x).
|
|
137
|
-
lockupPeriod: LockupPeriod.TWELVE_MONTHS,
|
|
138
|
-
dstAddress,
|
|
139
|
-
stake: true, // Auto-stake the migrated SODA into the xSODA vault
|
|
140
|
-
},
|
|
141
|
-
walletProvider,
|
|
142
|
-
});
|
|
143
|
-
console.log('BALN migrated:', txHashPair);
|
|
144
|
-
} catch (e) {
|
|
145
|
-
console.error(e);
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<button onClick={handleMigrate} disabled={isPending || !walletProvider}>
|
|
151
|
-
{isPending ? 'Migrating...' : 'Migrate BALN to SODA'}
|
|
152
|
-
</button>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## bnUSD Migration (bidirectional)
|
|
158
|
-
|
|
159
|
-
Works for legacy ↔ new bnUSD across spoke chains. May require approval on EVM/Stellar sources.
|
|
160
|
-
|
|
161
|
-
```tsx
|
|
162
|
-
import { useMigratebnUSD, useMigrationAllowance, useMigrationApprove } from '@sodax/dapp-kit';
|
|
163
|
-
import { useWalletProvider } from '@sodax/wallet-sdk-react';
|
|
164
|
-
import { ChainKeys } from '@sodax/sdk';
|
|
165
|
-
import type { UnifiedBnUSDMigrateParams } from '@sodax/sdk';
|
|
166
|
-
|
|
167
|
-
function BnUSDMigration({ srcAddress }: { srcAddress: string }) {
|
|
168
|
-
const walletProvider = useWalletProvider({ xChainId: ChainKeys.BASE_MAINNET });
|
|
169
|
-
|
|
170
|
-
const bnUSDParams: UnifiedBnUSDMigrateParams<typeof ChainKeys.BASE_MAINNET> = {
|
|
171
|
-
srcChainKey: ChainKeys.BASE_MAINNET,
|
|
172
|
-
srcAddress,
|
|
173
|
-
srcbnUSD: '0x...', // legacy bnUSD address on Base
|
|
174
|
-
dstChainKey: ChainKeys.ARBITRUM_MAINNET,
|
|
175
|
-
dstbnUSD: '0x...', // new bnUSD address on Arbitrum
|
|
176
|
-
amount: 1_000_000n, // 6 decimals
|
|
177
|
-
dstAddress: srcAddress,
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const { data: isApproved } = useMigrationAllowance({
|
|
181
|
-
params: { params: bnUSDParams, action: 'migrate' },
|
|
182
|
-
});
|
|
183
|
-
const { mutateAsync: approve, isPending: isApproving } = useMigrationApprove();
|
|
184
|
-
const { mutateAsync: migratebnUSD, isPending: isMigrating } = useMigratebnUSD();
|
|
185
|
-
|
|
186
|
-
const handleMigrate = async () => {
|
|
187
|
-
if (!walletProvider) return;
|
|
188
|
-
try {
|
|
189
|
-
if (!isApproved) {
|
|
190
|
-
await approve({ params: bnUSDParams, walletProvider, action: 'migrate' });
|
|
191
|
-
}
|
|
192
|
-
const txHashPair = await migratebnUSD({ params: bnUSDParams, walletProvider });
|
|
193
|
-
console.log('bnUSD migrated:', txHashPair);
|
|
194
|
-
} catch (e) {
|
|
195
|
-
console.error(e);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<button onClick={handleMigrate} disabled={isMigrating || isApproving || !walletProvider}>
|
|
201
|
-
{isApproving ? 'Approving...' : isMigrating ? 'Migrating...' : 'Migrate bnUSD'}
|
|
202
|
-
</button>
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Notes
|
|
208
|
-
|
|
209
|
-
- **ICX and BALN forward migrations** don't require approval — ICON has no ERC-20 allowance mechanism.
|
|
210
|
-
- **SODA → ICX revert** and **EVM/Stellar bnUSD sources** require approval before migrating.
|
|
211
|
-
- **BALN lock periods**: `0` = 0.5x reward, `6` = 0.75x, `12` = 1.0x, `24` = 1.5x (months).
|
|
212
|
-
- `useMigratebnUSD` is bidirectional — swap `srcbnUSD`/`dstbnUSD` and `srcChainKey`/`dstChainKey` to go the other direction.
|
|
@@ -1,206 +0,0 @@
|
|
|
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`.
|
|
@@ -1,118 +0,0 @@
|
|
|
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`.
|
|
@@ -1,93 +0,0 @@
|
|
|
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`.
|