@sodax/dapp-kit 2.0.0-rc.3 → 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.
Files changed (61) hide show
  1. package/README.md +9 -63
  2. package/dist/index.mjs +0 -2
  3. package/package.json +31 -20
  4. package/ai-exported/AGENTS.md +0 -134
  5. package/ai-exported/integration/README.md +0 -49
  6. package/ai-exported/integration/ai-rules.md +0 -80
  7. package/ai-exported/integration/architecture.md +0 -276
  8. package/ai-exported/integration/features/README.md +0 -29
  9. package/ai-exported/integration/features/auxiliary-services.md +0 -169
  10. package/ai-exported/integration/features/bitcoin.md +0 -87
  11. package/ai-exported/integration/features/bridge.md +0 -91
  12. package/ai-exported/integration/features/dex.md +0 -152
  13. package/ai-exported/integration/features/migration.md +0 -118
  14. package/ai-exported/integration/features/money-market.md +0 -144
  15. package/ai-exported/integration/features/staking.md +0 -123
  16. package/ai-exported/integration/features/swap.md +0 -101
  17. package/ai-exported/integration/quickstart.md +0 -187
  18. package/ai-exported/integration/recipes/README.md +0 -136
  19. package/ai-exported/integration/recipes/backend-queries.md +0 -157
  20. package/ai-exported/integration/recipes/bitcoin.md +0 -193
  21. package/ai-exported/integration/recipes/bridge.md +0 -174
  22. package/ai-exported/integration/recipes/dex.md +0 -204
  23. package/ai-exported/integration/recipes/invalidations.md +0 -115
  24. package/ai-exported/integration/recipes/migration.md +0 -212
  25. package/ai-exported/integration/recipes/money-market.md +0 -207
  26. package/ai-exported/integration/recipes/mutation-error-handling.md +0 -118
  27. package/ai-exported/integration/recipes/observability.md +0 -93
  28. package/ai-exported/integration/recipes/setup.md +0 -168
  29. package/ai-exported/integration/recipes/staking.md +0 -202
  30. package/ai-exported/integration/recipes/swap.md +0 -272
  31. package/ai-exported/integration/recipes/wallet-connectivity.md +0 -128
  32. package/ai-exported/integration/reference/README.md +0 -12
  33. package/ai-exported/integration/reference/glossary.md +0 -190
  34. package/ai-exported/integration/reference/hooks-index.md +0 -190
  35. package/ai-exported/integration/reference/public-api.md +0 -110
  36. package/ai-exported/integration/reference/querykey-conventions.md +0 -179
  37. package/ai-exported/migration/README.md +0 -60
  38. package/ai-exported/migration/ai-rules.md +0 -81
  39. package/ai-exported/migration/breaking-changes/hook-signatures.md +0 -233
  40. package/ai-exported/migration/breaking-changes/querykey-conventions.md +0 -108
  41. package/ai-exported/migration/breaking-changes/result-handling.md +0 -211
  42. package/ai-exported/migration/breaking-changes/sdk-leakage.md +0 -167
  43. package/ai-exported/migration/checklist.md +0 -89
  44. package/ai-exported/migration/features/README.md +0 -34
  45. package/ai-exported/migration/features/auxiliary-services.md +0 -114
  46. package/ai-exported/migration/features/bitcoin.md +0 -88
  47. package/ai-exported/migration/features/bridge.md +0 -160
  48. package/ai-exported/migration/features/dex.md +0 -101
  49. package/ai-exported/migration/features/migration.md +0 -120
  50. package/ai-exported/migration/features/money-market.md +0 -139
  51. package/ai-exported/migration/features/staking.md +0 -109
  52. package/ai-exported/migration/features/swap.md +0 -133
  53. package/ai-exported/migration/recipes.md +0 -185
  54. package/ai-exported/migration/reference/README.md +0 -15
  55. package/ai-exported/migration/reference/deleted-hooks.md +0 -110
  56. package/ai-exported/migration/reference/error-shape-crosswalk.md +0 -144
  57. package/ai-exported/migration/reference/renamed-hooks.md +0 -68
  58. package/dist/index.cjs +0 -2641
  59. package/dist/index.cjs.map +0 -1
  60. package/dist/index.d.cts +0 -1557
  61. package/dist/index.mjs.map +0 -1
