@avalabs/fusion-sdk 0.19.1 → 0.21.0
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/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/quoter/quoter.cjs +1 -1
- package/dist/quoter/quoter.cjs.map +1 -1
- package/dist/quoter/quoter.d.cts +8 -0
- package/dist/quoter/quoter.d.ts +8 -0
- package/dist/quoter/quoter.js +1 -1
- package/dist/quoter/quoter.js.map +1 -1
- package/dist/transfer-service/markr/_api.cjs.map +1 -1
- package/dist/transfer-service/markr/_api.js.map +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.cjs +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.cjs.map +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.js +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.js.map +1 -1
- package/dist/transfer-service/markr/_handlers/stream-quotes.cjs +1 -1
- package/dist/transfer-service/markr/_handlers/stream-quotes.cjs.map +1 -1
- package/dist/transfer-service/markr/_handlers/stream-quotes.js +1 -1
- package/dist/transfer-service/markr/_handlers/stream-quotes.js.map +1 -1
- package/dist/types/quote.d.cts +9 -0
- package/dist/types/quote.d.ts +9 -0
- package/dist/types/service.d.cts +12 -2
- package/dist/types/service.d.ts +12 -2
- package/package.json +1 -1
package/dist/mod.d.cts
CHANGED
|
@@ -11,7 +11,7 @@ import { AbortedError, ErrorCode, ErrorReason, EstimateNativeFeeError, EstimateN
|
|
|
11
11
|
import { CompletedTransfer, FailedTransfer, RefundedTransfer, SourceCompletedTransfer, SourcePendingTransfer, TargetPendingTransfer, Transfer, TransferBase, TransferSourceProgress, TransferStatus, TransferStepDetails, TransferTargetProgress } from "./types/transfer.cjs";
|
|
12
12
|
import { SolanaSendOptions, SolanaTransactionParams } from "./utils/solana-transaction.cjs";
|
|
13
13
|
import { BtcDispatch, BtcSign, BtcSigner, BtcTransactionRequest, EvmDispatch, EvmSign, EvmSignBatch, EvmSignMessage, EvmSignTypedData, EvmSigner, EvmSignerWithMessage, EvmTransactionRequest, EvmTypedData, Hex, SolanaSign, SolanaSigner } from "./types/signer.cjs";
|
|
14
|
-
import { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, TrackTransferProps, TransferAssetProps, TransferService } from "./types/service.cjs";
|
|
14
|
+
import { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, ServiceQuoteOptions, TrackTransferProps, TransferAssetProps, TransferService } from "./types/service.cjs";
|
|
15
15
|
import { AssetBridgeMap, CreateTransferManagerOptions, ServiceStatus, ServiceStatusBaseRecord, ServiceStatusErrorRecord, ServiceStatusInitializedRecord, ServiceStatusRecord, ServiceStatusUnsupportedEnvironmentRecord, TransferManager, TransferManagerStatus, TransferManagerStatusServicesRecord } from "./types/transfer-manager.cjs";
|
|
16
16
|
import { isEnvironment, isErc20Asset, isEvmBridgeInitializer, isLombardServiceInitializer, isMarkrServiceInitializer, isNativeAsset, isServiceInitializer, isSplAsset } from "./type-guards.cjs";
|
|
17
17
|
import { createTransferManager } from "./transfer-manager.cjs";
|
|
@@ -19,4 +19,4 @@ import { caip2ToEip155ChainId, caip2ToEip155HexChainId, eip155ChainIdToCaip2, is
|
|
|
19
19
|
import { dedupeBridgeableAssets } from "./utils/bridgeable-assets.cjs";
|
|
20
20
|
import { calculatePriceImpactFromQuote } from "./utils/price-impact.cjs";
|
|
21
21
|
import { parseTransfer, stringifyTransfer } from "./utils/transfer-utils.cjs";
|
|
22
|
-
export { AVALANCHE_CAIP2_TO_BLOCKCHAIN_ID_BY_NETWORK, AVALANCHE_FUJI_CHAIN, AVALANCHE_FUJI_P_CHAIN, AVALANCHE_FUJI_X_CHAIN, AVALANCHE_MAINNET_CHAIN, AVALANCHE_MAINNET_P_CHAIN, AVALANCHE_MAINNET_X_CHAIN, AbortedError, ArrayElement, Asset, AssetBridgeMap, AssetSearchQuery, AssetWithExtras, AvalancheBlockchainAlias, AvalancheCctInitializer, AvalancheChainIds, AvalancheFujiBlockchainChainIds, AvalancheFujiBlockchainIds, AvalancheMainnetBlockchainChainIds, AvalancheMainnetBlockchainIds, AvalancheSendTxFunction, AvalancheSendTxParams, BITCOIN_MAINNET_CHAIN, BITCOIN_TESTNET_CHAIN, BTC_SERVICE_TYPES, BitcoinChainIds, BitcoinFeeRateTier, BitcoinFunctions, BitcoinInputUTXO, BitcoinInputUTXOWithOptionalScript, BitcoinOutputUTXO, BitcoinTx, Blockchain, Brand, BridgeableAsset, BridgeableUiAsset, BtcDispatch, BtcSign, BtcSigner, BtcTransactionRequest, Caip10AccountId, Caip2ChainId, Chain, ChainAssetMap, CompletedTransfer, CreateTransferManagerOptions, DeepMutable, DestinationInfo, ERC_ZERO_ADDRESS, ETHEREUM_MAINNET_CHAIN, ETHEREUM_SEPOLIA_CHAIN, EVM_SERVICE_TYPES, Environment, Erc20Asset, ErrorCode, ErrorReason, EstimateNativeFeeError, type EstimateNativeFeeErrorDetails, EstimateNativeFeeOptions, EthereumChainIds, EvmChainId, EvmDispatch, EvmServiceInitializer, EvmSign, EvmSignBatch, EvmSignMessage, EvmSignTypedData, EvmSigner, EvmSignerWithMessage, EvmTransactionRequest, EvmTypedData, FEE_RATE_TIER_TO_BITCOIN, FailedTransfer, FeeRateTier, Fetch, GasSettings, type GetAtomicUtxosCallback, GetBridgeableAssetsProps, GetBridgeableAssetsResult, type GetCoreEthAddressCallback, GetMinimumTransferAmountProps, GetSupportedChainsResult, type GetUtxosCallback, type GetWalletAddressesForChainAliasCallback, type GetWalletChangeAddressForChainAliasCallback, Hex, HttpError, HyperliquidChainIds, InsufficientFundsError, InvalidParamsError, LombardServiceInitializer, MarkrServiceInitializer, Mutable, MutableGetSupportedChainsResult, NATIVE_AVAX, NATIVE_ETH, NATIVE_SOL_ADDRESS, NativeAsset, NativeFeeEstimate, Quote, QuoteFee, QuoteFeeFundingModel, QuoteFeeToken, QuoteFeeType, QuoteFees, QuoterDonePayload, QuoterDonePayloadNoEligibleServices, QuoterDonePayloadNoQuotes, QuoterDonePayloadUnsubscribed, QuoterDoneReason, QuoterEventArgs, QuoterEventHandler, QuoterInterface, QuoterProps, QuotesTuple, RefundedTransfer, ResponseValidationError, SOLANA_DEVNET_CHAIN, SOLANA_MAINNET_CHAIN, SdkError, ServiceInitializationError, ServiceInitializer, ServiceQuoteEventArgs, ServiceQuoteEventHandler, ServiceStatus, ServiceStatusBaseRecord, ServiceStatusErrorRecord, ServiceStatusInitializedRecord, ServiceStatusRecord, ServiceStatusUnsupportedEnvironmentRecord, ServiceType, ServiceUnavailableError, SolanaChainIds, SolanaSendOptions, SolanaSign, SolanaSigner, SolanaTransactionParams, SourceCompletedTransfer, SourcePendingTransfer, SplAsset, TargetPendingTransfer, TimeoutError, TokenType, TrackTransferProps, Transfer, TransferAssetProps, TransferBase, TransferManager, TransferManagerStatus, TransferManagerStatusServicesRecord, TransferService, TransferSignatureReason, TransferSourceProgress, TransferStatus, TransferStepDetails, TransferTargetProgress, TransferableAsset, caip2ToEip155ChainId, caip2ToEip155HexChainId, calculatePriceImpactFromQuote, createTransferManager, dedupeBridgeableAssets, eip155ChainIdToCaip2, isAbortedError, isCaip2ChainId, isEnvironment, isErc20Asset, isEstimateNativeFeeError, isEvmBridgeInitializer, isHttpError, isInsufficientFundsError, isInvalidParamsError, isLombardServiceInitializer, isMarkrServiceInitializer, isNativeAsset, isResponseValidationError, isSdkError, isServiceInitializationError, isServiceInitializer, isServiceUnavailableError, isSplAsset, isTimeoutError, parseTransfer, splitCaip2ChainId, stringifyTransfer };
|
|
22
|
+
export { AVALANCHE_CAIP2_TO_BLOCKCHAIN_ID_BY_NETWORK, AVALANCHE_FUJI_CHAIN, AVALANCHE_FUJI_P_CHAIN, AVALANCHE_FUJI_X_CHAIN, AVALANCHE_MAINNET_CHAIN, AVALANCHE_MAINNET_P_CHAIN, AVALANCHE_MAINNET_X_CHAIN, AbortedError, ArrayElement, Asset, AssetBridgeMap, AssetSearchQuery, AssetWithExtras, AvalancheBlockchainAlias, AvalancheCctInitializer, AvalancheChainIds, AvalancheFujiBlockchainChainIds, AvalancheFujiBlockchainIds, AvalancheMainnetBlockchainChainIds, AvalancheMainnetBlockchainIds, AvalancheSendTxFunction, AvalancheSendTxParams, BITCOIN_MAINNET_CHAIN, BITCOIN_TESTNET_CHAIN, BTC_SERVICE_TYPES, BitcoinChainIds, BitcoinFeeRateTier, BitcoinFunctions, BitcoinInputUTXO, BitcoinInputUTXOWithOptionalScript, BitcoinOutputUTXO, BitcoinTx, Blockchain, Brand, BridgeableAsset, BridgeableUiAsset, BtcDispatch, BtcSign, BtcSigner, BtcTransactionRequest, Caip10AccountId, Caip2ChainId, Chain, ChainAssetMap, CompletedTransfer, CreateTransferManagerOptions, DeepMutable, DestinationInfo, ERC_ZERO_ADDRESS, ETHEREUM_MAINNET_CHAIN, ETHEREUM_SEPOLIA_CHAIN, EVM_SERVICE_TYPES, Environment, Erc20Asset, ErrorCode, ErrorReason, EstimateNativeFeeError, type EstimateNativeFeeErrorDetails, EstimateNativeFeeOptions, EthereumChainIds, EvmChainId, EvmDispatch, EvmServiceInitializer, EvmSign, EvmSignBatch, EvmSignMessage, EvmSignTypedData, EvmSigner, EvmSignerWithMessage, EvmTransactionRequest, EvmTypedData, FEE_RATE_TIER_TO_BITCOIN, FailedTransfer, FeeRateTier, Fetch, GasSettings, type GetAtomicUtxosCallback, GetBridgeableAssetsProps, GetBridgeableAssetsResult, type GetCoreEthAddressCallback, GetMinimumTransferAmountProps, GetSupportedChainsResult, type GetUtxosCallback, type GetWalletAddressesForChainAliasCallback, type GetWalletChangeAddressForChainAliasCallback, Hex, HttpError, HyperliquidChainIds, InsufficientFundsError, InvalidParamsError, LombardServiceInitializer, MarkrServiceInitializer, Mutable, MutableGetSupportedChainsResult, NATIVE_AVAX, NATIVE_ETH, NATIVE_SOL_ADDRESS, NativeAsset, NativeFeeEstimate, Quote, QuoteFee, QuoteFeeFundingModel, QuoteFeeToken, QuoteFeeType, QuoteFees, QuoterDonePayload, QuoterDonePayloadNoEligibleServices, QuoterDonePayloadNoQuotes, QuoterDonePayloadUnsubscribed, QuoterDoneReason, QuoterEventArgs, QuoterEventHandler, QuoterInterface, QuoterProps, QuotesTuple, RefundedTransfer, ResponseValidationError, SOLANA_DEVNET_CHAIN, SOLANA_MAINNET_CHAIN, SdkError, ServiceInitializationError, ServiceInitializer, ServiceQuoteEventArgs, ServiceQuoteEventHandler, ServiceQuoteOptions, ServiceStatus, ServiceStatusBaseRecord, ServiceStatusErrorRecord, ServiceStatusInitializedRecord, ServiceStatusRecord, ServiceStatusUnsupportedEnvironmentRecord, ServiceType, ServiceUnavailableError, SolanaChainIds, SolanaSendOptions, SolanaSign, SolanaSigner, SolanaTransactionParams, SourceCompletedTransfer, SourcePendingTransfer, SplAsset, TargetPendingTransfer, TimeoutError, TokenType, TrackTransferProps, Transfer, TransferAssetProps, TransferBase, TransferManager, TransferManagerStatus, TransferManagerStatusServicesRecord, TransferService, TransferSignatureReason, TransferSourceProgress, TransferStatus, TransferStepDetails, TransferTargetProgress, TransferableAsset, caip2ToEip155ChainId, caip2ToEip155HexChainId, calculatePriceImpactFromQuote, createTransferManager, dedupeBridgeableAssets, eip155ChainIdToCaip2, isAbortedError, isCaip2ChainId, isEnvironment, isErc20Asset, isEstimateNativeFeeError, isEvmBridgeInitializer, isHttpError, isInsufficientFundsError, isInvalidParamsError, isLombardServiceInitializer, isMarkrServiceInitializer, isNativeAsset, isResponseValidationError, isSdkError, isServiceInitializationError, isServiceInitializer, isServiceUnavailableError, isSplAsset, isTimeoutError, parseTransfer, splitCaip2ChainId, stringifyTransfer };
|
package/dist/mod.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { AbortedError, ErrorCode, ErrorReason, EstimateNativeFeeError, EstimateN
|
|
|
11
11
|
import { CompletedTransfer, FailedTransfer, RefundedTransfer, SourceCompletedTransfer, SourcePendingTransfer, TargetPendingTransfer, Transfer, TransferBase, TransferSourceProgress, TransferStatus, TransferStepDetails, TransferTargetProgress } from "./types/transfer.js";
|
|
12
12
|
import { SolanaSendOptions, SolanaTransactionParams } from "./utils/solana-transaction.js";
|
|
13
13
|
import { BtcDispatch, BtcSign, BtcSigner, BtcTransactionRequest, EvmDispatch, EvmSign, EvmSignBatch, EvmSignMessage, EvmSignTypedData, EvmSigner, EvmSignerWithMessage, EvmTransactionRequest, EvmTypedData, Hex, SolanaSign, SolanaSigner } from "./types/signer.js";
|
|
14
|
-
import { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, TrackTransferProps, TransferAssetProps, TransferService } from "./types/service.js";
|
|
14
|
+
import { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, ServiceQuoteOptions, TrackTransferProps, TransferAssetProps, TransferService } from "./types/service.js";
|
|
15
15
|
import { AssetBridgeMap, CreateTransferManagerOptions, ServiceStatus, ServiceStatusBaseRecord, ServiceStatusErrorRecord, ServiceStatusInitializedRecord, ServiceStatusRecord, ServiceStatusUnsupportedEnvironmentRecord, TransferManager, TransferManagerStatus, TransferManagerStatusServicesRecord } from "./types/transfer-manager.js";
|
|
16
16
|
import { isEnvironment, isErc20Asset, isEvmBridgeInitializer, isLombardServiceInitializer, isMarkrServiceInitializer, isNativeAsset, isServiceInitializer, isSplAsset } from "./type-guards.js";
|
|
17
17
|
import { createTransferManager } from "./transfer-manager.js";
|
|
@@ -19,4 +19,4 @@ import { caip2ToEip155ChainId, caip2ToEip155HexChainId, eip155ChainIdToCaip2, is
|
|
|
19
19
|
import { dedupeBridgeableAssets } from "./utils/bridgeable-assets.js";
|
|
20
20
|
import { calculatePriceImpactFromQuote } from "./utils/price-impact.js";
|
|
21
21
|
import { parseTransfer, stringifyTransfer } from "./utils/transfer-utils.js";
|
|
22
|
-
export { AVALANCHE_CAIP2_TO_BLOCKCHAIN_ID_BY_NETWORK, AVALANCHE_FUJI_CHAIN, AVALANCHE_FUJI_P_CHAIN, AVALANCHE_FUJI_X_CHAIN, AVALANCHE_MAINNET_CHAIN, AVALANCHE_MAINNET_P_CHAIN, AVALANCHE_MAINNET_X_CHAIN, AbortedError, ArrayElement, Asset, AssetBridgeMap, AssetSearchQuery, AssetWithExtras, AvalancheBlockchainAlias, AvalancheCctInitializer, AvalancheChainIds, AvalancheFujiBlockchainChainIds, AvalancheFujiBlockchainIds, AvalancheMainnetBlockchainChainIds, AvalancheMainnetBlockchainIds, AvalancheSendTxFunction, AvalancheSendTxParams, BITCOIN_MAINNET_CHAIN, BITCOIN_TESTNET_CHAIN, BTC_SERVICE_TYPES, BitcoinChainIds, BitcoinFeeRateTier, BitcoinFunctions, BitcoinInputUTXO, BitcoinInputUTXOWithOptionalScript, BitcoinOutputUTXO, BitcoinTx, Blockchain, Brand, BridgeableAsset, BridgeableUiAsset, BtcDispatch, BtcSign, BtcSigner, BtcTransactionRequest, Caip10AccountId, Caip2ChainId, Chain, ChainAssetMap, CompletedTransfer, CreateTransferManagerOptions, DeepMutable, DestinationInfo, ERC_ZERO_ADDRESS, ETHEREUM_MAINNET_CHAIN, ETHEREUM_SEPOLIA_CHAIN, EVM_SERVICE_TYPES, Environment, Erc20Asset, ErrorCode, ErrorReason, EstimateNativeFeeError, type EstimateNativeFeeErrorDetails, EstimateNativeFeeOptions, EthereumChainIds, EvmChainId, EvmDispatch, EvmServiceInitializer, EvmSign, EvmSignBatch, EvmSignMessage, EvmSignTypedData, EvmSigner, EvmSignerWithMessage, EvmTransactionRequest, EvmTypedData, FEE_RATE_TIER_TO_BITCOIN, FailedTransfer, FeeRateTier, Fetch, GasSettings, type GetAtomicUtxosCallback, GetBridgeableAssetsProps, GetBridgeableAssetsResult, type GetCoreEthAddressCallback, GetMinimumTransferAmountProps, GetSupportedChainsResult, type GetUtxosCallback, type GetWalletAddressesForChainAliasCallback, type GetWalletChangeAddressForChainAliasCallback, Hex, HttpError, HyperliquidChainIds, InsufficientFundsError, InvalidParamsError, LombardServiceInitializer, MarkrServiceInitializer, Mutable, MutableGetSupportedChainsResult, NATIVE_AVAX, NATIVE_ETH, NATIVE_SOL_ADDRESS, NativeAsset, NativeFeeEstimate, Quote, QuoteFee, QuoteFeeFundingModel, QuoteFeeToken, QuoteFeeType, QuoteFees, QuoterDonePayload, QuoterDonePayloadNoEligibleServices, QuoterDonePayloadNoQuotes, QuoterDonePayloadUnsubscribed, QuoterDoneReason, QuoterEventArgs, QuoterEventHandler, QuoterInterface, QuoterProps, QuotesTuple, RefundedTransfer, ResponseValidationError, SOLANA_DEVNET_CHAIN, SOLANA_MAINNET_CHAIN, SdkError, ServiceInitializationError, ServiceInitializer, ServiceQuoteEventArgs, ServiceQuoteEventHandler, ServiceStatus, ServiceStatusBaseRecord, ServiceStatusErrorRecord, ServiceStatusInitializedRecord, ServiceStatusRecord, ServiceStatusUnsupportedEnvironmentRecord, ServiceType, ServiceUnavailableError, SolanaChainIds, SolanaSendOptions, SolanaSign, SolanaSigner, SolanaTransactionParams, SourceCompletedTransfer, SourcePendingTransfer, SplAsset, TargetPendingTransfer, TimeoutError, TokenType, TrackTransferProps, Transfer, TransferAssetProps, TransferBase, TransferManager, TransferManagerStatus, TransferManagerStatusServicesRecord, TransferService, TransferSignatureReason, TransferSourceProgress, TransferStatus, TransferStepDetails, TransferTargetProgress, TransferableAsset, caip2ToEip155ChainId, caip2ToEip155HexChainId, calculatePriceImpactFromQuote, createTransferManager, dedupeBridgeableAssets, eip155ChainIdToCaip2, isAbortedError, isCaip2ChainId, isEnvironment, isErc20Asset, isEstimateNativeFeeError, isEvmBridgeInitializer, isHttpError, isInsufficientFundsError, isInvalidParamsError, isLombardServiceInitializer, isMarkrServiceInitializer, isNativeAsset, isResponseValidationError, isSdkError, isServiceInitializationError, isServiceInitializer, isServiceUnavailableError, isSplAsset, isTimeoutError, parseTransfer, splitCaip2ChainId, stringifyTransfer };
|
|
22
|
+
export { AVALANCHE_CAIP2_TO_BLOCKCHAIN_ID_BY_NETWORK, AVALANCHE_FUJI_CHAIN, AVALANCHE_FUJI_P_CHAIN, AVALANCHE_FUJI_X_CHAIN, AVALANCHE_MAINNET_CHAIN, AVALANCHE_MAINNET_P_CHAIN, AVALANCHE_MAINNET_X_CHAIN, AbortedError, ArrayElement, Asset, AssetBridgeMap, AssetSearchQuery, AssetWithExtras, AvalancheBlockchainAlias, AvalancheCctInitializer, AvalancheChainIds, AvalancheFujiBlockchainChainIds, AvalancheFujiBlockchainIds, AvalancheMainnetBlockchainChainIds, AvalancheMainnetBlockchainIds, AvalancheSendTxFunction, AvalancheSendTxParams, BITCOIN_MAINNET_CHAIN, BITCOIN_TESTNET_CHAIN, BTC_SERVICE_TYPES, BitcoinChainIds, BitcoinFeeRateTier, BitcoinFunctions, BitcoinInputUTXO, BitcoinInputUTXOWithOptionalScript, BitcoinOutputUTXO, BitcoinTx, Blockchain, Brand, BridgeableAsset, BridgeableUiAsset, BtcDispatch, BtcSign, BtcSigner, BtcTransactionRequest, Caip10AccountId, Caip2ChainId, Chain, ChainAssetMap, CompletedTransfer, CreateTransferManagerOptions, DeepMutable, DestinationInfo, ERC_ZERO_ADDRESS, ETHEREUM_MAINNET_CHAIN, ETHEREUM_SEPOLIA_CHAIN, EVM_SERVICE_TYPES, Environment, Erc20Asset, ErrorCode, ErrorReason, EstimateNativeFeeError, type EstimateNativeFeeErrorDetails, EstimateNativeFeeOptions, EthereumChainIds, EvmChainId, EvmDispatch, EvmServiceInitializer, EvmSign, EvmSignBatch, EvmSignMessage, EvmSignTypedData, EvmSigner, EvmSignerWithMessage, EvmTransactionRequest, EvmTypedData, FEE_RATE_TIER_TO_BITCOIN, FailedTransfer, FeeRateTier, Fetch, GasSettings, type GetAtomicUtxosCallback, GetBridgeableAssetsProps, GetBridgeableAssetsResult, type GetCoreEthAddressCallback, GetMinimumTransferAmountProps, GetSupportedChainsResult, type GetUtxosCallback, type GetWalletAddressesForChainAliasCallback, type GetWalletChangeAddressForChainAliasCallback, Hex, HttpError, HyperliquidChainIds, InsufficientFundsError, InvalidParamsError, LombardServiceInitializer, MarkrServiceInitializer, Mutable, MutableGetSupportedChainsResult, NATIVE_AVAX, NATIVE_ETH, NATIVE_SOL_ADDRESS, NativeAsset, NativeFeeEstimate, Quote, QuoteFee, QuoteFeeFundingModel, QuoteFeeToken, QuoteFeeType, QuoteFees, QuoterDonePayload, QuoterDonePayloadNoEligibleServices, QuoterDonePayloadNoQuotes, QuoterDonePayloadUnsubscribed, QuoterDoneReason, QuoterEventArgs, QuoterEventHandler, QuoterInterface, QuoterProps, QuotesTuple, RefundedTransfer, ResponseValidationError, SOLANA_DEVNET_CHAIN, SOLANA_MAINNET_CHAIN, SdkError, ServiceInitializationError, ServiceInitializer, ServiceQuoteEventArgs, ServiceQuoteEventHandler, ServiceQuoteOptions, ServiceStatus, ServiceStatusBaseRecord, ServiceStatusErrorRecord, ServiceStatusInitializedRecord, ServiceStatusRecord, ServiceStatusUnsupportedEnvironmentRecord, ServiceType, ServiceUnavailableError, SolanaChainIds, SolanaSendOptions, SolanaSign, SolanaSigner, SolanaTransactionParams, SourceCompletedTransfer, SourcePendingTransfer, SplAsset, TargetPendingTransfer, TimeoutError, TokenType, TrackTransferProps, Transfer, TransferAssetProps, TransferBase, TransferManager, TransferManagerStatus, TransferManagerStatusServicesRecord, TransferService, TransferSignatureReason, TransferSourceProgress, TransferStatus, TransferStepDetails, TransferTargetProgress, TransferableAsset, caip2ToEip155ChainId, caip2ToEip155HexChainId, calculatePriceImpactFromQuote, createTransferManager, dedupeBridgeableAssets, eip155ChainIdToCaip2, isAbortedError, isCaip2ChainId, isEnvironment, isErc20Asset, isEstimateNativeFeeError, isEvmBridgeInitializer, isHttpError, isInsufficientFundsError, isInvalidParamsError, isLombardServiceInitializer, isMarkrServiceInitializer, isNativeAsset, isResponseValidationError, isSdkError, isServiceInitializationError, isServiceInitializer, isServiceUnavailableError, isSplAsset, isTimeoutError, parseTransfer, splitCaip2ChainId, stringifyTransfer };
|
package/dist/quoter/quoter.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../constants.cjs`),t=require(`../type-guards.cjs`),n=require(`./_utils.cjs`),r=require(`./constants.cjs`);let i=require(`zod`),a=require(`viem`);var o=class{clock;props;pruneIntervalMs;transferServices;refreshBufferSeconds;quotes=[];subscribers=new Set;started=!1;done=!1;isStartingFromSubscribe=!1;pruneTimerId=null;lastBestId=null;hasReceivedAnyQuote=!1;id=crypto.randomUUID();serviceState=new Map;constructor(e,t,n={}){this.clock=n.clock??(()=>Math.floor(Date.now()/1e3)),this.props=e,this.pruneIntervalMs=n.pruneIntervalMs??1e3,this.transferServices=t,this.refreshBufferSeconds=n.refreshBufferSeconds??5}getQuotes(){let e=this.clock(),t=n.sortQuotes(n.pruneExpiredQuotes({quotes:this.quotes,nowSeconds:e}));return[t[0]??null,t]}subscribe(e){this.subscribers.add(e);let t=!1;if(!this.started){this.isStartingFromSubscribe=!0;try{this.start()}finally{this.isStartingFromSubscribe=!1}}return()=>{if(!t){if(t=!0,this.done){this.subscribers.delete(e);return}if(this.subscribers.size===1&&this.subscribers.has(e)){this.complete(`unsubscribed`);return}this.subscribers.delete(e)}}}start(){if(this.started=!0,this.done=!1,this.quotes=[],this.lastBestId=null,this.hasReceivedAnyQuote=!1,c(this.props)){this.complete(`no-eligible-services`);return}let e=!1;for(let t of this.transferServices)t.analyzeSupport({sourceAsset:this.props.sourceAsset,sourceChainId:this.props.sourceChain.chainId,targetAsset:this.props.targetAsset,targetChainId:this.props.targetChain.chainId})&&(e=!0,this.startStreamForService(t));if(!e){this.complete(`no-eligible-services`);return}this.pruneTimerId=setInterval(()=>this.onPruneTick(),this.pruneIntervalMs)}stop(){this.started=!1,this.pruneTimerId&&=(clearInterval(this.pruneTimerId),null);for(let[e,t]of this.serviceState){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer),this.serviceState.set(e,{cancel:()=>{},done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}}startStreamForService(e){let t=this.serviceState.get(e.type);if(t){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer)}let n=this.makeServiceHandler(e.type),r=[],i=!0,{cancel:a}=e.streamQuotes(this.props,(...e)=>{if(i){r.push(e);return}n(...e)});i=!1,r.length>0&&queueMicrotask(()=>{for(let e of r)n(...e)}),this.serviceState.set(e.type,{cancel:a,done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:t?.retryAttempt??0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}makeServiceHandler(e){return(t,...n)=>{if(this.started){if(t===`quote`){let t=n[0];if(l(t)){if(t.serviceType!==e)return;let n=this.serviceState.get(e);n&&(n.hasReturnedQuote=!0),this.onIncomingQuote(t),this.scheduleServiceRefresh(e)}return}if(t===`done`){this.onServiceDone(e);return}if(t===`error`){let t=this.serviceState.get(e);t&&(t.hasErrored=!0);let r=n[0];r instanceof Error&&this.emitError(r)}}}}onIncomingQuote(e){let t=this.clock();if(n.isQuoteExpired({quote:e,nowSeconds:t}))return;this.hasReceivedAnyQuote=!0,this.resetRetryForService(e.serviceType),this.quotes=n.sortQuotes(n.pruneExpiredQuotes({quotes:n.upsertQuote(this.quotes,e),nowSeconds:t}));let r=this.quotes[0];r&&(this.lastBestId=r.id,this.emitQuote({bestQuote:r,quote:e,quotes:this.quotes}))}resetRetryForService(e){let t=this.serviceState.get(e);t&&(t.retryAttempt=0,t.retryTimer&&=(clearTimeout(t.retryTimer),null))}scheduleServiceRetry(e){let t=this.serviceState.get(e);if(!t)return;t.retryTimer&&=(clearTimeout(t.retryTimer),null);let n=Math.min(r.QUOTER_EMPTY_RETRY_MAX_DELAY_MS,r.QUOTER_EMPTY_RETRY_BASE_DELAY_MS*2**t.retryAttempt);t.retryAttempt+=1,t.retryTimer=setTimeout(()=>{let t=this.serviceState.get(e);if(t&&(t.retryTimer=null),!this.started)return;let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},Math.max(0,n))}onServiceDone(e){let t=this.serviceState.get(e);t&&(t.done=!0,this.scheduleServiceRefresh(e),this.hasReceivedAnyQuote&&!t.hasErrored&&!t.hasReturnedQuote&&this.scheduleServiceRetry(e),this.maybeCompleteNoQuotes())}maybeCompleteNoQuotes(){!this.started||this.done||this.hasReceivedAnyQuote||this.serviceState.size===0||[...this.serviceState.values()].every(e=>e.done)&&this.complete(`no-quotes`)}complete(e){if(this.done)return;let t=this.makeDonePayload(e);if(this.done=!0,this.stop(),this.isStartingFromSubscribe){queueMicrotask(()=>{let e=[...this.subscribers];this.subscribers.clear();for(let n of e)n(`done`,t)});return}let n=[...this.subscribers];this.subscribers.clear();for(let e of n)e(`done`,t)}getInitializedServices(){return[...new Set(this.transferServices.map(e=>e.type))]}getEligibleServices(){return[...this.serviceState.keys()]}makeDonePayload(e){if(e===`unsubscribed`)return{reason:e,data:void 0};let t=this.getInitializedServices();return e===`no-eligible-services`?{reason:e,data:{initializedServices:t,quoterProps:this.props}}:{reason:e,data:{eligibleServices:this.getEligibleServices(),initializedServices:t,quoterProps:this.props}}}onPruneTick(){let e=this.clock(),t=n.pruneExpiredQuotes({quotes:this.quotes,nowSeconds:e});if(t.length!==this.quotes.length){this.quotes=n.sortQuotes(t);let e=this.quotes[0]??null,r=e?e.id:null;e&&r!==this.lastBestId?(this.lastBestId=r,this.emitQuote({bestQuote:e,quote:e,quotes:this.quotes})):e||(this.lastBestId=null)}for(let e of this.transferServices)this.serviceState.has(e.type)&&this.scheduleServiceRefresh(e.type)}scheduleServiceRefresh(e){let t=this.serviceState.get(e);if(!t)return;let r=this.clock(),i=n.earliestExpirationForService({quotes:this.quotes,serviceType:e,nowSeconds:r});if(i===null){t.refreshTimer&&=(clearTimeout(t.refreshTimer),null),t.refreshAtSeconds=null;return}let a=Math.max(0,i-this.refreshBufferSeconds);if(a<=r||t.refreshAtSeconds===a&&t.refreshTimer)return;t.refreshTimer&&clearTimeout(t.refreshTimer);let o=Math.max(0,(a-r)*1e3);t.refreshAtSeconds=a,t.refreshTimer=setTimeout(()=>{let t=this.serviceState.get(e);t&&(t.refreshTimer=null,t.refreshAtSeconds=null);let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},o)}emitQuote(e){for(let t of this.subscribers)t(`quote`,e)}emitError(e){for(let t of this.subscribers)t(`error`,e)}};const s=i.z.object({id:i.z.string(),expiresAt:i.z.number().int().nonnegative(),amountOut:i.z.bigint().nonnegative(),amountIn:i.z.bigint().nonnegative(),serviceType:i.z.string()});function c(n){let{sourceAsset:r,sourceChain:i,targetAsset:o,targetChain:s}=n;return i.chainId===s.chainId?t.isNativeAsset(r)&&t.isNativeAsset(o)?!0:r.type===o.type?r.type===e.TokenType.ERC20&&o.type===e.TokenType.ERC20?(0,a.isAddressEqual)(r.address,o.address):r.type===e.TokenType.SPL&&o.type===e.TokenType.SPL?r.address===o.address:!1:!1:!1}function l(e){return s.safeParse(e).success}exports.Quoter=o;
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../constants.cjs`),t=require(`../type-guards.cjs`),n=require(`./_utils.cjs`),r=require(`./constants.cjs`);let i=require(`zod`),a=require(`viem`);var o=class{clock;props;pruneIntervalMs;serviceQuoteOptions;transferServices;refreshBufferSeconds;quotes=[];subscribers=new Set;started=!1;done=!1;isStartingFromSubscribe=!1;pruneTimerId=null;lastBestId=null;hasReceivedAnyQuote=!1;id=crypto.randomUUID();serviceState=new Map;constructor(e,t,n={}){this.clock=n.clock??(()=>Math.floor(Date.now()/1e3)),this.props=e,this.pruneIntervalMs=n.pruneIntervalMs??1e3,this.serviceQuoteOptions={dangerouslyAllowAddressMismatch:n.dangerouslyAllowAddressMismatch},this.transferServices=t,this.refreshBufferSeconds=n.refreshBufferSeconds??5}getQuotes(){let e=this.clock(),t=n.sortQuotes(n.pruneExpiredQuotes({quotes:this.quotes,nowSeconds:e}));return[t[0]??null,t]}subscribe(e){this.subscribers.add(e);let t=!1;if(!this.started){this.isStartingFromSubscribe=!0;try{this.start()}finally{this.isStartingFromSubscribe=!1}}return()=>{if(!t){if(t=!0,this.done){this.subscribers.delete(e);return}if(this.subscribers.size===1&&this.subscribers.has(e)){this.complete(`unsubscribed`);return}this.subscribers.delete(e)}}}start(){if(this.started=!0,this.done=!1,this.quotes=[],this.lastBestId=null,this.hasReceivedAnyQuote=!1,c(this.props)){this.complete(`no-eligible-services`);return}let e=!1;for(let t of this.transferServices)t.analyzeSupport({sourceAsset:this.props.sourceAsset,sourceChainId:this.props.sourceChain.chainId,targetAsset:this.props.targetAsset,targetChainId:this.props.targetChain.chainId})&&(e=!0,this.startStreamForService(t));if(!e){this.complete(`no-eligible-services`);return}this.pruneTimerId=setInterval(()=>this.onPruneTick(),this.pruneIntervalMs)}stop(){this.started=!1,this.pruneTimerId&&=(clearInterval(this.pruneTimerId),null);for(let[e,t]of this.serviceState){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer),this.serviceState.set(e,{cancel:()=>{},done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}}startStreamForService(e){let t=this.serviceState.get(e.type);if(t){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer)}let n=this.makeServiceHandler(e.type),r=[],i=!0,{cancel:a}=e.streamQuotes(this.props,(...e)=>{if(i){r.push(e);return}n(...e)},this.serviceQuoteOptions);i=!1,r.length>0&&queueMicrotask(()=>{for(let e of r)n(...e)}),this.serviceState.set(e.type,{cancel:a,done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:t?.retryAttempt??0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}makeServiceHandler(e){return(t,...n)=>{if(this.started){if(t===`quote`){let t=n[0];if(l(t)){if(t.serviceType!==e)return;let n=this.serviceState.get(e);n&&(n.hasReturnedQuote=!0),this.onIncomingQuote(t),this.scheduleServiceRefresh(e)}return}if(t===`done`){this.onServiceDone(e);return}if(t===`error`){let t=this.serviceState.get(e);t&&(t.hasErrored=!0);let r=n[0];r instanceof Error&&this.emitError(r)}}}}onIncomingQuote(e){let t=this.clock();if(n.isQuoteExpired({quote:e,nowSeconds:t}))return;this.hasReceivedAnyQuote=!0,this.resetRetryForService(e.serviceType),this.quotes=n.sortQuotes(n.pruneExpiredQuotes({quotes:n.upsertQuote(this.quotes,e),nowSeconds:t}));let r=this.quotes[0];r&&(this.lastBestId=r.id,this.emitQuote({bestQuote:r,quote:e,quotes:this.quotes}))}resetRetryForService(e){let t=this.serviceState.get(e);t&&(t.retryAttempt=0,t.retryTimer&&=(clearTimeout(t.retryTimer),null))}scheduleServiceRetry(e){let t=this.serviceState.get(e);if(!t)return;t.retryTimer&&=(clearTimeout(t.retryTimer),null);let n=Math.min(r.QUOTER_EMPTY_RETRY_MAX_DELAY_MS,r.QUOTER_EMPTY_RETRY_BASE_DELAY_MS*2**t.retryAttempt);t.retryAttempt+=1,t.retryTimer=setTimeout(()=>{let t=this.serviceState.get(e);if(t&&(t.retryTimer=null),!this.started)return;let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},Math.max(0,n))}onServiceDone(e){let t=this.serviceState.get(e);t&&(t.done=!0,this.scheduleServiceRefresh(e),this.hasReceivedAnyQuote&&!t.hasErrored&&!t.hasReturnedQuote&&this.scheduleServiceRetry(e),this.maybeCompleteNoQuotes())}maybeCompleteNoQuotes(){!this.started||this.done||this.hasReceivedAnyQuote||this.serviceState.size===0||[...this.serviceState.values()].every(e=>e.done)&&this.complete(`no-quotes`)}complete(e){if(this.done)return;let t=this.makeDonePayload(e);if(this.done=!0,this.stop(),this.isStartingFromSubscribe){queueMicrotask(()=>{let e=[...this.subscribers];this.subscribers.clear();for(let n of e)n(`done`,t)});return}let n=[...this.subscribers];this.subscribers.clear();for(let e of n)e(`done`,t)}getInitializedServices(){return[...new Set(this.transferServices.map(e=>e.type))]}getEligibleServices(){return[...this.serviceState.keys()]}makeDonePayload(e){if(e===`unsubscribed`)return{reason:e,data:void 0};let t=this.getInitializedServices();return e===`no-eligible-services`?{reason:e,data:{initializedServices:t,quoterProps:this.props}}:{reason:e,data:{eligibleServices:this.getEligibleServices(),initializedServices:t,quoterProps:this.props}}}onPruneTick(){let e=this.clock(),t=n.pruneExpiredQuotes({quotes:this.quotes,nowSeconds:e});if(t.length!==this.quotes.length){this.quotes=n.sortQuotes(t);let e=this.quotes[0]??null,r=e?e.id:null;e&&r!==this.lastBestId?(this.lastBestId=r,this.emitQuote({bestQuote:e,quote:e,quotes:this.quotes})):e||(this.lastBestId=null)}for(let e of this.transferServices)this.serviceState.has(e.type)&&this.scheduleServiceRefresh(e.type)}scheduleServiceRefresh(e){let t=this.serviceState.get(e);if(!t)return;let r=this.clock(),i=n.earliestExpirationForService({quotes:this.quotes,serviceType:e,nowSeconds:r});if(i===null){t.refreshTimer&&=(clearTimeout(t.refreshTimer),null),t.refreshAtSeconds=null;return}let a=Math.max(0,i-this.refreshBufferSeconds);if(a<=r||t.refreshAtSeconds===a&&t.refreshTimer)return;t.refreshTimer&&clearTimeout(t.refreshTimer);let o=Math.max(0,(a-r)*1e3);t.refreshAtSeconds=a,t.refreshTimer=setTimeout(()=>{let t=this.serviceState.get(e);t&&(t.refreshTimer=null,t.refreshAtSeconds=null);let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},o)}emitQuote(e){for(let t of this.subscribers)t(`quote`,e)}emitError(e){for(let t of this.subscribers)t(`error`,e)}};const s=i.z.object({id:i.z.string(),expiresAt:i.z.number().int().nonnegative(),amountOut:i.z.bigint().nonnegative(),amountIn:i.z.bigint().nonnegative(),serviceType:i.z.string()});function c(n){let{sourceAsset:r,sourceChain:i,targetAsset:o,targetChain:s}=n;return i.chainId===s.chainId?t.isNativeAsset(r)&&t.isNativeAsset(o)?!0:r.type===o.type?r.type===e.TokenType.ERC20&&o.type===e.TokenType.ERC20?(0,a.isAddressEqual)(r.address,o.address):r.type===e.TokenType.SPL&&o.type===e.TokenType.SPL?r.address===o.address:!1:!1:!1}function l(e){return s.safeParse(e).success}exports.Quoter=o;
|
|
2
2
|
//# sourceMappingURL=quoter.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quoter.cjs","names":["sortQuotes","pruneExpiredQuotes","isQuoteExpired","upsertQuote","QUOTER_EMPTY_RETRY_MAX_DELAY_MS","QUOTER_EMPTY_RETRY_BASE_DELAY_MS","earliestExpirationForService","z","isNativeAsset","TokenType"],"sources":["../../src/quoter/quoter.ts"],"sourcesContent":["import { z } from 'zod';\nimport type {\n Quote,\n QuoterDonePayload,\n QuoterDoneReason,\n QuoterEventHandler,\n QuoterInterface,\n QuoterProps,\n ServiceQuoteEventArgs,\n QuotesTuple,\n ServiceQuoteEventHandler,\n} from '../types/quote';\nimport type { TransferService } from '../types/service';\nimport { earliestExpirationForService, isQuoteExpired, pruneExpiredQuotes, sortQuotes, upsertQuote } from './_utils';\nimport {\n QUOTER_DEFAULT_PRUNE_INTERVAL_MS,\n QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS,\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n} from './constants';\nimport { ServiceType, TokenType } from '../constants';\nimport { isNativeAsset } from '../type-guards';\nimport { isAddressEqual } from 'viem';\n\n/**\n * Function that returns the current UNIX time in seconds.\n * Injected for deterministic testing.\n */\nexport type Clock = () => number;\n\n/**\n * Options for constructing a Quoter instance.\n *\n * These options tune how often quote state is maintained and when services are\n * proactively restarted before quote expiry.\n */\nexport interface QuoterOptions {\n /** Clock function returning current time in seconds (defaults to Date.now()/1000). */\n readonly clock?: Clock;\n /** Interval for pruning expired quotes (milliseconds).\n *\n * @default 1_000\n */\n readonly pruneIntervalMs?: number;\n /**\n * Amount of seconds to pre-buffer a stream restart before the earliest service quote\n * expiration. Helps ensure continuity before actual expiry is reached.\n *\n * @default 5\n */\n readonly refreshBufferSeconds?: number;\n}\n\n/**\n * Quoter orchestrates quote streaming across multiple transfer services and emits a unified event stream.\n *\n * High-level lifecycle:\n * - Idle until first subscriber.\n * - On first subscribe, starts all eligible services.\n * - Collects and ranks active quotes, pruning expired ones over time.\n * - Completes when explicitly unsubscribed (last subscriber), when no service is eligible,\n * or when all eligible services finish without producing any quote (`no-quotes`).\n *\n * Event model:\n * - Service `quote` -> Quoter upserts quote, recomputes best quote, emits `quote`.\n * - Service `error` -> Quoter emits `error` (non-terminal by itself).\n * - Service `done` -> Quoter marks that service attempt as completed and evaluates retry/complete rules.\n *\n * Retry/refresh behavior:\n * - Refresh: services that have active quotes are restarted shortly before the earliest quote expires.\n * - Retry: services that complete with no quote and no error are only retried after the quoter has\n * already observed at least one quote from any service in this session.\n *\n * This design keeps first-pass \"no quotes anywhere\" terminal and fast, while still allowing services\n * like Markr to be retried in sessions where other providers are returning quotes.\n */\nexport class Quoter implements QuoterInterface {\n private readonly clock: Clock;\n private readonly props: QuoterProps;\n private readonly pruneIntervalMs: number;\n private readonly transferServices: readonly TransferService[];\n private readonly refreshBufferSeconds: number;\n\n private quotes: Quote[] = [];\n private subscribers: Set<QuoterEventHandler> = new Set();\n private started = false;\n private done = false;\n private isStartingFromSubscribe = false;\n private pruneTimerId: ReturnType<typeof setInterval> | null = null;\n private lastBestId: string | null = null;\n private hasReceivedAnyQuote = false;\n\n public readonly id: string = crypto.randomUUID();\n\n /**\n * Per-service runtime state for the active quote session.\n *\n * This tracks whether a service has completed, errored, produced quotes,\n * and any pending timers used for retry/refresh orchestration.\n */\n private serviceState: Map<\n TransferService['type'],\n {\n cancel: () => void;\n done: boolean;\n hasErrored: boolean;\n hasReturnedQuote: boolean;\n retryAttempt: number;\n retryTimer: ReturnType<typeof setTimeout> | null;\n refreshTimer: ReturnType<typeof setTimeout> | null;\n refreshAtSeconds: number | null;\n }\n > = new Map();\n\n /**\n * Create a new Quoter instance.\n *\n * @param props Quoting request parameters shared across all services.\n * @param transferServices Candidate services; eligibility is resolved at `start()` time.\n * @param options Optional runtime tuning for pruning and refresh behavior.\n */\n constructor(props: QuoterProps, transferServices: readonly TransferService[], options: QuoterOptions = {}) {\n this.clock = options.clock ?? (() => Math.floor(Date.now() / 1_000));\n this.props = props;\n this.pruneIntervalMs = options.pruneIntervalMs ?? QUOTER_DEFAULT_PRUNE_INTERVAL_MS;\n this.transferServices = transferServices;\n this.refreshBufferSeconds = options.refreshBufferSeconds ?? QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS;\n }\n\n /**\n * Get the current best quote and all active quotes (sorted by desirability).\n */\n public getQuotes(): QuotesTuple {\n const now = this.clock();\n const active = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n const sorted = sortQuotes(active);\n const best = sorted[0] ?? null;\n\n return [best, sorted];\n }\n\n /**\n * Subscribe for quoter events.\n *\n * First subscriber lazily starts orchestration.\n * Last subscriber triggers terminal completion with reason `unsubscribed`.\n *\n * @returns Unsubscribe function (idempotent).\n */\n public subscribe(handler: QuoterEventHandler): () => void {\n this.subscribers.add(handler);\n let unsubscribed = false;\n\n if (!this.started) {\n this.isStartingFromSubscribe = true;\n try {\n this.start();\n } finally {\n this.isStartingFromSubscribe = false;\n }\n }\n\n return () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n\n if (this.done) {\n this.subscribers.delete(handler);\n return;\n }\n\n const wasLastSubscriber = this.subscribers.size === 1 && this.subscribers.has(handler);\n if (wasLastSubscriber) {\n this.complete('unsubscribed');\n return;\n }\n\n this.subscribers.delete(handler);\n };\n }\n\n // ----- Internal orchestration -----\n\n /**\n * Start a fresh quote session.\n *\n * Resets session-local state, validates basic request viability,\n * starts streams for eligible services, and begins prune ticks.\n */\n private start(): void {\n this.started = true;\n this.done = false;\n this.quotes = [];\n this.lastBestId = null;\n this.hasReceivedAnyQuote = false;\n\n // Same-chain quotes for the *same* asset are not a valid transfer scenario.\n if (isInvalidSameChainQuoteRequest(this.props)) {\n this.complete('no-eligible-services');\n return;\n }\n\n let hasEligibleService = false;\n\n // Start streams for eligible services\n for (const svc of this.transferServices) {\n const eligible = svc.analyzeSupport({\n sourceAsset: this.props.sourceAsset,\n sourceChainId: this.props.sourceChain.chainId,\n targetAsset: this.props.targetAsset,\n targetChainId: this.props.targetChain.chainId,\n });\n\n if (!eligible) continue;\n hasEligibleService = true;\n\n this.startStreamForService(svc);\n }\n\n if (!hasEligibleService) {\n this.complete('no-eligible-services');\n return;\n }\n\n // Start periodic prune\n this.pruneTimerId = setInterval(() => this.onPruneTick(), this.pruneIntervalMs);\n }\n\n /**\n * Stop all streams and timers.\n * Quotes are retained for snapshot access via getQuotes(), though they\n * can still be pruned due to expiration.\n */\n private stop(): void {\n this.started = false;\n\n if (this.pruneTimerId) {\n clearInterval(this.pruneTimerId);\n this.pruneTimerId = null;\n }\n\n for (const [type, state] of this.serviceState) {\n try {\n state.cancel();\n } catch {\n /* ignore */\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n }\n\n this.serviceState.set(type, {\n cancel: () => {},\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n }\n\n /**\n * Begin or restart streaming for a specific service.\n *\n * Any previous stream/timers for that service are canceled before starting a new attempt.\n */\n private startStreamForService(svc: TransferService): void {\n // Clean up any existing stream and refresh timer\n const existing = this.serviceState.get(svc.type);\n\n if (existing) {\n try {\n existing.cancel();\n } catch {\n /* ignore */\n }\n\n if (existing.refreshTimer) clearTimeout(existing.refreshTimer);\n if (existing.retryTimer) clearTimeout(existing.retryTimer);\n }\n\n const handler = this.makeServiceHandler(svc.type);\n const bufferedEvents: ServiceQuoteEventArgs[] = [];\n let isStartingStream = true;\n\n // Some services emit quote/done synchronously inside streamQuotes().\n // If those events run before subscribe() returns, consumers can hit TDZ\n // when they reference the returned unsubscribe function in their callback.\n // Buffer startup emissions and flush them after stream initialization.\n const guardedHandler: ServiceQuoteEventHandler = (...eventArgs) => {\n if (isStartingStream) {\n bufferedEvents.push(eventArgs);\n return;\n }\n\n handler(...eventArgs);\n };\n\n const { cancel } = svc.streamQuotes(this.props, guardedHandler);\n isStartingStream = false;\n\n if (bufferedEvents.length > 0) {\n // Use a microtask so subscribe() can return before the first callback fires.\n // This preserves event order while avoiding re-entrancy during subscription.\n queueMicrotask(() => {\n for (const bufferedEvent of bufferedEvents) {\n handler(...bufferedEvent);\n }\n });\n }\n\n this.serviceState.set(svc.type, {\n cancel,\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: existing?.retryAttempt ?? 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n\n /**\n * Create the service-scoped event handler consumed by `TransferService.streamQuotes`.\n *\n * The handler enforces service/quote consistency and translates service events into\n * quoter state transitions.\n */\n private makeServiceHandler(serviceType: TransferService['type']): ServiceQuoteEventHandler {\n return (event, ...args) => {\n // Ignore any service events once stopped to prevent post-stop mutations.\n if (!this.started) {\n return;\n }\n if (event === 'quote') {\n const maybeQuote = args[0];\n if (isQuoteValue(maybeQuote)) {\n // Enforce the quote belongs to the emitting service\n if (maybeQuote.serviceType !== serviceType) {\n // Ignore quotes mismatched to service; defensive\n return;\n }\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasReturnedQuote = true;\n }\n this.onIncomingQuote(maybeQuote);\n this.scheduleServiceRefresh(serviceType);\n }\n\n return;\n }\n if (event === 'done') {\n this.onServiceDone(serviceType);\n\n return;\n }\n if (event === 'error') {\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasErrored = true;\n }\n const maybeErr = args[0];\n if (maybeErr instanceof Error) {\n this.emitError(maybeErr);\n }\n }\n };\n }\n\n /**\n * Handle an incoming quote event.\n *\n * Expired quotes are ignored. Valid quotes are upserted into active state,\n * then sorted/pruned and emitted to subscribers.\n */\n private onIncomingQuote(quote: Quote): void {\n const now = this.clock();\n\n if (isQuoteExpired({ quote, nowSeconds: now })) {\n return;\n }\n\n this.hasReceivedAnyQuote = true;\n\n this.resetRetryForService(quote.serviceType);\n\n this.quotes = sortQuotes(pruneExpiredQuotes({ quotes: upsertQuote(this.quotes, quote), nowSeconds: now }));\n\n // Always emit on every incoming quote\n const best = this.quotes[0];\n if (best) {\n this.lastBestId = best.id;\n this.emitQuote({ bestQuote: best, quote, quotes: this.quotes });\n }\n }\n\n private resetRetryForService(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.retryAttempt = 0;\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n }\n\n /**\n * Schedule a retry attempt for a service using exponential backoff.\n *\n * This is used only for quote-less successful completions once the overall session\n * has already produced at least one quote from some service.\n */\n private scheduleServiceRetry(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n\n const delayMs = Math.min(\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS * 2 ** state.retryAttempt,\n );\n state.retryAttempt += 1;\n\n state.retryTimer = setTimeout(\n () => {\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.retryTimer = null;\n }\n\n if (!this.started) {\n return;\n }\n\n const svc = this.transferServices.find((s) => s.type === serviceType);\n if (svc) {\n this.startStreamForService(svc);\n }\n },\n Math.max(0, delayMs),\n );\n }\n\n /**\n * Handle service completion (`done`).\n *\n * A completion may schedule:\n * - refresh (if the service has active quotes), and/or\n * - retry (quote-less/non-error completion, but only after any quote has existed in session).\n *\n * Then evaluates whether the full quoter can complete with `no-quotes`.\n */\n private onServiceDone(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.done = true;\n this.scheduleServiceRefresh(serviceType);\n\n if (this.hasReceivedAnyQuote && !state.hasErrored && !state.hasReturnedQuote) {\n this.scheduleServiceRetry(serviceType);\n }\n\n this.maybeCompleteNoQuotes();\n }\n\n /**\n * Complete with `no-quotes` when every eligible service has finished and\n * no quote was ever observed during this session.\n */\n private maybeCompleteNoQuotes(): void {\n if (!this.started || this.done || this.hasReceivedAnyQuote || this.serviceState.size === 0) {\n return;\n }\n\n const allServicesDone = [...this.serviceState.values()].every((state) => state.done);\n\n if (allServicesDone) {\n this.complete('no-quotes');\n }\n }\n\n /**\n * Finalize the quoter session and broadcast terminal reason exactly once.\n */\n private complete(reason: QuoterDoneReason): void {\n if (this.done) {\n return;\n }\n\n const payload = this.makeDonePayload(reason);\n\n this.done = true;\n this.stop();\n\n // When complete() happens during subscribe()->start(), emit done on a microtask\n // so callers can safely reference the returned unsubscribe in their handler.\n if (this.isStartingFromSubscribe) {\n queueMicrotask(() => {\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n });\n return;\n }\n\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n }\n\n private getInitializedServices(): ServiceType[] {\n return [...new Set(this.transferServices.map((service) => service.type))];\n }\n\n private getEligibleServices(): ServiceType[] {\n return [...this.serviceState.keys()];\n }\n\n private makeDonePayload(reason: QuoterDoneReason): QuoterDonePayload {\n if (reason === 'unsubscribed') {\n return { reason, data: undefined };\n }\n\n const initializedServices = this.getInitializedServices();\n\n if (reason === 'no-eligible-services') {\n return {\n reason,\n data: {\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n return {\n reason,\n data: {\n eligibleServices: this.getEligibleServices(),\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n /** Periodic prune tick to evict expired quotes and emit best changes. */\n private onPruneTick(): void {\n const now = this.clock();\n const pruned = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n\n if (pruned.length !== this.quotes.length) {\n this.quotes = sortQuotes(pruned);\n const best = this.quotes[0] ?? null;\n const bestId = best ? best.id : null;\n\n if (best && bestId !== this.lastBestId) {\n this.lastBestId = bestId;\n this.emitQuote({ bestQuote: best, quote: best, quotes: this.quotes });\n } else if (!best) {\n this.lastBestId = null;\n }\n }\n\n // Re-evaluate refresh scheduling for all services on prune tick\n for (const svc of this.transferServices) {\n if (this.serviceState.has(svc.type)) {\n this.scheduleServiceRefresh(svc.type);\n }\n }\n }\n\n /** Schedule a refresh (restart) of streaming for the service at its earliest quote expiration. */\n private scheduleServiceRefresh(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) return;\n\n const now = this.clock();\n const earliest = earliestExpirationForService({ quotes: this.quotes, serviceType, nowSeconds: now });\n\n if (earliest === null) {\n // No quotes -> clear any pending refresh\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n state.refreshTimer = null;\n }\n state.refreshAtSeconds = null;\n return;\n }\n\n // Only reschedule if earlier than currently scheduled or no timer\n const targetAt = Math.max(0, earliest - this.refreshBufferSeconds);\n\n // If target time has already passed, do not schedule another refresh here.\n if (targetAt <= now) {\n return;\n }\n\n // Reschedule only when the desired refresh time changes (earlier OR later).\n // We key refresh off the soonest-expiring active quote for this service.\n if (state.refreshAtSeconds === targetAt && state.refreshTimer) {\n return;\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n const delayMs = Math.max(0, (targetAt - now) * 1_000);\n state.refreshAtSeconds = targetAt;\n state.refreshTimer = setTimeout(() => {\n // Clear timer/marker before restarting to avoid stale state if restart fails.\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.refreshTimer = null;\n current.refreshAtSeconds = null;\n }\n\n // Restart stream and clear timer/marker\n const svc = this.transferServices.find((s) => s.type === serviceType);\n\n if (svc) {\n this.startStreamForService(svc);\n }\n }, delayMs);\n }\n\n // ----- Emission helpers -----\n /** Emit a quote event to all subscribers. */\n private emitQuote(payload: { bestQuote: Quote; quote: Quote; quotes: readonly Quote[] }): void {\n for (const handler of this.subscribers) {\n handler('quote', payload);\n }\n }\n\n /** Emit an error event to all subscribers. */\n private emitError(error: Error): void {\n for (const handler of this.subscribers) {\n handler('error', error);\n }\n }\n}\n\n/**\n * Zod schema to validate the minimal Quote fields used for runtime gating.\n *\n * This is a simple schema, and is not a fully safe runtime validate for Quote.\n * That isn't necessary here since we only need to verify basic structure before\n * using the quote in internal logic.\n *\n * @internal\n */\nconst QuoteSchema = z.object({\n id: z.string(),\n expiresAt: z.number().int().nonnegative(),\n amountOut: z.bigint().nonnegative(),\n amountIn: z.bigint().nonnegative(),\n serviceType: z.string(),\n});\n\nfunction isInvalidSameChainQuoteRequest(props: QuoterProps): boolean {\n const { sourceAsset, sourceChain, targetAsset, targetChain } = props;\n\n if (sourceChain.chainId !== targetChain.chainId) {\n return false;\n }\n\n // Native -> native on the same chain is a no-op.\n if (isNativeAsset(sourceAsset) && isNativeAsset(targetAsset)) {\n return true;\n }\n\n // If token types differ, it's potentially a swap on the same chain.\n if (sourceAsset.type !== targetAsset.type) {\n return false;\n }\n\n // Same token type and address on the same chain is a no-op.\n if (sourceAsset.type === TokenType.ERC20 && targetAsset.type === TokenType.ERC20) {\n return isAddressEqual(sourceAsset.address, targetAsset.address);\n }\n\n if (sourceAsset.type === TokenType.SPL && targetAsset.type === TokenType.SPL) {\n return sourceAsset.address === targetAsset.address;\n }\n\n return false;\n}\n\nfunction isQuoteValue(value: unknown): value is Quote {\n const result = QuoteSchema.safeParse(value);\n\n return result.success;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"quoter.cjs","names":["sortQuotes","pruneExpiredQuotes","isQuoteExpired","upsertQuote","QUOTER_EMPTY_RETRY_MAX_DELAY_MS","QUOTER_EMPTY_RETRY_BASE_DELAY_MS","earliestExpirationForService","z","isNativeAsset","TokenType"],"sources":["../../src/quoter/quoter.ts"],"sourcesContent":["import { z } from 'zod';\nimport type {\n Quote,\n QuoterDonePayload,\n QuoterDoneReason,\n QuoterEventHandler,\n QuoterInterface,\n QuoterProps,\n ServiceQuoteEventArgs,\n QuotesTuple,\n ServiceQuoteEventHandler,\n} from '../types/quote';\nimport type { ServiceQuoteOptions, TransferService } from '../types/service';\nimport { earliestExpirationForService, isQuoteExpired, pruneExpiredQuotes, sortQuotes, upsertQuote } from './_utils';\nimport {\n QUOTER_DEFAULT_PRUNE_INTERVAL_MS,\n QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS,\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n} from './constants';\nimport { ServiceType, TokenType } from '../constants';\nimport { isNativeAsset } from '../type-guards';\nimport { isAddressEqual } from 'viem';\n\n/**\n * Function that returns the current UNIX time in seconds.\n * Injected for deterministic testing.\n */\nexport type Clock = () => number;\n\n/**\n * Options for constructing a Quoter instance.\n *\n * These options tune how often quote state is maintained and when services are\n * proactively restarted before quote expiry.\n */\nexport interface QuoterOptions {\n /**\n * Dangerously allows cross-chain quotes where fromAddress and toAddress differ\n * even when a service would otherwise require them to match.\n *\n * This is intended for explicit proxy-recipient funding flows. By default,\n * services preserve their existing address safety checks.\n */\n readonly dangerouslyAllowAddressMismatch?: boolean;\n /** Clock function returning current time in seconds (defaults to Date.now()/1000). */\n readonly clock?: Clock;\n /** Interval for pruning expired quotes (milliseconds).\n *\n * @default 1_000\n */\n readonly pruneIntervalMs?: number;\n /**\n * Amount of seconds to pre-buffer a stream restart before the earliest service quote\n * expiration. Helps ensure continuity before actual expiry is reached.\n *\n * @default 5\n */\n readonly refreshBufferSeconds?: number;\n}\n\n/**\n * Quoter orchestrates quote streaming across multiple transfer services and emits a unified event stream.\n *\n * High-level lifecycle:\n * - Idle until first subscriber.\n * - On first subscribe, starts all eligible services.\n * - Collects and ranks active quotes, pruning expired ones over time.\n * - Completes when explicitly unsubscribed (last subscriber), when no service is eligible,\n * or when all eligible services finish without producing any quote (`no-quotes`).\n *\n * Event model:\n * - Service `quote` -> Quoter upserts quote, recomputes best quote, emits `quote`.\n * - Service `error` -> Quoter emits `error` (non-terminal by itself).\n * - Service `done` -> Quoter marks that service attempt as completed and evaluates retry/complete rules.\n *\n * Retry/refresh behavior:\n * - Refresh: services that have active quotes are restarted shortly before the earliest quote expires.\n * - Retry: services that complete with no quote and no error are only retried after the quoter has\n * already observed at least one quote from any service in this session.\n *\n * This design keeps first-pass \"no quotes anywhere\" terminal and fast, while still allowing services\n * like Markr to be retried in sessions where other providers are returning quotes.\n */\nexport class Quoter implements QuoterInterface {\n private readonly clock: Clock;\n private readonly props: QuoterProps;\n private readonly pruneIntervalMs: number;\n private readonly serviceQuoteOptions: ServiceQuoteOptions;\n private readonly transferServices: readonly TransferService[];\n private readonly refreshBufferSeconds: number;\n\n private quotes: Quote[] = [];\n private subscribers: Set<QuoterEventHandler> = new Set();\n private started = false;\n private done = false;\n private isStartingFromSubscribe = false;\n private pruneTimerId: ReturnType<typeof setInterval> | null = null;\n private lastBestId: string | null = null;\n private hasReceivedAnyQuote = false;\n\n public readonly id: string = crypto.randomUUID();\n\n /**\n * Per-service runtime state for the active quote session.\n *\n * This tracks whether a service has completed, errored, produced quotes,\n * and any pending timers used for retry/refresh orchestration.\n */\n private serviceState: Map<\n TransferService['type'],\n {\n cancel: () => void;\n done: boolean;\n hasErrored: boolean;\n hasReturnedQuote: boolean;\n retryAttempt: number;\n retryTimer: ReturnType<typeof setTimeout> | null;\n refreshTimer: ReturnType<typeof setTimeout> | null;\n refreshAtSeconds: number | null;\n }\n > = new Map();\n\n /**\n * Create a new Quoter instance.\n *\n * @param props Quoting request parameters shared across all services.\n * @param transferServices Candidate services; eligibility is resolved at `start()` time.\n * @param options Optional runtime tuning for pruning and refresh behavior.\n */\n constructor(props: QuoterProps, transferServices: readonly TransferService[], options: QuoterOptions = {}) {\n this.clock = options.clock ?? (() => Math.floor(Date.now() / 1_000));\n this.props = props;\n this.pruneIntervalMs = options.pruneIntervalMs ?? QUOTER_DEFAULT_PRUNE_INTERVAL_MS;\n this.serviceQuoteOptions = {\n dangerouslyAllowAddressMismatch: options.dangerouslyAllowAddressMismatch,\n };\n this.transferServices = transferServices;\n this.refreshBufferSeconds = options.refreshBufferSeconds ?? QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS;\n }\n\n /**\n * Get the current best quote and all active quotes (sorted by desirability).\n */\n public getQuotes(): QuotesTuple {\n const now = this.clock();\n const active = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n const sorted = sortQuotes(active);\n const best = sorted[0] ?? null;\n\n return [best, sorted];\n }\n\n /**\n * Subscribe for quoter events.\n *\n * First subscriber lazily starts orchestration.\n * Last subscriber triggers terminal completion with reason `unsubscribed`.\n *\n * @returns Unsubscribe function (idempotent).\n */\n public subscribe(handler: QuoterEventHandler): () => void {\n this.subscribers.add(handler);\n let unsubscribed = false;\n\n if (!this.started) {\n this.isStartingFromSubscribe = true;\n try {\n this.start();\n } finally {\n this.isStartingFromSubscribe = false;\n }\n }\n\n return () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n\n if (this.done) {\n this.subscribers.delete(handler);\n return;\n }\n\n const wasLastSubscriber = this.subscribers.size === 1 && this.subscribers.has(handler);\n if (wasLastSubscriber) {\n this.complete('unsubscribed');\n return;\n }\n\n this.subscribers.delete(handler);\n };\n }\n\n // ----- Internal orchestration -----\n\n /**\n * Start a fresh quote session.\n *\n * Resets session-local state, validates basic request viability,\n * starts streams for eligible services, and begins prune ticks.\n */\n private start(): void {\n this.started = true;\n this.done = false;\n this.quotes = [];\n this.lastBestId = null;\n this.hasReceivedAnyQuote = false;\n\n // Same-chain quotes for the *same* asset are not a valid transfer scenario.\n if (isInvalidSameChainQuoteRequest(this.props)) {\n this.complete('no-eligible-services');\n return;\n }\n\n let hasEligibleService = false;\n\n // Start streams for eligible services\n for (const svc of this.transferServices) {\n const eligible = svc.analyzeSupport({\n sourceAsset: this.props.sourceAsset,\n sourceChainId: this.props.sourceChain.chainId,\n targetAsset: this.props.targetAsset,\n targetChainId: this.props.targetChain.chainId,\n });\n\n if (!eligible) continue;\n hasEligibleService = true;\n\n this.startStreamForService(svc);\n }\n\n if (!hasEligibleService) {\n this.complete('no-eligible-services');\n return;\n }\n\n // Start periodic prune\n this.pruneTimerId = setInterval(() => this.onPruneTick(), this.pruneIntervalMs);\n }\n\n /**\n * Stop all streams and timers.\n * Quotes are retained for snapshot access via getQuotes(), though they\n * can still be pruned due to expiration.\n */\n private stop(): void {\n this.started = false;\n\n if (this.pruneTimerId) {\n clearInterval(this.pruneTimerId);\n this.pruneTimerId = null;\n }\n\n for (const [type, state] of this.serviceState) {\n try {\n state.cancel();\n } catch {\n /* ignore */\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n }\n\n this.serviceState.set(type, {\n cancel: () => {},\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n }\n\n /**\n * Begin or restart streaming for a specific service.\n *\n * Any previous stream/timers for that service are canceled before starting a new attempt.\n */\n private startStreamForService(svc: TransferService): void {\n // Clean up any existing stream and refresh timer\n const existing = this.serviceState.get(svc.type);\n\n if (existing) {\n try {\n existing.cancel();\n } catch {\n /* ignore */\n }\n\n if (existing.refreshTimer) clearTimeout(existing.refreshTimer);\n if (existing.retryTimer) clearTimeout(existing.retryTimer);\n }\n\n const handler = this.makeServiceHandler(svc.type);\n const bufferedEvents: ServiceQuoteEventArgs[] = [];\n let isStartingStream = true;\n\n // Some services emit quote/done synchronously inside streamQuotes().\n // If those events run before subscribe() returns, consumers can hit TDZ\n // when they reference the returned unsubscribe function in their callback.\n // Buffer startup emissions and flush them after stream initialization.\n const guardedHandler: ServiceQuoteEventHandler = (...eventArgs) => {\n if (isStartingStream) {\n bufferedEvents.push(eventArgs);\n return;\n }\n\n handler(...eventArgs);\n };\n\n const { cancel } = svc.streamQuotes(this.props, guardedHandler, this.serviceQuoteOptions);\n isStartingStream = false;\n\n if (bufferedEvents.length > 0) {\n // Use a microtask so subscribe() can return before the first callback fires.\n // This preserves event order while avoiding re-entrancy during subscription.\n queueMicrotask(() => {\n for (const bufferedEvent of bufferedEvents) {\n handler(...bufferedEvent);\n }\n });\n }\n\n this.serviceState.set(svc.type, {\n cancel,\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: existing?.retryAttempt ?? 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n\n /**\n * Create the service-scoped event handler consumed by `TransferService.streamQuotes`.\n *\n * The handler enforces service/quote consistency and translates service events into\n * quoter state transitions.\n */\n private makeServiceHandler(serviceType: TransferService['type']): ServiceQuoteEventHandler {\n return (event, ...args) => {\n // Ignore any service events once stopped to prevent post-stop mutations.\n if (!this.started) {\n return;\n }\n if (event === 'quote') {\n const maybeQuote = args[0];\n if (isQuoteValue(maybeQuote)) {\n // Enforce the quote belongs to the emitting service\n if (maybeQuote.serviceType !== serviceType) {\n // Ignore quotes mismatched to service; defensive\n return;\n }\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasReturnedQuote = true;\n }\n this.onIncomingQuote(maybeQuote);\n this.scheduleServiceRefresh(serviceType);\n }\n\n return;\n }\n if (event === 'done') {\n this.onServiceDone(serviceType);\n\n return;\n }\n if (event === 'error') {\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasErrored = true;\n }\n const maybeErr = args[0];\n if (maybeErr instanceof Error) {\n this.emitError(maybeErr);\n }\n }\n };\n }\n\n /**\n * Handle an incoming quote event.\n *\n * Expired quotes are ignored. Valid quotes are upserted into active state,\n * then sorted/pruned and emitted to subscribers.\n */\n private onIncomingQuote(quote: Quote): void {\n const now = this.clock();\n\n if (isQuoteExpired({ quote, nowSeconds: now })) {\n return;\n }\n\n this.hasReceivedAnyQuote = true;\n\n this.resetRetryForService(quote.serviceType);\n\n this.quotes = sortQuotes(pruneExpiredQuotes({ quotes: upsertQuote(this.quotes, quote), nowSeconds: now }));\n\n // Always emit on every incoming quote\n const best = this.quotes[0];\n if (best) {\n this.lastBestId = best.id;\n this.emitQuote({ bestQuote: best, quote, quotes: this.quotes });\n }\n }\n\n private resetRetryForService(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.retryAttempt = 0;\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n }\n\n /**\n * Schedule a retry attempt for a service using exponential backoff.\n *\n * This is used only for quote-less successful completions once the overall session\n * has already produced at least one quote from some service.\n */\n private scheduleServiceRetry(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n\n const delayMs = Math.min(\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS * 2 ** state.retryAttempt,\n );\n state.retryAttempt += 1;\n\n state.retryTimer = setTimeout(\n () => {\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.retryTimer = null;\n }\n\n if (!this.started) {\n return;\n }\n\n const svc = this.transferServices.find((s) => s.type === serviceType);\n if (svc) {\n this.startStreamForService(svc);\n }\n },\n Math.max(0, delayMs),\n );\n }\n\n /**\n * Handle service completion (`done`).\n *\n * A completion may schedule:\n * - refresh (if the service has active quotes), and/or\n * - retry (quote-less/non-error completion, but only after any quote has existed in session).\n *\n * Then evaluates whether the full quoter can complete with `no-quotes`.\n */\n private onServiceDone(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.done = true;\n this.scheduleServiceRefresh(serviceType);\n\n if (this.hasReceivedAnyQuote && !state.hasErrored && !state.hasReturnedQuote) {\n this.scheduleServiceRetry(serviceType);\n }\n\n this.maybeCompleteNoQuotes();\n }\n\n /**\n * Complete with `no-quotes` when every eligible service has finished and\n * no quote was ever observed during this session.\n */\n private maybeCompleteNoQuotes(): void {\n if (!this.started || this.done || this.hasReceivedAnyQuote || this.serviceState.size === 0) {\n return;\n }\n\n const allServicesDone = [...this.serviceState.values()].every((state) => state.done);\n\n if (allServicesDone) {\n this.complete('no-quotes');\n }\n }\n\n /**\n * Finalize the quoter session and broadcast terminal reason exactly once.\n */\n private complete(reason: QuoterDoneReason): void {\n if (this.done) {\n return;\n }\n\n const payload = this.makeDonePayload(reason);\n\n this.done = true;\n this.stop();\n\n // When complete() happens during subscribe()->start(), emit done on a microtask\n // so callers can safely reference the returned unsubscribe in their handler.\n if (this.isStartingFromSubscribe) {\n queueMicrotask(() => {\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n });\n return;\n }\n\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n }\n\n private getInitializedServices(): ServiceType[] {\n return [...new Set(this.transferServices.map((service) => service.type))];\n }\n\n private getEligibleServices(): ServiceType[] {\n return [...this.serviceState.keys()];\n }\n\n private makeDonePayload(reason: QuoterDoneReason): QuoterDonePayload {\n if (reason === 'unsubscribed') {\n return { reason, data: undefined };\n }\n\n const initializedServices = this.getInitializedServices();\n\n if (reason === 'no-eligible-services') {\n return {\n reason,\n data: {\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n return {\n reason,\n data: {\n eligibleServices: this.getEligibleServices(),\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n /** Periodic prune tick to evict expired quotes and emit best changes. */\n private onPruneTick(): void {\n const now = this.clock();\n const pruned = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n\n if (pruned.length !== this.quotes.length) {\n this.quotes = sortQuotes(pruned);\n const best = this.quotes[0] ?? null;\n const bestId = best ? best.id : null;\n\n if (best && bestId !== this.lastBestId) {\n this.lastBestId = bestId;\n this.emitQuote({ bestQuote: best, quote: best, quotes: this.quotes });\n } else if (!best) {\n this.lastBestId = null;\n }\n }\n\n // Re-evaluate refresh scheduling for all services on prune tick\n for (const svc of this.transferServices) {\n if (this.serviceState.has(svc.type)) {\n this.scheduleServiceRefresh(svc.type);\n }\n }\n }\n\n /** Schedule a refresh (restart) of streaming for the service at its earliest quote expiration. */\n private scheduleServiceRefresh(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) return;\n\n const now = this.clock();\n const earliest = earliestExpirationForService({ quotes: this.quotes, serviceType, nowSeconds: now });\n\n if (earliest === null) {\n // No quotes -> clear any pending refresh\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n state.refreshTimer = null;\n }\n state.refreshAtSeconds = null;\n return;\n }\n\n // Only reschedule if earlier than currently scheduled or no timer\n const targetAt = Math.max(0, earliest - this.refreshBufferSeconds);\n\n // If target time has already passed, do not schedule another refresh here.\n if (targetAt <= now) {\n return;\n }\n\n // Reschedule only when the desired refresh time changes (earlier OR later).\n // We key refresh off the soonest-expiring active quote for this service.\n if (state.refreshAtSeconds === targetAt && state.refreshTimer) {\n return;\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n const delayMs = Math.max(0, (targetAt - now) * 1_000);\n state.refreshAtSeconds = targetAt;\n state.refreshTimer = setTimeout(() => {\n // Clear timer/marker before restarting to avoid stale state if restart fails.\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.refreshTimer = null;\n current.refreshAtSeconds = null;\n }\n\n // Restart stream and clear timer/marker\n const svc = this.transferServices.find((s) => s.type === serviceType);\n\n if (svc) {\n this.startStreamForService(svc);\n }\n }, delayMs);\n }\n\n // ----- Emission helpers -----\n /** Emit a quote event to all subscribers. */\n private emitQuote(payload: { bestQuote: Quote; quote: Quote; quotes: readonly Quote[] }): void {\n for (const handler of this.subscribers) {\n handler('quote', payload);\n }\n }\n\n /** Emit an error event to all subscribers. */\n private emitError(error: Error): void {\n for (const handler of this.subscribers) {\n handler('error', error);\n }\n }\n}\n\n/**\n * Zod schema to validate the minimal Quote fields used for runtime gating.\n *\n * This is a simple schema, and is not a fully safe runtime validate for Quote.\n * That isn't necessary here since we only need to verify basic structure before\n * using the quote in internal logic.\n *\n * @internal\n */\nconst QuoteSchema = z.object({\n id: z.string(),\n expiresAt: z.number().int().nonnegative(),\n amountOut: z.bigint().nonnegative(),\n amountIn: z.bigint().nonnegative(),\n serviceType: z.string(),\n});\n\nfunction isInvalidSameChainQuoteRequest(props: QuoterProps): boolean {\n const { sourceAsset, sourceChain, targetAsset, targetChain } = props;\n\n if (sourceChain.chainId !== targetChain.chainId) {\n return false;\n }\n\n // Native -> native on the same chain is a no-op.\n if (isNativeAsset(sourceAsset) && isNativeAsset(targetAsset)) {\n return true;\n }\n\n // If token types differ, it's potentially a swap on the same chain.\n if (sourceAsset.type !== targetAsset.type) {\n return false;\n }\n\n // Same token type and address on the same chain is a no-op.\n if (sourceAsset.type === TokenType.ERC20 && targetAsset.type === TokenType.ERC20) {\n return isAddressEqual(sourceAsset.address, targetAsset.address);\n }\n\n if (sourceAsset.type === TokenType.SPL && targetAsset.type === TokenType.SPL) {\n return sourceAsset.address === targetAsset.address;\n }\n\n return false;\n}\n\nfunction isQuoteValue(value: unknown): value is Quote {\n const result = QuoteSchema.safeParse(value);\n\n return result.success;\n}\n"],"mappings":"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"}
|
package/dist/quoter/quoter.d.cts
CHANGED
|
@@ -11,6 +11,14 @@ type Clock = () => number;
|
|
|
11
11
|
* proactively restarted before quote expiry.
|
|
12
12
|
*/
|
|
13
13
|
interface QuoterOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Dangerously allows cross-chain quotes where fromAddress and toAddress differ
|
|
16
|
+
* even when a service would otherwise require them to match.
|
|
17
|
+
*
|
|
18
|
+
* This is intended for explicit proxy-recipient funding flows. By default,
|
|
19
|
+
* services preserve their existing address safety checks.
|
|
20
|
+
*/
|
|
21
|
+
readonly dangerouslyAllowAddressMismatch?: boolean;
|
|
14
22
|
/** Clock function returning current time in seconds (defaults to Date.now()/1000). */
|
|
15
23
|
readonly clock?: Clock;
|
|
16
24
|
/** Interval for pruning expired quotes (milliseconds).
|
package/dist/quoter/quoter.d.ts
CHANGED
|
@@ -11,6 +11,14 @@ type Clock = () => number;
|
|
|
11
11
|
* proactively restarted before quote expiry.
|
|
12
12
|
*/
|
|
13
13
|
interface QuoterOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Dangerously allows cross-chain quotes where fromAddress and toAddress differ
|
|
16
|
+
* even when a service would otherwise require them to match.
|
|
17
|
+
*
|
|
18
|
+
* This is intended for explicit proxy-recipient funding flows. By default,
|
|
19
|
+
* services preserve their existing address safety checks.
|
|
20
|
+
*/
|
|
21
|
+
readonly dangerouslyAllowAddressMismatch?: boolean;
|
|
14
22
|
/** Clock function returning current time in seconds (defaults to Date.now()/1000). */
|
|
15
23
|
readonly clock?: Clock;
|
|
16
24
|
/** Interval for pruning expired quotes (milliseconds).
|
package/dist/quoter/quoter.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{TokenType as e}from"../constants.js";import{isNativeAsset as t}from"../type-guards.js";import{earliestExpirationForService as n,isQuoteExpired as r,pruneExpiredQuotes as i,sortQuotes as a,upsertQuote as o}from"./_utils.js";import{QUOTER_EMPTY_RETRY_BASE_DELAY_MS as s,QUOTER_EMPTY_RETRY_MAX_DELAY_MS as c}from"./constants.js";import{z as l}from"zod";import{isAddressEqual as u}from"viem";var d=class{clock;props;pruneIntervalMs;transferServices;refreshBufferSeconds;quotes=[];subscribers=new Set;started=!1;done=!1;isStartingFromSubscribe=!1;pruneTimerId=null;lastBestId=null;hasReceivedAnyQuote=!1;id=crypto.randomUUID();serviceState=new Map;constructor(e,t,n={}){this.clock=n.clock??(()=>Math.floor(Date.now()/1e3)),this.props=e,this.pruneIntervalMs=n.pruneIntervalMs??1e3,this.transferServices=t,this.refreshBufferSeconds=n.refreshBufferSeconds??5}getQuotes(){let e=this.clock(),t=a(i({quotes:this.quotes,nowSeconds:e}));return[t[0]??null,t]}subscribe(e){this.subscribers.add(e);let t=!1;if(!this.started){this.isStartingFromSubscribe=!0;try{this.start()}finally{this.isStartingFromSubscribe=!1}}return()=>{if(!t){if(t=!0,this.done){this.subscribers.delete(e);return}if(this.subscribers.size===1&&this.subscribers.has(e)){this.complete(`unsubscribed`);return}this.subscribers.delete(e)}}}start(){if(this.started=!0,this.done=!1,this.quotes=[],this.lastBestId=null,this.hasReceivedAnyQuote=!1,p(this.props)){this.complete(`no-eligible-services`);return}let e=!1;for(let t of this.transferServices)t.analyzeSupport({sourceAsset:this.props.sourceAsset,sourceChainId:this.props.sourceChain.chainId,targetAsset:this.props.targetAsset,targetChainId:this.props.targetChain.chainId})&&(e=!0,this.startStreamForService(t));if(!e){this.complete(`no-eligible-services`);return}this.pruneTimerId=setInterval(()=>this.onPruneTick(),this.pruneIntervalMs)}stop(){this.started=!1,this.pruneTimerId&&=(clearInterval(this.pruneTimerId),null);for(let[e,t]of this.serviceState){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer),this.serviceState.set(e,{cancel:()=>{},done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}}startStreamForService(e){let t=this.serviceState.get(e.type);if(t){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer)}let n=this.makeServiceHandler(e.type),r=[],i=!0,{cancel:a}=e.streamQuotes(this.props,(...e)=>{if(i){r.push(e);return}n(...e)});i=!1,r.length>0&&queueMicrotask(()=>{for(let e of r)n(...e)}),this.serviceState.set(e.type,{cancel:a,done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:t?.retryAttempt??0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}makeServiceHandler(e){return(t,...n)=>{if(this.started){if(t===`quote`){let t=n[0];if(m(t)){if(t.serviceType!==e)return;let n=this.serviceState.get(e);n&&(n.hasReturnedQuote=!0),this.onIncomingQuote(t),this.scheduleServiceRefresh(e)}return}if(t===`done`){this.onServiceDone(e);return}if(t===`error`){let t=this.serviceState.get(e);t&&(t.hasErrored=!0);let r=n[0];r instanceof Error&&this.emitError(r)}}}}onIncomingQuote(e){let t=this.clock();if(r({quote:e,nowSeconds:t}))return;this.hasReceivedAnyQuote=!0,this.resetRetryForService(e.serviceType),this.quotes=a(i({quotes:o(this.quotes,e),nowSeconds:t}));let n=this.quotes[0];n&&(this.lastBestId=n.id,this.emitQuote({bestQuote:n,quote:e,quotes:this.quotes}))}resetRetryForService(e){let t=this.serviceState.get(e);t&&(t.retryAttempt=0,t.retryTimer&&=(clearTimeout(t.retryTimer),null))}scheduleServiceRetry(e){let t=this.serviceState.get(e);if(!t)return;t.retryTimer&&=(clearTimeout(t.retryTimer),null);let n=Math.min(c,s*2**t.retryAttempt);t.retryAttempt+=1,t.retryTimer=setTimeout(()=>{let t=this.serviceState.get(e);if(t&&(t.retryTimer=null),!this.started)return;let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},Math.max(0,n))}onServiceDone(e){let t=this.serviceState.get(e);t&&(t.done=!0,this.scheduleServiceRefresh(e),this.hasReceivedAnyQuote&&!t.hasErrored&&!t.hasReturnedQuote&&this.scheduleServiceRetry(e),this.maybeCompleteNoQuotes())}maybeCompleteNoQuotes(){!this.started||this.done||this.hasReceivedAnyQuote||this.serviceState.size===0||[...this.serviceState.values()].every(e=>e.done)&&this.complete(`no-quotes`)}complete(e){if(this.done)return;let t=this.makeDonePayload(e);if(this.done=!0,this.stop(),this.isStartingFromSubscribe){queueMicrotask(()=>{let e=[...this.subscribers];this.subscribers.clear();for(let n of e)n(`done`,t)});return}let n=[...this.subscribers];this.subscribers.clear();for(let e of n)e(`done`,t)}getInitializedServices(){return[...new Set(this.transferServices.map(e=>e.type))]}getEligibleServices(){return[...this.serviceState.keys()]}makeDonePayload(e){if(e===`unsubscribed`)return{reason:e,data:void 0};let t=this.getInitializedServices();return e===`no-eligible-services`?{reason:e,data:{initializedServices:t,quoterProps:this.props}}:{reason:e,data:{eligibleServices:this.getEligibleServices(),initializedServices:t,quoterProps:this.props}}}onPruneTick(){let e=this.clock(),t=i({quotes:this.quotes,nowSeconds:e});if(t.length!==this.quotes.length){this.quotes=a(t);let e=this.quotes[0]??null,n=e?e.id:null;e&&n!==this.lastBestId?(this.lastBestId=n,this.emitQuote({bestQuote:e,quote:e,quotes:this.quotes})):e||(this.lastBestId=null)}for(let e of this.transferServices)this.serviceState.has(e.type)&&this.scheduleServiceRefresh(e.type)}scheduleServiceRefresh(e){let t=this.serviceState.get(e);if(!t)return;let r=this.clock(),i=n({quotes:this.quotes,serviceType:e,nowSeconds:r});if(i===null){t.refreshTimer&&=(clearTimeout(t.refreshTimer),null),t.refreshAtSeconds=null;return}let a=Math.max(0,i-this.refreshBufferSeconds);if(a<=r||t.refreshAtSeconds===a&&t.refreshTimer)return;t.refreshTimer&&clearTimeout(t.refreshTimer);let o=Math.max(0,(a-r)*1e3);t.refreshAtSeconds=a,t.refreshTimer=setTimeout(()=>{let t=this.serviceState.get(e);t&&(t.refreshTimer=null,t.refreshAtSeconds=null);let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},o)}emitQuote(e){for(let t of this.subscribers)t(`quote`,e)}emitError(e){for(let t of this.subscribers)t(`error`,e)}};const f=l.object({id:l.string(),expiresAt:l.number().int().nonnegative(),amountOut:l.bigint().nonnegative(),amountIn:l.bigint().nonnegative(),serviceType:l.string()});function p(n){let{sourceAsset:r,sourceChain:i,targetAsset:a,targetChain:o}=n;return i.chainId===o.chainId?t(r)&&t(a)?!0:r.type===a.type?r.type===e.ERC20&&a.type===e.ERC20?u(r.address,a.address):r.type===e.SPL&&a.type===e.SPL?r.address===a.address:!1:!1:!1}function m(e){return f.safeParse(e).success}export{d as Quoter};
|
|
1
|
+
import{TokenType as e}from"../constants.js";import{isNativeAsset as t}from"../type-guards.js";import{earliestExpirationForService as n,isQuoteExpired as r,pruneExpiredQuotes as i,sortQuotes as a,upsertQuote as o}from"./_utils.js";import{QUOTER_EMPTY_RETRY_BASE_DELAY_MS as s,QUOTER_EMPTY_RETRY_MAX_DELAY_MS as c}from"./constants.js";import{z as l}from"zod";import{isAddressEqual as u}from"viem";var d=class{clock;props;pruneIntervalMs;serviceQuoteOptions;transferServices;refreshBufferSeconds;quotes=[];subscribers=new Set;started=!1;done=!1;isStartingFromSubscribe=!1;pruneTimerId=null;lastBestId=null;hasReceivedAnyQuote=!1;id=crypto.randomUUID();serviceState=new Map;constructor(e,t,n={}){this.clock=n.clock??(()=>Math.floor(Date.now()/1e3)),this.props=e,this.pruneIntervalMs=n.pruneIntervalMs??1e3,this.serviceQuoteOptions={dangerouslyAllowAddressMismatch:n.dangerouslyAllowAddressMismatch},this.transferServices=t,this.refreshBufferSeconds=n.refreshBufferSeconds??5}getQuotes(){let e=this.clock(),t=a(i({quotes:this.quotes,nowSeconds:e}));return[t[0]??null,t]}subscribe(e){this.subscribers.add(e);let t=!1;if(!this.started){this.isStartingFromSubscribe=!0;try{this.start()}finally{this.isStartingFromSubscribe=!1}}return()=>{if(!t){if(t=!0,this.done){this.subscribers.delete(e);return}if(this.subscribers.size===1&&this.subscribers.has(e)){this.complete(`unsubscribed`);return}this.subscribers.delete(e)}}}start(){if(this.started=!0,this.done=!1,this.quotes=[],this.lastBestId=null,this.hasReceivedAnyQuote=!1,p(this.props)){this.complete(`no-eligible-services`);return}let e=!1;for(let t of this.transferServices)t.analyzeSupport({sourceAsset:this.props.sourceAsset,sourceChainId:this.props.sourceChain.chainId,targetAsset:this.props.targetAsset,targetChainId:this.props.targetChain.chainId})&&(e=!0,this.startStreamForService(t));if(!e){this.complete(`no-eligible-services`);return}this.pruneTimerId=setInterval(()=>this.onPruneTick(),this.pruneIntervalMs)}stop(){this.started=!1,this.pruneTimerId&&=(clearInterval(this.pruneTimerId),null);for(let[e,t]of this.serviceState){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer),this.serviceState.set(e,{cancel:()=>{},done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}}startStreamForService(e){let t=this.serviceState.get(e.type);if(t){try{t.cancel()}catch{}t.refreshTimer&&clearTimeout(t.refreshTimer),t.retryTimer&&clearTimeout(t.retryTimer)}let n=this.makeServiceHandler(e.type),r=[],i=!0,{cancel:a}=e.streamQuotes(this.props,(...e)=>{if(i){r.push(e);return}n(...e)},this.serviceQuoteOptions);i=!1,r.length>0&&queueMicrotask(()=>{for(let e of r)n(...e)}),this.serviceState.set(e.type,{cancel:a,done:!1,hasErrored:!1,hasReturnedQuote:!1,retryAttempt:t?.retryAttempt??0,retryTimer:null,refreshTimer:null,refreshAtSeconds:null})}makeServiceHandler(e){return(t,...n)=>{if(this.started){if(t===`quote`){let t=n[0];if(m(t)){if(t.serviceType!==e)return;let n=this.serviceState.get(e);n&&(n.hasReturnedQuote=!0),this.onIncomingQuote(t),this.scheduleServiceRefresh(e)}return}if(t===`done`){this.onServiceDone(e);return}if(t===`error`){let t=this.serviceState.get(e);t&&(t.hasErrored=!0);let r=n[0];r instanceof Error&&this.emitError(r)}}}}onIncomingQuote(e){let t=this.clock();if(r({quote:e,nowSeconds:t}))return;this.hasReceivedAnyQuote=!0,this.resetRetryForService(e.serviceType),this.quotes=a(i({quotes:o(this.quotes,e),nowSeconds:t}));let n=this.quotes[0];n&&(this.lastBestId=n.id,this.emitQuote({bestQuote:n,quote:e,quotes:this.quotes}))}resetRetryForService(e){let t=this.serviceState.get(e);t&&(t.retryAttempt=0,t.retryTimer&&=(clearTimeout(t.retryTimer),null))}scheduleServiceRetry(e){let t=this.serviceState.get(e);if(!t)return;t.retryTimer&&=(clearTimeout(t.retryTimer),null);let n=Math.min(c,s*2**t.retryAttempt);t.retryAttempt+=1,t.retryTimer=setTimeout(()=>{let t=this.serviceState.get(e);if(t&&(t.retryTimer=null),!this.started)return;let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},Math.max(0,n))}onServiceDone(e){let t=this.serviceState.get(e);t&&(t.done=!0,this.scheduleServiceRefresh(e),this.hasReceivedAnyQuote&&!t.hasErrored&&!t.hasReturnedQuote&&this.scheduleServiceRetry(e),this.maybeCompleteNoQuotes())}maybeCompleteNoQuotes(){!this.started||this.done||this.hasReceivedAnyQuote||this.serviceState.size===0||[...this.serviceState.values()].every(e=>e.done)&&this.complete(`no-quotes`)}complete(e){if(this.done)return;let t=this.makeDonePayload(e);if(this.done=!0,this.stop(),this.isStartingFromSubscribe){queueMicrotask(()=>{let e=[...this.subscribers];this.subscribers.clear();for(let n of e)n(`done`,t)});return}let n=[...this.subscribers];this.subscribers.clear();for(let e of n)e(`done`,t)}getInitializedServices(){return[...new Set(this.transferServices.map(e=>e.type))]}getEligibleServices(){return[...this.serviceState.keys()]}makeDonePayload(e){if(e===`unsubscribed`)return{reason:e,data:void 0};let t=this.getInitializedServices();return e===`no-eligible-services`?{reason:e,data:{initializedServices:t,quoterProps:this.props}}:{reason:e,data:{eligibleServices:this.getEligibleServices(),initializedServices:t,quoterProps:this.props}}}onPruneTick(){let e=this.clock(),t=i({quotes:this.quotes,nowSeconds:e});if(t.length!==this.quotes.length){this.quotes=a(t);let e=this.quotes[0]??null,n=e?e.id:null;e&&n!==this.lastBestId?(this.lastBestId=n,this.emitQuote({bestQuote:e,quote:e,quotes:this.quotes})):e||(this.lastBestId=null)}for(let e of this.transferServices)this.serviceState.has(e.type)&&this.scheduleServiceRefresh(e.type)}scheduleServiceRefresh(e){let t=this.serviceState.get(e);if(!t)return;let r=this.clock(),i=n({quotes:this.quotes,serviceType:e,nowSeconds:r});if(i===null){t.refreshTimer&&=(clearTimeout(t.refreshTimer),null),t.refreshAtSeconds=null;return}let a=Math.max(0,i-this.refreshBufferSeconds);if(a<=r||t.refreshAtSeconds===a&&t.refreshTimer)return;t.refreshTimer&&clearTimeout(t.refreshTimer);let o=Math.max(0,(a-r)*1e3);t.refreshAtSeconds=a,t.refreshTimer=setTimeout(()=>{let t=this.serviceState.get(e);t&&(t.refreshTimer=null,t.refreshAtSeconds=null);let n=this.transferServices.find(t=>t.type===e);n&&this.startStreamForService(n)},o)}emitQuote(e){for(let t of this.subscribers)t(`quote`,e)}emitError(e){for(let t of this.subscribers)t(`error`,e)}};const f=l.object({id:l.string(),expiresAt:l.number().int().nonnegative(),amountOut:l.bigint().nonnegative(),amountIn:l.bigint().nonnegative(),serviceType:l.string()});function p(n){let{sourceAsset:r,sourceChain:i,targetAsset:a,targetChain:o}=n;return i.chainId===o.chainId?t(r)&&t(a)?!0:r.type===a.type?r.type===e.ERC20&&a.type===e.ERC20?u(r.address,a.address):r.type===e.SPL&&a.type===e.SPL?r.address===a.address:!1:!1:!1}function m(e){return f.safeParse(e).success}export{d as Quoter};
|
|
2
2
|
//# sourceMappingURL=quoter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quoter.js","names":[],"sources":["../../src/quoter/quoter.ts"],"sourcesContent":["import { z } from 'zod';\nimport type {\n Quote,\n QuoterDonePayload,\n QuoterDoneReason,\n QuoterEventHandler,\n QuoterInterface,\n QuoterProps,\n ServiceQuoteEventArgs,\n QuotesTuple,\n ServiceQuoteEventHandler,\n} from '../types/quote';\nimport type { TransferService } from '../types/service';\nimport { earliestExpirationForService, isQuoteExpired, pruneExpiredQuotes, sortQuotes, upsertQuote } from './_utils';\nimport {\n QUOTER_DEFAULT_PRUNE_INTERVAL_MS,\n QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS,\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n} from './constants';\nimport { ServiceType, TokenType } from '../constants';\nimport { isNativeAsset } from '../type-guards';\nimport { isAddressEqual } from 'viem';\n\n/**\n * Function that returns the current UNIX time in seconds.\n * Injected for deterministic testing.\n */\nexport type Clock = () => number;\n\n/**\n * Options for constructing a Quoter instance.\n *\n * These options tune how often quote state is maintained and when services are\n * proactively restarted before quote expiry.\n */\nexport interface QuoterOptions {\n /** Clock function returning current time in seconds (defaults to Date.now()/1000). */\n readonly clock?: Clock;\n /** Interval for pruning expired quotes (milliseconds).\n *\n * @default 1_000\n */\n readonly pruneIntervalMs?: number;\n /**\n * Amount of seconds to pre-buffer a stream restart before the earliest service quote\n * expiration. Helps ensure continuity before actual expiry is reached.\n *\n * @default 5\n */\n readonly refreshBufferSeconds?: number;\n}\n\n/**\n * Quoter orchestrates quote streaming across multiple transfer services and emits a unified event stream.\n *\n * High-level lifecycle:\n * - Idle until first subscriber.\n * - On first subscribe, starts all eligible services.\n * - Collects and ranks active quotes, pruning expired ones over time.\n * - Completes when explicitly unsubscribed (last subscriber), when no service is eligible,\n * or when all eligible services finish without producing any quote (`no-quotes`).\n *\n * Event model:\n * - Service `quote` -> Quoter upserts quote, recomputes best quote, emits `quote`.\n * - Service `error` -> Quoter emits `error` (non-terminal by itself).\n * - Service `done` -> Quoter marks that service attempt as completed and evaluates retry/complete rules.\n *\n * Retry/refresh behavior:\n * - Refresh: services that have active quotes are restarted shortly before the earliest quote expires.\n * - Retry: services that complete with no quote and no error are only retried after the quoter has\n * already observed at least one quote from any service in this session.\n *\n * This design keeps first-pass \"no quotes anywhere\" terminal and fast, while still allowing services\n * like Markr to be retried in sessions where other providers are returning quotes.\n */\nexport class Quoter implements QuoterInterface {\n private readonly clock: Clock;\n private readonly props: QuoterProps;\n private readonly pruneIntervalMs: number;\n private readonly transferServices: readonly TransferService[];\n private readonly refreshBufferSeconds: number;\n\n private quotes: Quote[] = [];\n private subscribers: Set<QuoterEventHandler> = new Set();\n private started = false;\n private done = false;\n private isStartingFromSubscribe = false;\n private pruneTimerId: ReturnType<typeof setInterval> | null = null;\n private lastBestId: string | null = null;\n private hasReceivedAnyQuote = false;\n\n public readonly id: string = crypto.randomUUID();\n\n /**\n * Per-service runtime state for the active quote session.\n *\n * This tracks whether a service has completed, errored, produced quotes,\n * and any pending timers used for retry/refresh orchestration.\n */\n private serviceState: Map<\n TransferService['type'],\n {\n cancel: () => void;\n done: boolean;\n hasErrored: boolean;\n hasReturnedQuote: boolean;\n retryAttempt: number;\n retryTimer: ReturnType<typeof setTimeout> | null;\n refreshTimer: ReturnType<typeof setTimeout> | null;\n refreshAtSeconds: number | null;\n }\n > = new Map();\n\n /**\n * Create a new Quoter instance.\n *\n * @param props Quoting request parameters shared across all services.\n * @param transferServices Candidate services; eligibility is resolved at `start()` time.\n * @param options Optional runtime tuning for pruning and refresh behavior.\n */\n constructor(props: QuoterProps, transferServices: readonly TransferService[], options: QuoterOptions = {}) {\n this.clock = options.clock ?? (() => Math.floor(Date.now() / 1_000));\n this.props = props;\n this.pruneIntervalMs = options.pruneIntervalMs ?? QUOTER_DEFAULT_PRUNE_INTERVAL_MS;\n this.transferServices = transferServices;\n this.refreshBufferSeconds = options.refreshBufferSeconds ?? QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS;\n }\n\n /**\n * Get the current best quote and all active quotes (sorted by desirability).\n */\n public getQuotes(): QuotesTuple {\n const now = this.clock();\n const active = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n const sorted = sortQuotes(active);\n const best = sorted[0] ?? null;\n\n return [best, sorted];\n }\n\n /**\n * Subscribe for quoter events.\n *\n * First subscriber lazily starts orchestration.\n * Last subscriber triggers terminal completion with reason `unsubscribed`.\n *\n * @returns Unsubscribe function (idempotent).\n */\n public subscribe(handler: QuoterEventHandler): () => void {\n this.subscribers.add(handler);\n let unsubscribed = false;\n\n if (!this.started) {\n this.isStartingFromSubscribe = true;\n try {\n this.start();\n } finally {\n this.isStartingFromSubscribe = false;\n }\n }\n\n return () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n\n if (this.done) {\n this.subscribers.delete(handler);\n return;\n }\n\n const wasLastSubscriber = this.subscribers.size === 1 && this.subscribers.has(handler);\n if (wasLastSubscriber) {\n this.complete('unsubscribed');\n return;\n }\n\n this.subscribers.delete(handler);\n };\n }\n\n // ----- Internal orchestration -----\n\n /**\n * Start a fresh quote session.\n *\n * Resets session-local state, validates basic request viability,\n * starts streams for eligible services, and begins prune ticks.\n */\n private start(): void {\n this.started = true;\n this.done = false;\n this.quotes = [];\n this.lastBestId = null;\n this.hasReceivedAnyQuote = false;\n\n // Same-chain quotes for the *same* asset are not a valid transfer scenario.\n if (isInvalidSameChainQuoteRequest(this.props)) {\n this.complete('no-eligible-services');\n return;\n }\n\n let hasEligibleService = false;\n\n // Start streams for eligible services\n for (const svc of this.transferServices) {\n const eligible = svc.analyzeSupport({\n sourceAsset: this.props.sourceAsset,\n sourceChainId: this.props.sourceChain.chainId,\n targetAsset: this.props.targetAsset,\n targetChainId: this.props.targetChain.chainId,\n });\n\n if (!eligible) continue;\n hasEligibleService = true;\n\n this.startStreamForService(svc);\n }\n\n if (!hasEligibleService) {\n this.complete('no-eligible-services');\n return;\n }\n\n // Start periodic prune\n this.pruneTimerId = setInterval(() => this.onPruneTick(), this.pruneIntervalMs);\n }\n\n /**\n * Stop all streams and timers.\n * Quotes are retained for snapshot access via getQuotes(), though they\n * can still be pruned due to expiration.\n */\n private stop(): void {\n this.started = false;\n\n if (this.pruneTimerId) {\n clearInterval(this.pruneTimerId);\n this.pruneTimerId = null;\n }\n\n for (const [type, state] of this.serviceState) {\n try {\n state.cancel();\n } catch {\n /* ignore */\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n }\n\n this.serviceState.set(type, {\n cancel: () => {},\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n }\n\n /**\n * Begin or restart streaming for a specific service.\n *\n * Any previous stream/timers for that service are canceled before starting a new attempt.\n */\n private startStreamForService(svc: TransferService): void {\n // Clean up any existing stream and refresh timer\n const existing = this.serviceState.get(svc.type);\n\n if (existing) {\n try {\n existing.cancel();\n } catch {\n /* ignore */\n }\n\n if (existing.refreshTimer) clearTimeout(existing.refreshTimer);\n if (existing.retryTimer) clearTimeout(existing.retryTimer);\n }\n\n const handler = this.makeServiceHandler(svc.type);\n const bufferedEvents: ServiceQuoteEventArgs[] = [];\n let isStartingStream = true;\n\n // Some services emit quote/done synchronously inside streamQuotes().\n // If those events run before subscribe() returns, consumers can hit TDZ\n // when they reference the returned unsubscribe function in their callback.\n // Buffer startup emissions and flush them after stream initialization.\n const guardedHandler: ServiceQuoteEventHandler = (...eventArgs) => {\n if (isStartingStream) {\n bufferedEvents.push(eventArgs);\n return;\n }\n\n handler(...eventArgs);\n };\n\n const { cancel } = svc.streamQuotes(this.props, guardedHandler);\n isStartingStream = false;\n\n if (bufferedEvents.length > 0) {\n // Use a microtask so subscribe() can return before the first callback fires.\n // This preserves event order while avoiding re-entrancy during subscription.\n queueMicrotask(() => {\n for (const bufferedEvent of bufferedEvents) {\n handler(...bufferedEvent);\n }\n });\n }\n\n this.serviceState.set(svc.type, {\n cancel,\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: existing?.retryAttempt ?? 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n\n /**\n * Create the service-scoped event handler consumed by `TransferService.streamQuotes`.\n *\n * The handler enforces service/quote consistency and translates service events into\n * quoter state transitions.\n */\n private makeServiceHandler(serviceType: TransferService['type']): ServiceQuoteEventHandler {\n return (event, ...args) => {\n // Ignore any service events once stopped to prevent post-stop mutations.\n if (!this.started) {\n return;\n }\n if (event === 'quote') {\n const maybeQuote = args[0];\n if (isQuoteValue(maybeQuote)) {\n // Enforce the quote belongs to the emitting service\n if (maybeQuote.serviceType !== serviceType) {\n // Ignore quotes mismatched to service; defensive\n return;\n }\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasReturnedQuote = true;\n }\n this.onIncomingQuote(maybeQuote);\n this.scheduleServiceRefresh(serviceType);\n }\n\n return;\n }\n if (event === 'done') {\n this.onServiceDone(serviceType);\n\n return;\n }\n if (event === 'error') {\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasErrored = true;\n }\n const maybeErr = args[0];\n if (maybeErr instanceof Error) {\n this.emitError(maybeErr);\n }\n }\n };\n }\n\n /**\n * Handle an incoming quote event.\n *\n * Expired quotes are ignored. Valid quotes are upserted into active state,\n * then sorted/pruned and emitted to subscribers.\n */\n private onIncomingQuote(quote: Quote): void {\n const now = this.clock();\n\n if (isQuoteExpired({ quote, nowSeconds: now })) {\n return;\n }\n\n this.hasReceivedAnyQuote = true;\n\n this.resetRetryForService(quote.serviceType);\n\n this.quotes = sortQuotes(pruneExpiredQuotes({ quotes: upsertQuote(this.quotes, quote), nowSeconds: now }));\n\n // Always emit on every incoming quote\n const best = this.quotes[0];\n if (best) {\n this.lastBestId = best.id;\n this.emitQuote({ bestQuote: best, quote, quotes: this.quotes });\n }\n }\n\n private resetRetryForService(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.retryAttempt = 0;\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n }\n\n /**\n * Schedule a retry attempt for a service using exponential backoff.\n *\n * This is used only for quote-less successful completions once the overall session\n * has already produced at least one quote from some service.\n */\n private scheduleServiceRetry(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n\n const delayMs = Math.min(\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS * 2 ** state.retryAttempt,\n );\n state.retryAttempt += 1;\n\n state.retryTimer = setTimeout(\n () => {\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.retryTimer = null;\n }\n\n if (!this.started) {\n return;\n }\n\n const svc = this.transferServices.find((s) => s.type === serviceType);\n if (svc) {\n this.startStreamForService(svc);\n }\n },\n Math.max(0, delayMs),\n );\n }\n\n /**\n * Handle service completion (`done`).\n *\n * A completion may schedule:\n * - refresh (if the service has active quotes), and/or\n * - retry (quote-less/non-error completion, but only after any quote has existed in session).\n *\n * Then evaluates whether the full quoter can complete with `no-quotes`.\n */\n private onServiceDone(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.done = true;\n this.scheduleServiceRefresh(serviceType);\n\n if (this.hasReceivedAnyQuote && !state.hasErrored && !state.hasReturnedQuote) {\n this.scheduleServiceRetry(serviceType);\n }\n\n this.maybeCompleteNoQuotes();\n }\n\n /**\n * Complete with `no-quotes` when every eligible service has finished and\n * no quote was ever observed during this session.\n */\n private maybeCompleteNoQuotes(): void {\n if (!this.started || this.done || this.hasReceivedAnyQuote || this.serviceState.size === 0) {\n return;\n }\n\n const allServicesDone = [...this.serviceState.values()].every((state) => state.done);\n\n if (allServicesDone) {\n this.complete('no-quotes');\n }\n }\n\n /**\n * Finalize the quoter session and broadcast terminal reason exactly once.\n */\n private complete(reason: QuoterDoneReason): void {\n if (this.done) {\n return;\n }\n\n const payload = this.makeDonePayload(reason);\n\n this.done = true;\n this.stop();\n\n // When complete() happens during subscribe()->start(), emit done on a microtask\n // so callers can safely reference the returned unsubscribe in their handler.\n if (this.isStartingFromSubscribe) {\n queueMicrotask(() => {\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n });\n return;\n }\n\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n }\n\n private getInitializedServices(): ServiceType[] {\n return [...new Set(this.transferServices.map((service) => service.type))];\n }\n\n private getEligibleServices(): ServiceType[] {\n return [...this.serviceState.keys()];\n }\n\n private makeDonePayload(reason: QuoterDoneReason): QuoterDonePayload {\n if (reason === 'unsubscribed') {\n return { reason, data: undefined };\n }\n\n const initializedServices = this.getInitializedServices();\n\n if (reason === 'no-eligible-services') {\n return {\n reason,\n data: {\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n return {\n reason,\n data: {\n eligibleServices: this.getEligibleServices(),\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n /** Periodic prune tick to evict expired quotes and emit best changes. */\n private onPruneTick(): void {\n const now = this.clock();\n const pruned = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n\n if (pruned.length !== this.quotes.length) {\n this.quotes = sortQuotes(pruned);\n const best = this.quotes[0] ?? null;\n const bestId = best ? best.id : null;\n\n if (best && bestId !== this.lastBestId) {\n this.lastBestId = bestId;\n this.emitQuote({ bestQuote: best, quote: best, quotes: this.quotes });\n } else if (!best) {\n this.lastBestId = null;\n }\n }\n\n // Re-evaluate refresh scheduling for all services on prune tick\n for (const svc of this.transferServices) {\n if (this.serviceState.has(svc.type)) {\n this.scheduleServiceRefresh(svc.type);\n }\n }\n }\n\n /** Schedule a refresh (restart) of streaming for the service at its earliest quote expiration. */\n private scheduleServiceRefresh(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) return;\n\n const now = this.clock();\n const earliest = earliestExpirationForService({ quotes: this.quotes, serviceType, nowSeconds: now });\n\n if (earliest === null) {\n // No quotes -> clear any pending refresh\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n state.refreshTimer = null;\n }\n state.refreshAtSeconds = null;\n return;\n }\n\n // Only reschedule if earlier than currently scheduled or no timer\n const targetAt = Math.max(0, earliest - this.refreshBufferSeconds);\n\n // If target time has already passed, do not schedule another refresh here.\n if (targetAt <= now) {\n return;\n }\n\n // Reschedule only when the desired refresh time changes (earlier OR later).\n // We key refresh off the soonest-expiring active quote for this service.\n if (state.refreshAtSeconds === targetAt && state.refreshTimer) {\n return;\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n const delayMs = Math.max(0, (targetAt - now) * 1_000);\n state.refreshAtSeconds = targetAt;\n state.refreshTimer = setTimeout(() => {\n // Clear timer/marker before restarting to avoid stale state if restart fails.\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.refreshTimer = null;\n current.refreshAtSeconds = null;\n }\n\n // Restart stream and clear timer/marker\n const svc = this.transferServices.find((s) => s.type === serviceType);\n\n if (svc) {\n this.startStreamForService(svc);\n }\n }, delayMs);\n }\n\n // ----- Emission helpers -----\n /** Emit a quote event to all subscribers. */\n private emitQuote(payload: { bestQuote: Quote; quote: Quote; quotes: readonly Quote[] }): void {\n for (const handler of this.subscribers) {\n handler('quote', payload);\n }\n }\n\n /** Emit an error event to all subscribers. */\n private emitError(error: Error): void {\n for (const handler of this.subscribers) {\n handler('error', error);\n }\n }\n}\n\n/**\n * Zod schema to validate the minimal Quote fields used for runtime gating.\n *\n * This is a simple schema, and is not a fully safe runtime validate for Quote.\n * That isn't necessary here since we only need to verify basic structure before\n * using the quote in internal logic.\n *\n * @internal\n */\nconst QuoteSchema = z.object({\n id: z.string(),\n expiresAt: z.number().int().nonnegative(),\n amountOut: z.bigint().nonnegative(),\n amountIn: z.bigint().nonnegative(),\n serviceType: z.string(),\n});\n\nfunction isInvalidSameChainQuoteRequest(props: QuoterProps): boolean {\n const { sourceAsset, sourceChain, targetAsset, targetChain } = props;\n\n if (sourceChain.chainId !== targetChain.chainId) {\n return false;\n }\n\n // Native -> native on the same chain is a no-op.\n if (isNativeAsset(sourceAsset) && isNativeAsset(targetAsset)) {\n return true;\n }\n\n // If token types differ, it's potentially a swap on the same chain.\n if (sourceAsset.type !== targetAsset.type) {\n return false;\n }\n\n // Same token type and address on the same chain is a no-op.\n if (sourceAsset.type === TokenType.ERC20 && targetAsset.type === TokenType.ERC20) {\n return isAddressEqual(sourceAsset.address, targetAsset.address);\n }\n\n if (sourceAsset.type === TokenType.SPL && targetAsset.type === TokenType.SPL) {\n return sourceAsset.address === targetAsset.address;\n }\n\n return false;\n}\n\nfunction isQuoteValue(value: unknown): value is Quote {\n const result = QuoteSchema.safeParse(value);\n\n return result.success;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"quoter.js","names":[],"sources":["../../src/quoter/quoter.ts"],"sourcesContent":["import { z } from 'zod';\nimport type {\n Quote,\n QuoterDonePayload,\n QuoterDoneReason,\n QuoterEventHandler,\n QuoterInterface,\n QuoterProps,\n ServiceQuoteEventArgs,\n QuotesTuple,\n ServiceQuoteEventHandler,\n} from '../types/quote';\nimport type { ServiceQuoteOptions, TransferService } from '../types/service';\nimport { earliestExpirationForService, isQuoteExpired, pruneExpiredQuotes, sortQuotes, upsertQuote } from './_utils';\nimport {\n QUOTER_DEFAULT_PRUNE_INTERVAL_MS,\n QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS,\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n} from './constants';\nimport { ServiceType, TokenType } from '../constants';\nimport { isNativeAsset } from '../type-guards';\nimport { isAddressEqual } from 'viem';\n\n/**\n * Function that returns the current UNIX time in seconds.\n * Injected for deterministic testing.\n */\nexport type Clock = () => number;\n\n/**\n * Options for constructing a Quoter instance.\n *\n * These options tune how often quote state is maintained and when services are\n * proactively restarted before quote expiry.\n */\nexport interface QuoterOptions {\n /**\n * Dangerously allows cross-chain quotes where fromAddress and toAddress differ\n * even when a service would otherwise require them to match.\n *\n * This is intended for explicit proxy-recipient funding flows. By default,\n * services preserve their existing address safety checks.\n */\n readonly dangerouslyAllowAddressMismatch?: boolean;\n /** Clock function returning current time in seconds (defaults to Date.now()/1000). */\n readonly clock?: Clock;\n /** Interval for pruning expired quotes (milliseconds).\n *\n * @default 1_000\n */\n readonly pruneIntervalMs?: number;\n /**\n * Amount of seconds to pre-buffer a stream restart before the earliest service quote\n * expiration. Helps ensure continuity before actual expiry is reached.\n *\n * @default 5\n */\n readonly refreshBufferSeconds?: number;\n}\n\n/**\n * Quoter orchestrates quote streaming across multiple transfer services and emits a unified event stream.\n *\n * High-level lifecycle:\n * - Idle until first subscriber.\n * - On first subscribe, starts all eligible services.\n * - Collects and ranks active quotes, pruning expired ones over time.\n * - Completes when explicitly unsubscribed (last subscriber), when no service is eligible,\n * or when all eligible services finish without producing any quote (`no-quotes`).\n *\n * Event model:\n * - Service `quote` -> Quoter upserts quote, recomputes best quote, emits `quote`.\n * - Service `error` -> Quoter emits `error` (non-terminal by itself).\n * - Service `done` -> Quoter marks that service attempt as completed and evaluates retry/complete rules.\n *\n * Retry/refresh behavior:\n * - Refresh: services that have active quotes are restarted shortly before the earliest quote expires.\n * - Retry: services that complete with no quote and no error are only retried after the quoter has\n * already observed at least one quote from any service in this session.\n *\n * This design keeps first-pass \"no quotes anywhere\" terminal and fast, while still allowing services\n * like Markr to be retried in sessions where other providers are returning quotes.\n */\nexport class Quoter implements QuoterInterface {\n private readonly clock: Clock;\n private readonly props: QuoterProps;\n private readonly pruneIntervalMs: number;\n private readonly serviceQuoteOptions: ServiceQuoteOptions;\n private readonly transferServices: readonly TransferService[];\n private readonly refreshBufferSeconds: number;\n\n private quotes: Quote[] = [];\n private subscribers: Set<QuoterEventHandler> = new Set();\n private started = false;\n private done = false;\n private isStartingFromSubscribe = false;\n private pruneTimerId: ReturnType<typeof setInterval> | null = null;\n private lastBestId: string | null = null;\n private hasReceivedAnyQuote = false;\n\n public readonly id: string = crypto.randomUUID();\n\n /**\n * Per-service runtime state for the active quote session.\n *\n * This tracks whether a service has completed, errored, produced quotes,\n * and any pending timers used for retry/refresh orchestration.\n */\n private serviceState: Map<\n TransferService['type'],\n {\n cancel: () => void;\n done: boolean;\n hasErrored: boolean;\n hasReturnedQuote: boolean;\n retryAttempt: number;\n retryTimer: ReturnType<typeof setTimeout> | null;\n refreshTimer: ReturnType<typeof setTimeout> | null;\n refreshAtSeconds: number | null;\n }\n > = new Map();\n\n /**\n * Create a new Quoter instance.\n *\n * @param props Quoting request parameters shared across all services.\n * @param transferServices Candidate services; eligibility is resolved at `start()` time.\n * @param options Optional runtime tuning for pruning and refresh behavior.\n */\n constructor(props: QuoterProps, transferServices: readonly TransferService[], options: QuoterOptions = {}) {\n this.clock = options.clock ?? (() => Math.floor(Date.now() / 1_000));\n this.props = props;\n this.pruneIntervalMs = options.pruneIntervalMs ?? QUOTER_DEFAULT_PRUNE_INTERVAL_MS;\n this.serviceQuoteOptions = {\n dangerouslyAllowAddressMismatch: options.dangerouslyAllowAddressMismatch,\n };\n this.transferServices = transferServices;\n this.refreshBufferSeconds = options.refreshBufferSeconds ?? QUOTER_DEFAULT_REFRESH_BUFFER_SECONDS;\n }\n\n /**\n * Get the current best quote and all active quotes (sorted by desirability).\n */\n public getQuotes(): QuotesTuple {\n const now = this.clock();\n const active = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n const sorted = sortQuotes(active);\n const best = sorted[0] ?? null;\n\n return [best, sorted];\n }\n\n /**\n * Subscribe for quoter events.\n *\n * First subscriber lazily starts orchestration.\n * Last subscriber triggers terminal completion with reason `unsubscribed`.\n *\n * @returns Unsubscribe function (idempotent).\n */\n public subscribe(handler: QuoterEventHandler): () => void {\n this.subscribers.add(handler);\n let unsubscribed = false;\n\n if (!this.started) {\n this.isStartingFromSubscribe = true;\n try {\n this.start();\n } finally {\n this.isStartingFromSubscribe = false;\n }\n }\n\n return () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n\n if (this.done) {\n this.subscribers.delete(handler);\n return;\n }\n\n const wasLastSubscriber = this.subscribers.size === 1 && this.subscribers.has(handler);\n if (wasLastSubscriber) {\n this.complete('unsubscribed');\n return;\n }\n\n this.subscribers.delete(handler);\n };\n }\n\n // ----- Internal orchestration -----\n\n /**\n * Start a fresh quote session.\n *\n * Resets session-local state, validates basic request viability,\n * starts streams for eligible services, and begins prune ticks.\n */\n private start(): void {\n this.started = true;\n this.done = false;\n this.quotes = [];\n this.lastBestId = null;\n this.hasReceivedAnyQuote = false;\n\n // Same-chain quotes for the *same* asset are not a valid transfer scenario.\n if (isInvalidSameChainQuoteRequest(this.props)) {\n this.complete('no-eligible-services');\n return;\n }\n\n let hasEligibleService = false;\n\n // Start streams for eligible services\n for (const svc of this.transferServices) {\n const eligible = svc.analyzeSupport({\n sourceAsset: this.props.sourceAsset,\n sourceChainId: this.props.sourceChain.chainId,\n targetAsset: this.props.targetAsset,\n targetChainId: this.props.targetChain.chainId,\n });\n\n if (!eligible) continue;\n hasEligibleService = true;\n\n this.startStreamForService(svc);\n }\n\n if (!hasEligibleService) {\n this.complete('no-eligible-services');\n return;\n }\n\n // Start periodic prune\n this.pruneTimerId = setInterval(() => this.onPruneTick(), this.pruneIntervalMs);\n }\n\n /**\n * Stop all streams and timers.\n * Quotes are retained for snapshot access via getQuotes(), though they\n * can still be pruned due to expiration.\n */\n private stop(): void {\n this.started = false;\n\n if (this.pruneTimerId) {\n clearInterval(this.pruneTimerId);\n this.pruneTimerId = null;\n }\n\n for (const [type, state] of this.serviceState) {\n try {\n state.cancel();\n } catch {\n /* ignore */\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n }\n\n this.serviceState.set(type, {\n cancel: () => {},\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n }\n\n /**\n * Begin or restart streaming for a specific service.\n *\n * Any previous stream/timers for that service are canceled before starting a new attempt.\n */\n private startStreamForService(svc: TransferService): void {\n // Clean up any existing stream and refresh timer\n const existing = this.serviceState.get(svc.type);\n\n if (existing) {\n try {\n existing.cancel();\n } catch {\n /* ignore */\n }\n\n if (existing.refreshTimer) clearTimeout(existing.refreshTimer);\n if (existing.retryTimer) clearTimeout(existing.retryTimer);\n }\n\n const handler = this.makeServiceHandler(svc.type);\n const bufferedEvents: ServiceQuoteEventArgs[] = [];\n let isStartingStream = true;\n\n // Some services emit quote/done synchronously inside streamQuotes().\n // If those events run before subscribe() returns, consumers can hit TDZ\n // when they reference the returned unsubscribe function in their callback.\n // Buffer startup emissions and flush them after stream initialization.\n const guardedHandler: ServiceQuoteEventHandler = (...eventArgs) => {\n if (isStartingStream) {\n bufferedEvents.push(eventArgs);\n return;\n }\n\n handler(...eventArgs);\n };\n\n const { cancel } = svc.streamQuotes(this.props, guardedHandler, this.serviceQuoteOptions);\n isStartingStream = false;\n\n if (bufferedEvents.length > 0) {\n // Use a microtask so subscribe() can return before the first callback fires.\n // This preserves event order while avoiding re-entrancy during subscription.\n queueMicrotask(() => {\n for (const bufferedEvent of bufferedEvents) {\n handler(...bufferedEvent);\n }\n });\n }\n\n this.serviceState.set(svc.type, {\n cancel,\n done: false,\n hasErrored: false,\n hasReturnedQuote: false,\n retryAttempt: existing?.retryAttempt ?? 0,\n retryTimer: null,\n refreshTimer: null,\n refreshAtSeconds: null,\n });\n }\n\n /**\n * Create the service-scoped event handler consumed by `TransferService.streamQuotes`.\n *\n * The handler enforces service/quote consistency and translates service events into\n * quoter state transitions.\n */\n private makeServiceHandler(serviceType: TransferService['type']): ServiceQuoteEventHandler {\n return (event, ...args) => {\n // Ignore any service events once stopped to prevent post-stop mutations.\n if (!this.started) {\n return;\n }\n if (event === 'quote') {\n const maybeQuote = args[0];\n if (isQuoteValue(maybeQuote)) {\n // Enforce the quote belongs to the emitting service\n if (maybeQuote.serviceType !== serviceType) {\n // Ignore quotes mismatched to service; defensive\n return;\n }\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasReturnedQuote = true;\n }\n this.onIncomingQuote(maybeQuote);\n this.scheduleServiceRefresh(serviceType);\n }\n\n return;\n }\n if (event === 'done') {\n this.onServiceDone(serviceType);\n\n return;\n }\n if (event === 'error') {\n const state = this.serviceState.get(serviceType);\n if (state) {\n state.hasErrored = true;\n }\n const maybeErr = args[0];\n if (maybeErr instanceof Error) {\n this.emitError(maybeErr);\n }\n }\n };\n }\n\n /**\n * Handle an incoming quote event.\n *\n * Expired quotes are ignored. Valid quotes are upserted into active state,\n * then sorted/pruned and emitted to subscribers.\n */\n private onIncomingQuote(quote: Quote): void {\n const now = this.clock();\n\n if (isQuoteExpired({ quote, nowSeconds: now })) {\n return;\n }\n\n this.hasReceivedAnyQuote = true;\n\n this.resetRetryForService(quote.serviceType);\n\n this.quotes = sortQuotes(pruneExpiredQuotes({ quotes: upsertQuote(this.quotes, quote), nowSeconds: now }));\n\n // Always emit on every incoming quote\n const best = this.quotes[0];\n if (best) {\n this.lastBestId = best.id;\n this.emitQuote({ bestQuote: best, quote, quotes: this.quotes });\n }\n }\n\n private resetRetryForService(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.retryAttempt = 0;\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n }\n\n /**\n * Schedule a retry attempt for a service using exponential backoff.\n *\n * This is used only for quote-less successful completions once the overall session\n * has already produced at least one quote from some service.\n */\n private scheduleServiceRetry(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n if (state.retryTimer) {\n clearTimeout(state.retryTimer);\n state.retryTimer = null;\n }\n\n const delayMs = Math.min(\n QUOTER_EMPTY_RETRY_MAX_DELAY_MS,\n QUOTER_EMPTY_RETRY_BASE_DELAY_MS * 2 ** state.retryAttempt,\n );\n state.retryAttempt += 1;\n\n state.retryTimer = setTimeout(\n () => {\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.retryTimer = null;\n }\n\n if (!this.started) {\n return;\n }\n\n const svc = this.transferServices.find((s) => s.type === serviceType);\n if (svc) {\n this.startStreamForService(svc);\n }\n },\n Math.max(0, delayMs),\n );\n }\n\n /**\n * Handle service completion (`done`).\n *\n * A completion may schedule:\n * - refresh (if the service has active quotes), and/or\n * - retry (quote-less/non-error completion, but only after any quote has existed in session).\n *\n * Then evaluates whether the full quoter can complete with `no-quotes`.\n */\n private onServiceDone(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) {\n return;\n }\n\n state.done = true;\n this.scheduleServiceRefresh(serviceType);\n\n if (this.hasReceivedAnyQuote && !state.hasErrored && !state.hasReturnedQuote) {\n this.scheduleServiceRetry(serviceType);\n }\n\n this.maybeCompleteNoQuotes();\n }\n\n /**\n * Complete with `no-quotes` when every eligible service has finished and\n * no quote was ever observed during this session.\n */\n private maybeCompleteNoQuotes(): void {\n if (!this.started || this.done || this.hasReceivedAnyQuote || this.serviceState.size === 0) {\n return;\n }\n\n const allServicesDone = [...this.serviceState.values()].every((state) => state.done);\n\n if (allServicesDone) {\n this.complete('no-quotes');\n }\n }\n\n /**\n * Finalize the quoter session and broadcast terminal reason exactly once.\n */\n private complete(reason: QuoterDoneReason): void {\n if (this.done) {\n return;\n }\n\n const payload = this.makeDonePayload(reason);\n\n this.done = true;\n this.stop();\n\n // When complete() happens during subscribe()->start(), emit done on a microtask\n // so callers can safely reference the returned unsubscribe in their handler.\n if (this.isStartingFromSubscribe) {\n queueMicrotask(() => {\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n });\n return;\n }\n\n const handlers = [...this.subscribers];\n this.subscribers.clear();\n\n for (const handler of handlers) {\n handler('done', payload);\n }\n }\n\n private getInitializedServices(): ServiceType[] {\n return [...new Set(this.transferServices.map((service) => service.type))];\n }\n\n private getEligibleServices(): ServiceType[] {\n return [...this.serviceState.keys()];\n }\n\n private makeDonePayload(reason: QuoterDoneReason): QuoterDonePayload {\n if (reason === 'unsubscribed') {\n return { reason, data: undefined };\n }\n\n const initializedServices = this.getInitializedServices();\n\n if (reason === 'no-eligible-services') {\n return {\n reason,\n data: {\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n return {\n reason,\n data: {\n eligibleServices: this.getEligibleServices(),\n initializedServices,\n quoterProps: this.props,\n },\n };\n }\n\n /** Periodic prune tick to evict expired quotes and emit best changes. */\n private onPruneTick(): void {\n const now = this.clock();\n const pruned = pruneExpiredQuotes({ quotes: this.quotes, nowSeconds: now });\n\n if (pruned.length !== this.quotes.length) {\n this.quotes = sortQuotes(pruned);\n const best = this.quotes[0] ?? null;\n const bestId = best ? best.id : null;\n\n if (best && bestId !== this.lastBestId) {\n this.lastBestId = bestId;\n this.emitQuote({ bestQuote: best, quote: best, quotes: this.quotes });\n } else if (!best) {\n this.lastBestId = null;\n }\n }\n\n // Re-evaluate refresh scheduling for all services on prune tick\n for (const svc of this.transferServices) {\n if (this.serviceState.has(svc.type)) {\n this.scheduleServiceRefresh(svc.type);\n }\n }\n }\n\n /** Schedule a refresh (restart) of streaming for the service at its earliest quote expiration. */\n private scheduleServiceRefresh(serviceType: TransferService['type']): void {\n const state = this.serviceState.get(serviceType);\n\n if (!state) return;\n\n const now = this.clock();\n const earliest = earliestExpirationForService({ quotes: this.quotes, serviceType, nowSeconds: now });\n\n if (earliest === null) {\n // No quotes -> clear any pending refresh\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n state.refreshTimer = null;\n }\n state.refreshAtSeconds = null;\n return;\n }\n\n // Only reschedule if earlier than currently scheduled or no timer\n const targetAt = Math.max(0, earliest - this.refreshBufferSeconds);\n\n // If target time has already passed, do not schedule another refresh here.\n if (targetAt <= now) {\n return;\n }\n\n // Reschedule only when the desired refresh time changes (earlier OR later).\n // We key refresh off the soonest-expiring active quote for this service.\n if (state.refreshAtSeconds === targetAt && state.refreshTimer) {\n return;\n }\n\n if (state.refreshTimer) {\n clearTimeout(state.refreshTimer);\n }\n\n const delayMs = Math.max(0, (targetAt - now) * 1_000);\n state.refreshAtSeconds = targetAt;\n state.refreshTimer = setTimeout(() => {\n // Clear timer/marker before restarting to avoid stale state if restart fails.\n const current = this.serviceState.get(serviceType);\n if (current) {\n current.refreshTimer = null;\n current.refreshAtSeconds = null;\n }\n\n // Restart stream and clear timer/marker\n const svc = this.transferServices.find((s) => s.type === serviceType);\n\n if (svc) {\n this.startStreamForService(svc);\n }\n }, delayMs);\n }\n\n // ----- Emission helpers -----\n /** Emit a quote event to all subscribers. */\n private emitQuote(payload: { bestQuote: Quote; quote: Quote; quotes: readonly Quote[] }): void {\n for (const handler of this.subscribers) {\n handler('quote', payload);\n }\n }\n\n /** Emit an error event to all subscribers. */\n private emitError(error: Error): void {\n for (const handler of this.subscribers) {\n handler('error', error);\n }\n }\n}\n\n/**\n * Zod schema to validate the minimal Quote fields used for runtime gating.\n *\n * This is a simple schema, and is not a fully safe runtime validate for Quote.\n * That isn't necessary here since we only need to verify basic structure before\n * using the quote in internal logic.\n *\n * @internal\n */\nconst QuoteSchema = z.object({\n id: z.string(),\n expiresAt: z.number().int().nonnegative(),\n amountOut: z.bigint().nonnegative(),\n amountIn: z.bigint().nonnegative(),\n serviceType: z.string(),\n});\n\nfunction isInvalidSameChainQuoteRequest(props: QuoterProps): boolean {\n const { sourceAsset, sourceChain, targetAsset, targetChain } = props;\n\n if (sourceChain.chainId !== targetChain.chainId) {\n return false;\n }\n\n // Native -> native on the same chain is a no-op.\n if (isNativeAsset(sourceAsset) && isNativeAsset(targetAsset)) {\n return true;\n }\n\n // If token types differ, it's potentially a swap on the same chain.\n if (sourceAsset.type !== targetAsset.type) {\n return false;\n }\n\n // Same token type and address on the same chain is a no-op.\n if (sourceAsset.type === TokenType.ERC20 && targetAsset.type === TokenType.ERC20) {\n return isAddressEqual(sourceAsset.address, targetAsset.address);\n }\n\n if (sourceAsset.type === TokenType.SPL && targetAsset.type === TokenType.SPL) {\n return sourceAsset.address === targetAsset.address;\n }\n\n return false;\n}\n\nfunction isQuoteValue(value: unknown): value is Quote {\n const result = QuoteSchema.safeParse(value);\n\n return result.success;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_api.cjs","names":["fetchJson","combineUrlPathnames","SupportedChainsResponseSchema","ResponseValidationError","TokenListResponseSchema","fetchEventStream","QuoteResponseSchema","SwapResponseSchema","PartnerInfoResponseSchema","AbortedError","CrossChainStatusResponseSchema","SpenderAddressResponseSchema","MarkrAuthorizeResponseSchema"],"sources":["../../../src/transfer-service/markr/_api.ts"],"sourcesContent":["import type { Address as SolanaAddress } from '@solana/kit';\nimport type { Address as EvmAddress } from 'viem';\nimport { AbortedError, ResponseValidationError } from '../../errors';\nimport { combineUrlPathnames, fetchEventStream, fetchJson } from '../fetch-utilities';\nimport {\n CrossChainStatusResponseSchema,\n MarkrAuthorizeResponseSchema,\n PartnerInfoResponseSchema,\n QuoteResponseSchema,\n SpenderAddressResponseSchema,\n SupportedChainsResponseSchema,\n SwapResponseSchema,\n TokenListResponseSchema,\n type CrossChainStatusResponse,\n type PartnerInfoResponse,\n type QuoteResponseData,\n type SpenderAddressResponse,\n type SupportedChainsResponse,\n type SwapResponse,\n type TokenListResponse,\n} from './_schema';\nimport type { Caip2ChainId } from '../../types/caip';\nimport type { Fetch } from '../../types/utility-types';\n\nexport interface ApiOptions {\n apiBaseUrl: URL;\n apiToken?: string;\n fetch?: Fetch;\n}\n\nfunction getAuthHeaders(apiToken?: string): Record<string, string> | undefined {\n if (!apiToken) {\n return undefined;\n }\n\n return { Authorization: `Bearer ${apiToken}` };\n}\n\nexport async function markrGetInfoChains({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<SupportedChainsResponse> {\n const endpoint = '/info/chains';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SupportedChainsResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetTokenList(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n chainId: number,\n): Promise<TokenListResponse> {\n const endpoint = `/tokens/${chainId}/list`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = TokenListResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport interface MarkrStreamQuoteParams {\n amount: string;\n appId: string;\n chainId: number | Caip2ChainId;\n destinationChainId?: number | Caip2ChainId;\n slippage?: number;\n tokenIn: string;\n tokenInDecimals: number;\n tokenOut: string;\n tokenOutDecimals: number;\n /**\n * Required ONLY for cross-chain swaps involving EVM.\n *\n * EVM -> EVM (different chains)\n * EVM -> Solana\n * Solana -> EVM\n */\n userEvmAddress?: EvmAddress;\n /**\n * Required ONLY for cross-chain swaps involving Solana.\n *\n * Solana -> EVM\n * EVM -> Solana\n * Solana -> Solana\n */\n userSolanaAddress?: SolanaAddress;\n}\n\nexport interface MarkrStreamQuoteOptions {\n onDone: () => void;\n onError: (error: Error) => void;\n onQuote: (data: QuoteResponseData) => void;\n signal?: AbortSignal;\n}\n\nexport async function markrStreamQuote(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrStreamQuoteParams,\n { onDone, onError, onQuote, signal }: MarkrStreamQuoteOptions,\n): Promise<ReadonlyArray<QuoteResponseData>> {\n const endpoint = '/quote';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const quotes: QuoteResponseData[] = [];\n\n try {\n for await (const event of fetchEventStream(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n signal,\n })) {\n const parsedData = QuoteResponseSchema.safeParse(JSON.parse(event.data));\n\n if (!parsedData.success) {\n onError(\n new ResponseValidationError(\n `Invalid quote data received from Markr \"${endpoint}\" endpoint.`,\n parsedData.error.issues,\n ),\n );\n continue;\n }\n\n if (!('done' in parsedData.data)) {\n onQuote(parsedData.data);\n quotes.push(parsedData.data);\n } else {\n onDone();\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n onError(error);\n } else {\n onError(new Error('An unknown error occurred during the quote stream.', { cause: error }));\n }\n\n onDone();\n }\n\n return quotes;\n}\n\nexport interface MarkrGetSwapParams {\n appId: string;\n /** Input amount in token's smallest unit */\n amountIn: string;\n /** Return raw transaction data alongside wrapped data */\n debug?: boolean;\n /** Minimum expected output amount */\n minAmountOut: string;\n /** Input token contract address */\n tokenIn: string;\n /** Output token contract address */\n tokenOut: string;\n /** Solana wallet public key (required for Solana swaps) */\n userPublicKey?: string;\n /** Quote UUID from previous quote request */\n uuid: string;\n}\n\nexport async function markrSwap(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrGetSwapParams,\n): Promise<SwapResponse> {\n const endpoint = '/swap';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n });\n\n const parsed = SwapResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetPartnerInfo({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<PartnerInfoResponse> {\n const endpoint = '/info/partner';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = PartnerInfoResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetCrossChainStatus(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n sourceTxHash: string,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<CrossChainStatusResponse> {\n const endpoint = `/cross-chain/status/${sourceTxHash}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n headers: getAuthHeaders(apiToken),\n fetch: customFetch,\n retries: 10,\n retryOn(ctx) {\n const method = (ctx.init.method ?? 'GET').toUpperCase();\n\n if (!['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method)) {\n return false;\n }\n\n if (ctx.error) {\n if (ctx.error instanceof AbortedError) {\n return false;\n }\n\n return true;\n }\n\n const status = ctx.response?.status ?? 0;\n\n // Retry on 404 requests since the endpoint may not return a result immediately after the source transaction is executed.\n if (status === 404 || status === 408 || status === 425 || status === 429 || (status >= 500 && status <= 599)) {\n return true;\n }\n\n return false;\n },\n signal,\n });\n\n const parsed = CrossChainStatusResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(\n `Invalid response from Markr \"${endpoint}\" endpoint.`,\n parsed.error.issues,\n rawJson,\n );\n }\n\n return parsed.data;\n}\n\n/**\n * Returns the spender address (wrapped contract) that needs to be approved\n * for token swaps on the specified chain.\n *\n * Not applicable for Solana.\n */\nexport async function markrGetSpenderAddress(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n {\n chainId,\n crossChainSwap,\n quoteId,\n }: {\n chainId: number;\n crossChainSwap?: boolean;\n quoteId: string;\n },\n): Promise<SpenderAddressResponse> {\n const endpoint = `/spender-address?chainId=${chainId}&cross=${crossChainSwap ? 'true' : 'false'}&uuid=${quoteId}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SpenderAddressResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\n// ---------------------------------------------------------------------------\n// Hyperliquid 2-phase withdrawal: nonce-mapping authorize\n// ---------------------------------------------------------------------------\n\n/**\n * Legacy (`protocolVersion: 1`) authorize body — Markr rebuilds the upstream\n * Relay request from a fixed schema. Kept for back-compat; new integrations\n * should use {@link MarkrPostAuthorizeParamsV2}.\n *\n * **Caveat:** when Relay adds a required field (as it did with `depositor` in\n * v2), the rebuilt body silently omits it and the order is orphaned upstream.\n */\nexport interface MarkrPostAuthorizeParamsV1 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over the `RelayNonceMapping` typed data (`0x{r}{s}{v}`, 65 bytes hex). */\n readonly signature: `0x${string}`;\n /** EVM address of the signer. */\n readonly wallet: EvmAddress;\n /** Always `1337` (Hyperliquid). */\n readonly walletChainId: 1337;\n /** EIP-712 domain.chainId used in the signature — the wallet's active chain. */\n readonly signatureChainId: number;\n /**\n * Shared nonce — JSON number, matching Relay's upstream `/authorize` contract\n * and HL `/exchange`. HL nonces are millisecond timestamps, well under\n * `Number.MAX_SAFE_INTEGER`. Sending it as a string makes Relay reject the\n * payload (strict type validation), so we coerce here at the wire boundary.\n */\n readonly nonce: number;\n /** Relay request id (`0x{64hex}`). */\n readonly id: `0x${string}`;\n}\n\n/**\n * Pass-through (`protocolVersion: 2`) authorize body. The SDK forwards Relay's\n * `authorize.body` envelope from the `/swap` response **verbatim**. Markr only\n * normalizes `nonce` to a JSON number and defaults `referrer`; every other\n * field is untouched, so additive Relay schema changes don't break SDKs.\n */\nexport interface MarkrPostAuthorizeParamsV2 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over `swap.sign.value` using `swap.sign.domain` / `swap.sign.types`. */\n readonly signature: `0x${string}`;\n /** Relay's `/authorize` POST body, taken from `swap.authorize.body`. Forwarded verbatim. */\n readonly body: Record<string, unknown>;\n}\n\nexport type MarkrPostAuthorizeParams = MarkrPostAuthorizeParamsV1 | MarkrPostAuthorizeParamsV2;\n\n/**\n * Submit the user's signed nonce-mapping to Markr's `/authorize` endpoint.\n *\n * Markr proxies the request to Relay's upstream `/authorize`. Returns on any\n * 2xx; throws `HttpError` otherwise.\n */\nexport async function markrPostAuthorize(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrPostAuthorizeParams,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<void> {\n const endpoint = '/authorize';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n // `fetchJson` throws `HttpError` on non-2xx; the schema additionally rejects\n // upstream error envelopes that come back with a 200 (Relay sometimes does\n // this when authorize is rejected after HTTP success).\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n signal,\n });\n\n const parsed = MarkrAuthorizeResponseSchema.safeParse(rawJson);\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n}\n"],"mappings":"mGA8BA,SAAS,EAAe,EAAuD,CACxE,KAIL,MAAO,CAAE,cAAe,UAAU,IAAY,CAGhD,eAAsB,EAAmB,CACvC,aACA,WACA,MAAO,GACwC,CAC/C,IAAM,EAAW,eAGX,EAAU,MAAMA,EAAAA,UAFVC,EAAAA,oBAAoB,EAAY,EAAS,CAEhB,CAAE,QAAS,EAAe,EAAS,CAAE,MAAO,EAAa,CAAC,CAEzF,EAASC,EAAAA,8BAA8B,UAAU,EAAQ,CAE/D,GAAI,CAAC,EAAO,QACV,MAAM,IAAIC,EAAAA,wBAAwB,gCAAgC,EAAS,aAAc,EAAO,MAAM,OAAO,CAG/G,OAAO,EAAO,KAGhB,eAAsB,EACpB,CAAE,aAAY,WAAU,MAAO,GAC/B,EAC4B,CAC5B,IAAM,EAAW,WAAW,EAAQ,OAG9B,EAAU,MAAMH,EAAAA,UAFVC,EAAAA,oBAAoB,EAAY,EAAS,CAEhB,CAAE,QAAS,EAAe,EAAS,CAAE,MAAO,EAAa,CAAC,CAEzF,EAASG,EAAAA,wBAAwB,UAAU,EAAQ,CAEzD,GAAI,CAAC,EAAO,QACV,MAAM,IAAID,EAAAA,wBAAwB,gCAAgC,EAAS,aAAc,EAAO,MAAM,OAAO,CAG/G,OAAO,EAAO,KAsChB,eAAsB,EACpB,CAAE,aAAY,WAAU,MAAO,GAC/B,EACA,CAAE,SAAQ,UAAS,UAAS,UACe,CAC3C,IAAM,EAAW,SACX,EAAMF,EAAAA,oBAAoB,EAAY,EAAS,CAE/C,EAA8B,EAAE,CAEtC,GAAI,CACF,UAAW,IAAM,KAASI,EAAAA,iBAAiB,EAAK,CAC9C,KAAM,KAAK,UAAU,EAAO,CAC5B,MAAO,EACP,QAAS,EAAe,EAAS,CACjC,SACD,CAAC,CAAE,CACF,IAAM,EAAaC,EAAAA,oBAAoB,UAAU,KAAK,MAAM,EAAM,KAAK,CAAC,CAExE,GAAI,CAAC,EAAW,QAAS,CACvB,EACE,IAAIH,EAAAA,wBACF,2CAA2C,EAAS,aACpD,EAAW,MAAM,OAClB,CACF,CACD,SAGI,SAAU,EAAW,KAIzB,GAAQ,EAHR,EAAQ,EAAW,KAAK,CACxB,EAAO,KAAK,EAAW,KAAK,SAKzB,EAAO,CACV,aAAiB,MACnB,EAAQ,EAAM,CAEd,EAAY,MAAM,qDAAsD,CAAE,MAAO,EAAO,CAAC,CAAC,CAG5F,GAAQ,CAGV,OAAO,EAqBT,eAAsB,EACpB,CAAE,aAAY,WAAU,MAAO,GAC/B,EACuB,CACvB,IAAM,EAAW,QAGX,EAAU,MAAMH,EAAAA,UAFVC,EAAAA,oBAAoB,EAAY,EAAS,CAEhB,CACnC,KAAM,KAAK,UAAU,EAAO,CAC5B,MAAO,EACP,QAAS,EAAe,EAAS,CACjC,OAAQ,OACT,CAAC,CAEI,EAASM,EAAAA,mBAAmB,UAAU,EAAQ,CAEpD,GAAI,CAAC,EAAO,QACV,MAAM,IAAIJ,EAAAA,wBAAwB,gCAAgC,EAAS,aAAc,EAAO,MAAM,OAAO,CAG/G,OAAO,EAAO,KAGhB,eAAsB,EAAoB,CACxC,aACA,WACA,MAAO,GACoC,CAC3C,IAAM,EAAW,gBAGX,EAAU,MAAMH,EAAAA,UAFVC,EAAAA,oBAAoB,EAAY,EAAS,CAEhB,CAAE,QAAS,EAAe,EAAS,CAAE,MAAO,EAAa,CAAC,CAEzF,EAASO,EAAAA,0BAA0B,UAAU,EAAQ,CAE3D,GAAI,CAAC,EAAO,QACV,MAAM,IAAIL,EAAAA,wBAAwB,gCAAgC,EAAS,aAAc,EAAO,MAAM,OAAO,CAG/G,OAAO,EAAO,KAGhB,eAAsB,EACpB,CAAE,aAAY,WAAU,MAAO,GAC/B,EACA,CAAE,UAAqC,EAAE,CACN,CACnC,IAAM,EAAW,uBAAuB,IAGlC,EAAU,MAAMH,EAAAA,UAFVC,EAAAA,oBAAoB,EAAY,EAAS,CAEhB,CACnC,QAAS,EAAe,EAAS,CACjC,MAAO,EACP,QAAS,GACT,QAAQ,EAAK,CACX,IAAM,GAAU,EAAI,KAAK,QAAU,OAAO,aAAa,CAEvD,GAAI,CAAC,CAAC,MAAO,OAAQ,UAAW,QAAQ,CAAC,SAAS,EAAO,CACvD,MAAO,GAGT,GAAI,EAAI,MAKN,MAJA,EAAI,EAAI,iBAAiBQ,EAAAA,cAO3B,IAAM,EAAS,EAAI,UAAU,QAAU,EAOvC,OAJI,IAAW,KAAO,IAAW,KAAO,IAAW,KAAO,IAAW,KAAQ,GAAU,KAAO,GAAU,KAM1G,SACD,CAAC,CAEI,EAASC,EAAAA,+BAA+B,UAAU,EAAQ,CAEhE,GAAI,CAAC,EAAO,QACV,MAAM,IAAIP,EAAAA,wBACR,gCAAgC,EAAS,aACzC,EAAO,MAAM,OACb,EACD,CAGH,OAAO,EAAO,KAShB,eAAsB,EACpB,CAAE,aAAY,WAAU,MAAO,GAC/B,CACE,UACA,iBACA,WAM+B,CACjC,IAAM,EAAW,4BAA4B,EAAQ,SAAS,EAAiB,OAAS,QAAQ,QAAQ,IAGlG,EAAU,MAAMH,EAAAA,UAFVC,EAAAA,oBAAoB,EAAY,EAAS,CAEhB,CAAE,QAAS,EAAe,EAAS,CAAE,MAAO,EAAa,CAAC,CAEzF,EAASU,EAAAA,6BAA6B,UAAU,EAAQ,CAE9D,GAAI,CAAC,EAAO,QACV,MAAM,IAAIR,EAAAA,wBAAwB,gCAAgC,EAAS,aAAc,EAAO,MAAM,OAAO,CAG/G,OAAO,EAAO,KA4DhB,eAAsB,EACpB,CAAE,aAAY,WAAU,MAAO,GAC/B,EACA,CAAE,UAAqC,EAAE,CAC1B,CACf,IAAM,EAAW,aAMX,EAAU,MAAMH,EAAAA,UALVC,EAAAA,oBAAoB,EAAY,EAAS,CAKhB,CACnC,KAAM,KAAK,UAAU,EAAO,CAC5B,MAAO,EACP,QAAS,EAAe,EAAS,CACjC,OAAQ,OACR,SACD,CAAC,CAEI,EAASW,EAAAA,6BAA6B,UAAU,EAAQ,CAC9D,GAAI,CAAC,EAAO,QACV,MAAM,IAAIT,EAAAA,wBAAwB,gCAAgC,EAAS,aAAc,EAAO,MAAM,OAAO"}
|
|
1
|
+
{"version":3,"file":"_api.cjs","names":["fetchJson","combineUrlPathnames","SupportedChainsResponseSchema","ResponseValidationError","TokenListResponseSchema","fetchEventStream","QuoteResponseSchema","SwapResponseSchema","PartnerInfoResponseSchema","AbortedError","CrossChainStatusResponseSchema","SpenderAddressResponseSchema","MarkrAuthorizeResponseSchema"],"sources":["../../../src/transfer-service/markr/_api.ts"],"sourcesContent":["import type { Address as SolanaAddress } from '@solana/kit';\nimport type { Address as EvmAddress } from 'viem';\nimport { AbortedError, ResponseValidationError } from '../../errors';\nimport { combineUrlPathnames, fetchEventStream, fetchJson } from '../fetch-utilities';\nimport {\n CrossChainStatusResponseSchema,\n MarkrAuthorizeResponseSchema,\n PartnerInfoResponseSchema,\n QuoteResponseSchema,\n SpenderAddressResponseSchema,\n SupportedChainsResponseSchema,\n SwapResponseSchema,\n TokenListResponseSchema,\n type CrossChainStatusResponse,\n type PartnerInfoResponse,\n type QuoteResponseData,\n type SpenderAddressResponse,\n type SupportedChainsResponse,\n type SwapResponse,\n type TokenListResponse,\n} from './_schema';\nimport type { Caip2ChainId } from '../../types/caip';\nimport type { Fetch } from '../../types/utility-types';\n\nexport interface ApiOptions {\n apiBaseUrl: URL;\n apiToken?: string;\n fetch?: Fetch;\n}\n\nfunction getAuthHeaders(apiToken?: string): Record<string, string> | undefined {\n if (!apiToken) {\n return undefined;\n }\n\n return { Authorization: `Bearer ${apiToken}` };\n}\n\nexport async function markrGetInfoChains({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<SupportedChainsResponse> {\n const endpoint = '/info/chains';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SupportedChainsResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetTokenList(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n chainId: number,\n): Promise<TokenListResponse> {\n const endpoint = `/tokens/${chainId}/list`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = TokenListResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport interface MarkrStreamQuoteParams {\n amount: string;\n /**\n * Pin the quote to a single upstream aggregator (e.g. `\"relay\"`). When\n * omitted, Markr races every eligible aggregator. Used to obtain\n * native-fee-free routes that only a specific aggregator supports.\n */\n aggregator?: string;\n appId: string;\n chainId: number | Caip2ChainId;\n destinationChainId?: number | Caip2ChainId;\n slippage?: number;\n tokenIn: string;\n tokenInDecimals: number;\n tokenOut: string;\n tokenOutDecimals: number;\n /**\n * Required ONLY for cross-chain swaps involving EVM.\n *\n * EVM -> EVM (different chains)\n * EVM -> Solana\n * Solana -> EVM\n */\n userEvmAddress?: EvmAddress;\n /**\n * Required ONLY for cross-chain swaps involving Solana.\n *\n * Solana -> EVM\n * EVM -> Solana\n * Solana -> Solana\n */\n userSolanaAddress?: SolanaAddress;\n}\n\nexport interface MarkrStreamQuoteOptions {\n onDone: () => void;\n onError: (error: Error) => void;\n onQuote: (data: QuoteResponseData) => void;\n signal?: AbortSignal;\n}\n\nexport async function markrStreamQuote(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrStreamQuoteParams,\n { onDone, onError, onQuote, signal }: MarkrStreamQuoteOptions,\n): Promise<ReadonlyArray<QuoteResponseData>> {\n const endpoint = '/quote';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const quotes: QuoteResponseData[] = [];\n\n try {\n for await (const event of fetchEventStream(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n signal,\n })) {\n const parsedData = QuoteResponseSchema.safeParse(JSON.parse(event.data));\n\n if (!parsedData.success) {\n onError(\n new ResponseValidationError(\n `Invalid quote data received from Markr \"${endpoint}\" endpoint.`,\n parsedData.error.issues,\n ),\n );\n continue;\n }\n\n if (!('done' in parsedData.data)) {\n onQuote(parsedData.data);\n quotes.push(parsedData.data);\n } else {\n onDone();\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n onError(error);\n } else {\n onError(new Error('An unknown error occurred during the quote stream.', { cause: error }));\n }\n\n onDone();\n }\n\n return quotes;\n}\n\nexport interface MarkrGetSwapParams {\n appId: string;\n /** Input amount in token's smallest unit */\n amountIn: string;\n /** Return raw transaction data alongside wrapped data */\n debug?: boolean;\n /** Minimum expected output amount */\n minAmountOut: string;\n /** Input token contract address */\n tokenIn: string;\n /** Output token contract address */\n tokenOut: string;\n /** Solana wallet public key (required for Solana swaps) */\n userPublicKey?: string;\n /** Quote UUID from previous quote request */\n uuid: string;\n}\n\nexport async function markrSwap(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrGetSwapParams,\n): Promise<SwapResponse> {\n const endpoint = '/swap';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n });\n\n const parsed = SwapResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetPartnerInfo({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<PartnerInfoResponse> {\n const endpoint = '/info/partner';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = PartnerInfoResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetCrossChainStatus(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n sourceTxHash: string,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<CrossChainStatusResponse> {\n const endpoint = `/cross-chain/status/${sourceTxHash}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n headers: getAuthHeaders(apiToken),\n fetch: customFetch,\n retries: 10,\n retryOn(ctx) {\n const method = (ctx.init.method ?? 'GET').toUpperCase();\n\n if (!['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method)) {\n return false;\n }\n\n if (ctx.error) {\n if (ctx.error instanceof AbortedError) {\n return false;\n }\n\n return true;\n }\n\n const status = ctx.response?.status ?? 0;\n\n // Retry on 404 requests since the endpoint may not return a result immediately after the source transaction is executed.\n if (status === 404 || status === 408 || status === 425 || status === 429 || (status >= 500 && status <= 599)) {\n return true;\n }\n\n return false;\n },\n signal,\n });\n\n const parsed = CrossChainStatusResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(\n `Invalid response from Markr \"${endpoint}\" endpoint.`,\n parsed.error.issues,\n rawJson,\n );\n }\n\n return parsed.data;\n}\n\n/**\n * Returns the spender address (wrapped contract) that needs to be approved\n * for token swaps on the specified chain.\n *\n * Not applicable for Solana.\n */\nexport async function markrGetSpenderAddress(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n {\n chainId,\n crossChainSwap,\n quoteId,\n }: {\n chainId: number;\n crossChainSwap?: boolean;\n quoteId: string;\n },\n): Promise<SpenderAddressResponse> {\n const endpoint = `/spender-address?chainId=${chainId}&cross=${crossChainSwap ? 'true' : 'false'}&uuid=${quoteId}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SpenderAddressResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\n// ---------------------------------------------------------------------------\n// Hyperliquid 2-phase withdrawal: nonce-mapping authorize\n// ---------------------------------------------------------------------------\n\n/**\n * Legacy (`protocolVersion: 1`) authorize body — Markr rebuilds the upstream\n * Relay request from a fixed schema. Kept for back-compat; new integrations\n * should use {@link MarkrPostAuthorizeParamsV2}.\n *\n * **Caveat:** when Relay adds a required field (as it did with `depositor` in\n * v2), the rebuilt body silently omits it and the order is orphaned upstream.\n */\nexport interface MarkrPostAuthorizeParamsV1 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over the `RelayNonceMapping` typed data (`0x{r}{s}{v}`, 65 bytes hex). */\n readonly signature: `0x${string}`;\n /** EVM address of the signer. */\n readonly wallet: EvmAddress;\n /** Always `1337` (Hyperliquid). */\n readonly walletChainId: 1337;\n /** EIP-712 domain.chainId used in the signature — the wallet's active chain. */\n readonly signatureChainId: number;\n /**\n * Shared nonce — JSON number, matching Relay's upstream `/authorize` contract\n * and HL `/exchange`. HL nonces are millisecond timestamps, well under\n * `Number.MAX_SAFE_INTEGER`. Sending it as a string makes Relay reject the\n * payload (strict type validation), so we coerce here at the wire boundary.\n */\n readonly nonce: number;\n /** Relay request id (`0x{64hex}`). */\n readonly id: `0x${string}`;\n}\n\n/**\n * Pass-through (`protocolVersion: 2`) authorize body. The SDK forwards Relay's\n * `authorize.body` envelope from the `/swap` response **verbatim**. Markr only\n * normalizes `nonce` to a JSON number and defaults `referrer`; every other\n * field is untouched, so additive Relay schema changes don't break SDKs.\n */\nexport interface MarkrPostAuthorizeParamsV2 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over `swap.sign.value` using `swap.sign.domain` / `swap.sign.types`. */\n readonly signature: `0x${string}`;\n /** Relay's `/authorize` POST body, taken from `swap.authorize.body`. Forwarded verbatim. */\n readonly body: Record<string, unknown>;\n}\n\nexport type MarkrPostAuthorizeParams = MarkrPostAuthorizeParamsV1 | MarkrPostAuthorizeParamsV2;\n\n/**\n * Submit the user's signed nonce-mapping to Markr's `/authorize` endpoint.\n *\n * Markr proxies the request to Relay's upstream `/authorize`. Returns on any\n * 2xx; throws `HttpError` otherwise.\n */\nexport async function markrPostAuthorize(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrPostAuthorizeParams,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<void> {\n const endpoint = '/authorize';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n // `fetchJson` throws `HttpError` on non-2xx; the schema additionally rejects\n // upstream error envelopes that come back with a 200 (Relay sometimes does\n // this when authorize is rejected after HTTP success).\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n signal,\n });\n\n const parsed = MarkrAuthorizeResponseSchema.safeParse(rawJson);\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_api.js","names":[],"sources":["../../../src/transfer-service/markr/_api.ts"],"sourcesContent":["import type { Address as SolanaAddress } from '@solana/kit';\nimport type { Address as EvmAddress } from 'viem';\nimport { AbortedError, ResponseValidationError } from '../../errors';\nimport { combineUrlPathnames, fetchEventStream, fetchJson } from '../fetch-utilities';\nimport {\n CrossChainStatusResponseSchema,\n MarkrAuthorizeResponseSchema,\n PartnerInfoResponseSchema,\n QuoteResponseSchema,\n SpenderAddressResponseSchema,\n SupportedChainsResponseSchema,\n SwapResponseSchema,\n TokenListResponseSchema,\n type CrossChainStatusResponse,\n type PartnerInfoResponse,\n type QuoteResponseData,\n type SpenderAddressResponse,\n type SupportedChainsResponse,\n type SwapResponse,\n type TokenListResponse,\n} from './_schema';\nimport type { Caip2ChainId } from '../../types/caip';\nimport type { Fetch } from '../../types/utility-types';\n\nexport interface ApiOptions {\n apiBaseUrl: URL;\n apiToken?: string;\n fetch?: Fetch;\n}\n\nfunction getAuthHeaders(apiToken?: string): Record<string, string> | undefined {\n if (!apiToken) {\n return undefined;\n }\n\n return { Authorization: `Bearer ${apiToken}` };\n}\n\nexport async function markrGetInfoChains({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<SupportedChainsResponse> {\n const endpoint = '/info/chains';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SupportedChainsResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetTokenList(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n chainId: number,\n): Promise<TokenListResponse> {\n const endpoint = `/tokens/${chainId}/list`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = TokenListResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport interface MarkrStreamQuoteParams {\n amount: string;\n appId: string;\n chainId: number | Caip2ChainId;\n destinationChainId?: number | Caip2ChainId;\n slippage?: number;\n tokenIn: string;\n tokenInDecimals: number;\n tokenOut: string;\n tokenOutDecimals: number;\n /**\n * Required ONLY for cross-chain swaps involving EVM.\n *\n * EVM -> EVM (different chains)\n * EVM -> Solana\n * Solana -> EVM\n */\n userEvmAddress?: EvmAddress;\n /**\n * Required ONLY for cross-chain swaps involving Solana.\n *\n * Solana -> EVM\n * EVM -> Solana\n * Solana -> Solana\n */\n userSolanaAddress?: SolanaAddress;\n}\n\nexport interface MarkrStreamQuoteOptions {\n onDone: () => void;\n onError: (error: Error) => void;\n onQuote: (data: QuoteResponseData) => void;\n signal?: AbortSignal;\n}\n\nexport async function markrStreamQuote(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrStreamQuoteParams,\n { onDone, onError, onQuote, signal }: MarkrStreamQuoteOptions,\n): Promise<ReadonlyArray<QuoteResponseData>> {\n const endpoint = '/quote';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const quotes: QuoteResponseData[] = [];\n\n try {\n for await (const event of fetchEventStream(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n signal,\n })) {\n const parsedData = QuoteResponseSchema.safeParse(JSON.parse(event.data));\n\n if (!parsedData.success) {\n onError(\n new ResponseValidationError(\n `Invalid quote data received from Markr \"${endpoint}\" endpoint.`,\n parsedData.error.issues,\n ),\n );\n continue;\n }\n\n if (!('done' in parsedData.data)) {\n onQuote(parsedData.data);\n quotes.push(parsedData.data);\n } else {\n onDone();\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n onError(error);\n } else {\n onError(new Error('An unknown error occurred during the quote stream.', { cause: error }));\n }\n\n onDone();\n }\n\n return quotes;\n}\n\nexport interface MarkrGetSwapParams {\n appId: string;\n /** Input amount in token's smallest unit */\n amountIn: string;\n /** Return raw transaction data alongside wrapped data */\n debug?: boolean;\n /** Minimum expected output amount */\n minAmountOut: string;\n /** Input token contract address */\n tokenIn: string;\n /** Output token contract address */\n tokenOut: string;\n /** Solana wallet public key (required for Solana swaps) */\n userPublicKey?: string;\n /** Quote UUID from previous quote request */\n uuid: string;\n}\n\nexport async function markrSwap(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrGetSwapParams,\n): Promise<SwapResponse> {\n const endpoint = '/swap';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n });\n\n const parsed = SwapResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetPartnerInfo({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<PartnerInfoResponse> {\n const endpoint = '/info/partner';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = PartnerInfoResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetCrossChainStatus(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n sourceTxHash: string,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<CrossChainStatusResponse> {\n const endpoint = `/cross-chain/status/${sourceTxHash}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n headers: getAuthHeaders(apiToken),\n fetch: customFetch,\n retries: 10,\n retryOn(ctx) {\n const method = (ctx.init.method ?? 'GET').toUpperCase();\n\n if (!['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method)) {\n return false;\n }\n\n if (ctx.error) {\n if (ctx.error instanceof AbortedError) {\n return false;\n }\n\n return true;\n }\n\n const status = ctx.response?.status ?? 0;\n\n // Retry on 404 requests since the endpoint may not return a result immediately after the source transaction is executed.\n if (status === 404 || status === 408 || status === 425 || status === 429 || (status >= 500 && status <= 599)) {\n return true;\n }\n\n return false;\n },\n signal,\n });\n\n const parsed = CrossChainStatusResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(\n `Invalid response from Markr \"${endpoint}\" endpoint.`,\n parsed.error.issues,\n rawJson,\n );\n }\n\n return parsed.data;\n}\n\n/**\n * Returns the spender address (wrapped contract) that needs to be approved\n * for token swaps on the specified chain.\n *\n * Not applicable for Solana.\n */\nexport async function markrGetSpenderAddress(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n {\n chainId,\n crossChainSwap,\n quoteId,\n }: {\n chainId: number;\n crossChainSwap?: boolean;\n quoteId: string;\n },\n): Promise<SpenderAddressResponse> {\n const endpoint = `/spender-address?chainId=${chainId}&cross=${crossChainSwap ? 'true' : 'false'}&uuid=${quoteId}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SpenderAddressResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\n// ---------------------------------------------------------------------------\n// Hyperliquid 2-phase withdrawal: nonce-mapping authorize\n// ---------------------------------------------------------------------------\n\n/**\n * Legacy (`protocolVersion: 1`) authorize body — Markr rebuilds the upstream\n * Relay request from a fixed schema. Kept for back-compat; new integrations\n * should use {@link MarkrPostAuthorizeParamsV2}.\n *\n * **Caveat:** when Relay adds a required field (as it did with `depositor` in\n * v2), the rebuilt body silently omits it and the order is orphaned upstream.\n */\nexport interface MarkrPostAuthorizeParamsV1 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over the `RelayNonceMapping` typed data (`0x{r}{s}{v}`, 65 bytes hex). */\n readonly signature: `0x${string}`;\n /** EVM address of the signer. */\n readonly wallet: EvmAddress;\n /** Always `1337` (Hyperliquid). */\n readonly walletChainId: 1337;\n /** EIP-712 domain.chainId used in the signature — the wallet's active chain. */\n readonly signatureChainId: number;\n /**\n * Shared nonce — JSON number, matching Relay's upstream `/authorize` contract\n * and HL `/exchange`. HL nonces are millisecond timestamps, well under\n * `Number.MAX_SAFE_INTEGER`. Sending it as a string makes Relay reject the\n * payload (strict type validation), so we coerce here at the wire boundary.\n */\n readonly nonce: number;\n /** Relay request id (`0x{64hex}`). */\n readonly id: `0x${string}`;\n}\n\n/**\n * Pass-through (`protocolVersion: 2`) authorize body. The SDK forwards Relay's\n * `authorize.body` envelope from the `/swap` response **verbatim**. Markr only\n * normalizes `nonce` to a JSON number and defaults `referrer`; every other\n * field is untouched, so additive Relay schema changes don't break SDKs.\n */\nexport interface MarkrPostAuthorizeParamsV2 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over `swap.sign.value` using `swap.sign.domain` / `swap.sign.types`. */\n readonly signature: `0x${string}`;\n /** Relay's `/authorize` POST body, taken from `swap.authorize.body`. Forwarded verbatim. */\n readonly body: Record<string, unknown>;\n}\n\nexport type MarkrPostAuthorizeParams = MarkrPostAuthorizeParamsV1 | MarkrPostAuthorizeParamsV2;\n\n/**\n * Submit the user's signed nonce-mapping to Markr's `/authorize` endpoint.\n *\n * Markr proxies the request to Relay's upstream `/authorize`. Returns on any\n * 2xx; throws `HttpError` otherwise.\n */\nexport async function markrPostAuthorize(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrPostAuthorizeParams,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<void> {\n const endpoint = '/authorize';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n // `fetchJson` throws `HttpError` on non-2xx; the schema additionally rejects\n // upstream error envelopes that come back with a 200 (Relay sometimes does\n // this when authorize is rejected after HTTP success).\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n signal,\n });\n\n const parsed = MarkrAuthorizeResponseSchema.safeParse(rawJson);\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"_api.js","names":[],"sources":["../../../src/transfer-service/markr/_api.ts"],"sourcesContent":["import type { Address as SolanaAddress } from '@solana/kit';\nimport type { Address as EvmAddress } from 'viem';\nimport { AbortedError, ResponseValidationError } from '../../errors';\nimport { combineUrlPathnames, fetchEventStream, fetchJson } from '../fetch-utilities';\nimport {\n CrossChainStatusResponseSchema,\n MarkrAuthorizeResponseSchema,\n PartnerInfoResponseSchema,\n QuoteResponseSchema,\n SpenderAddressResponseSchema,\n SupportedChainsResponseSchema,\n SwapResponseSchema,\n TokenListResponseSchema,\n type CrossChainStatusResponse,\n type PartnerInfoResponse,\n type QuoteResponseData,\n type SpenderAddressResponse,\n type SupportedChainsResponse,\n type SwapResponse,\n type TokenListResponse,\n} from './_schema';\nimport type { Caip2ChainId } from '../../types/caip';\nimport type { Fetch } from '../../types/utility-types';\n\nexport interface ApiOptions {\n apiBaseUrl: URL;\n apiToken?: string;\n fetch?: Fetch;\n}\n\nfunction getAuthHeaders(apiToken?: string): Record<string, string> | undefined {\n if (!apiToken) {\n return undefined;\n }\n\n return { Authorization: `Bearer ${apiToken}` };\n}\n\nexport async function markrGetInfoChains({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<SupportedChainsResponse> {\n const endpoint = '/info/chains';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SupportedChainsResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetTokenList(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n chainId: number,\n): Promise<TokenListResponse> {\n const endpoint = `/tokens/${chainId}/list`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = TokenListResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport interface MarkrStreamQuoteParams {\n amount: string;\n /**\n * Pin the quote to a single upstream aggregator (e.g. `\"relay\"`). When\n * omitted, Markr races every eligible aggregator. Used to obtain\n * native-fee-free routes that only a specific aggregator supports.\n */\n aggregator?: string;\n appId: string;\n chainId: number | Caip2ChainId;\n destinationChainId?: number | Caip2ChainId;\n slippage?: number;\n tokenIn: string;\n tokenInDecimals: number;\n tokenOut: string;\n tokenOutDecimals: number;\n /**\n * Required ONLY for cross-chain swaps involving EVM.\n *\n * EVM -> EVM (different chains)\n * EVM -> Solana\n * Solana -> EVM\n */\n userEvmAddress?: EvmAddress;\n /**\n * Required ONLY for cross-chain swaps involving Solana.\n *\n * Solana -> EVM\n * EVM -> Solana\n * Solana -> Solana\n */\n userSolanaAddress?: SolanaAddress;\n}\n\nexport interface MarkrStreamQuoteOptions {\n onDone: () => void;\n onError: (error: Error) => void;\n onQuote: (data: QuoteResponseData) => void;\n signal?: AbortSignal;\n}\n\nexport async function markrStreamQuote(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrStreamQuoteParams,\n { onDone, onError, onQuote, signal }: MarkrStreamQuoteOptions,\n): Promise<ReadonlyArray<QuoteResponseData>> {\n const endpoint = '/quote';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const quotes: QuoteResponseData[] = [];\n\n try {\n for await (const event of fetchEventStream(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n signal,\n })) {\n const parsedData = QuoteResponseSchema.safeParse(JSON.parse(event.data));\n\n if (!parsedData.success) {\n onError(\n new ResponseValidationError(\n `Invalid quote data received from Markr \"${endpoint}\" endpoint.`,\n parsedData.error.issues,\n ),\n );\n continue;\n }\n\n if (!('done' in parsedData.data)) {\n onQuote(parsedData.data);\n quotes.push(parsedData.data);\n } else {\n onDone();\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n onError(error);\n } else {\n onError(new Error('An unknown error occurred during the quote stream.', { cause: error }));\n }\n\n onDone();\n }\n\n return quotes;\n}\n\nexport interface MarkrGetSwapParams {\n appId: string;\n /** Input amount in token's smallest unit */\n amountIn: string;\n /** Return raw transaction data alongside wrapped data */\n debug?: boolean;\n /** Minimum expected output amount */\n minAmountOut: string;\n /** Input token contract address */\n tokenIn: string;\n /** Output token contract address */\n tokenOut: string;\n /** Solana wallet public key (required for Solana swaps) */\n userPublicKey?: string;\n /** Quote UUID from previous quote request */\n uuid: string;\n}\n\nexport async function markrSwap(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrGetSwapParams,\n): Promise<SwapResponse> {\n const endpoint = '/swap';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n });\n\n const parsed = SwapResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetPartnerInfo({\n apiBaseUrl,\n apiToken,\n fetch: customFetch,\n}: ApiOptions): Promise<PartnerInfoResponse> {\n const endpoint = '/info/partner';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = PartnerInfoResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\nexport async function markrGetCrossChainStatus(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n sourceTxHash: string,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<CrossChainStatusResponse> {\n const endpoint = `/cross-chain/status/${sourceTxHash}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, {\n headers: getAuthHeaders(apiToken),\n fetch: customFetch,\n retries: 10,\n retryOn(ctx) {\n const method = (ctx.init.method ?? 'GET').toUpperCase();\n\n if (!['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method)) {\n return false;\n }\n\n if (ctx.error) {\n if (ctx.error instanceof AbortedError) {\n return false;\n }\n\n return true;\n }\n\n const status = ctx.response?.status ?? 0;\n\n // Retry on 404 requests since the endpoint may not return a result immediately after the source transaction is executed.\n if (status === 404 || status === 408 || status === 425 || status === 429 || (status >= 500 && status <= 599)) {\n return true;\n }\n\n return false;\n },\n signal,\n });\n\n const parsed = CrossChainStatusResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(\n `Invalid response from Markr \"${endpoint}\" endpoint.`,\n parsed.error.issues,\n rawJson,\n );\n }\n\n return parsed.data;\n}\n\n/**\n * Returns the spender address (wrapped contract) that needs to be approved\n * for token swaps on the specified chain.\n *\n * Not applicable for Solana.\n */\nexport async function markrGetSpenderAddress(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n {\n chainId,\n crossChainSwap,\n quoteId,\n }: {\n chainId: number;\n crossChainSwap?: boolean;\n quoteId: string;\n },\n): Promise<SpenderAddressResponse> {\n const endpoint = `/spender-address?chainId=${chainId}&cross=${crossChainSwap ? 'true' : 'false'}&uuid=${quoteId}`;\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n const rawJson = await fetchJson(url, { headers: getAuthHeaders(apiToken), fetch: customFetch });\n\n const parsed = SpenderAddressResponseSchema.safeParse(rawJson);\n\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n\n return parsed.data;\n}\n\n// ---------------------------------------------------------------------------\n// Hyperliquid 2-phase withdrawal: nonce-mapping authorize\n// ---------------------------------------------------------------------------\n\n/**\n * Legacy (`protocolVersion: 1`) authorize body — Markr rebuilds the upstream\n * Relay request from a fixed schema. Kept for back-compat; new integrations\n * should use {@link MarkrPostAuthorizeParamsV2}.\n *\n * **Caveat:** when Relay adds a required field (as it did with `depositor` in\n * v2), the rebuilt body silently omits it and the order is orphaned upstream.\n */\nexport interface MarkrPostAuthorizeParamsV1 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over the `RelayNonceMapping` typed data (`0x{r}{s}{v}`, 65 bytes hex). */\n readonly signature: `0x${string}`;\n /** EVM address of the signer. */\n readonly wallet: EvmAddress;\n /** Always `1337` (Hyperliquid). */\n readonly walletChainId: 1337;\n /** EIP-712 domain.chainId used in the signature — the wallet's active chain. */\n readonly signatureChainId: number;\n /**\n * Shared nonce — JSON number, matching Relay's upstream `/authorize` contract\n * and HL `/exchange`. HL nonces are millisecond timestamps, well under\n * `Number.MAX_SAFE_INTEGER`. Sending it as a string makes Relay reject the\n * payload (strict type validation), so we coerce here at the wire boundary.\n */\n readonly nonce: number;\n /** Relay request id (`0x{64hex}`). */\n readonly id: `0x${string}`;\n}\n\n/**\n * Pass-through (`protocolVersion: 2`) authorize body. The SDK forwards Relay's\n * `authorize.body` envelope from the `/swap` response **verbatim**. Markr only\n * normalizes `nonce` to a JSON number and defaults `referrer`; every other\n * field is untouched, so additive Relay schema changes don't break SDKs.\n */\nexport interface MarkrPostAuthorizeParamsV2 {\n /** Quote uuid — lets Markr correlate the authorize call with the cached Relay step. */\n readonly uuid: string;\n /** User's EIP-712 signature over `swap.sign.value` using `swap.sign.domain` / `swap.sign.types`. */\n readonly signature: `0x${string}`;\n /** Relay's `/authorize` POST body, taken from `swap.authorize.body`. Forwarded verbatim. */\n readonly body: Record<string, unknown>;\n}\n\nexport type MarkrPostAuthorizeParams = MarkrPostAuthorizeParamsV1 | MarkrPostAuthorizeParamsV2;\n\n/**\n * Submit the user's signed nonce-mapping to Markr's `/authorize` endpoint.\n *\n * Markr proxies the request to Relay's upstream `/authorize`. Returns on any\n * 2xx; throws `HttpError` otherwise.\n */\nexport async function markrPostAuthorize(\n { apiBaseUrl, apiToken, fetch: customFetch }: ApiOptions,\n params: MarkrPostAuthorizeParams,\n { signal }: { signal?: AbortSignal } = {},\n): Promise<void> {\n const endpoint = '/authorize';\n const url = combineUrlPathnames(apiBaseUrl, endpoint);\n\n // `fetchJson` throws `HttpError` on non-2xx; the schema additionally rejects\n // upstream error envelopes that come back with a 200 (Relay sometimes does\n // this when authorize is rejected after HTTP success).\n const rawJson = await fetchJson(url, {\n body: JSON.stringify(params),\n fetch: customFetch,\n headers: getAuthHeaders(apiToken),\n method: 'POST',\n signal,\n });\n\n const parsed = MarkrAuthorizeResponseSchema.safeParse(rawJson);\n if (!parsed.success) {\n throw new ResponseValidationError(`Invalid response from Markr \"${endpoint}\" endpoint.`, parsed.error.issues);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../errors.cjs`),t=require(`../../../utils/caip.cjs`),n=require(`../../_utils.cjs`),r=require(`../../../_utils/chain.cjs`),i=require(`../../_evm-gas.cjs`),a=require(`../../_evm-errors.cjs`),o=require(`../_api.cjs`),s=require(`../_utils.cjs`),c=require(`../_type-guards.cjs`),l=require(`../../../utils/solana.cjs`);let u=require(`viem`),d=require(`@solana/kit`);function f(t){return async(n,i)=>{if(r.isEvmNamespace(n.sourceChain.chainId))return await p(n,i,t);if(r.isSolanaNamespace(n.sourceChain.chainId))return await m(n,i,t);throw new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Unsupported source chain namespace for estimateNativeFee: ${n.sourceChain.chainId}`)}}async function p(r,l,{apiOptions:d,appId:f}){if(!(0,u.isAddress)(r.fromAddress))throw new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Invalid fromAddress: ${r.fromAddress}`);let p=n.getEvmClientForChain({chain:r.sourceChain}),m=s.assetToAddressString(r.assetIn,r.sourceChain.chainId);if(!(0,u.isAddress)(m))throw new e.SdkError(e.ErrorReason.INVALID_PARAMS,e.ErrorCode.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address. Can not call estimateGas.`});let g=s.isTokenAddressNative(m),_=r.sourceChain.chainId.toLowerCase()!==r.targetChain.chainId.toLowerCase(),v=r.amountIn+s.getAdditiveSourceAssetFeeAmount(r),y=0n;if(!g){let n,{address:i}=await o.markrGetSpenderAddress(d,{chainId:t.caip2ToEip155ChainId(r.sourceChain.chainId),crossChainSwap:_,quoteId:r.id});try{n=await p.readContract({address:m,abi:u.erc20Abi,functionName:`allowance`,args:[r.fromAddress,i]})}catch(t){throw new e.SdkError(`Error during allowance check`,e.ErrorCode.VIEM_ERROR,{cause:t,details:`Failed to read ERC20 allowance for Markr spender.`})}n<v&&(y=await a.estimateGasWithRevert(p,{account:r.fromAddress,to:m,data:(0,u.encodeFunctionData)({abi:u.erc20Abi,functionName:`approve`,args:[i,v]})},u.erc20Abi,`Failed to estimate gas for ERC20 approval transaction.`))}let b=y>0n,x=s.calculateMarkrMinimumAmountOut({amountOut:r.amountOut,assetOut:r.assetOut,slippageBps:r.slippageBps}),S=await o.markrSwap(d,{amountIn:r.amountIn.toString(),appId:f,minAmountOut:x.toString(),tokenIn:m,tokenOut:s.assetToAddressString(r.assetOut,r.targetChain.chainId),uuid:r.id});if(!c.isEvmSwapResponse(S)){let t=c.isSolanaSwapResponse(S)?S.chainType:c.isHyperliquidWithdrawSwapResponse(S)?S.type:`unknown`;throw new e.SdkError(e.ErrorReason.CHAIN_NOT_SUPPORTED,e.ErrorCode.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant "${t}".`})}let C=0n;C=b?r.gasEstimate
|
|
1
|
+
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../errors.cjs`),t=require(`../../../utils/caip.cjs`),n=require(`../../_utils.cjs`),r=require(`../../../_utils/chain.cjs`),i=require(`../../_evm-gas.cjs`),a=require(`../../_evm-errors.cjs`),o=require(`../_api.cjs`),s=require(`../_utils.cjs`),c=require(`../_type-guards.cjs`),l=require(`../../../utils/solana.cjs`);let u=require(`viem`),d=require(`@solana/kit`);function f(t){return async(n,i)=>{if(r.isEvmNamespace(n.sourceChain.chainId))return await p(n,i,t);if(r.isSolanaNamespace(n.sourceChain.chainId))return await m(n,i,t);throw new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Unsupported source chain namespace for estimateNativeFee: ${n.sourceChain.chainId}`)}}async function p(r,l,{apiOptions:d,appId:f}){if(!(0,u.isAddress)(r.fromAddress))throw new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Invalid fromAddress: ${r.fromAddress}`);let p=n.getEvmClientForChain({chain:r.sourceChain}),m=s.assetToAddressString(r.assetIn,r.sourceChain.chainId);if(!(0,u.isAddress)(m))throw new e.SdkError(e.ErrorReason.INVALID_PARAMS,e.ErrorCode.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address. Can not call estimateGas.`});let g=s.isTokenAddressNative(m),_=r.sourceChain.chainId.toLowerCase()!==r.targetChain.chainId.toLowerCase(),v=r.amountIn+s.getAdditiveSourceAssetFeeAmount(r),y=0n;if(!g){let n,{address:i}=await o.markrGetSpenderAddress(d,{chainId:t.caip2ToEip155ChainId(r.sourceChain.chainId),crossChainSwap:_,quoteId:r.id});try{n=await p.readContract({address:m,abi:u.erc20Abi,functionName:`allowance`,args:[r.fromAddress,i]})}catch(t){throw new e.SdkError(`Error during allowance check`,e.ErrorCode.VIEM_ERROR,{cause:t,details:`Failed to read ERC20 allowance for Markr spender.`})}n<v&&(y=await a.estimateGasWithRevert(p,{account:r.fromAddress,to:m,data:(0,u.encodeFunctionData)({abi:u.erc20Abi,functionName:`approve`,args:[i,v]})},u.erc20Abi,`Failed to estimate gas for ERC20 approval transaction.`))}let b=y>0n,x=s.calculateMarkrMinimumAmountOut({amountOut:r.amountOut,assetOut:r.assetOut,slippageBps:r.slippageBps}),S=await o.markrSwap(d,{amountIn:r.amountIn.toString(),appId:f,minAmountOut:x.toString(),tokenIn:m,tokenOut:s.assetToAddressString(r.assetOut,r.targetChain.chainId),uuid:r.id});if(!c.isEvmSwapResponse(S)){let t=c.isSolanaSwapResponse(S)?S.chainType:c.isHyperliquidWithdrawSwapResponse(S)?S.type:`unknown`;throw new e.SdkError(e.ErrorReason.CHAIN_NOT_SUPPORTED,e.ErrorCode.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant "${t}".`})}let C=0n;C=b?r.gasEstimate||700000n:await h({crossChain:_,fromAddress:r.fromAddress,sourceClient:p,swap:S});let w=await i.estimateEvmFeesPerGas(p,r.sourceChain,l?.overrides?.feeRateTier),T=l?.overrides?.maxFeePerGas??w.maxFeePerGas,E=l?.overrides?.maxPriorityFeePerGas??w.maxPriorityFeePerGas,D=(C+y)*T;return{asset:r.sourceChain.networkToken,totalFee:n.applyFeeUnitsBpsMargin(D,l?.feeUnitsMarginBps),totalFeeWithoutMargin:D,meta:{approvalFee:b?y*T:void 0,maxFeePerGas:T,maxPriorityFeePerGas:E}}}async function m(t,r,{apiOptions:i,appId:a}){let u=s.assetToAddressString(t.assetIn,t.sourceChain.chainId);if(!(0,d.isAddress)(u))throw new e.SdkError(e.ErrorReason.INVALID_PARAMS,e.ErrorCode.INVALID_PARAMS,{details:`assetIn address is not a valid Solana address. Can not call estimateGas.`});let f=s.getAdditiveSourceNativeAssetFeeAmount(t),p=t.amountIn+f,m=s.calculateMarkrMinimumAmountOut({amountOut:t.amountOut,assetOut:t.assetOut,slippageBps:t.slippageBps}),h=await o.markrSwap(i,{amountIn:t.amountIn.toString(),appId:a,minAmountOut:m.toString(),tokenIn:u,tokenOut:s.assetToAddressString(t.assetOut,t.targetChain.chainId),userPublicKey:t.fromAddress,uuid:t.id});if(!c.isSolanaSwapResponse(h))throw new e.SdkError(e.ErrorReason.CHAIN_NOT_SUPPORTED,e.ErrorCode.INVALID_PARAMS,{details:`Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.`});let{baseFeeLamports:g,priorityFeeLamports:_,...v}=await l.estimateSolanaFee(n.getSolanaRpcForChain({chain:t.sourceChain}),h.swapTransaction),y=v.feePayerBalanceDiffLamports-p;return{asset:t.sourceChain.networkToken,totalFee:n.applyFeeUnitsBpsMargin(y,r?.feeUnitsMarginBps),totalFeeWithoutMargin:y,meta:{...v,baseFee:g,priorityFee:_}}}async function h({crossChain:e,feeUnitsMarginBps:t,fromAddress:r,sourceClient:i,swap:o}){let c=await s.getMarkrSwapWrapperAbi(e);return n.applyFeeUnitsBpsMargin(await a.estimateGasWithRevert(i,{account:r,to:o.to,data:o.data,value:o.value},c,`Failed to estimate gas for Markr swap transaction.`),t)}exports._estimateGasFromSwapResponse=h,exports.estimateNativeFeeFactory=f;
|
|
2
2
|
//# sourceMappingURL=estimate-native-fee.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estimate-native-fee.cjs","names":["isEvmNamespace","isSolanaNamespace","InvalidParamsError","ErrorReason","getEvmClientForChain","assetToAddressString","SdkError","ErrorCode","isTokenAddressNative","getAdditiveSourceAssetFeeAmount","markrGetSpenderAddress","caip2ToEip155ChainId","erc20Abi","estimateGasWithRevert","calculateMarkrMinimumAmountOut","markrSwap","isEvmSwapResponse","isSolanaSwapResponse","isHyperliquidWithdrawSwapResponse","estimateEvmFeesPerGas","applyFeeUnitsBpsMargin","getAdditiveSourceNativeAssetFeeAmount","estimateSolanaFee","getSolanaRpcForChain","getMarkrSwapWrapperAbi"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import { isAddress as isSolanaAddress } from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { estimateGasWithRevert } from '../../_evm-errors';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n getAdditiveSourceAssetFeeAmount,\n getAdditiveSourceNativeAssetFeeAmount,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isHyperliquidWithdrawSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { estimateSolanaFee } from '../../../utils/solana';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n const allowanceAmount = quote.amountIn + getAdditiveSourceAssetFeeAmount(quote);\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < allowanceAmount;\n\n if (approvalNeeded) {\n allowanceApprovalGas = await estimateGasWithRevert(\n sourceClient,\n {\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, allowanceAmount],\n }),\n },\n erc20Abi,\n 'Failed to estimate gas for ERC20 approval transaction.',\n );\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n const variantTag = isSolanaSwapResponse(swap)\n ? swap.chainType\n : isHyperliquidWithdrawSwapResponse(swap)\n ? swap.type\n : 'unknown';\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant \"${variantTag}\".`,\n });\n }\n\n let gas = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // Attempt to use the Markr provided `gasEstimate` first if available.\n // Otherwise we attempt to get the gas from the `fees` component that are applicable\n // to the source chain. If neither of those are available, we fall back to a hardcoded value.\n if (quote.gasEstimate) {\n gas = quote.gasEstimate;\n } else {\n const sourceGasFee = quote.fees\n .filter((fee) => fee.type === 'gas' && fee.chainId === quote.sourceChain.chainId)\n .reduce((acc, fee) => acc + fee.amount, 0n);\n\n gas = sourceGasFee || EVM_SWAP_FALLBACK_GAS_ESTIMATE;\n }\n } else {\n gas = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFeeWithoutMargin = (gas + allowanceApprovalGas) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n approvalFee: allowanceApprovalIsNeeded ? allowanceApprovalGas * maxFeePerGas : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const additiveNativeFee: bigint = getAdditiveSourceNativeAssetFeeAmount(quote);\n const totalInput: bigint = quote.amountIn + additiveNativeFee;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n\n const { baseFeeLamports, priorityFeeLamports, ...rest } = await estimateSolanaFee(rpc, swap.swapTransaction);\n\n const totalFeeWithoutMargin = rest.feePayerBalanceDiffLamports - totalInput;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n ...rest,\n baseFee: baseFeeLamports,\n priorityFee: priorityFeeLamports,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n const markrAbi = await getMarkrSwapWrapperAbi(crossChain);\n\n // Use raw estimateGas — the calldata is pre-encoded and may target any\n // contract (Markr wrapper, deBridge, etc.). The ABI is only used for\n // best-effort error decoding on the failure path.\n const gasEstimate = await estimateGasWithRevert(\n sourceClient,\n {\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n },\n markrAbi,\n 'Failed to estimate gas for Markr swap transaction.',\n );\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"estimate-native-fee.cjs","names":["isEvmNamespace","isSolanaNamespace","InvalidParamsError","ErrorReason","getEvmClientForChain","assetToAddressString","SdkError","ErrorCode","isTokenAddressNative","getAdditiveSourceAssetFeeAmount","markrGetSpenderAddress","caip2ToEip155ChainId","erc20Abi","estimateGasWithRevert","calculateMarkrMinimumAmountOut","markrSwap","isEvmSwapResponse","isSolanaSwapResponse","isHyperliquidWithdrawSwapResponse","estimateEvmFeesPerGas","applyFeeUnitsBpsMargin","getAdditiveSourceNativeAssetFeeAmount","estimateSolanaFee","getSolanaRpcForChain","getMarkrSwapWrapperAbi"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import { isAddress as isSolanaAddress } from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { estimateGasWithRevert } from '../../_evm-errors';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n getAdditiveSourceAssetFeeAmount,\n getAdditiveSourceNativeAssetFeeAmount,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isHyperliquidWithdrawSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { estimateSolanaFee } from '../../../utils/solana';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n const allowanceAmount = quote.amountIn + getAdditiveSourceAssetFeeAmount(quote);\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < allowanceAmount;\n\n if (approvalNeeded) {\n allowanceApprovalGas = await estimateGasWithRevert(\n sourceClient,\n {\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, allowanceAmount],\n }),\n },\n erc20Abi,\n 'Failed to estimate gas for ERC20 approval transaction.',\n );\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n const variantTag = isSolanaSwapResponse(swap)\n ? swap.chainType\n : isHyperliquidWithdrawSwapResponse(swap)\n ? swap.type\n : 'unknown';\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant \"${variantTag}\".`,\n });\n }\n\n let gas = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // While the approval is still pending we can't `eth_estimateGas` the swap (it would\n // revert on the missing allowance), so use Markr's gas-unit estimate when present and\n // otherwise a fixed gas-unit fallback. `gasEstimate` is documented as optional and may\n // be 0/omitted for relay.\n //\n // We deliberately do NOT derive this from the `fees` array. A `type: 'gas'` fee is a\n // token-denominated *cost amount* (e.g. native wei), not a gas-unit count — feeding it\n // into the `gas * maxFeePerGas` formula below produces a nonsensical fee.\n gas = quote.gasEstimate || EVM_SWAP_FALLBACK_GAS_ESTIMATE;\n } else {\n gas = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFeeWithoutMargin = (gas + allowanceApprovalGas) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n approvalFee: allowanceApprovalIsNeeded ? allowanceApprovalGas * maxFeePerGas : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const additiveNativeFee: bigint = getAdditiveSourceNativeAssetFeeAmount(quote);\n const totalInput: bigint = quote.amountIn + additiveNativeFee;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n\n const { baseFeeLamports, priorityFeeLamports, ...rest } = await estimateSolanaFee(rpc, swap.swapTransaction);\n\n const totalFeeWithoutMargin = rest.feePayerBalanceDiffLamports - totalInput;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n ...rest,\n baseFee: baseFeeLamports,\n priorityFee: priorityFeeLamports,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n const markrAbi = await getMarkrSwapWrapperAbi(crossChain);\n\n // Use raw estimateGas — the calldata is pre-encoded and may target any\n // contract (Markr wrapper, deBridge, etc.). The ABI is only used for\n // best-effort error decoding on the failure path.\n const gasEstimate = await estimateGasWithRevert(\n sourceClient,\n {\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n },\n markrAbi,\n 'Failed to estimate gas for Markr swap transaction.',\n );\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ErrorCode as e,ErrorReason as t,InvalidParamsError as n,SdkError as r}from"../../../errors.js";import{caip2ToEip155ChainId as i}from"../../../utils/caip.js";import{applyFeeUnitsBpsMargin as a,getEvmClientForChain as o,getSolanaRpcForChain as s}from"../../_utils.js";import{isEvmNamespace as c,isSolanaNamespace as l}from"../../../_utils/chain.js";import{estimateEvmFeesPerGas as u}from"../../_evm-gas.js";import{estimateGasWithRevert as d}from"../../_evm-errors.js";import{markrGetSpenderAddress as f,markrSwap as p}from"../_api.js";import{assetToAddressString as m,calculateMarkrMinimumAmountOut as h,getAdditiveSourceAssetFeeAmount as g,getAdditiveSourceNativeAssetFeeAmount as _,getMarkrSwapWrapperAbi as v,isTokenAddressNative as y}from"../_utils.js";import{isEvmSwapResponse as b,isHyperliquidWithdrawSwapResponse as x,isSolanaSwapResponse as S}from"../_type-guards.js";import{estimateSolanaFee as C}from"../../../utils/solana.js";import{encodeFunctionData as w,erc20Abi as T,isAddress as E}from"viem";import{isAddress as D}from"@solana/kit";function O(e){return async(r,i)=>{if(c(r.sourceChain.chainId))return await k(r,i,e);if(l(r.sourceChain.chainId))return await A(r,i,e);throw new n(t.INVALID_PARAMS,`Unsupported source chain namespace for estimateNativeFee: ${r.sourceChain.chainId}`)}}async function k(s,c,{apiOptions:l,appId:_}){if(!E(s.fromAddress))throw new n(t.INVALID_PARAMS,`Invalid fromAddress: ${s.fromAddress}`);let v=o({chain:s.sourceChain}),C=m(s.assetIn,s.sourceChain.chainId);if(!E(C))throw new r(t.INVALID_PARAMS,e.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address. Can not call estimateGas.`});let D=y(C),O=s.sourceChain.chainId.toLowerCase()!==s.targetChain.chainId.toLowerCase(),k=s.amountIn+g(s),A=0n;if(!D){let t,{address:n}=await f(l,{chainId:i(s.sourceChain.chainId),crossChainSwap:O,quoteId:s.id});try{t=await v.readContract({address:C,abi:T,functionName:`allowance`,args:[s.fromAddress,n]})}catch(t){throw new r(`Error during allowance check`,e.VIEM_ERROR,{cause:t,details:`Failed to read ERC20 allowance for Markr spender.`})}t<k&&(A=await d(v,{account:s.fromAddress,to:C,data:w({abi:T,functionName:`approve`,args:[n,k]})},T,`Failed to estimate gas for ERC20 approval transaction.`))}let M=A>0n,N=h({amountOut:s.amountOut,assetOut:s.assetOut,slippageBps:s.slippageBps}),P=await p(l,{amountIn:s.amountIn.toString(),appId:_,minAmountOut:N.toString(),tokenIn:C,tokenOut:m(s.assetOut,s.targetChain.chainId),uuid:s.id});if(!b(P)){let n=S(P)?P.chainType:x(P)?P.type:`unknown`;throw new r(t.CHAIN_NOT_SUPPORTED,e.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant "${n}".`})}let F=0n;F=M?s.gasEstimate
|
|
1
|
+
import{ErrorCode as e,ErrorReason as t,InvalidParamsError as n,SdkError as r}from"../../../errors.js";import{caip2ToEip155ChainId as i}from"../../../utils/caip.js";import{applyFeeUnitsBpsMargin as a,getEvmClientForChain as o,getSolanaRpcForChain as s}from"../../_utils.js";import{isEvmNamespace as c,isSolanaNamespace as l}from"../../../_utils/chain.js";import{estimateEvmFeesPerGas as u}from"../../_evm-gas.js";import{estimateGasWithRevert as d}from"../../_evm-errors.js";import{markrGetSpenderAddress as f,markrSwap as p}from"../_api.js";import{assetToAddressString as m,calculateMarkrMinimumAmountOut as h,getAdditiveSourceAssetFeeAmount as g,getAdditiveSourceNativeAssetFeeAmount as _,getMarkrSwapWrapperAbi as v,isTokenAddressNative as y}from"../_utils.js";import{isEvmSwapResponse as b,isHyperliquidWithdrawSwapResponse as x,isSolanaSwapResponse as S}from"../_type-guards.js";import{estimateSolanaFee as C}from"../../../utils/solana.js";import{encodeFunctionData as w,erc20Abi as T,isAddress as E}from"viem";import{isAddress as D}from"@solana/kit";function O(e){return async(r,i)=>{if(c(r.sourceChain.chainId))return await k(r,i,e);if(l(r.sourceChain.chainId))return await A(r,i,e);throw new n(t.INVALID_PARAMS,`Unsupported source chain namespace for estimateNativeFee: ${r.sourceChain.chainId}`)}}async function k(s,c,{apiOptions:l,appId:_}){if(!E(s.fromAddress))throw new n(t.INVALID_PARAMS,`Invalid fromAddress: ${s.fromAddress}`);let v=o({chain:s.sourceChain}),C=m(s.assetIn,s.sourceChain.chainId);if(!E(C))throw new r(t.INVALID_PARAMS,e.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address. Can not call estimateGas.`});let D=y(C),O=s.sourceChain.chainId.toLowerCase()!==s.targetChain.chainId.toLowerCase(),k=s.amountIn+g(s),A=0n;if(!D){let t,{address:n}=await f(l,{chainId:i(s.sourceChain.chainId),crossChainSwap:O,quoteId:s.id});try{t=await v.readContract({address:C,abi:T,functionName:`allowance`,args:[s.fromAddress,n]})}catch(t){throw new r(`Error during allowance check`,e.VIEM_ERROR,{cause:t,details:`Failed to read ERC20 allowance for Markr spender.`})}t<k&&(A=await d(v,{account:s.fromAddress,to:C,data:w({abi:T,functionName:`approve`,args:[n,k]})},T,`Failed to estimate gas for ERC20 approval transaction.`))}let M=A>0n,N=h({amountOut:s.amountOut,assetOut:s.assetOut,slippageBps:s.slippageBps}),P=await p(l,{amountIn:s.amountIn.toString(),appId:_,minAmountOut:N.toString(),tokenIn:C,tokenOut:m(s.assetOut,s.targetChain.chainId),uuid:s.id});if(!b(P)){let n=S(P)?P.chainType:x(P)?P.type:`unknown`;throw new r(t.CHAIN_NOT_SUPPORTED,e.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant "${n}".`})}let F=0n;F=M?s.gasEstimate||700000n:await j({crossChain:O,fromAddress:s.fromAddress,sourceClient:v,swap:P});let I=await u(v,s.sourceChain,c?.overrides?.feeRateTier),L=c?.overrides?.maxFeePerGas??I.maxFeePerGas,R=c?.overrides?.maxPriorityFeePerGas??I.maxPriorityFeePerGas,z=(F+A)*L;return{asset:s.sourceChain.networkToken,totalFee:a(z,c?.feeUnitsMarginBps),totalFeeWithoutMargin:z,meta:{approvalFee:M?A*L:void 0,maxFeePerGas:L,maxPriorityFeePerGas:R}}}async function A(n,i,{apiOptions:o,appId:c}){let l=m(n.assetIn,n.sourceChain.chainId);if(!D(l))throw new r(t.INVALID_PARAMS,e.INVALID_PARAMS,{details:`assetIn address is not a valid Solana address. Can not call estimateGas.`});let u=_(n),d=n.amountIn+u,f=h({amountOut:n.amountOut,assetOut:n.assetOut,slippageBps:n.slippageBps}),g=await p(o,{amountIn:n.amountIn.toString(),appId:c,minAmountOut:f.toString(),tokenIn:l,tokenOut:m(n.assetOut,n.targetChain.chainId),userPublicKey:n.fromAddress,uuid:n.id});if(!S(g))throw new r(t.CHAIN_NOT_SUPPORTED,e.INVALID_PARAMS,{details:`Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.`});let{baseFeeLamports:v,priorityFeeLamports:y,...b}=await C(s({chain:n.sourceChain}),g.swapTransaction),x=b.feePayerBalanceDiffLamports-d;return{asset:n.sourceChain.networkToken,totalFee:a(x,i?.feeUnitsMarginBps),totalFeeWithoutMargin:x,meta:{...b,baseFee:v,priorityFee:y}}}async function j({crossChain:e,feeUnitsMarginBps:t,fromAddress:n,sourceClient:r,swap:i}){let o=await v(e);return a(await d(r,{account:n,to:i.to,data:i.data,value:i.value},o,`Failed to estimate gas for Markr swap transaction.`),t)}export{j as _estimateGasFromSwapResponse,O as estimateNativeFeeFactory};
|
|
2
2
|
//# sourceMappingURL=estimate-native-fee.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estimate-native-fee.js","names":["isEvmAddress","isSolanaAddress"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import { isAddress as isSolanaAddress } from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { estimateGasWithRevert } from '../../_evm-errors';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n getAdditiveSourceAssetFeeAmount,\n getAdditiveSourceNativeAssetFeeAmount,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isHyperliquidWithdrawSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { estimateSolanaFee } from '../../../utils/solana';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n const allowanceAmount = quote.amountIn + getAdditiveSourceAssetFeeAmount(quote);\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < allowanceAmount;\n\n if (approvalNeeded) {\n allowanceApprovalGas = await estimateGasWithRevert(\n sourceClient,\n {\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, allowanceAmount],\n }),\n },\n erc20Abi,\n 'Failed to estimate gas for ERC20 approval transaction.',\n );\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n const variantTag = isSolanaSwapResponse(swap)\n ? swap.chainType\n : isHyperliquidWithdrawSwapResponse(swap)\n ? swap.type\n : 'unknown';\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant \"${variantTag}\".`,\n });\n }\n\n let gas = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // Attempt to use the Markr provided `gasEstimate` first if available.\n // Otherwise we attempt to get the gas from the `fees` component that are applicable\n // to the source chain. If neither of those are available, we fall back to a hardcoded value.\n if (quote.gasEstimate) {\n gas = quote.gasEstimate;\n } else {\n const sourceGasFee = quote.fees\n .filter((fee) => fee.type === 'gas' && fee.chainId === quote.sourceChain.chainId)\n .reduce((acc, fee) => acc + fee.amount, 0n);\n\n gas = sourceGasFee || EVM_SWAP_FALLBACK_GAS_ESTIMATE;\n }\n } else {\n gas = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFeeWithoutMargin = (gas + allowanceApprovalGas) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n approvalFee: allowanceApprovalIsNeeded ? allowanceApprovalGas * maxFeePerGas : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const additiveNativeFee: bigint = getAdditiveSourceNativeAssetFeeAmount(quote);\n const totalInput: bigint = quote.amountIn + additiveNativeFee;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n\n const { baseFeeLamports, priorityFeeLamports, ...rest } = await estimateSolanaFee(rpc, swap.swapTransaction);\n\n const totalFeeWithoutMargin = rest.feePayerBalanceDiffLamports - totalInput;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n ...rest,\n baseFee: baseFeeLamports,\n priorityFee: priorityFeeLamports,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n const markrAbi = await getMarkrSwapWrapperAbi(crossChain);\n\n // Use raw estimateGas — the calldata is pre-encoded and may target any\n // contract (Markr wrapper, deBridge, etc.). The ABI is only used for\n // best-effort error decoding on the failure path.\n const gasEstimate = await estimateGasWithRevert(\n sourceClient,\n {\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n },\n markrAbi,\n 'Failed to estimate gas for Markr swap transaction.',\n );\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"estimate-native-fee.js","names":["isEvmAddress","isSolanaAddress"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import { isAddress as isSolanaAddress } from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { estimateGasWithRevert } from '../../_evm-errors';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n getAdditiveSourceAssetFeeAmount,\n getAdditiveSourceNativeAssetFeeAmount,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isHyperliquidWithdrawSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { estimateSolanaFee } from '../../../utils/solana';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n const allowanceAmount = quote.amountIn + getAdditiveSourceAssetFeeAmount(quote);\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < allowanceAmount;\n\n if (approvalNeeded) {\n allowanceApprovalGas = await estimateGasWithRevert(\n sourceClient,\n {\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, allowanceAmount],\n }),\n },\n erc20Abi,\n 'Failed to estimate gas for ERC20 approval transaction.',\n );\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n const variantTag = isSolanaSwapResponse(swap)\n ? swap.chainType\n : isHyperliquidWithdrawSwapResponse(swap)\n ? swap.type\n : 'unknown';\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response variant \"${variantTag}\".`,\n });\n }\n\n let gas = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // While the approval is still pending we can't `eth_estimateGas` the swap (it would\n // revert on the missing allowance), so use Markr's gas-unit estimate when present and\n // otherwise a fixed gas-unit fallback. `gasEstimate` is documented as optional and may\n // be 0/omitted for relay.\n //\n // We deliberately do NOT derive this from the `fees` array. A `type: 'gas'` fee is a\n // token-denominated *cost amount* (e.g. native wei), not a gas-unit count — feeding it\n // into the `gas * maxFeePerGas` formula below produces a nonsensical fee.\n gas = quote.gasEstimate || EVM_SWAP_FALLBACK_GAS_ESTIMATE;\n } else {\n gas = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFeeWithoutMargin = (gas + allowanceApprovalGas) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n approvalFee: allowanceApprovalIsNeeded ? allowanceApprovalGas * maxFeePerGas : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const additiveNativeFee: bigint = getAdditiveSourceNativeAssetFeeAmount(quote);\n const totalInput: bigint = quote.amountIn + additiveNativeFee;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n\n const { baseFeeLamports, priorityFeeLamports, ...rest } = await estimateSolanaFee(rpc, swap.swapTransaction);\n\n const totalFeeWithoutMargin = rest.feePayerBalanceDiffLamports - totalInput;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: applyFeeUnitsBpsMargin(totalFeeWithoutMargin, options?.feeUnitsMarginBps),\n totalFeeWithoutMargin,\n meta: {\n ...rest,\n baseFee: baseFeeLamports,\n priorityFee: priorityFeeLamports,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n const markrAbi = await getMarkrSwapWrapperAbi(crossChain);\n\n // Use raw estimateGas — the calldata is pre-encoded and may target any\n // contract (Markr wrapper, deBridge, etc.). The ABI is only used for\n // best-effort error decoding on the failure path.\n const gasEstimate = await estimateGasWithRevert(\n sourceClient,\n {\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n },\n markrAbi,\n 'Failed to estimate gas for Markr swap transaction.',\n );\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`../../../errors.cjs`),t=require(`../../../utils/caip.cjs`),n=require(`../../../_utils/chain.cjs`),r=require(`../../../utils/evm-address.cjs`),i=require(`../_api.cjs`),a=require(`../../../utils/sol-address.cjs`),o=require(`../_utils.cjs`);function s({apiOptions:e,appId:r,partnerFeeBps:a}){return(s,l)=>{let
|
|
1
|
+
const e=require(`../../../errors.cjs`),t=require(`../../../utils/caip.cjs`),n=require(`../../../_utils/chain.cjs`),r=require(`../../../utils/evm-address.cjs`),i=require(`../_api.cjs`),a=require(`../../../utils/sol-address.cjs`),o=require(`../_utils.cjs`);function s({apiOptions:e,appId:r,partnerFeeBps:a}){return(s,l,u)=>{let d=new AbortController,{userEvmAddress:f,userSolanaAddress:p,validationError:m}=c(s,u);return m?(l(`error`,m),l(`done`),{cancel:()=>{}}):(i.markrStreamQuote(e,{amount:s.amount.toString(),aggregator:s.aggregator,appId:r,chainId:n.isEvmNamespace(s.sourceChain.chainId)?t.caip2ToEip155ChainId(s.sourceChain.chainId):s.sourceChain.chainId,destinationChainId:s.sourceChain.chainId===s.targetChain.chainId?void 0:n.isEvmNamespace(s.targetChain.chainId)?t.caip2ToEip155ChainId(s.targetChain.chainId):s.targetChain.chainId,slippage:s.slippageBps,tokenIn:o.assetToAddressString(s.sourceAsset,s.sourceChain.chainId),tokenInDecimals:s.sourceAsset.decimals,tokenOut:o.assetToAddressString(s.targetAsset,s.targetChain.chainId),tokenOutDecimals:s.targetAsset.decimals,userEvmAddress:f,userSolanaAddress:p},{onDone:()=>{d.signal.aborted||l(`done`)},onError:e=>{d.signal.aborted||(l(`error`,e),l(`done`))},onQuote:e=>{d.signal.aborted||l(`quote`,o.quoteFromMarkrQuoteResponseData(e,s,a))},signal:d.signal}),{cancel:()=>{d.abort()}})}}function c(t,i){let o=t.sourceChain.chainId!==t.targetChain.chainId,s=n.isEvmNamespace(t.sourceChain.chainId),c=n.isEvmNamespace(t.targetChain.chainId),l=n.isSolanaNamespace(t.sourceChain.chainId),u=n.isSolanaNamespace(t.targetChain.chainId);return s&&!r.isEvmAddress(t.fromAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Invalid fromAddress for EVM source chain.`)}:c&&!r.isEvmAddress(t.toAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Invalid toAddress for EVM target chain.`)}:l&&!a.isSolAddress(t.fromAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Invalid fromAddress for Solana source chain.`)}:u&&!a.isSolAddress(t.toAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Invalid toAddress for Solana target chain.`)}:o&&(s&&c||l&&u)&&!i?.dangerouslyAllowAddressMismatch&&t.fromAddress!==t.toAddress?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`fromAddress and toAddress must match for same-namespace cross-chain swaps.`)}:o?{userEvmAddress:c&&r.isEvmAddress(t.toAddress)?t.toAddress:s&&r.isEvmAddress(t.fromAddress)?t.fromAddress:void 0,userSolanaAddress:u&&a.isSolAddress(t.toAddress)?t.toAddress:l&&a.isSolAddress(t.fromAddress)?t.fromAddress:void 0}:t.fromAddress===t.toAddress?{userEvmAddress:void 0,userSolanaAddress:void 0}:{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`fromAddress and toAddress must match for same-chain swaps.`)}}exports.streamQuotesFactory=s;
|
|
2
2
|
//# sourceMappingURL=stream-quotes.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-quotes.cjs","names":["markrStreamQuote","isEvmNamespace","caip2ToEip155ChainId","assetToAddressString","quoteFromMarkrQuoteResponseData","isSolanaNamespace","isEvmAddress","InvalidParamsError","ErrorReason","isSolAddress"],"sources":["../../../../src/transfer-service/markr/_handlers/stream-quotes.ts"],"sourcesContent":["import { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { TransferService } from '../../../types/service';\nimport { markrStreamQuote, type ApiOptions, type MarkrStreamQuoteParams } from '../_api';\nimport { assetToAddressString, quoteFromMarkrQuoteResponseData } from '../_utils';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { isEvmAddress } from '../../../utils/evm-address';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport { ErrorReason, InvalidParamsError } from '../../../errors';\n\nexport interface StreamQuotesFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n partnerFeeBps: number;\n}\n\nexport function streamQuotesFactory({\n apiOptions,\n appId,\n partnerFeeBps,\n}: StreamQuotesFactoryConfig): TransferService['streamQuotes'] {\n return (props, handler) => {\n const ac = new AbortController();\n const { userEvmAddress, userSolanaAddress, validationError } = _validateAndGetUserAddresses(props);\n\n if (validationError) {\n handler('error', validationError);\n handler('done');\n\n return {\n cancel: () => {},\n };\n }\n\n void markrStreamQuote(\n apiOptions,\n {\n amount: props.amount.toString(),\n appId,\n chainId: isEvmNamespace(props.sourceChain.chainId)\n ? caip2ToEip155ChainId(props.sourceChain.chainId)\n : props.sourceChain.chainId,\n // For cross-chain requests, destinationChainId is required.\n // Use EVM numeric chain IDs for EVM targets and CAIP-2 IDs for non-EVM targets.\n destinationChainId:\n props.sourceChain.chainId !== props.targetChain.chainId\n ? isEvmNamespace(props.targetChain.chainId)\n ? caip2ToEip155ChainId(props.targetChain.chainId)\n : props.targetChain.chainId\n : undefined,\n slippage: props.slippageBps,\n tokenIn: assetToAddressString(props.sourceAsset, props.sourceChain.chainId),\n tokenInDecimals: props.sourceAsset.decimals,\n tokenOut: assetToAddressString(props.targetAsset, props.targetChain.chainId),\n tokenOutDecimals: props.targetAsset.decimals,\n userEvmAddress,\n userSolanaAddress,\n },\n {\n onDone: () => {\n if (!ac.signal.aborted) {\n handler('done');\n }\n },\n onError: (error) => {\n if (!ac.signal.aborted) {\n handler('error', error);\n handler('done');\n }\n },\n onQuote: (data) => {\n if (!ac.signal.aborted) {\n handler('quote', quoteFromMarkrQuoteResponseData(data, props, partnerFeeBps));\n }\n },\n signal: ac.signal,\n },\n );\n\n return {\n cancel: () => {\n ac.abort();\n },\n };\n };\n}\n\nexport function _validateAndGetUserAddresses(\n props: Parameters<TransferService['streamQuotes']>[0],\n): Pick<MarkrStreamQuoteParams, 'userEvmAddress' | 'userSolanaAddress'> & { validationError?: InvalidParamsError } {\n const isCrossChainSwap = props.sourceChain.chainId !== props.targetChain.chainId;\n const sourceIsEvm = isEvmNamespace(props.sourceChain.chainId);\n const targetIsEvm = isEvmNamespace(props.targetChain.chainId);\n const sourceIsSolana = isSolanaNamespace(props.sourceChain.chainId);\n const targetIsSolana = isSolanaNamespace(props.targetChain.chainId);\n\n if (sourceIsEvm && !isEvmAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid fromAddress for EVM source chain.'),\n };\n }\n\n if (targetIsEvm && !isEvmAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for EVM target chain.'),\n };\n }\n\n if (sourceIsSolana && !isSolAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'Invalid fromAddress for Solana source chain.',\n ),\n };\n }\n\n if (targetIsSolana && !isSolAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for Solana target chain.'),\n };\n }\n\n if (isCrossChainSwap
|
|
1
|
+
{"version":3,"file":"stream-quotes.cjs","names":["markrStreamQuote","isEvmNamespace","caip2ToEip155ChainId","assetToAddressString","quoteFromMarkrQuoteResponseData","isSolanaNamespace","isEvmAddress","InvalidParamsError","ErrorReason","isSolAddress"],"sources":["../../../../src/transfer-service/markr/_handlers/stream-quotes.ts"],"sourcesContent":["import { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { ServiceQuoteOptions, TransferService } from '../../../types/service';\nimport { markrStreamQuote, type ApiOptions, type MarkrStreamQuoteParams } from '../_api';\nimport { assetToAddressString, quoteFromMarkrQuoteResponseData } from '../_utils';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { isEvmAddress } from '../../../utils/evm-address';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport { ErrorReason, InvalidParamsError } from '../../../errors';\n\nexport interface StreamQuotesFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n partnerFeeBps: number;\n}\n\nexport function streamQuotesFactory({\n apiOptions,\n appId,\n partnerFeeBps,\n}: StreamQuotesFactoryConfig): TransferService['streamQuotes'] {\n return (props, handler, options) => {\n const ac = new AbortController();\n const { userEvmAddress, userSolanaAddress, validationError } = _validateAndGetUserAddresses(props, options);\n\n if (validationError) {\n handler('error', validationError);\n handler('done');\n\n return {\n cancel: () => {},\n };\n }\n\n void markrStreamQuote(\n apiOptions,\n {\n amount: props.amount.toString(),\n // Forwarded only when the caller pins one; otherwise Markr races every aggregator.\n aggregator: props.aggregator,\n appId,\n chainId: isEvmNamespace(props.sourceChain.chainId)\n ? caip2ToEip155ChainId(props.sourceChain.chainId)\n : props.sourceChain.chainId,\n // For cross-chain requests, destinationChainId is required.\n // Use EVM numeric chain IDs for EVM targets and CAIP-2 IDs for non-EVM targets.\n destinationChainId:\n props.sourceChain.chainId !== props.targetChain.chainId\n ? isEvmNamespace(props.targetChain.chainId)\n ? caip2ToEip155ChainId(props.targetChain.chainId)\n : props.targetChain.chainId\n : undefined,\n slippage: props.slippageBps,\n tokenIn: assetToAddressString(props.sourceAsset, props.sourceChain.chainId),\n tokenInDecimals: props.sourceAsset.decimals,\n tokenOut: assetToAddressString(props.targetAsset, props.targetChain.chainId),\n tokenOutDecimals: props.targetAsset.decimals,\n userEvmAddress,\n userSolanaAddress,\n },\n {\n onDone: () => {\n if (!ac.signal.aborted) {\n handler('done');\n }\n },\n onError: (error) => {\n if (!ac.signal.aborted) {\n handler('error', error);\n handler('done');\n }\n },\n onQuote: (data) => {\n if (!ac.signal.aborted) {\n handler('quote', quoteFromMarkrQuoteResponseData(data, props, partnerFeeBps));\n }\n },\n signal: ac.signal,\n },\n );\n\n return {\n cancel: () => {\n ac.abort();\n },\n };\n };\n}\n\nexport function _validateAndGetUserAddresses(\n props: Parameters<TransferService['streamQuotes']>[0],\n options?: ServiceQuoteOptions,\n): Pick<MarkrStreamQuoteParams, 'userEvmAddress' | 'userSolanaAddress'> & { validationError?: InvalidParamsError } {\n const isCrossChainSwap = props.sourceChain.chainId !== props.targetChain.chainId;\n const sourceIsEvm = isEvmNamespace(props.sourceChain.chainId);\n const targetIsEvm = isEvmNamespace(props.targetChain.chainId);\n const sourceIsSolana = isSolanaNamespace(props.sourceChain.chainId);\n const targetIsSolana = isSolanaNamespace(props.targetChain.chainId);\n\n if (sourceIsEvm && !isEvmAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid fromAddress for EVM source chain.'),\n };\n }\n\n if (targetIsEvm && !isEvmAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for EVM target chain.'),\n };\n }\n\n if (sourceIsSolana && !isSolAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'Invalid fromAddress for Solana source chain.',\n ),\n };\n }\n\n if (targetIsSolana && !isSolAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for Solana target chain.'),\n };\n }\n\n if (\n isCrossChainSwap &&\n ((sourceIsEvm && targetIsEvm) || (sourceIsSolana && targetIsSolana)) &&\n !options?.dangerouslyAllowAddressMismatch\n ) {\n if (props.fromAddress !== props.toAddress) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'fromAddress and toAddress must match for same-namespace cross-chain swaps.',\n ),\n };\n }\n }\n\n if (!isCrossChainSwap) {\n if (props.fromAddress !== props.toAddress) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'fromAddress and toAddress must match for same-chain swaps.',\n ),\n };\n }\n\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n };\n }\n\n const userEvmAddress =\n targetIsEvm && isEvmAddress(props.toAddress)\n ? props.toAddress\n : sourceIsEvm && isEvmAddress(props.fromAddress)\n ? props.fromAddress\n : undefined;\n\n const userSolanaAddress =\n targetIsSolana && isSolAddress(props.toAddress)\n ? props.toAddress\n : sourceIsSolana && isSolAddress(props.fromAddress)\n ? props.fromAddress\n : undefined;\n\n return {\n userEvmAddress,\n userSolanaAddress,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ErrorReason as e,InvalidParamsError as t}from"../../../errors.js";import{caip2ToEip155ChainId as n}from"../../../utils/caip.js";import{isEvmNamespace as r,isSolanaNamespace as i}from"../../../_utils/chain.js";import{isEvmAddress as a}from"../../../utils/evm-address.js";import{markrStreamQuote as o}from"../_api.js";import{isSolAddress as s}from"../../../utils/sol-address.js";import{assetToAddressString as c,quoteFromMarkrQuoteResponseData as l}from"../_utils.js";function u({apiOptions:e,appId:t,partnerFeeBps:i}){return(a,s)=>{let
|
|
1
|
+
import{ErrorReason as e,InvalidParamsError as t}from"../../../errors.js";import{caip2ToEip155ChainId as n}from"../../../utils/caip.js";import{isEvmNamespace as r,isSolanaNamespace as i}from"../../../_utils/chain.js";import{isEvmAddress as a}from"../../../utils/evm-address.js";import{markrStreamQuote as o}from"../_api.js";import{isSolAddress as s}from"../../../utils/sol-address.js";import{assetToAddressString as c,quoteFromMarkrQuoteResponseData as l}from"../_utils.js";function u({apiOptions:e,appId:t,partnerFeeBps:i}){return(a,s,u)=>{let f=new AbortController,{userEvmAddress:p,userSolanaAddress:m,validationError:h}=d(a,u);return h?(s(`error`,h),s(`done`),{cancel:()=>{}}):(o(e,{amount:a.amount.toString(),aggregator:a.aggregator,appId:t,chainId:r(a.sourceChain.chainId)?n(a.sourceChain.chainId):a.sourceChain.chainId,destinationChainId:a.sourceChain.chainId===a.targetChain.chainId?void 0:r(a.targetChain.chainId)?n(a.targetChain.chainId):a.targetChain.chainId,slippage:a.slippageBps,tokenIn:c(a.sourceAsset,a.sourceChain.chainId),tokenInDecimals:a.sourceAsset.decimals,tokenOut:c(a.targetAsset,a.targetChain.chainId),tokenOutDecimals:a.targetAsset.decimals,userEvmAddress:p,userSolanaAddress:m},{onDone:()=>{f.signal.aborted||s(`done`)},onError:e=>{f.signal.aborted||(s(`error`,e),s(`done`))},onQuote:e=>{f.signal.aborted||s(`quote`,l(e,a,i))},signal:f.signal}),{cancel:()=>{f.abort()}})}}function d(n,o){let c=n.sourceChain.chainId!==n.targetChain.chainId,l=r(n.sourceChain.chainId),u=r(n.targetChain.chainId),d=i(n.sourceChain.chainId),f=i(n.targetChain.chainId);return l&&!a(n.fromAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new t(e.INVALID_PARAMS,`Invalid fromAddress for EVM source chain.`)}:u&&!a(n.toAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new t(e.INVALID_PARAMS,`Invalid toAddress for EVM target chain.`)}:d&&!s(n.fromAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new t(e.INVALID_PARAMS,`Invalid fromAddress for Solana source chain.`)}:f&&!s(n.toAddress)?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new t(e.INVALID_PARAMS,`Invalid toAddress for Solana target chain.`)}:c&&(l&&u||d&&f)&&!o?.dangerouslyAllowAddressMismatch&&n.fromAddress!==n.toAddress?{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new t(e.INVALID_PARAMS,`fromAddress and toAddress must match for same-namespace cross-chain swaps.`)}:c?{userEvmAddress:u&&a(n.toAddress)?n.toAddress:l&&a(n.fromAddress)?n.fromAddress:void 0,userSolanaAddress:f&&s(n.toAddress)?n.toAddress:d&&s(n.fromAddress)?n.fromAddress:void 0}:n.fromAddress===n.toAddress?{userEvmAddress:void 0,userSolanaAddress:void 0}:{userEvmAddress:void 0,userSolanaAddress:void 0,validationError:new t(e.INVALID_PARAMS,`fromAddress and toAddress must match for same-chain swaps.`)}}export{u as streamQuotesFactory};
|
|
2
2
|
//# sourceMappingURL=stream-quotes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-quotes.js","names":[],"sources":["../../../../src/transfer-service/markr/_handlers/stream-quotes.ts"],"sourcesContent":["import { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { TransferService } from '../../../types/service';\nimport { markrStreamQuote, type ApiOptions, type MarkrStreamQuoteParams } from '../_api';\nimport { assetToAddressString, quoteFromMarkrQuoteResponseData } from '../_utils';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { isEvmAddress } from '../../../utils/evm-address';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport { ErrorReason, InvalidParamsError } from '../../../errors';\n\nexport interface StreamQuotesFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n partnerFeeBps: number;\n}\n\nexport function streamQuotesFactory({\n apiOptions,\n appId,\n partnerFeeBps,\n}: StreamQuotesFactoryConfig): TransferService['streamQuotes'] {\n return (props, handler) => {\n const ac = new AbortController();\n const { userEvmAddress, userSolanaAddress, validationError } = _validateAndGetUserAddresses(props);\n\n if (validationError) {\n handler('error', validationError);\n handler('done');\n\n return {\n cancel: () => {},\n };\n }\n\n void markrStreamQuote(\n apiOptions,\n {\n amount: props.amount.toString(),\n appId,\n chainId: isEvmNamespace(props.sourceChain.chainId)\n ? caip2ToEip155ChainId(props.sourceChain.chainId)\n : props.sourceChain.chainId,\n // For cross-chain requests, destinationChainId is required.\n // Use EVM numeric chain IDs for EVM targets and CAIP-2 IDs for non-EVM targets.\n destinationChainId:\n props.sourceChain.chainId !== props.targetChain.chainId\n ? isEvmNamespace(props.targetChain.chainId)\n ? caip2ToEip155ChainId(props.targetChain.chainId)\n : props.targetChain.chainId\n : undefined,\n slippage: props.slippageBps,\n tokenIn: assetToAddressString(props.sourceAsset, props.sourceChain.chainId),\n tokenInDecimals: props.sourceAsset.decimals,\n tokenOut: assetToAddressString(props.targetAsset, props.targetChain.chainId),\n tokenOutDecimals: props.targetAsset.decimals,\n userEvmAddress,\n userSolanaAddress,\n },\n {\n onDone: () => {\n if (!ac.signal.aborted) {\n handler('done');\n }\n },\n onError: (error) => {\n if (!ac.signal.aborted) {\n handler('error', error);\n handler('done');\n }\n },\n onQuote: (data) => {\n if (!ac.signal.aborted) {\n handler('quote', quoteFromMarkrQuoteResponseData(data, props, partnerFeeBps));\n }\n },\n signal: ac.signal,\n },\n );\n\n return {\n cancel: () => {\n ac.abort();\n },\n };\n };\n}\n\nexport function _validateAndGetUserAddresses(\n props: Parameters<TransferService['streamQuotes']>[0],\n): Pick<MarkrStreamQuoteParams, 'userEvmAddress' | 'userSolanaAddress'> & { validationError?: InvalidParamsError } {\n const isCrossChainSwap = props.sourceChain.chainId !== props.targetChain.chainId;\n const sourceIsEvm = isEvmNamespace(props.sourceChain.chainId);\n const targetIsEvm = isEvmNamespace(props.targetChain.chainId);\n const sourceIsSolana = isSolanaNamespace(props.sourceChain.chainId);\n const targetIsSolana = isSolanaNamespace(props.targetChain.chainId);\n\n if (sourceIsEvm && !isEvmAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid fromAddress for EVM source chain.'),\n };\n }\n\n if (targetIsEvm && !isEvmAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for EVM target chain.'),\n };\n }\n\n if (sourceIsSolana && !isSolAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'Invalid fromAddress for Solana source chain.',\n ),\n };\n }\n\n if (targetIsSolana && !isSolAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for Solana target chain.'),\n };\n }\n\n if (isCrossChainSwap
|
|
1
|
+
{"version":3,"file":"stream-quotes.js","names":[],"sources":["../../../../src/transfer-service/markr/_handlers/stream-quotes.ts"],"sourcesContent":["import { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { ServiceQuoteOptions, TransferService } from '../../../types/service';\nimport { markrStreamQuote, type ApiOptions, type MarkrStreamQuoteParams } from '../_api';\nimport { assetToAddressString, quoteFromMarkrQuoteResponseData } from '../_utils';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { isEvmAddress } from '../../../utils/evm-address';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport { ErrorReason, InvalidParamsError } from '../../../errors';\n\nexport interface StreamQuotesFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n partnerFeeBps: number;\n}\n\nexport function streamQuotesFactory({\n apiOptions,\n appId,\n partnerFeeBps,\n}: StreamQuotesFactoryConfig): TransferService['streamQuotes'] {\n return (props, handler, options) => {\n const ac = new AbortController();\n const { userEvmAddress, userSolanaAddress, validationError } = _validateAndGetUserAddresses(props, options);\n\n if (validationError) {\n handler('error', validationError);\n handler('done');\n\n return {\n cancel: () => {},\n };\n }\n\n void markrStreamQuote(\n apiOptions,\n {\n amount: props.amount.toString(),\n // Forwarded only when the caller pins one; otherwise Markr races every aggregator.\n aggregator: props.aggregator,\n appId,\n chainId: isEvmNamespace(props.sourceChain.chainId)\n ? caip2ToEip155ChainId(props.sourceChain.chainId)\n : props.sourceChain.chainId,\n // For cross-chain requests, destinationChainId is required.\n // Use EVM numeric chain IDs for EVM targets and CAIP-2 IDs for non-EVM targets.\n destinationChainId:\n props.sourceChain.chainId !== props.targetChain.chainId\n ? isEvmNamespace(props.targetChain.chainId)\n ? caip2ToEip155ChainId(props.targetChain.chainId)\n : props.targetChain.chainId\n : undefined,\n slippage: props.slippageBps,\n tokenIn: assetToAddressString(props.sourceAsset, props.sourceChain.chainId),\n tokenInDecimals: props.sourceAsset.decimals,\n tokenOut: assetToAddressString(props.targetAsset, props.targetChain.chainId),\n tokenOutDecimals: props.targetAsset.decimals,\n userEvmAddress,\n userSolanaAddress,\n },\n {\n onDone: () => {\n if (!ac.signal.aborted) {\n handler('done');\n }\n },\n onError: (error) => {\n if (!ac.signal.aborted) {\n handler('error', error);\n handler('done');\n }\n },\n onQuote: (data) => {\n if (!ac.signal.aborted) {\n handler('quote', quoteFromMarkrQuoteResponseData(data, props, partnerFeeBps));\n }\n },\n signal: ac.signal,\n },\n );\n\n return {\n cancel: () => {\n ac.abort();\n },\n };\n };\n}\n\nexport function _validateAndGetUserAddresses(\n props: Parameters<TransferService['streamQuotes']>[0],\n options?: ServiceQuoteOptions,\n): Pick<MarkrStreamQuoteParams, 'userEvmAddress' | 'userSolanaAddress'> & { validationError?: InvalidParamsError } {\n const isCrossChainSwap = props.sourceChain.chainId !== props.targetChain.chainId;\n const sourceIsEvm = isEvmNamespace(props.sourceChain.chainId);\n const targetIsEvm = isEvmNamespace(props.targetChain.chainId);\n const sourceIsSolana = isSolanaNamespace(props.sourceChain.chainId);\n const targetIsSolana = isSolanaNamespace(props.targetChain.chainId);\n\n if (sourceIsEvm && !isEvmAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid fromAddress for EVM source chain.'),\n };\n }\n\n if (targetIsEvm && !isEvmAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for EVM target chain.'),\n };\n }\n\n if (sourceIsSolana && !isSolAddress(props.fromAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'Invalid fromAddress for Solana source chain.',\n ),\n };\n }\n\n if (targetIsSolana && !isSolAddress(props.toAddress)) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(ErrorReason.INVALID_PARAMS, 'Invalid toAddress for Solana target chain.'),\n };\n }\n\n if (\n isCrossChainSwap &&\n ((sourceIsEvm && targetIsEvm) || (sourceIsSolana && targetIsSolana)) &&\n !options?.dangerouslyAllowAddressMismatch\n ) {\n if (props.fromAddress !== props.toAddress) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'fromAddress and toAddress must match for same-namespace cross-chain swaps.',\n ),\n };\n }\n }\n\n if (!isCrossChainSwap) {\n if (props.fromAddress !== props.toAddress) {\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n validationError: new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n 'fromAddress and toAddress must match for same-chain swaps.',\n ),\n };\n }\n\n return {\n userEvmAddress: undefined,\n userSolanaAddress: undefined,\n };\n }\n\n const userEvmAddress =\n targetIsEvm && isEvmAddress(props.toAddress)\n ? props.toAddress\n : sourceIsEvm && isEvmAddress(props.fromAddress)\n ? props.fromAddress\n : undefined;\n\n const userSolanaAddress =\n targetIsSolana && isSolAddress(props.toAddress)\n ? props.toAddress\n : sourceIsSolana && isSolAddress(props.fromAddress)\n ? props.fromAddress\n : undefined;\n\n return {\n userEvmAddress,\n userSolanaAddress,\n };\n}\n"],"mappings":"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"}
|
package/dist/types/quote.d.cts
CHANGED
|
@@ -147,6 +147,15 @@ interface QuoterProps {
|
|
|
147
147
|
readonly targetChain: Chain;
|
|
148
148
|
/** Recipient address on target chain. */
|
|
149
149
|
readonly toAddress: string;
|
|
150
|
+
/**
|
|
151
|
+
* Pin quoting to a single upstream aggregator (Markr only).
|
|
152
|
+
*
|
|
153
|
+
* Markr otherwise races every eligible aggregator. Passing an id (e.g.
|
|
154
|
+
* `"relay"`) restricts the quote to that aggregator — used to obtain
|
|
155
|
+
* native-fee-free routes that only a specific aggregator supports.
|
|
156
|
+
*
|
|
157
|
+
*/
|
|
158
|
+
readonly aggregator?: string;
|
|
150
159
|
}
|
|
151
160
|
interface QuoterInterface {
|
|
152
161
|
/**
|
package/dist/types/quote.d.ts
CHANGED
|
@@ -147,6 +147,15 @@ interface QuoterProps {
|
|
|
147
147
|
readonly targetChain: Chain;
|
|
148
148
|
/** Recipient address on target chain. */
|
|
149
149
|
readonly toAddress: string;
|
|
150
|
+
/**
|
|
151
|
+
* Pin quoting to a single upstream aggregator (Markr only).
|
|
152
|
+
*
|
|
153
|
+
* Markr otherwise races every eligible aggregator. Passing an id (e.g.
|
|
154
|
+
* `"relay"`) restricts the quote to that aggregator — used to obtain
|
|
155
|
+
* native-fee-free routes that only a specific aggregator supports.
|
|
156
|
+
*
|
|
157
|
+
*/
|
|
158
|
+
readonly aggregator?: string;
|
|
150
159
|
}
|
|
151
160
|
interface QuoterInterface {
|
|
152
161
|
/**
|
package/dist/types/service.d.cts
CHANGED
|
@@ -123,6 +123,16 @@ interface EstimateNativeFeeOptions {
|
|
|
123
123
|
maxPriorityFeePerGas?: bigint;
|
|
124
124
|
}>;
|
|
125
125
|
}
|
|
126
|
+
interface ServiceQuoteOptions {
|
|
127
|
+
/**
|
|
128
|
+
* Dangerously allows cross-chain quotes where fromAddress and toAddress differ
|
|
129
|
+
* even when a service would otherwise require them to match.
|
|
130
|
+
*
|
|
131
|
+
* This is intended for explicit proxy-recipient funding flows. Services should
|
|
132
|
+
* preserve their default address safety checks unless this is set.
|
|
133
|
+
*/
|
|
134
|
+
readonly dangerouslyAllowAddressMismatch?: boolean;
|
|
135
|
+
}
|
|
126
136
|
/**
|
|
127
137
|
* Asset search input used by bridgeable-assets queries.
|
|
128
138
|
*
|
|
@@ -187,7 +197,7 @@ interface TransferService {
|
|
|
187
197
|
/** Get all supported chains for this service. */
|
|
188
198
|
getSupportedChains(): Promise<GetSupportedChainsResult>;
|
|
189
199
|
/** Stream time-sensitive quotes. */
|
|
190
|
-
streamQuotes(props: QuoterProps, handler: ServiceQuoteEventHandler): {
|
|
200
|
+
streamQuotes(props: QuoterProps, handler: ServiceQuoteEventHandler, options?: ServiceQuoteOptions): {
|
|
191
201
|
cancel: () => void;
|
|
192
202
|
};
|
|
193
203
|
/** Begin tracking a submitted transfer. */
|
|
@@ -304,5 +314,5 @@ type MarkrServiceInitializer = {
|
|
|
304
314
|
};
|
|
305
315
|
type ServiceInitializer = AvalancheCctInitializer | EvmServiceInitializer | LombardServiceInitializer | MarkrServiceInitializer;
|
|
306
316
|
//#endregion
|
|
307
|
-
export { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, TrackTransferProps, TransferAssetProps, TransferService };
|
|
317
|
+
export { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, ServiceQuoteOptions, TrackTransferProps, TransferAssetProps, TransferService };
|
|
308
318
|
//# sourceMappingURL=service.d.cts.map
|
package/dist/types/service.d.ts
CHANGED
|
@@ -123,6 +123,16 @@ interface EstimateNativeFeeOptions {
|
|
|
123
123
|
maxPriorityFeePerGas?: bigint;
|
|
124
124
|
}>;
|
|
125
125
|
}
|
|
126
|
+
interface ServiceQuoteOptions {
|
|
127
|
+
/**
|
|
128
|
+
* Dangerously allows cross-chain quotes where fromAddress and toAddress differ
|
|
129
|
+
* even when a service would otherwise require them to match.
|
|
130
|
+
*
|
|
131
|
+
* This is intended for explicit proxy-recipient funding flows. Services should
|
|
132
|
+
* preserve their default address safety checks unless this is set.
|
|
133
|
+
*/
|
|
134
|
+
readonly dangerouslyAllowAddressMismatch?: boolean;
|
|
135
|
+
}
|
|
126
136
|
/**
|
|
127
137
|
* Asset search input used by bridgeable-assets queries.
|
|
128
138
|
*
|
|
@@ -187,7 +197,7 @@ interface TransferService {
|
|
|
187
197
|
/** Get all supported chains for this service. */
|
|
188
198
|
getSupportedChains(): Promise<GetSupportedChainsResult>;
|
|
189
199
|
/** Stream time-sensitive quotes. */
|
|
190
|
-
streamQuotes(props: QuoterProps, handler: ServiceQuoteEventHandler): {
|
|
200
|
+
streamQuotes(props: QuoterProps, handler: ServiceQuoteEventHandler, options?: ServiceQuoteOptions): {
|
|
191
201
|
cancel: () => void;
|
|
192
202
|
};
|
|
193
203
|
/** Begin tracking a submitted transfer. */
|
|
@@ -304,5 +314,5 @@ type MarkrServiceInitializer = {
|
|
|
304
314
|
};
|
|
305
315
|
type ServiceInitializer = AvalancheCctInitializer | EvmServiceInitializer | LombardServiceInitializer | MarkrServiceInitializer;
|
|
306
316
|
//#endregion
|
|
307
|
-
export { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, TrackTransferProps, TransferAssetProps, TransferService };
|
|
317
|
+
export { AssetSearchQuery, AvalancheCctInitializer, AvalancheSendTxFunction, AvalancheSendTxParams, EstimateNativeFeeOptions, EvmServiceInitializer, GasSettings, GetBridgeableAssetsProps, GetBridgeableAssetsResult, GetMinimumTransferAmountProps, GetSupportedChainsResult, LombardServiceInitializer, MarkrServiceInitializer, MutableGetSupportedChainsResult, NativeFeeEstimate, ServiceInitializer, ServiceQuoteOptions, TrackTransferProps, TransferAssetProps, TransferService };
|
|
308
318
|
//# sourceMappingURL=service.d.ts.map
|