@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.
- package/README.md +9 -63
- package/dist/index.mjs +0 -2
- package/package.json +31 -20
- package/ai-exported/AGENTS.md +0 -134
- package/ai-exported/integration/README.md +0 -49
- package/ai-exported/integration/ai-rules.md +0 -80
- package/ai-exported/integration/architecture.md +0 -276
- 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 -144
- 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 -207
- 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 -168
- 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 -128
- package/ai-exported/integration/reference/README.md +0 -12
- package/ai-exported/integration/reference/glossary.md +0 -190
- 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 -167
- 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 -160
- 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 -139
- package/ai-exported/migration/features/staking.md +0 -109
- package/ai-exported/migration/features/swap.md +0 -133
- package/ai-exported/migration/recipes.md +0 -185
- 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 -68
- package/dist/index.cjs +0 -2641
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1557
- 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
|
-
```
|