@@ -1,168 +0,0 @@
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
- ### Config reactivity
51
-
52
- `SodaxProvider`'s `config` prop is tracked by **reference**, not by value. The SDK is re-instantiated whenever the prop identity changes - resetting wagmi connection state, in-flight RPC, and any persisted state inside `useSodaxContext` consumers. Choose the pattern that matches your config source:
53
-
54
- ```tsx
55
- // @ai-snippets-skip — illustrative; uses placeholder values + JSX without surrounding imports
56
- // ✅ Static config — module constant (preferred when nothing depends on runtime state).
57
- const sodaxConfig: DeepPartial<SodaxConfig> = {
58
- chains: { [ChainKeys.SONIC_MAINNET]: { rpcUrl: '...' } },
59
- };
60
-
61
- // ✅ Runtime-switchable config — useMemo with explicit deps.
62
- // Re-runs only when listed deps change, so the SDK survives unrelated re-renders.
63
- const sodaxConfig = useMemo(
64
- () => ({ solver: solverConfigMap[solverEnv], chains: { ... } }),
65
- [solverEnv], // SDK re-inits when solverEnv switches.
66
- );
67
-
68
- // ❌ Inline — new identity every parent render, SDK churns on every render.
69
- <SodaxProvider config={{ chains: { ... } }}>
70
- ```
71
-
72
- Drive runtime config switches (solver env, feature flags, etc.) through `useMemo` deps - never remount `SodaxProvider` for them.
73
-
74
- ### Optional: Add Wallet Provider
75
-
76
- If you want to use `@sodax/wallet-sdk-react` for wallet connectivity, wrap `SodaxWalletProvider` inside `QueryClientProvider`:
77
-
78
- ```tsx
79
- // @ai-snippets-skip
80
- import { SodaxWalletProvider, type SodaxWalletConfig } from '@sodax/wallet-sdk-react';
81
-
82
- const walletConfig: SodaxWalletConfig = {
83
- EVM: {
84
- chains: {
85
- [ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
86
- [ChainKeys.BASE_MAINNET]: { rpcUrl: 'https://mainnet.base.org' },
87
- },
88
- },
89
- };
90
-
91
- export function Providers({ children }: { children: React.ReactNode }) {
92
- return (
93
- <SodaxProvider config={sodaxConfig}>
94
- <QueryClientProvider client={queryClient}>
95
- <SodaxWalletProvider config={walletConfig}>{children}</SodaxWalletProvider>
96
- </QueryClientProvider>
97
- </SodaxProvider>
98
- );
99
- }
100
- ```
101
-
102
- ## `createSodaxQueryClient` (optional)
103
-
104
- `createSodaxQueryClient` returns a `QueryClient` pre-wired with a `MutationCache.onError` hook for global mutation observability. Use it instead of `new QueryClient()`:
105
-
106
- ```tsx
107
- // @ai-snippets-skip — illustrative — multiple createSodaxQueryClient variations
108
- import { createSodaxQueryClient } from '@sodax/dapp-kit';
109
-
110
- // Default: logs every mutation failure as `[sodax] Mutation error: <error>`
111
- const queryClient = createSodaxQueryClient();
112
-
113
- // Wire to your own logger
114
- const queryClient = createSodaxQueryClient({
115
- onMutationError: (e) => Sentry.captureException(e),
116
- });
117
-
118
- // Silence a specific mutation locally via meta.silent
119
- const swap = useSwap({
120
- mutationOptions: { meta: { silent: true }, onError: (e) => toast.error(e.message) },
121
- });
122
- ```
123
-
124
- ## Initialize SDK (Optional)
125
-
126
- For dynamic config (latest tokens/chains from backend API):
127
-
128
- ```tsx
129
- import { useEffect } from 'react';
130
- import { useSodaxContext } from '@sodax/dapp-kit';
131
-
132
- export function useInitializeSodax() {
133
- const { sodax } = useSodaxContext();
134
-
135
- useEffect(() => {
136
- sodax.initialize().then((result) => {
137
- if (!result.ok) console.error('Failed to initialize Sodax:', result.error);
138
- });
139
- }, [sodax]);
140
- }
141
- ```
142
-
143
- ## Chain Key Constants
144
-
145
- ```tsx
146
- import { ChainKeys } from '@sodax/sdk';
147
-
148
- // Examples (full list lives in @sodax/sdk's reference):
149
- ChainKeys.SONIC_MAINNET; // 'sonic' (hub)
150
- ChainKeys.ARBITRUM_MAINNET; // '0xa4b1.arbitrum'
151
- ChainKeys.BASE_MAINNET; // '0x2105.base'
152
- ChainKeys.BSC_MAINNET; // '0x38.bsc'
153
- ChainKeys.ETHEREUM_MAINNET; // '0x1.ethereum'
154
- ChainKeys.POLYGON_MAINNET; // '0x89.polygon'
155
- ChainKeys.OPTIMISM_MAINNET; // '0xa.optimism'
156
- ChainKeys.AVALANCHE_MAINNET; // '0xa86a.avax'
157
- ChainKeys.SUI_MAINNET; // 'sui'
158
- ChainKeys.STELLAR_MAINNET; // 'stellar'
159
- ChainKeys.SOLANA_MAINNET; // 'solana'
160
- ChainKeys.ICON_MAINNET; // '0x1.icon'
161
- ChainKeys.INJECTIVE_MAINNET; // 'injective-1'
162
- ChainKeys.NEAR_MAINNET; // 'near'
163
- ChainKeys.STACKS_MAINNET; // 'stacks'
164
- ChainKeys.BITCOIN_MAINNET; // 'bitcoin'
165
- // HyperEVM, Lightlink, Redbelly, Kaia also available.
166
- ```
167
-
168
- **v1 → v2:** the legacy `*_MAINNET_CHAIN_ID` constants (e.g. `BSC_MAINNET_CHAIN_ID`) are gone. Use `ChainKeys.X_MAINNET` namespace access.
@@ -1,202 +0,0 @@
1
- # Recipe: Staking
2
-
3
- SODA token staking via xSODA ERC-4626 vault.
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
- | `useStake` | Stake SODA, receive xSODA |
14
- | `useStakeApprove` | Approve SODA for staking |
15
- | `useUnstake` | Request unstake (waiting period) |
16
- | `useUnstakeApprove` | Approve xSODA for unstaking |
17
- | `useInstantUnstake` | Instant unstake with slippage |
18
- | `useInstantUnstakeApprove` | Approve xSODA for instant unstaking |
19
- | `useClaim` | Claim SODA after waiting period |
20
- | `useCancelUnstake` | Cancel pending unstake |
21
-
22
- ### Queries
23
-
24
- | Hook | Purpose |
25
- |------|---------|
26
- | `useStakeAllowance` | Check SODA approval for staking |
27
- | `useUnstakeAllowance` | Check xSODA approval for unstaking |
28
- | `useInstantUnstakeAllowance` | Check xSODA approval for instant unstaking |
29
- | `useStakingInfo` | Staking position (total staked, xSODA balance, value) |
30
- | `useUnstakingInfo` | Pending unstake requests |
31
- | `useUnstakingInfoWithPenalty` | Unstake requests with penalty calcs |
32
- | `useStakingConfig` | Unstaking period, max penalty |
33
- | `useStakeRatio` | SODA-to-xSODA exchange rate |
34
- | `useInstantUnstakeRatio` | Instant unstake rate |
35
- | `useConvertedAssets` | xSODA to SODA conversion |
36
-
37
- ## Staking Dashboard
38
-
39
- ```tsx
40
- import { useStakingInfo, useStakingConfig, useStakeRatio } from '@sodax/dapp-kit';
41
- import type { SpokeChainKey } from '@sodax/sdk';
42
- import { formatUnits } from 'viem';
43
-
44
- function StakingDashboard({ srcAddress, srcChainKey }: { srcAddress: `0x${string}`; srcChainKey: SpokeChainKey }) {
45
- const { data: info } = useStakingInfo({ params: { srcAddress, srcChainKey } });
46
- const { data: config } = useStakingConfig({});
47
- const { data: ratio } = useStakeRatio({ params: { amount: 1000000000000000000n } });
48
-
49
- if (!info) return <div>Loading...</div>;
50
- return (
51
- <div>
52
- <p>Total Staked: {formatUnits(info.totalStaked, 18)} SODA</p>
53
- <p>Your xSODA: {formatUnits(info.userXSodaBalance, 18)}</p>
54
- <p>Your Value: {formatUnits(info.userXSodaValue, 18)} SODA</p>
55
- {ratio && <p>Rate: 1 SODA = {formatUnits(ratio[0], 18)} xSODA</p>}
56
- {config && <p>Unstaking: {(Number(config.unstakingPeriod) / 86400).toFixed(1)} days</p>}
57
- </div>
58
- );
59
- }
60
- ```
61
-
62
- ## Stake
63
-
64
- ```tsx
65
- import { useState } from 'react';
66
- import { useStake, useStakeAllowance, useStakeApprove, useStakeRatio } from '@sodax/dapp-kit';
67
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
68
- import { ChainKeys } from '@sodax/sdk';
69
- import { parseUnits, formatUnits, type Address } from 'viem';
70
-
71
- function StakeForm({ srcAddress }: { srcAddress: Address }) {
72
- const [amount, setAmount] = useState('');
73
- const chainKey = ChainKeys.BASE_MAINNET;
74
- const walletProvider = useWalletProvider({ xChainId: chainKey });
75
- const parsedAmount = amount ? parseUnits(amount, 18) : 0n;
76
-
77
- const { data: ratio } = useStakeRatio({ params: { amount: parsedAmount } });
78
-
79
- const stakeParams = parsedAmount > 0n
80
- ? { srcChainKey: chainKey, srcAddress, amount: parsedAmount, minReceive: ratio ? (ratio[0] * 95n) / 100n : 0n, action: 'stake' as const }
81
- : undefined;
82
-
83
- // useStakeAllowance wraps `Omit<StakeParams, 'action'>` under params.payload. Read-only,
84
- // no walletProvider needed (calls `staking.isAllowanceValid` with `raw: true` internally).
85
- const { data: isApproved } = useStakeAllowance({
86
- params: stakeParams
87
- ? { payload: { srcChainKey: chainKey, srcAddress, amount: parsedAmount, minReceive: stakeParams.minReceive } }
88
- : undefined,
89
- });
90
- const { mutateAsync: approve, isPending: isApproving } = useStakeApprove();
91
- const { mutateAsync: stake, isPending: isStaking } = useStake();
92
-
93
- const handleStake = async () => {
94
- if (!stakeParams || !walletProvider) return;
95
- try {
96
- if (!isApproved) await approve({ params: stakeParams, walletProvider });
97
- const txHashPair = await stake({ params: stakeParams, walletProvider });
98
- console.log('Staked:', txHashPair);
99
- } catch (e) {
100
- console.error(e);
101
- }
102
- };
103
-
104
- return (
105
- <div>
106
- <input placeholder="SODA amount" value={amount} onChange={(e) => setAmount(e.target.value)} />
107
- {ratio && <p>~{formatUnits(ratio[0], 18)} xSODA</p>}
108
- <button onClick={handleStake} disabled={isStaking || isApproving || !stakeParams || !walletProvider}>
109
- {isApproving ? 'Approving...' : isStaking ? 'Staking...' : 'Stake'}
110
- </button>
111
- </div>
112
- );
113
- }
114
- ```
115
-
116
- ## Unstake + Claim
117
-
118
- ```tsx
119
- import { useUnstakingInfoWithPenalty, useClaim } from '@sodax/dapp-kit';
120
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
121
- import { ChainKeys } from '@sodax/sdk';
122
- import { formatUnits, type Address } from 'viem';
123
-
124
- function UnstakePanel({ srcAddress }: { srcAddress: Address }) {
125
- const chainKey = ChainKeys.BASE_MAINNET;
126
- const walletProvider = useWalletProvider({ xChainId: chainKey });
127
- const { data: info } = useUnstakingInfoWithPenalty({ params: { srcAddress, srcChainKey: chainKey } });
128
- const { mutateAsync: claim } = useClaim();
129
-
130
- return (
131
- <div>
132
- {info?.requestsWithPenalty.map((req, i) => (
133
- <div key={i}>
134
- <p>{formatUnits(req.claimableAmount, 18)} SODA claimable (penalty: {req.penaltyPercentage}%)</p>
135
- <button
136
- onClick={() => walletProvider && claim({
137
- // req.id is the requestId on the UserUnstakeInfo shape; req.request holds the
138
- // original UnstakeSodaRequest. ClaimParams takes the id and the post-penalty amount.
139
- params: { srcChainKey: chainKey, srcAddress, requestId: req.id, amount: req.claimableAmount, action: 'claim' },
140
- walletProvider,
141
- })}
142
- >
143
- Claim
144
- </button>
145
- </div>
146
- ))}
147
- </div>
148
- );
149
- }
150
- ```
151
-
152
- ## Instant Unstake
153
-
154
- ```tsx
155
- import { useInstantUnstake, useInstantUnstakeRatio } from '@sodax/dapp-kit';
156
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
157
- import { ChainKeys } from '@sodax/sdk';
158
- import { type Address } from 'viem';
159
-
160
- function InstantUnstakeButton({ xSodaAmount, srcAddress }: { xSodaAmount: bigint; srcAddress: Address }) {
161
- const chainKey = ChainKeys.BASE_MAINNET;
162
- const walletProvider = useWalletProvider({ xChainId: chainKey });
163
- const { data: ratio } = useInstantUnstakeRatio({ params: { amount: xSodaAmount } });
164
- const { mutateAsync: instantUnstake, isPending } = useInstantUnstake();
165
-
166
- return (
167
- <button
168
- disabled={isPending || !walletProvider}
169
- onClick={() => walletProvider && instantUnstake({
170
- params: {
171
- srcChainKey: chainKey,
172
- srcAddress,
173
- amount: xSodaAmount,
174
- minAmount: ratio ? (ratio * 95n) / 100n : 0n,
175
- action: 'instantUnstake',
176
- },
177
- walletProvider,
178
- })}
179
- >
180
- {isPending ? 'Processing...' : 'Instant Unstake'}
181
- </button>
182
- );
183
- }
184
- ```
185
-
186
- ## Types
187
-
188
- ```typescript
189
- type StakeParams<K> = { srcChainKey: K; srcAddress: Address; amount: bigint; minReceive: bigint; action: 'stake' };
190
- type UnstakeParams<K> = { srcChainKey: K; srcAddress: Address; amount: bigint; action: 'unstake' };
191
- type InstantUnstakeParams<K> = { srcChainKey: K; srcAddress: Address; amount: bigint; minAmount: bigint; action: 'instantUnstake' };
192
- type ClaimParams<K> = { srcChainKey: K; srcAddress: Address; requestId: bigint; amount: bigint; action: 'claim' };
193
- type CancelUnstakeParams<K> = { srcChainKey: K; srcAddress: Address; requestId: bigint; action: 'cancelUnstake' };
194
- // All wrapped as: { params: ParamsType, walletProvider }
195
- ```
196
-
197
- ## Notes
198
-
199
- - **Unstaking period**: configurable, check `useStakingConfig`.
200
- - **Penalty**: linear from `maxPenalty` to 0 over the unstaking period.
201
- - **Instant unstake**: no waiting, but pays slippage via StakingRouter.
202
- - Query hooks (`useStakingInfo`, `useUnstakingInfoWithPenalty`, etc.) take `{ params: { srcAddress, srcChainKey } }` — they derive the hub wallet internally.
@@ -1,272 +0,0 @@
1
- # Recipe: Swap
2
-
3
- Cross-chain token swaps via the intent-based solver.
4
-
5
- **Depends on:** [setup.md](setup.md), [wallet-connectivity.md](wallet-connectivity.md)
6
-
7
- ## Hooks
8
-
9
- | Hook | Type | Purpose |
10
- |------|------|---------|
11
- | `useQuote` | Query | Real-time swap quote (auto-refreshes 3s) |
12
- | `useSwap` | Mutation | Execute a complete cross-chain swap |
13
- | `useSwapAllowance` | Query | Check if token approval is needed |
14
- | `useSwapApprove` | Mutation | Approve tokens for the swap contract |
15
- | `useStatus` | Query | Track intent execution status |
16
- | `useCancelSwap` | Mutation | Cancel an active swap intent |
17
- | `useCreateLimitOrder` | Mutation | Create a limit order (no deadline) |
18
- | `useCancelLimitOrder` | Mutation | Cancel an active limit order |
19
-
20
- ## Get a Quote
21
-
22
- ```tsx
23
- import { useQuote } from '@sodax/dapp-kit';
24
- import { ChainKeys } from '@sodax/sdk';
25
-
26
- function SwapQuote({ inputAmount }: { inputAmount: bigint }) {
27
- const { data: quoteResult, isLoading } = useQuote({
28
- params: {
29
- payload: inputAmount > 0n
30
- ? {
31
- token_src: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8',
32
- token_dst: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
33
- token_src_blockchain_id: ChainKeys.BSC_MAINNET,
34
- token_dst_blockchain_id: ChainKeys.ARBITRUM_MAINNET,
35
- amount: inputAmount,
36
- quote_type: 'exact_input',
37
- }
38
- : undefined,
39
- },
40
- });
41
-
42
- if (isLoading) return <div>Fetching quote...</div>;
43
- if (quoteResult?.ok) return <div>Output: {quoteResult.value.quoted_amount}</div>;
44
- return null;
45
- }
46
- ```
47
-
48
- ## Check Allowance + Approve
49
-
50
- ```tsx
51
- import { useSwapAllowance, useSwapApprove } from '@sodax/dapp-kit';
52
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
53
- import { ChainKeys } from '@sodax/sdk';
54
- import type { CreateIntentParams } from '@sodax/sdk';
55
-
56
- function SwapApproval({ intentParams }: { intentParams: CreateIntentParams }) {
57
- const walletProvider = useWalletProvider({ xChainId: ChainKeys.BSC_MAINNET });
58
-
59
- // useSwapAllowance wraps the request under params.payload and takes walletProvider + srcChainKey
60
- // alongside (all under `params`, not at the top level).
61
- const { data: isApproved } = useSwapAllowance({
62
- params: {
63
- payload: intentParams,
64
- srcChainKey: ChainKeys.BSC_MAINNET,
65
- walletProvider,
66
- },
67
- });
68
- const { mutateAsync: approve, isPending } = useSwapApprove();
69
-
70
- // useSwapAllowance data is `boolean | undefined` (already unwrapped from Result by the hook).
71
- if (isApproved) return null;
72
- return (
73
- <button onClick={() => walletProvider && approve({ params: intentParams, walletProvider })} disabled={isPending}>
74
- {isPending ? 'Approving...' : 'Approve Token'}
75
- </button>
76
- );
77
- }
78
- ```
79
-
80
- ## Execute Swap
81
-
82
- ```tsx
83
- import { useSwap } from '@sodax/dapp-kit';
84
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
85
- import { ChainKeys } from '@sodax/sdk';
86
- import type { CreateIntentParams } from '@sodax/sdk';
87
-
88
- function SwapButton({ intentParams }: { intentParams: CreateIntentParams }) {
89
- const walletProvider = useWalletProvider({ xChainId: ChainKeys.BSC_MAINNET });
90
- const { mutateAsync: swap, isPending } = useSwap();
91
-
92
- const handleSwap = async () => {
93
- if (!walletProvider) return;
94
- try {
95
- const { solverExecutionResponse, intent, intentDeliveryInfo } = await swap({
96
- params: intentParams,
97
- walletProvider,
98
- });
99
- console.log('Swap successful!', solverExecutionResponse);
100
- } catch (e) {
101
- // surfaced via mutation.error / onError
102
- }
103
- };
104
-
105
- return (
106
- <button onClick={handleSwap} disabled={isPending || !walletProvider}>
107
- {isPending ? 'Swapping...' : 'Swap'}
108
- </button>
109
- );
110
- }
111
- ```
112
-
113
- ## Full Example
114
-
115
- ```tsx
116
- import { useState } from 'react';
117
- import { useQuote, useSwap, useSwapAllowance, useSwapApprove } from '@sodax/dapp-kit';
118
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
119
- import { ChainKeys } from '@sodax/sdk';
120
- import type { CreateIntentParams, SolverIntentQuoteRequest } from '@sodax/sdk';
121
- import { parseUnits } from 'viem';
122
-
123
- const SRC_TOKEN = '0x2170Ed0880ac9A755fd29B2688956BD959F933F8';
124
- const DST_TOKEN = '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f';
125
-
126
- export function SwapPage() {
127
- const [inputAmount, setInputAmount] = useState('');
128
- const walletProvider = useWalletProvider({ xChainId: ChainKeys.BSC_MAINNET });
129
- const parsedAmount = inputAmount ? parseUnits(inputAmount, 18) : 0n;
130
-
131
- // 1. Quote — useQuote takes { params: { payload: SolverIntentQuoteRequest } }.
132
- const { data: quoteResult, isLoading: isQuoting } = useQuote({
133
- params: {
134
- payload: parsedAmount > 0n
135
- ? {
136
- token_src: SRC_TOKEN,
137
- token_dst: DST_TOKEN,
138
- token_src_blockchain_id: ChainKeys.BSC_MAINNET,
139
- token_dst_blockchain_id: ChainKeys.ARBITRUM_MAINNET,
140
- amount: parsedAmount,
141
- quote_type: 'exact_input',
142
- }
143
- : undefined,
144
- },
145
- });
146
-
147
- // 2. Build intent params. The request-side fields are `srcChainKey` / `dstChainKey`
148
- // (distinct from the read-side `Intent.srcChain` / `Intent.dstChain` which are
149
- // `IntentRelayChainId` bigints — a separate shape).
150
- const intentParams: CreateIntentParams | undefined =
151
- quoteResult?.ok
152
- ? {
153
- inputToken: SRC_TOKEN,
154
- outputToken: DST_TOKEN,
155
- inputAmount: parsedAmount,
156
- minOutputAmount: BigInt(quoteResult.value.quoted_amount),
157
- deadline: 0n,
158
- allowPartialFill: false,
159
- srcChainKey: ChainKeys.BSC_MAINNET,
160
- dstChainKey: ChainKeys.ARBITRUM_MAINNET,
161
- srcAddress: '0x0000000000000000000000000000000000000000', // connected wallet address
162
- dstAddress: '0x0000000000000000000000000000000000000000', // destination address
163
- solver: '0x0000000000000000000000000000000000000000',
164
- data: '0x',
165
- }
166
- : undefined;
167
-
168
- // 3. Allowance — useSwapAllowance nests payload + srcChainKey + walletProvider under params.
169
- const { data: isApproved } = useSwapAllowance({
170
- params: intentParams
171
- ? { payload: intentParams, srcChainKey: ChainKeys.BSC_MAINNET, walletProvider }
172
- : undefined,
173
- });
174
-
175
- // 4. Approve + Swap (using mutateAsyncSafe — no try/catch, no unhandled rejections)
176
- const { mutateAsyncSafe: approve, isPending: isApproving } = useSwapApprove();
177
- const { mutateAsyncSafe: swap, isPending: isSwapping } = useSwap();
178
-
179
- const handleSwap = async () => {
180
- if (!intentParams || !walletProvider) return;
181
- if (!isApproved) {
182
- const r = await approve({ params: intentParams, walletProvider });
183
- if (!r.ok) { alert(r.error instanceof Error ? r.error.message : 'Approve failed'); return; }
184
- }
185
- const r = await swap({ params: intentParams, walletProvider });
186
- if (r.ok) alert('Swap successful!');
187
- else alert(r.error instanceof Error ? r.error.message : 'Swap failed');
188
- };
189
-
190
- return (
191
- <div>
192
- <input placeholder="Amount" value={inputAmount} onChange={(e) => setInputAmount(e.target.value)} />
193
- {isQuoting && <p>Fetching quote...</p>}
194
- {quoteResult?.ok && <p>Output: {quoteResult.value.quoted_amount}</p>}
195
- <button onClick={handleSwap} disabled={isSwapping || isApproving || !intentParams}>
196
- {isApproving ? 'Approving...' : isSwapping ? 'Swapping...' : 'Swap'}
197
- </button>
198
- </div>
199
- );
200
- }
201
- ```
202
-
203
- ## Limit Orders
204
-
205
- ```tsx
206
- import { useCreateLimitOrder, useCancelLimitOrder } from '@sodax/dapp-kit';
207
- import type { Intent } from '@sodax/sdk';
208
-
209
- const { mutateAsync: createLimitOrder } = useCreateLimitOrder();
210
- const { mutateAsync: cancelLimitOrder } = useCancelLimitOrder();
211
-
212
- // Limit orders have no deadline, must be cancelled manually.
213
- // `useCancelLimitOrder` TVars are FLAT: `{ srcChainKey, intent, walletProvider }` (no `params` wrapper).
214
- async function flow(intent: Intent) {
215
- if (!walletProvider) return;
216
- await createLimitOrder({ params: limitOrderParams, walletProvider });
217
- await cancelLimitOrder({ srcChainKey, intent, walletProvider });
218
- }
219
- ```
220
-
221
- ## Customize TanStack Query behavior
222
-
223
- Every mutation hook accepts an optional `mutationOptions` slot for consumers to override TanStack Query knobs (`retry`, `onError`, `mutationKey`, etc.). The hook's `mutationFn` throws on SDK failure (so `mutation.error`, `onError`, and `retry` engage natively); its own `onSuccess` invalidations run first on real success, then the consumer's `onSuccess` is awaited.
224
-
225
- ```tsx
226
- import { useSwap } from '@sodax/dapp-kit';
227
- import { useIsMutating } from '@tanstack/react-query';
228
-
229
- const { mutateAsync: swap, isError, error } = useSwap({
230
- mutationOptions: {
231
- retry: 5,
232
- onError: err => toast.error(err.message),
233
- onSuccess: swapResponse => {
234
- // Runs AFTER dapp-kit's xBalances invalidations — only on confirmed success.
235
- trackSwap(swapResponse);
236
- },
237
- },
238
- });
239
-
240
- // Track in-flight swaps anywhere in the app via the default mutationKey
241
- const swapsInFlight = useIsMutating({ mutationKey: ['swap'] });
242
- console.log({ swap, isError, error, swapsInFlight });
243
- ```
244
-
245
- ## Gotchas
246
-
247
- ### Token list has duplicate addresses
248
-
249
- `getSupportedSolverTokens()` can return multiple tokens sharing the same contract address (same token on different chains). When rendering token lists, use a composite key like `${token.address}-${token.blockchain_id}` — not `token.address` alone.
250
-
251
- ### Balance display
252
-
253
- Balances come from `@sodax/wallet-sdk-react`, not from dapp-kit. See `wallet-connectivity.md` for `useXBalances`.
254
-
255
- ## Types
256
-
257
- ```typescript
258
- type CreateIntentParams = {
259
- inputToken: string;
260
- outputToken: string;
261
- inputAmount: bigint;
262
- minOutputAmount: bigint;
263
- deadline: bigint; // 0n = no deadline
264
- allowPartialFill: boolean;
265
- srcChain: SpokeChainId;
266
- dstChain: SpokeChainId;
267
- srcAddress: string;
268
- dstAddress: string;
269
- solver: Address; // address(0) = any solver
270
- data: Hex;
271
- };
272
- ```