@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.
Files changed (63) hide show
  1. package/README.md +9 -63
  2. package/dist/index.d.ts +11 -4
  3. package/dist/index.mjs +3 -6
  4. package/package.json +31 -19
  5. package/src/providers/SodaxProvider.tsx +15 -11
  6. package/ai-exported/AGENTS.md +0 -134
  7. package/ai-exported/integration/README.md +0 -49
  8. package/ai-exported/integration/ai-rules.md +0 -79
  9. package/ai-exported/integration/architecture.md +0 -274
  10. package/ai-exported/integration/features/README.md +0 -29
  11. package/ai-exported/integration/features/auxiliary-services.md +0 -169
  12. package/ai-exported/integration/features/bitcoin.md +0 -87
  13. package/ai-exported/integration/features/bridge.md +0 -91
  14. package/ai-exported/integration/features/dex.md +0 -152
  15. package/ai-exported/integration/features/migration.md +0 -118
  16. package/ai-exported/integration/features/money-market.md +0 -116
  17. package/ai-exported/integration/features/staking.md +0 -123
  18. package/ai-exported/integration/features/swap.md +0 -101
  19. package/ai-exported/integration/quickstart.md +0 -187
  20. package/ai-exported/integration/recipes/README.md +0 -136
  21. package/ai-exported/integration/recipes/backend-queries.md +0 -157
  22. package/ai-exported/integration/recipes/bitcoin.md +0 -193
  23. package/ai-exported/integration/recipes/bridge.md +0 -174
  24. package/ai-exported/integration/recipes/dex.md +0 -204
  25. package/ai-exported/integration/recipes/invalidations.md +0 -115
  26. package/ai-exported/integration/recipes/migration.md +0 -212
  27. package/ai-exported/integration/recipes/money-market.md +0 -206
  28. package/ai-exported/integration/recipes/mutation-error-handling.md +0 -118
  29. package/ai-exported/integration/recipes/observability.md +0 -93
  30. package/ai-exported/integration/recipes/setup.md +0 -144
  31. package/ai-exported/integration/recipes/staking.md +0 -202
  32. package/ai-exported/integration/recipes/swap.md +0 -272
  33. package/ai-exported/integration/recipes/wallet-connectivity.md +0 -101
  34. package/ai-exported/integration/reference/README.md +0 -12
  35. package/ai-exported/integration/reference/glossary.md +0 -188
  36. package/ai-exported/integration/reference/hooks-index.md +0 -190
  37. package/ai-exported/integration/reference/public-api.md +0 -110
  38. package/ai-exported/integration/reference/querykey-conventions.md +0 -179
  39. package/ai-exported/migration/README.md +0 -60
  40. package/ai-exported/migration/ai-rules.md +0 -81
  41. package/ai-exported/migration/breaking-changes/hook-signatures.md +0 -233
  42. package/ai-exported/migration/breaking-changes/querykey-conventions.md +0 -108
  43. package/ai-exported/migration/breaking-changes/result-handling.md +0 -211
  44. package/ai-exported/migration/breaking-changes/sdk-leakage.md +0 -165
  45. package/ai-exported/migration/checklist.md +0 -89
  46. package/ai-exported/migration/features/README.md +0 -34
  47. package/ai-exported/migration/features/auxiliary-services.md +0 -114
  48. package/ai-exported/migration/features/bitcoin.md +0 -88
  49. package/ai-exported/migration/features/bridge.md +0 -123
  50. package/ai-exported/migration/features/dex.md +0 -101
  51. package/ai-exported/migration/features/migration.md +0 -120
  52. package/ai-exported/migration/features/money-market.md +0 -97
  53. package/ai-exported/migration/features/staking.md +0 -109
  54. package/ai-exported/migration/features/swap.md +0 -118
  55. package/ai-exported/migration/recipes.md +0 -188
  56. package/ai-exported/migration/reference/README.md +0 -15
  57. package/ai-exported/migration/reference/deleted-hooks.md +0 -110
  58. package/ai-exported/migration/reference/error-shape-crosswalk.md +0 -144
  59. package/ai-exported/migration/reference/renamed-hooks.md +0 -66
  60. package/dist/index.cjs +0 -2642
  61. package/dist/index.cjs.map +0 -1
  62. package/dist/index.d.cts +0 -1550
  63. package/dist/index.mjs.map +0 -1
@@ -1,144 +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
- ### 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.
@@ -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
- ```