@shogun-sdk/intents-sdk 1.4.1 → 1.4.3
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/dist/index.cjs +249 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -2
- package/dist/index.d.ts +58 -2
- package/dist/index.js +249 -102
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/core/orders/cross-chain.ts +5 -1
- package/src/core/orders/single-chain.ts +11 -21
- package/src/index.ts +10 -1
- package/src/utils/quote/aggregator.ts +187 -58
- package/src/utils/quote/paraswap.ts +7 -0
- package/src/utils/quote/pricing/constants.ts +50 -6
- package/src/utils/quote/pricing/expenses.ts +78 -22
- package/src/utils/quote/pricing/gas.ts +71 -6
- package/src/utils/quote/pricing/limit-amount.ts +21 -0
- package/src/utils/quote/raydium.ts +1 -4
|
@@ -3,11 +3,17 @@ import {
|
|
|
3
3
|
NATIVE_EVM_ETH_ADDRESSES,
|
|
4
4
|
NATIVE_SOLANA_TOKEN_ADDRESS,
|
|
5
5
|
NATIVE_SUI_TOKEN_ADDRESS,
|
|
6
|
+
WRAPPED_ETH_ADDRESSES,
|
|
7
|
+
WRAPPED_SOL_MINT_ADDRESS,
|
|
6
8
|
isNativeEvmToken,
|
|
7
9
|
} from '../../../constants.js';
|
|
8
10
|
import { compareAddresses } from '../address.js';
|
|
9
11
|
import type { Expenses } from './estimate-amount-out.js';
|
|
10
|
-
import { getNativeGasCost } from './gas.js';
|
|
12
|
+
import { getNativeGasCost, type GasFlow } from './gas.js';
|
|
13
|
+
import { exactOutLimitAmount } from './limit-amount.js';
|
|
14
|
+
|
|
15
|
+
/** An extra transfer as the pricing layer sees it — token and amount only, no receiver. */
|
|
16
|
+
export type ExtraTransferAmount = { token: string; amount: bigint };
|
|
11
17
|
|
|
12
18
|
/** Quotes a native->tokenOut swap on the given chain. Injected to keep this module a leaf (no dependency on the aggregator). */
|
|
13
19
|
type GasQuoteFn = (params: {
|
|
@@ -25,38 +31,88 @@ function nativeTokenAddressForChain(chainId: SupportedChain): string {
|
|
|
25
31
|
return NATIVE_EVM_ETH_ADDRESSES[0]!;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
/**
|
|
35
|
+
* True when 1 native smallest-unit equals 1 unit of `address`: the native token itself
|
|
36
|
+
* or its canonical wrapped form (wrapping is 1:1, and routers have no "route" for it,
|
|
37
|
+
* so quoting native->wrapped would fail despite being trivially priceable).
|
|
38
|
+
*/
|
|
39
|
+
function isNativeOrWrappedNative(chainId: SupportedChain, address: string): boolean {
|
|
40
|
+
if (chainId === ChainID.Solana) {
|
|
41
|
+
return (
|
|
42
|
+
compareAddresses(address, NATIVE_SOLANA_TOKEN_ADDRESS) || compareAddresses(address, WRAPPED_SOL_MINT_ADDRESS)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
30
45
|
if (chainId === ChainID.Sui) return compareAddresses(address, NATIVE_SUI_TOKEN_ADDRESS);
|
|
31
|
-
return isNativeEvmToken(address);
|
|
46
|
+
return isNativeEvmToken(address) || compareAddresses(address, WRAPPED_ETH_ADDRESSES[chainId]);
|
|
32
47
|
}
|
|
33
48
|
|
|
34
49
|
/**
|
|
35
50
|
* Builds the Expenses bundle in the output token. Gas is fetched in native units and
|
|
36
51
|
* swapped into tokenOut via the injected quote fn, mirroring how the solver converts
|
|
37
|
-
* expenses into the output token.
|
|
52
|
+
* expenses into the output token. Extra transfers (e.g. relayer/affiliate payouts) are
|
|
53
|
+
* taken 1:1 when already denominated in tokenOut, or quoted otherwise.
|
|
54
|
+
*
|
|
55
|
+
* Padding mirrors the flow's solver code path:
|
|
56
|
+
* - single-chain applies get_limit_amount(ExactOut) uniformly to every expense conversion
|
|
57
|
+
* (evaluate.rs:116-128), even same-token / native-wrapped short-circuits — pad everything;
|
|
58
|
+
* - cross-chain values expenses via estimate_tokens_value_in_stablecoins
|
|
59
|
+
* (cross_chain_estimation.rs:452-475), which adds stablecoin-denominated amounts directly
|
|
60
|
+
* and pads only USD-converted ones — leave amounts already in tokenOut unpadded.
|
|
38
61
|
*/
|
|
39
62
|
export async function estimateExpensesInTokenOut(params: {
|
|
40
63
|
chainId: SupportedChain;
|
|
41
64
|
tokenOut: string;
|
|
42
|
-
gasLimit: bigint;
|
|
43
65
|
getQuote: GasQuoteFn;
|
|
66
|
+
extraTransfers?: ExtraTransferAmount[];
|
|
67
|
+
protocolFeeInTokenOut?: bigint;
|
|
68
|
+
flow?: GasFlow;
|
|
44
69
|
rpcProviderUrl?: string;
|
|
45
70
|
}): Promise<Expenses> {
|
|
46
|
-
const { chainId, tokenOut,
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
const { chainId, tokenOut, getQuote, extraTransfers, protocolFeeInTokenOut, flow, rpcProviderUrl } = params;
|
|
72
|
+
const padDirectAmounts = (flow ?? 'singleChain') === 'singleChain';
|
|
73
|
+
const padDirect = (amount: bigint) => (padDirectAmounts ? exactOutLimitAmount(amount) : amount);
|
|
74
|
+
|
|
75
|
+
// Value each extra transfer in tokenOut concurrently with the gas legs — they share no inputs.
|
|
76
|
+
// Those already in tokenOut skip the quote.
|
|
77
|
+
const extraTransfersPromise = Promise.all(
|
|
78
|
+
(extraTransfers ?? []).map((transfer) =>
|
|
79
|
+
compareAddresses(transfer.token, tokenOut)
|
|
80
|
+
? Promise.resolve(padDirect(transfer.amount))
|
|
81
|
+
: getQuote({ chainId, amount: transfer.amount, tokenIn: transfer.token, tokenOut }).then((q) =>
|
|
82
|
+
exactOutLimitAmount(q.amountOut),
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
// Register a handler so a transfer-quote rejection is not reported as unhandled
|
|
87
|
+
// if the gas leg throws first; the await below still surfaces the error.
|
|
88
|
+
extraTransfersPromise.catch(() => {});
|
|
89
|
+
|
|
90
|
+
// Worst-case receiver count: destination address plus one distinct receiver per extra
|
|
91
|
+
// transfer — mirrors the solver's max_cost_of_accounts_creation (Solana ATA per receiver).
|
|
92
|
+
const uniqueReceivers = 1 + (extraTransfers?.length ?? 0);
|
|
93
|
+
const nativeGasCost = await getNativeGasCost({ chainId, flow, uniqueReceivers, rpcProviderUrl });
|
|
94
|
+
|
|
95
|
+
// A zero-amount swap always yields zero out, so skip the quote call entirely
|
|
96
|
+
// rather than round-tripping to the quoting service for nothing.
|
|
97
|
+
const gasInTokenOut = exactOutLimitAmount(
|
|
98
|
+
isNativeOrWrappedNative(chainId, tokenOut) || nativeGasCost === 0n
|
|
99
|
+
? nativeGasCost
|
|
100
|
+
: (
|
|
101
|
+
await getQuote({
|
|
102
|
+
chainId,
|
|
103
|
+
amount: nativeGasCost,
|
|
104
|
+
tokenIn: nativeTokenAddressForChain(chainId),
|
|
105
|
+
tokenOut,
|
|
106
|
+
})
|
|
107
|
+
).amountOut,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const extraTransferAmounts = await extraTransfersPromise;
|
|
111
|
+
const extraTransfersInTokenOut = extraTransferAmounts.reduce((sum, amount) => sum + amount, 0n);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
gasInTokenOut,
|
|
115
|
+
protocolFeeInTokenOut: padDirect(protocolFeeInTokenOut ?? 0n),
|
|
116
|
+
extraTransfersInTokenOut,
|
|
117
|
+
};
|
|
62
118
|
}
|
|
@@ -1,23 +1,88 @@
|
|
|
1
1
|
import { getGasPrice } from 'viem/actions';
|
|
2
|
-
import { isEvmChain, type SupportedChain } from '../../../chains.js';
|
|
2
|
+
import { ChainID, isEvmChain, type SupportedChain } from '../../../chains.js';
|
|
3
3
|
import { ChainProvider } from '../../../core/evm/chain-provider.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
EVM_CROSS_CHAIN_GAS_LIMIT,
|
|
6
|
+
EVM_CROSS_CHAIN_SOURCE_GAS_LIMIT,
|
|
7
|
+
EVM_CROSS_CHAIN_SOURCE_NO_SWAP_GAS_LIMIT,
|
|
8
|
+
EVM_SINGLE_CHAIN_GAS_LIMIT,
|
|
9
|
+
GAS_PRICE_SAFETY_MARGIN_BPS,
|
|
10
|
+
NON_EVM_FIXED_NATIVE_GAS_COST,
|
|
11
|
+
SOLANA_BASE_TX_COST,
|
|
12
|
+
SOLANA_CLAIM_TOKENS_COST,
|
|
13
|
+
SOLANA_CREATE_ATA_COST,
|
|
14
|
+
SOLANA_CREATE_EMPTY_ACC_COST,
|
|
15
|
+
SOLANA_JITO_TIP,
|
|
16
|
+
SOLANA_START_WITHOUT_SWAP_COST,
|
|
17
|
+
} from './constants.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Which solver execution the gas estimate models. crossChainSourceNoSwap is the source
|
|
21
|
+
* leg when token_in needs no swap into the stablecoin (start_order_execution_without_swap).
|
|
22
|
+
*/
|
|
23
|
+
export type GasFlow = 'singleChain' | 'crossChainSource' | 'crossChainSourceNoSwap' | 'crossChainDest';
|
|
24
|
+
|
|
25
|
+
/** EVM gas units per execution flow — pairs each flow with its solver gas limit in one place. */
|
|
26
|
+
const EVM_GAS_LIMIT_BY_FLOW: Record<GasFlow, bigint> = {
|
|
27
|
+
singleChain: EVM_SINGLE_CHAIN_GAS_LIMIT,
|
|
28
|
+
crossChainSource: EVM_CROSS_CHAIN_SOURCE_GAS_LIMIT,
|
|
29
|
+
crossChainSourceNoSwap: EVM_CROSS_CHAIN_SOURCE_NO_SWAP_GAS_LIMIT,
|
|
30
|
+
crossChainDest: EVM_CROSS_CHAIN_GAS_LIMIT,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Pads the execution-price component of gas (fees/tips), never rent-exempt deposits. */
|
|
34
|
+
function padGasPrice(amount: bigint): bigint {
|
|
35
|
+
return (amount * (10_000n + GAS_PRICE_SAFETY_MARGIN_BPS)) / 10_000n;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Mirrors solver chains.rs Solana branches with gas_price = 1 (the solver's unwrap_or default).
|
|
40
|
+
* ATA creation scales with payout receivers; cross-chain adds the escrow PDA / claim legs.
|
|
41
|
+
* Only the transaction/tip component takes the safety margin — rent-exempt account
|
|
42
|
+
* creation (ATA, escrow PDA) is runtime-fixed and cannot drift.
|
|
43
|
+
*/
|
|
44
|
+
function solanaNativeGasCost(flow: GasFlow, uniqueReceivers: number): bigint {
|
|
45
|
+
switch (flow) {
|
|
46
|
+
case 'crossChainSource':
|
|
47
|
+
// start_order_execution_with_swap_cost + claim_tokens_cost (receivers do not apply)
|
|
48
|
+
return padGasPrice(SOLANA_BASE_TX_COST + SOLANA_JITO_TIP + SOLANA_CLAIM_TOKENS_COST);
|
|
49
|
+
case 'crossChainSourceNoSwap':
|
|
50
|
+
// start_order_execution_without_swap_cost + claim_tokens_cost
|
|
51
|
+
return padGasPrice(SOLANA_START_WITHOUT_SWAP_COST + SOLANA_CLAIM_TOKENS_COST);
|
|
52
|
+
case 'crossChainDest':
|
|
53
|
+
// fulfill_cross_chain_order_cost
|
|
54
|
+
return (
|
|
55
|
+
SOLANA_CREATE_EMPTY_ACC_COST +
|
|
56
|
+
SOLANA_CREATE_ATA_COST * BigInt(uniqueReceivers) +
|
|
57
|
+
padGasPrice(SOLANA_BASE_TX_COST + SOLANA_JITO_TIP)
|
|
58
|
+
);
|
|
59
|
+
case 'singleChain':
|
|
60
|
+
// fulfill_single_chain_order_cost
|
|
61
|
+
return SOLANA_CREATE_ATA_COST * BigInt(uniqueReceivers) + padGasPrice(SOLANA_BASE_TX_COST + SOLANA_JITO_TIP);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
5
64
|
|
|
6
65
|
/**
|
|
7
66
|
* Gas cost in the chain's native smallest unit (wei / lamports / MIST).
|
|
8
|
-
* EVM: live gas price × gas limit.
|
|
67
|
+
* EVM: live gas price × the flow's gas limit. Solana: solver-mirrored per-flow formula.
|
|
68
|
+
* Other non-EVM: solver-mirrored fixed cost.
|
|
9
69
|
*/
|
|
10
70
|
export async function getNativeGasCost(params: {
|
|
11
71
|
chainId: SupportedChain;
|
|
12
|
-
|
|
72
|
+
flow?: GasFlow;
|
|
73
|
+
uniqueReceivers?: number;
|
|
13
74
|
rpcProviderUrl?: string;
|
|
14
75
|
}): Promise<bigint> {
|
|
15
|
-
const { chainId,
|
|
76
|
+
const { chainId, flow = 'singleChain', uniqueReceivers = 1, rpcProviderUrl } = params;
|
|
16
77
|
|
|
17
78
|
if (isEvmChain(chainId)) {
|
|
18
79
|
const client = ChainProvider.getClient(chainId, rpcProviderUrl);
|
|
19
80
|
const gasPrice = await getGasPrice(client);
|
|
20
|
-
return
|
|
81
|
+
return padGasPrice(EVM_GAS_LIMIT_BY_FLOW[flow] * gasPrice);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (chainId === ChainID.Solana) {
|
|
85
|
+
return solanaNativeGasCost(flow, uniqueReceivers);
|
|
21
86
|
}
|
|
22
87
|
|
|
23
88
|
const fixed = NON_EVM_FIXED_NATIVE_GAS_COST[chainId];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SOLVER_DEFAULT_SLIPPAGE_BPS } from './constants.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mirrors the solver's get_limit_amount (swap_estimator_rust/src/utils/limit_amount.rs)
|
|
5
|
+
* with Slippage::Percent(DEFAULT_SLIPPAGE): the diff is `mul_div(amount, slippage, base,
|
|
6
|
+
* round_up=true)` — always rounded UP — then subtracted for ExactIn (swap outputs are
|
|
7
|
+
* worst-cased down) or added for ExactOut (expense valuations are worst-cased up).
|
|
8
|
+
*/
|
|
9
|
+
function slippageDiff(amountQuote: bigint): bigint {
|
|
10
|
+
return (amountQuote * SOLVER_DEFAULT_SLIPPAGE_BPS + 9_999n) / 10_000n;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Swap output the solver actually credits: `amount_limit`, not the nominal quote. */
|
|
14
|
+
export function exactInLimitAmount(amountQuote: bigint): bigint {
|
|
15
|
+
return amountQuote - slippageDiff(amountQuote);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Expense valuation the solver actually charges: quote plus the rounded-up slippage diff. */
|
|
19
|
+
export function exactOutLimitAmount(amountQuote: bigint): bigint {
|
|
20
|
+
return amountQuote + slippageDiff(amountQuote);
|
|
21
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type LaunchpadPoolInfo,
|
|
11
11
|
} from '@raydium-io/raydium-sdk-v2';
|
|
12
12
|
import { type SimpleQuote } from './aggregator.js';
|
|
13
|
+
import { compareAddresses } from './address.js';
|
|
13
14
|
import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'; // Used this package for raydium-sdk-v2 compatibility
|
|
14
15
|
import BN from 'bn.js';
|
|
15
16
|
import { Decimal } from 'decimal.js';
|
|
@@ -51,10 +52,6 @@ const platformConfigCache = new Map<
|
|
|
51
52
|
|
|
52
53
|
const mintInfoCache = new Map<string, Awaited<ReturnType<Raydium['token']['getTokenInfo']>>>();
|
|
53
54
|
|
|
54
|
-
// Local implementation of compareAddresses function
|
|
55
|
-
const compareAddresses = (firstAddress?: string, secondAddress?: string): boolean => {
|
|
56
|
-
return !!firstAddress && !!secondAddress && firstAddress.toLowerCase() === secondAddress.toLowerCase();
|
|
57
|
-
};
|
|
58
55
|
|
|
59
56
|
const LAUNCHPAD_WHITELISTED_QUOTE_TOKENS = [
|
|
60
57
|
new PublicKey('So11111111111111111111111111111111111111112'), // WSOL
|