@sodax/sdk 1.5.7-beta → 2.0.0-rc.2
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 +91 -7
- package/ai-exported/AGENTS.md +99 -0
- package/ai-exported/integration/README.md +41 -0
- package/ai-exported/integration/ai-rules.md +75 -0
- package/ai-exported/integration/architecture.md +519 -0
- package/ai-exported/integration/chain-specifics.md +189 -0
- package/ai-exported/integration/features/README.md +19 -0
- package/ai-exported/integration/features/auxiliary-services.md +189 -0
- package/ai-exported/integration/features/bridge.md +136 -0
- package/ai-exported/integration/features/dex.md +182 -0
- package/ai-exported/integration/features/icx-bnusd-baln.md +181 -0
- package/ai-exported/integration/features/money-market.md +198 -0
- package/ai-exported/integration/features/staking.md +166 -0
- package/ai-exported/integration/features/swap.md +207 -0
- package/ai-exported/integration/quickstart.md +213 -0
- package/ai-exported/integration/recipes/README.md +21 -0
- package/ai-exported/integration/recipes/backend-server-init.md +69 -0
- package/ai-exported/integration/recipes/chain-key-narrowing.md +65 -0
- package/ai-exported/integration/recipes/gas-estimation.md +33 -0
- package/ai-exported/integration/recipes/initialize-sodax.md +53 -0
- package/ai-exported/integration/recipes/raw-tx-flow.md +71 -0
- package/ai-exported/integration/recipes/result-and-errors.md +104 -0
- package/ai-exported/integration/recipes/signed-tx-flow.md +46 -0
- package/ai-exported/integration/recipes/testing.md +101 -0
- package/ai-exported/integration/reference/README.md +18 -0
- package/ai-exported/integration/reference/chain-keys.md +67 -0
- package/ai-exported/integration/reference/error-codes.md +165 -0
- package/ai-exported/integration/reference/glossary.md +32 -0
- package/ai-exported/integration/reference/public-api.md +138 -0
- package/ai-exported/integration/reference/wallet-providers.md +62 -0
- package/ai-exported/migration/README.md +58 -0
- package/ai-exported/migration/ai-rules.md +80 -0
- package/ai-exported/migration/breaking-changes/architecture.md +342 -0
- package/ai-exported/migration/breaking-changes/result-and-errors.md +363 -0
- package/ai-exported/migration/breaking-changes/type-system.md +321 -0
- package/ai-exported/migration/checklist.md +61 -0
- package/ai-exported/migration/features/README.md +35 -0
- package/ai-exported/migration/features/auxiliary-services.md +156 -0
- package/ai-exported/migration/features/bridge.md +125 -0
- package/ai-exported/migration/features/dex.md +143 -0
- package/ai-exported/migration/features/icx-bnusd-baln.md +151 -0
- package/ai-exported/migration/features/money-market.md +214 -0
- package/ai-exported/migration/features/staking.md +138 -0
- package/ai-exported/migration/features/swap.md +198 -0
- package/ai-exported/migration/recipes.md +288 -0
- package/ai-exported/migration/reference/README.md +18 -0
- package/ai-exported/migration/reference/deleted-exports.md +126 -0
- package/ai-exported/migration/reference/error-code-crosswalk.md +104 -0
- package/ai-exported/migration/reference/return-shapes.md +49 -0
- package/ai-exported/migration/reference/sodax-config.md +52 -0
- package/dist/index.cjs +32076 -31544
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8604 -7136
- package/dist/index.d.ts +8604 -7136
- package/dist/index.mjs +31893 -31402
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -12
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Chain-key narrowing (cast-at-boundary)
|
|
2
|
+
|
|
3
|
+
When you have a runtime chain key (e.g. user-selected from a UI) and need a chain-narrowed wallet provider, narrow once at the boundary and let the narrowed binding flow downstream.
|
|
4
|
+
|
|
5
|
+
### Narrowing inside a feature flow
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { ChainKeys, type GetWalletProviderType } from '@sodax/sdk';
|
|
9
|
+
|
|
10
|
+
function isStellar(chainKey: SpokeChainKey): chainKey is typeof ChainKeys.STELLAR_MAINNET {
|
|
11
|
+
return chainKey === ChainKeys.STELLAR_MAINNET;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const stellarWp = isStellar(srcChainKey)
|
|
15
|
+
? (walletProvider as GetWalletProviderType<typeof ChainKeys.STELLAR_MAINNET> | undefined)
|
|
16
|
+
: undefined;
|
|
17
|
+
|
|
18
|
+
if (stellarWp) {
|
|
19
|
+
await checkStellarTrustline({ token, amount, walletProvider: stellarWp });
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The cast is local — the `isStellar` guard proves correctness at runtime. **Don't propagate the cast** beyond the narrowed binding; downstream code reads `stellarWp` as the chain-specific type without further casts.
|
|
24
|
+
|
|
25
|
+
### The `chainType` runtime alternative
|
|
26
|
+
|
|
27
|
+
Every `I*WalletProvider` has a `readonly chainType: '<CHAIN>'` literal. Use it when you don't have the chain key in scope but do have a wallet provider:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
if (walletProvider.chainType === 'BITCOIN') {
|
|
31
|
+
// walletProvider: IBitcoinWalletProvider
|
|
32
|
+
await checkBitcoinPSBT(walletProvider);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
No `as` cast needed — `chainType` is part of the interface.
|
|
37
|
+
|
|
38
|
+
### Pitfall
|
|
39
|
+
|
|
40
|
+
Do **not** chain a cast through every helper. The cast belongs at the chain-key guard. Anywhere downstream of the guard, the binding is already typed correctly:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// Bad — chained casts:
|
|
44
|
+
async function approveSomething(wp: IWalletProvider) {
|
|
45
|
+
await sodax.swaps.approve({
|
|
46
|
+
params,
|
|
47
|
+
walletProvider: wp as GetWalletProviderType<typeof ChainKeys.BITCOIN_MAINNET>,
|
|
48
|
+
raw: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Good — one cast at the boundary, narrowed binding flows:
|
|
53
|
+
const btcWp = isBitcoin(srcChainKey)
|
|
54
|
+
? (walletProvider as GetWalletProviderType<typeof ChainKeys.BITCOIN_MAINNET>)
|
|
55
|
+
: null;
|
|
56
|
+
if (btcWp) await sodax.swaps.approve({ params, walletProvider: btcWp, raw: false });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Cross-references
|
|
62
|
+
|
|
63
|
+
- [`README.md`](README.md) — recipe index.
|
|
64
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these patterns.
|
|
65
|
+
- [`../reference/`](../reference/) — chain keys, error codes, public API surface.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Gas estimation
|
|
2
|
+
|
|
3
|
+
Pre-flight gas estimation for raw-tx flows. Most signed flows include gas estimation internally; you only need this when building unsigned payloads.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const result = await sodax.swaps.estimateGas({
|
|
7
|
+
params: { /* same as createIntent params */ },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
if (!result.ok) {
|
|
11
|
+
// result.error: SodaxError<'VALIDATION_FAILED' | 'GAS_ESTIMATION_FAILED' | 'UNKNOWN'>
|
|
12
|
+
if (result.error.code === 'GAS_ESTIMATION_FAILED') {
|
|
13
|
+
// Retry — gas estimation failures are usually transient.
|
|
14
|
+
}
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { gasLimit, gasPrice } = result.value;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`estimateGas` returns chain-family-specific shapes — EVM gas params, Solana compute units, etc. See per-feature docs in [`../features/`](../features/) for specifics.
|
|
22
|
+
|
|
23
|
+
### Distinct from `LOOKUP_FAILED`
|
|
24
|
+
|
|
25
|
+
The SDK keeps `GAS_ESTIMATION_FAILED` as its own code (rather than folding into `LOOKUP_FAILED`) because retry semantics differ. Re-estimation is cheap and almost always the right move on transient failure. Generic `LOOKUP_FAILED` errors typically need user intervention.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Cross-references
|
|
30
|
+
|
|
31
|
+
- [`README.md`](README.md) — recipe index.
|
|
32
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these patterns.
|
|
33
|
+
- [`../reference/`](../reference/) — chain keys, error codes, public API surface.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Initialize Sodax
|
|
2
|
+
|
|
3
|
+
The minimal init — packaged defaults, no config override:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Sodax } from '@sodax/sdk';
|
|
7
|
+
|
|
8
|
+
const sodax = new Sodax();
|
|
9
|
+
await sodax.config.initialize(); // load fresh config from backend; falls back to packaged defaults
|
|
10
|
+
|
|
11
|
+
// All feature services are wired and ready:
|
|
12
|
+
const result = sodax.config.isValidSpokeChainKey(ChainKeys.ARBITRUM_MAINNET); // returns boolean (sync)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### With config override
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Sodax, ChainKeys, type SodaxConfig, type DeepPartial } from '@sodax/sdk';
|
|
19
|
+
|
|
20
|
+
const config: DeepPartial<SodaxConfig> = {
|
|
21
|
+
// Per-chain overrides — merged with packaged defaults at the field level.
|
|
22
|
+
chains: {
|
|
23
|
+
[ChainKeys.SONIC_MAINNET]: { rpcUrl: process.env.SONIC_RPC_URL },
|
|
24
|
+
[ChainKeys.ARBITRUM_MAINNET]: { rpcUrl: process.env.ARBITRUM_RPC_URL },
|
|
25
|
+
},
|
|
26
|
+
// Backend API override (default: https://api.sodax.com/v1/be).
|
|
27
|
+
api: {
|
|
28
|
+
baseURL: 'https://my-sandbox-backend.example.com',
|
|
29
|
+
},
|
|
30
|
+
// Solver endpoints (default: https://api.sodax.com/v1/intent + production contracts).
|
|
31
|
+
solver: {
|
|
32
|
+
solverApiEndpoint: 'https://my-solver.example.com',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const sodax = new Sodax(config);
|
|
37
|
+
await sodax.config.initialize();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Lazy initialization
|
|
41
|
+
|
|
42
|
+
`config.initialize()` is idempotent — calling it twice is a no-op. The first call fetches; subsequent calls return cached data. Treat it as "make sure config is loaded before any feature method".
|
|
43
|
+
|
|
44
|
+
### Pitfall
|
|
45
|
+
|
|
46
|
+
`initialize()` is the only initialization step. Don't `await` it inside every feature call — call it once at app startup. If you skip it entirely, feature services fall back to packaged defaults, which may be stale relative to the latest backend config (new tokens, new chains, fee parameter changes).
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Cross-references
|
|
50
|
+
|
|
51
|
+
- [`README.md`](README.md) — recipe index.
|
|
52
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these patterns.
|
|
53
|
+
- [`../reference/`](../reference/) — chain keys, error codes, public API surface.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Raw-tx flow
|
|
2
|
+
|
|
3
|
+
`raw: true`. The SDK builds the unsigned payload; you sign it elsewhere (gnosis safe, hardware wallet, multi-sig, etc.).
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const result = await sodax.swaps.createIntent({
|
|
7
|
+
params: { /* same as signed flow */ },
|
|
8
|
+
raw: true,
|
|
9
|
+
// walletProvider is FORBIDDEN — TypeScript rejects it.
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (!result.ok) return;
|
|
13
|
+
const { tx, intent, relayData } = result.value;
|
|
14
|
+
// tx is now a chain-specific raw-tx payload:
|
|
15
|
+
// - EVM: EvmRawTransaction { to, data, value, chainId }
|
|
16
|
+
// - Solana: SolanaRawTransaction
|
|
17
|
+
// - Stellar: StellarRawTransaction
|
|
18
|
+
// - …
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Submit the raw tx via your own signing infrastructure. Once you have the spoke tx hash, you'll typically need to manually call the relay to complete the cross-chain flow:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { relayTxAndWaitPacket, type RelayExtraData } from '@sodax/sdk';
|
|
25
|
+
|
|
26
|
+
// After your custom signer broadcasts and you have the spoke tx hash:
|
|
27
|
+
const spokeTxHash = await mySigningInfra.signAndBroadcast(tx);
|
|
28
|
+
|
|
29
|
+
// `relayTxAndWaitPacket` is a top-level function (not a class). Pass your
|
|
30
|
+
// relayer endpoint (same one you'd configure on the `Sodax` instance) and
|
|
31
|
+
// the relay payload returned by `createIntent`.
|
|
32
|
+
const relayResult = await relayTxAndWaitPacket({
|
|
33
|
+
relayerApiEndpoint,
|
|
34
|
+
srcChainKey: params.srcChainKey,
|
|
35
|
+
dstChainKey: params.dstChainKey,
|
|
36
|
+
txHash: spokeTxHash,
|
|
37
|
+
payload: relayData.payload,
|
|
38
|
+
timeout: 60_000,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This pattern is rare. Prefer signed flow unless you have a specific reason to defer signing.
|
|
43
|
+
|
|
44
|
+
### Type narrowing
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// Discriminate raw return shapes by chain family at runtime:
|
|
48
|
+
if (getChainType(srcChainKey) === 'EVM') {
|
|
49
|
+
const evmTx = result.value.tx as EvmRawTransaction;
|
|
50
|
+
// …
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or use the chain-key generic to narrow at the type level (most useful when `srcChainKey` is a literal):
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const result = await sodax.swaps.createIntent({
|
|
58
|
+
params: { ...params, srcChainKey: ChainKeys.ETHEREUM_MAINNET as const },
|
|
59
|
+
raw: true,
|
|
60
|
+
});
|
|
61
|
+
// result.value.tx is statically narrowed to EvmRawTransaction
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Cross-references
|
|
68
|
+
|
|
69
|
+
- [`README.md`](README.md) — recipe index.
|
|
70
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these patterns.
|
|
71
|
+
- [`../reference/`](../reference/) — chain keys, error codes, public API surface.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Result handling and error discrimination
|
|
2
|
+
|
|
3
|
+
Every async public method on every feature service returns `Promise<Result<T, SodaxError<C>>>`.
|
|
4
|
+
|
|
5
|
+
### Branching pattern
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { isSodaxError } from '@sodax/sdk';
|
|
9
|
+
|
|
10
|
+
const result = await sodax.swaps.createIntent({ params, raw: false, walletProvider });
|
|
11
|
+
|
|
12
|
+
if (!result.ok) {
|
|
13
|
+
// Always isSodaxError-narrow before reading .feature / .code
|
|
14
|
+
if (isSodaxError(result.error)) {
|
|
15
|
+
if (result.error.code === 'RELAY_TIMEOUT') {
|
|
16
|
+
// retry strategy
|
|
17
|
+
} else if (result.error.feature === 'swap' && result.error.code === 'INTENT_CREATION_FAILED') {
|
|
18
|
+
// input-error UX
|
|
19
|
+
} else if (result.error.code === 'EXTERNAL_API_ERROR' && result.error.context?.api === 'solver') {
|
|
20
|
+
// solver-side problem; surface error.context.solverDetail to the user
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { tx, intent, relayData } = result.value;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Switch-style with narrow code unions
|
|
30
|
+
|
|
31
|
+
When you know the method, the narrow code union from its declaration enables an exhaustive switch:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
type CreateSupplyIntentErrorCode = 'VALIDATION_FAILED' | 'INTENT_CREATION_FAILED' | 'UNKNOWN';
|
|
35
|
+
|
|
36
|
+
const result = await sodax.moneyMarket.createSupplyIntent({ params, raw: true });
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
switch (result.error.code as CreateSupplyIntentErrorCode) {
|
|
39
|
+
case 'VALIDATION_FAILED': return setError('Invalid input');
|
|
40
|
+
case 'INTENT_CREATION_FAILED': return setError('Could not build supply');
|
|
41
|
+
case 'UNKNOWN': return setError('Unexpected error');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Per-feature guard factory
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { isFeatureError } from '@sodax/sdk';
|
|
50
|
+
|
|
51
|
+
const isSwapError = isFeatureError('swap');
|
|
52
|
+
const isMmError = isFeatureError('moneyMarket');
|
|
53
|
+
|
|
54
|
+
if (!result.ok) {
|
|
55
|
+
if (isSwapError(result.error)) /* … */;
|
|
56
|
+
else if (isMmError(result.error)) /* … */;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Sub-Result propagation
|
|
61
|
+
|
|
62
|
+
For wrapper methods you write yourself:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
async function myWorkflow(): Promise<Result<MyOutput, SodaxError>> {
|
|
66
|
+
const sub = await sodax.swaps.createIntent({ params, raw: false, walletProvider });
|
|
67
|
+
if (!sub.ok) return sub; // forward as-is
|
|
68
|
+
|
|
69
|
+
const { tx, intent } = sub.value;
|
|
70
|
+
return { ok: true, value: { tx, intent, ts: Date.now() } };
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Logging
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { isSodaxError } from '@sodax/sdk';
|
|
78
|
+
|
|
79
|
+
if (!result.ok) {
|
|
80
|
+
if (isSodaxError(result.error)) {
|
|
81
|
+
Sentry.captureException(result.error, {
|
|
82
|
+
tags: {
|
|
83
|
+
feature: result.error.feature,
|
|
84
|
+
code: result.error.code,
|
|
85
|
+
action: result.error.context?.action ?? null,
|
|
86
|
+
relayCode: result.error.context?.relayCode ?? null,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
Sentry.captureException(result.error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`SodaxError.toJSON()` is invoked automatically by `JSON.stringify(error)` — Pino, Datadog, and Winston pick it up without configuration.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Cross-references
|
|
101
|
+
|
|
102
|
+
- [`README.md`](README.md) — recipe index.
|
|
103
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these patterns.
|
|
104
|
+
- [`../reference/`](../reference/) — chain keys, error codes, public API surface.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Signed-tx flow
|
|
2
|
+
|
|
3
|
+
`raw: false` + a chain-narrowed `walletProvider`. The SDK signs and broadcasts; returns a tx hash (or tx-pair for cross-chain methods).
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const result = await sodax.swaps.createIntent({
|
|
7
|
+
params: {
|
|
8
|
+
srcChainKey: ChainKeys.ARBITRUM_MAINNET,
|
|
9
|
+
dstChainKey: ChainKeys.STELLAR_MAINNET,
|
|
10
|
+
srcAddress: '0x…',
|
|
11
|
+
dstAddress: 'G…',
|
|
12
|
+
inputToken, // XToken
|
|
13
|
+
outputToken, // XToken
|
|
14
|
+
inputAmount: 1_000_000n,
|
|
15
|
+
minOutputAmount: 998_000n,
|
|
16
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
|
|
17
|
+
allowPartialFill: false,
|
|
18
|
+
solver: '0x0000000000000000000000000000000000000000',
|
|
19
|
+
data: '0x',
|
|
20
|
+
},
|
|
21
|
+
raw: false,
|
|
22
|
+
walletProvider: evmWallet, // IEvmWalletProvider — narrowed by srcChainKey
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!result.ok) return;
|
|
26
|
+
const { tx, intent, relayData } = result.value;
|
|
27
|
+
// tx: the spoke tx hash (string for EVM, base58 for Solana, …)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For cross-chain mutations (`bridge.bridge`, `staking.stake`, `moneyMarket.supply/borrow/withdraw/repay`, `dex.deposit/withdraw/supplyLiquidity/…`, `migration.migratebnUSD/…`) the success value is `TxHashPair = { srcChainTxHash, dstChainTxHash }` — the spoke transaction hash on the source chain plus the relayed hub transaction hash:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const result = await sodax.bridge.bridge({ params, raw: false, walletProvider });
|
|
34
|
+
if (!result.ok) return;
|
|
35
|
+
const { srcChainTxHash, dstChainTxHash } = result.value;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The same shape is used by every cross-chain mutation in v2 — there is no array-form variant. When the user is already on the hub, both fields hold the same hash.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Cross-references
|
|
43
|
+
|
|
44
|
+
- [`README.md`](README.md) — recipe index.
|
|
45
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these patterns.
|
|
46
|
+
- [`../reference/`](../reference/) — chain keys, error codes, public API surface.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Testing (mocks and stubs)
|
|
2
|
+
|
|
3
|
+
### Mock the entire `Sodax` instance
|
|
4
|
+
|
|
5
|
+
For unit tests where you want to verify your code calls the SDK correctly without hitting network:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import type { Sodax } from '@sodax/sdk';
|
|
9
|
+
import { vi } from 'vitest';
|
|
10
|
+
|
|
11
|
+
const mockSodax = {
|
|
12
|
+
swaps: {
|
|
13
|
+
createIntent: vi.fn(),
|
|
14
|
+
swap: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
config: {
|
|
17
|
+
initialize: vi.fn().mockResolvedValue(undefined),
|
|
18
|
+
isValidSpokeChainKey: vi.fn().mockReturnValue(true),
|
|
19
|
+
},
|
|
20
|
+
} as unknown as Sodax;
|
|
21
|
+
|
|
22
|
+
mockSodax.swaps.createIntent.mockResolvedValue({
|
|
23
|
+
ok: true,
|
|
24
|
+
value: {
|
|
25
|
+
tx: '0xabc' as `0x${string}`,
|
|
26
|
+
intent: { /* … */ },
|
|
27
|
+
relayData: { payload: '0x…' },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Use mockSodax in your code under test
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The cast `as unknown as Sodax` is the **only** place where `as unknown as` is acceptable per the project conventions — test mocks intentionally defeat types.
|
|
35
|
+
|
|
36
|
+
### Stub the relay layer for E2E tests
|
|
37
|
+
|
|
38
|
+
When you want real spoke txs but stubbed relay coordination, mock at the **feature service** boundary — `relayTxAndWaitPacket` is consumed by feature-service implementations internally, and is not exposed on the `Sodax` instance.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { Sodax } from '@sodax/sdk';
|
|
42
|
+
import { vi } from 'vitest';
|
|
43
|
+
|
|
44
|
+
const sodax = new Sodax({ /* … */ });
|
|
45
|
+
|
|
46
|
+
// Stub the full feature method (it wraps relay coordination internally):
|
|
47
|
+
vi.spyOn(sodax.swaps, 'swap').mockResolvedValue({
|
|
48
|
+
ok: true,
|
|
49
|
+
value: {
|
|
50
|
+
solverExecutionResponse: { /* … */ },
|
|
51
|
+
intent: { /* … */ },
|
|
52
|
+
intentDeliveryInfo: { /* … */ },
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Or stub just the relay portion of an end-to-end flow by mocking
|
|
57
|
+
// `sodax.swaps.postExecution` after `createIntent` returns.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Result-style assertions
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const result = await sodax.swaps.createIntent({ params, raw: false, walletProvider });
|
|
64
|
+
expect(result.ok).toBe(true);
|
|
65
|
+
if (result.ok) {
|
|
66
|
+
expect(result.value.tx).toMatch(/^0x[0-9a-f]{64}$/);
|
|
67
|
+
}
|
|
68
|
+
// or for failures:
|
|
69
|
+
expect(result.ok).toBe(false);
|
|
70
|
+
if (!result.ok) {
|
|
71
|
+
expect(result.error).toBeInstanceOf(Error);
|
|
72
|
+
expect(result.error.code).toBe('VALIDATION_FAILED');
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Custom `IConfigApi` for sandbox
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { Sodax, type IConfigApi } from '@sodax/sdk';
|
|
80
|
+
|
|
81
|
+
const sandboxApi: IConfigApi = {
|
|
82
|
+
async getChains() { return { ok: true, value: [/* fixture */] }; },
|
|
83
|
+
async getSwapTokens() { return { ok: true, value: { /* … */ } }; },
|
|
84
|
+
async getSwapTokensByChainId(chainKey) { return { ok: true, value: [/* … */] }; },
|
|
85
|
+
async getMoneyMarketTokens() { return { ok: true, value: { /* … */ } }; },
|
|
86
|
+
async getMoneyMarketTokensByChainId(chainKey) { return { ok: true, value: [/* … */] }; },
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const sodax = new Sodax({
|
|
90
|
+
backendApi: { url: 'unused', api: sandboxApi },
|
|
91
|
+
});
|
|
92
|
+
await sodax.config.initialize();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Cross-references
|
|
98
|
+
|
|
99
|
+
- [`README.md`](README.md) — recipe index.
|
|
100
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these patterns.
|
|
101
|
+
- [`../reference/`](../reference/) — chain keys, error codes, public API surface.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Reference — `@sodax/sdk` v2
|
|
2
|
+
|
|
3
|
+
Lookup tables and inventories. Each file is focused on one domain — token-cheap retrieval.
|
|
4
|
+
|
|
5
|
+
| File | Contents |
|
|
6
|
+
|---|---|
|
|
7
|
+
| [`chain-keys.md`](chain-keys.md) | All 20 `ChainKeys.*` values × chain family × address-type mapping. Chain-family helper functions. Type aliases (`ChainKey`, `SpokeChainKey`, `EvmChainKey`, `HubChainKey`). |
|
|
8
|
+
| [`wallet-providers.md`](wallet-providers.md) | `I*WalletProvider` interfaces per chain family. `chainType` discriminant. `GetWalletProviderType<K>`. `IWalletProvider` broad union. Implementation source (consumer's own, or `@sodax/wallet-sdk-core` separate package). |
|
|
9
|
+
| [`error-codes.md`](error-codes.md) | The 13 unified `SodaxErrorCode` values + meanings + retry guidance + common context fields. The 8 `SodaxFeature` tags. The `SodaxPhase` orchestration tags. Per-method narrow code unions for every public method on every service. |
|
|
10
|
+
| [`public-api.md`](public-api.md) | What `@sodax/sdk` barrel exports. Import-rule contract (root-only, no deep imports, types re-exported from `@sodax/types`). |
|
|
11
|
+
| [`glossary.md`](glossary.md) | Domain terms (hub, spoke, intent, relay, vault, hub-wallet abstraction, etc.) used throughout the docs. |
|
|
12
|
+
|
|
13
|
+
## Cross-references
|
|
14
|
+
|
|
15
|
+
- v2 architectural concepts (the prose behind these tables): [`../architecture.md`](../architecture.md).
|
|
16
|
+
- Recipes that use these lookups: [`../recipes/`](../recipes/).
|
|
17
|
+
- DO / DO NOT / workflow: [`../ai-rules.md`](../ai-rules.md).
|
|
18
|
+
- v1 → v2 migration: [`../../migration/`](../../migration/).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Chain keys
|
|
2
|
+
|
|
3
|
+
20 supported chains. The `ChainKey` type is the union of every `ChainKeys.*` value. `SpokeChainKey` is the same minus `ChainKeys.SONIC_MAINNET` (the hub).
|
|
4
|
+
|
|
5
|
+
| `ChainKeys.*` | String value | Family | Hub vs spoke | Address type |
|
|
6
|
+
|---|---|---|---|---|
|
|
7
|
+
| `SONIC_MAINNET` | `'sonic'` | EVM | **Hub** | `0x${string}` |
|
|
8
|
+
| `ETHEREUM_MAINNET` | `'ethereum'` | EVM | spoke | `0x${string}` |
|
|
9
|
+
| `ARBITRUM_MAINNET` | `'0xa4b1.arbitrum'` | EVM | spoke | `0x${string}` |
|
|
10
|
+
| `BASE_MAINNET` | `'0x2105.base'` | EVM | spoke | `0x${string}` |
|
|
11
|
+
| `BSC_MAINNET` | `'0x38.bsc'` | EVM | spoke | `0x${string}` |
|
|
12
|
+
| `OPTIMISM_MAINNET` | `'0xa.optimism'` | EVM | spoke | `0x${string}` |
|
|
13
|
+
| `POLYGON_MAINNET` | `'0x89.polygon'` | EVM | spoke | `0x${string}` |
|
|
14
|
+
| `AVALANCHE_MAINNET` | `'0xa86a.avax'` | EVM | spoke | `0x${string}` |
|
|
15
|
+
| `HYPEREVM_MAINNET` | `'hyper'` | EVM | spoke | `0x${string}` |
|
|
16
|
+
| `LIGHTLINK_MAINNET` | `'lightlink'` | EVM | spoke | `0x${string}` |
|
|
17
|
+
| `REDBELLY_MAINNET` | `'redbelly'` | EVM | spoke | `0x${string}` |
|
|
18
|
+
| `KAIA_MAINNET` | `'0x2019.kaia'` | EVM | spoke | `0x${string}` |
|
|
19
|
+
| `SOLANA_MAINNET` | `'solana'` | SOLANA | spoke | base58 PublicKey string |
|
|
20
|
+
| `SUI_MAINNET` | `'sui'` | SUI | spoke | `0x${string}` (32-byte) |
|
|
21
|
+
| `STELLAR_MAINNET` | `'stellar'` | STELLAR | spoke | `G…` |
|
|
22
|
+
| `ICON_MAINNET` | `'0x1.icon'` | ICON | spoke | `hx…` / `cx…` |
|
|
23
|
+
| `INJECTIVE_MAINNET` | `'injective-1'` | INJECTIVE | spoke | `inj1…` |
|
|
24
|
+
| `NEAR_MAINNET` | `'near'` | NEAR | spoke | `<account>.near` / `<hex>` |
|
|
25
|
+
| `STACKS_MAINNET` | `'stacks'` | STACKS | spoke | `SP…` / `ST…` |
|
|
26
|
+
| `BITCOIN_MAINNET` | `'bitcoin'` | BITCOIN | spoke | `bc1…` / `1…` / `3…` |
|
|
27
|
+
|
|
28
|
+
### Notes
|
|
29
|
+
|
|
30
|
+
- `ChainKeys.ICON_MAINNET` is the **string** `'0x1.icon'`, not the legacy numeric chain id. `Number(chainKey)` returns `NaN` for ICON.
|
|
31
|
+
- `SONIC_MAINNET` is special-cased — it's `'sonic'` (a simple string) and is the hub chain. `getChainType(ChainKeys.SONIC_MAINNET)` returns `'EVM'` (since Sonic is EVM-compatible) and `'SONIC'` is also a valid family in some contexts.
|
|
32
|
+
- Relay chain IDs (used internally for cross-chain coordination) are different from `ChainKey` strings. Convert via `sodax.config.getSpokeChainKeyFromIntentRelayChainId(BigInt(relayId))`.
|
|
33
|
+
|
|
34
|
+
### Type aliases
|
|
35
|
+
|
|
36
|
+
| Type | What it is |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `ChainKey` | Union of all `ChainKeys.*` values (20 chains). |
|
|
39
|
+
| `SpokeChainKey` | `ChainKey` minus `'sonic'` (19 spoke chains). |
|
|
40
|
+
| `EvmChainKey` | Subset of `ChainKey` for the 12 EVM chains. |
|
|
41
|
+
| `HubChainKey` | The literal `'sonic'`. |
|
|
42
|
+
|
|
43
|
+
### Chain-family helpers
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import {
|
|
47
|
+
getChainType, // (chainKey) => 'EVM' | 'BITCOIN' | ...
|
|
48
|
+
isEvmChainKeyType,
|
|
49
|
+
isSolanaChainKeyType,
|
|
50
|
+
isStellarChainKeyType,
|
|
51
|
+
isSuiChainKeyType,
|
|
52
|
+
isIconChainKeyType,
|
|
53
|
+
isInjectiveChainKeyType,
|
|
54
|
+
isStacksChainKeyType,
|
|
55
|
+
isNearChainKeyType,
|
|
56
|
+
isBitcoinChainKeyType,
|
|
57
|
+
isHubChainKeyType,
|
|
58
|
+
} from '@sodax/sdk';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Cross-references
|
|
65
|
+
|
|
66
|
+
- [`README.md`](README.md) — reference index.
|
|
67
|
+
- [`../architecture.md`](../architecture.md) — concepts behind these tables.
|