@eco-foundation/routes-sdk
Version:
Eco Routes SDK
715 lines (703 loc) • 24.2 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
OpenQuotingClient: () => OpenQuotingClient,
RoutesService: () => RoutesService,
chainIds: () => chainIds,
selectCheapestQuote: () => selectCheapestQuote,
selectCheapestQuoteNativeSend: () => selectCheapestQuoteNativeSend,
stableAddresses: () => stableAddresses,
stables: () => stables
});
module.exports = __toCommonJS(index_exports);
// src/constants.ts
var chainIds = [
1,
// ETH Mainnet
10,
// Optimism
56,
// BSC
130,
// Unichain
137,
// Polygon
146,
// Sonic
169,
// Manta Pacific
360,
// Molten
466,
// Appchain
478,
// Form
480,
// Worldchain
// 999, // HyperEVM
1996,
// Sanko
2525,
// inEVM
// 2741, // Abstract
// 5000, // Mantle
5330,
// Superseed
// 7887, // Kinto
8333,
// B3
8453,
// Base
33139,
// ApeChain
42161,
// Arbitrum
42220,
// Celo
57073,
// Ink
// 543210, // ZERO
10241024,
// AlienX
1380012617
// RARI
];
var stables = ["USDC", "USDbC", "USDCe", "USDT", "oUSDT", "USDT0", "ApeUSD", "sUSDe"];
var stableAddresses = {
1: {
USDC: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
USDT: "0xdac17f958d2ee523a2206206994597c13d831ec7",
oUSDT: "0x1217bfe6c773eec6cc4a38b5dc45b92292b6e189"
},
10: {
USDC: "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
USDCe: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
USDT: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
oUSDT: "0x1217bfe6c773eec6cc4a38b5dc45b92292b6e189"
},
56: {
USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
USDT: "0x55d398326f99059fF775485246999027B3197955"
},
130: {
USDC: "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
USDT0: "0x9151434b16b9763660705744891fa906f660ecc5"
},
137: {
USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
USDCe: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
USDT: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"
},
146: {
USDC: "0x29219dd400f2Bf60E5a23d13Be72B486D4038894"
},
169: {
USDC: "0xb73603C5d87fA094B7314C74ACE2e64D165016fb",
USDT: "0xf417F5A458eC102B90352F697D6e2Ac3A3d2851f"
},
360: {
USDC: "0xDf0195C990a94006869959a9c77add160164207e"
},
466: {
USDC: "0x675C3ce7F43b00045a4Dab954AF36160fb57cB45"
},
478: {
USDC: "0xFBf489bb4783D4B1B2e7D07ba39873Fb8068507D",
USDT: "0xFA3198ecF05303a6d96E57a45E6c815055D255b1"
},
480: {
USDC: "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1"
},
// 999: {
// USDT0: '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb'
// },
1996: {
USDC: "0x13D675BC5e659b11CFA331594cF35A20815dCF02"
},
2525: {
USDC: "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147",
USDT: "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5"
},
// 2741: {
// USDT: "0x0709f39376deee2a2dfc94a58edeb2eb9df012bd",
// USDCe: "0x84a71ccd554cc1b02749b35d22f684cc8ec987e1",
// },
// 5000: {
// USDC: "0x09bc4e0d864854c6afb6eb9a9cdf58ac190d0df9",
// USDT: "0x201EBa5CC46D216Ce6DC03F6a759e8E766e956aE",
// },
5330: {
USDC: "0xC316C8252B5F2176d0135Ebb0999E99296998F2e",
oUSDT: "0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189"
},
// 7887: {
// USDC: '0x05DC0010C9902EcF6CBc921c6A4bd971c69E5A2E',
// USDT: '0x06D7002A76545f255657aC27313B28831Aa6BceD',
// sUSDe: '0x505de0f7a5d786063348aB5BC31e3a21344fA7B0',
// USDCe: '0x05dE0003C333A503bea5224fCc64f0D4b5446C38',
// },
8333: {
USDC: "0x2Af198A85F9AA11cd6042A0596FbF23978514DA3"
},
8453: {
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
USDbC: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
oUSDT: "0x1217bfe6c773eec6cc4a38b5dc45b92292b6e189"
},
33139: {
ApeUSD: "0xA2235d059F80e176D931Ef76b6C51953Eb3fBEf4",
USDCe: "0xF1815bd50389c46847f0Bda824eC8da914045D14"
},
42161: {
USDC: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
USDCe: "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8",
USDT0: "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"
},
42220: {
USDC: "0xceba9300f2b948710d2653dd7b07f33a8b32118c",
USDT: "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e"
},
57073: {
USDCe: "0xF1815bd50389c46847f0Bda824eC8da914045D14",
USDT0: "0x0200C29006150606B650577BBE7B6248F58470c1"
},
10241024: {
USDC: "0x14B90E36Ca85D8B563430C0940E80d3A040285cC",
USDT: "0x486bee264E0F6EB9A1d5947F5FCB097C5378c8E3"
},
1380012617: {
USDC: "0x46B991aCbD9290967a3A9e02f14895c2F9FE809A",
USDT: "0x362FAE9A75B27BBc550aAc28a7c1F96C8D483120",
USDCe: "0xFbDa5F676cB37624f28265A144A48B0d6e87d3b6"
}
};
// src/routes/RoutesService.ts
var import_viem = require("viem");
// src/utils.ts
var import_crypto = require("crypto");
function getSecondsFromNow(seconds) {
return new Date(Date.now() + 1e3 * seconds);
}
function dateToTimestamp(date) {
return BigInt(Math.floor(date.getTime() / 1e3));
}
function isAmountInvalid(amount) {
return amount < BigInt(0);
}
function sum(items) {
return items.reduce((acc, amount) => acc + BigInt(amount), BigInt(0));
}
function generateRandomHex(length = 32) {
return `0x${(0, import_crypto.randomBytes)(length).toString("hex")}`;
}
// src/routes/RoutesService.ts
var import_routes_ts = require("@eco-foundation/routes-ts");
// src/config.ts
var ECO_SDK_CONFIG = {
openQuotingBaseUrl: "https://quotes.eco.com",
isPreprod: false
};
// src/routes/RoutesService.ts
var RoutesService = class {
constructor({ isPreprod, customProtocolAddresses } = {}) {
this.isPreprod = isPreprod || ECO_SDK_CONFIG.isPreprod || false;
this.protocolAddresses = this.mergeProtocolAddresses(customProtocolAddresses);
this.supportedChainIds = [...new Set(Object.keys(this.protocolAddresses).map((chainIdEnv) => parseInt(chainIdEnv.replace("-pre", ""))))];
}
mergeProtocolAddresses(customProtocolAddresses) {
const target = customProtocolAddresses || {};
const source = import_routes_ts.EcoProtocolAddresses;
function deepMergeProtocolAddresses(target2, source2) {
const result = { ...target2 };
for (const chainIdEnv in source2) {
if (!Object.prototype.hasOwnProperty.call(source2, chainIdEnv)) continue;
const sourceChain = source2[chainIdEnv];
const targetChain = result[chainIdEnv];
if (targetChain && sourceChain) {
result[chainIdEnv] = { ...sourceChain, ...targetChain };
} else if (sourceChain) {
result[chainIdEnv] = { ...sourceChain };
}
}
return result;
}
return deepMergeProtocolAddresses(target, source);
}
/**
* Creates a simple intent.
*
* @param {CreateSimpleIntentParams} params - The parameters for creating the simple intent.
*
* @returns {IntentType} The created intent.
*
* @throws {Error} If the creator address is invalid, the origin and destination chain are the same, the amount is invalid, or the expiry time is in the past. Or if there is no prover for the specified configuration.
*/
createSimpleIntent({
creator,
originChainID,
destinationChainID,
receivingToken,
spendingToken,
spendingTokenLimit,
amount,
recipient = creator,
prover = "HyperProver",
expiryTime
}) {
if (!(0, import_viem.isAddress)(creator, { strict: false })) {
throw new Error("Invalid creator address");
}
if (!(0, import_viem.isAddress)(recipient, { strict: false })) {
throw new Error("Invalid recipient address");
}
if (originChainID === destinationChainID) {
throw new Error("originChainID and destinationChainID cannot be the same");
}
if (isAmountInvalid(amount)) {
throw new Error("Invalid amount");
}
if (isAmountInvalid(spendingTokenLimit)) {
throw new Error("Invalid spendingTokenLimit");
}
if (expiryTime && expiryTime < getSecondsFromNow(60)) {
throw new Error("Expiry time must be 60 seconds or more in the future");
}
const deadline = expiryTime || this.getDefaultDeadline(prover);
const data = (0, import_viem.encodeFunctionData)({
abi: import_viem.erc20Abi,
functionName: "transfer",
args: [recipient, amount]
});
return {
route: {
salt: generateRandomHex(),
source: BigInt(originChainID),
destination: BigInt(destinationChainID),
inbox: this.getProtocolContractAddress(destinationChainID, "Inbox"),
tokens: [
{
token: receivingToken,
amount
}
],
calls: [
{
target: receivingToken,
data,
value: BigInt(0)
}
]
},
reward: {
creator,
prover: this.getProverContract(prover, originChainID),
deadline: dateToTimestamp(deadline),
nativeValue: BigInt(0),
tokens: [
{
token: spendingToken,
amount: spendingTokenLimit
}
]
}
};
}
/**
* Creates an intent.
*
* @param {CreateRouteParams} params - The parameters for creating the intent.
*
* @returns {IntentType} The created intent.
*
* @throws {Error} If the creator address is invalid, the origin and destination chain are the same, the calls or tokens are invalid, or the expiry time is in the past.
*/
createIntent({
creator,
originChainID,
destinationChainID,
calls,
callTokens,
tokens,
prover,
expiryTime,
nativeValue = BigInt(0)
}) {
if (!(0, import_viem.isAddress)(creator, { strict: false })) {
throw new Error("Invalid creator address");
}
if (originChainID === destinationChainID) {
throw new Error("originChainID and destinationChainID cannot be the same");
}
if (!calls.length || calls.some((call) => !(0, import_viem.isAddress)(call.target, { strict: false }) || isAmountInvalid(call.value))) {
throw new Error("Invalid calls");
}
if (!callTokens.length || callTokens.some((token) => !(0, import_viem.isAddress)(token.token, { strict: false }) || isAmountInvalid(token.amount))) {
throw new Error("Invalid callTokens");
}
if (calls.every((call) => call.value === BigInt(0)) && (!tokens.length || tokens.some((token) => !(0, import_viem.isAddress)(token.token, { strict: false }) || isAmountInvalid(token.amount)))) {
throw new Error("Invalid tokens");
}
if (expiryTime && expiryTime < getSecondsFromNow(60)) {
throw new Error("Expiry time must be 60 seconds or more in the future");
}
const deadline = expiryTime || this.getDefaultDeadline(prover);
return {
route: {
salt: generateRandomHex(),
source: BigInt(originChainID),
destination: BigInt(destinationChainID),
inbox: this.getProtocolContractAddress(destinationChainID, "Inbox"),
tokens: callTokens,
calls
},
reward: {
creator,
prover: this.getProverContract(prover, originChainID),
deadline: dateToTimestamp(deadline),
nativeValue,
tokens
}
};
}
/**
* Creates a native send intent.
*
* @param {CreateNativeSendIntentParams} params - The parameters for creating the native send intent.
* @returns {IntentType} The created intent.
* @throws {Error} If the creator address is invalid, the origin and destination chain are the same, the amount is invalid, or the expiry time is in the past.
*/
createNativeSendIntent({
creator,
originChainID,
destinationChainID,
amount,
limit,
recipient = creator,
prover = "HyperProver",
expiryTime
}) {
if (!(0, import_viem.isAddress)(creator, { strict: false })) {
throw new Error("Invalid creator address");
}
if (!(0, import_viem.isAddress)(recipient, { strict: false })) {
throw new Error("Invalid recipient address");
}
if (originChainID === destinationChainID) {
throw new Error("originChainID and destinationChainID cannot be the same");
}
if (isAmountInvalid(amount)) {
throw new Error("Invalid amount");
}
if (isAmountInvalid(limit)) {
throw new Error("Invalid limit");
}
if (limit < amount) {
throw new Error("Insufficient limit");
}
if (expiryTime && expiryTime < getSecondsFromNow(60)) {
throw new Error("Expiry time must be 60 seconds or more in the future");
}
const deadline = expiryTime || this.getDefaultDeadline(prover);
return {
route: {
salt: generateRandomHex(),
source: BigInt(originChainID),
destination: BigInt(destinationChainID),
inbox: this.getProtocolContractAddress(destinationChainID, "Inbox"),
tokens: [],
calls: [{
target: recipient,
data: "0x",
value: amount
}]
},
reward: {
creator,
prover: this.getProverContract(prover, originChainID),
deadline: dateToTimestamp(deadline),
nativeValue: limit,
tokens: []
}
};
}
/**
* Applies a quote to an intent, modifying the reward tokens.
*
* @param {ApplyQuoteToIntentParams} params - The parameters for applying the quote to the intent.
*
* @returns {IntentType} The intent with the quote applied.
*
* @throws {Error} If the quote is invalid.
*/
applyQuoteToIntent({ intent, quote }) {
if (quote.quoteData.nativeValue === "0" && !quote.quoteData.tokens.length) {
throw new Error("Invalid quoteData: tokens array must have length greater than 0");
}
intent.reward.tokens = quote.quoteData.tokens.map(({ token, amount }) => ({
token,
amount: BigInt(amount)
}));
intent.reward.nativeValue = BigInt(quote.quoteData.nativeValue || 0);
return intent;
}
/**
* Returns the EcoChainId for a given chainId, appending "-pre" if the environment is pre-production.
*
* @param chainId - The chain ID to be converted to an EcoChainId.
* @returns The EcoChainId, with "-pre" appended if the environment is pre-production.
*/
getEcoChainId(chainId) {
return `${chainId}${this.isPreprod ? "-pre" : ""}`;
}
/**
* Returns the EcoChainId for a given chainId, appending "-pre" if the environment is pre-production.
*
* @param chainId - The chain ID to be converted to an EcoChainId.
* @returns The EcoChainId, with "-pre" appended if the environment is pre-production.
*/
getChainIdEnv(chainId) {
return `${chainId}${this.isPreprod ? "-pre" : ""}`;
}
/**
* Checks if a protocol contract exists for a given chain ID and protocol contract.
*
* @param {number} chainId - The chain ID to check for the protocol contract.
* @param {EcoProtocolContract} protocolContract - The protocol contract to check for existence.
* @returns {boolean} True if the protocol contract exists, false otherwise.
*/
protocolContractExists(chainId, protocolContract) {
const chainIdEnv = this.getChainIdEnv(chainId);
if (!this.protocolAddresses[chainIdEnv]) {
return false;
}
const address = this.protocolAddresses[chainIdEnv][protocolContract];
return Boolean(address) && (0, import_viem.isAddress)(address) && !(0, import_viem.isAddressEqual)(address, import_viem.zeroAddress);
}
/**
* Returns the address of a protocol contract for a given chain ID.
*
* @param {number} chainId - The chain ID to get the protocol address for.
* @param {EcoProtocolContract} protocolContract - The protocol contract to get the address for.
* @returns {Hex} The address of the protocol contract.
*
* @throws {Error} If no protocol contract exists on the specified chain ID.
*/
getProtocolContractAddress(chainId, protocolContract) {
const chainIdEnv = this.getChainIdEnv(chainId);
if (!this.protocolAddresses[chainIdEnv]) {
throw new Error(`No protocol addresses found for chain '${chainIdEnv}'`);
}
const address = this.protocolAddresses[chainIdEnv][protocolContract];
if (!address || (0, import_viem.isAddressEqual)(address, import_viem.zeroAddress)) {
throw new Error(`No ${protocolContract} exists on '${chainIdEnv}'`);
}
return address;
}
getProverContract(prover, chainId) {
let proverContract;
const chainIdEnv = this.getChainIdEnv(chainId);
switch (prover) {
case "HyperProver": {
proverContract = this.protocolAddresses[chainIdEnv]?.HyperProver;
if (!proverContract || (0, import_viem.isAddressEqual)(proverContract, import_viem.zeroAddress)) {
throw new Error(`No HyperProver exists on '${chainIdEnv}'`);
}
break;
}
case "MetaProver": {
proverContract = this.protocolAddresses[chainIdEnv]?.MetaProver;
if (!proverContract || (0, import_viem.isAddressEqual)(proverContract, import_viem.zeroAddress)) {
throw new Error(`No MetaProver exists on '${chainIdEnv}'`);
}
break;
}
case void 0: {
proverContract = this.protocolAddresses[chainIdEnv]?.HyperProver;
if (!proverContract || (0, import_viem.isAddressEqual)(proverContract, import_viem.zeroAddress)) {
proverContract = this.protocolAddresses[chainIdEnv]?.MetaProver;
}
if (!proverContract || (0, import_viem.isAddressEqual)(proverContract, import_viem.zeroAddress)) {
throw new Error(`No Prover found for '${chainIdEnv}'`);
}
break;
}
default: {
proverContract = prover;
}
}
return proverContract;
}
getDefaultDeadline(prover) {
switch (prover) {
case "MetaProver":
return getSecondsFromNow(150 * 60);
// 150 minutes from now
case "HyperProver":
default:
return getSecondsFromNow(90 * 60);
}
}
static getStableAddress(chainID, stable) {
const stableAddress = stableAddresses[chainID][stable];
if (!stableAddress) {
throw new Error(`Stable ${stable} not found on chain ${chainID}`);
}
return stableAddress;
}
static getStableFromAddress(chainID, address) {
for (const stable in stableAddresses[chainID]) {
if (stableAddresses[chainID][stable]?.toLowerCase() === address.toLowerCase()) {
return stable;
}
}
throw new Error(`Stable not found for address ${address} on chain ${chainID}`);
}
static parseIntentFromIntentCreatedEventArgs(args) {
const requiredFields = ["salt", "source", "destination", "inbox", "routeTokens", "calls", "creator", "prover", "deadline", "nativeValue", "rewardTokens"];
const missing = requiredFields.filter((field) => args[field] === void 0);
if (missing.length) {
throw new Error(`Missing event args: ${missing.join(", ")}`);
}
return {
route: {
salt: args.salt,
source: args.source,
destination: args.destination,
inbox: args.inbox,
tokens: args.routeTokens,
calls: args.calls
},
reward: {
creator: args.creator,
prover: args.prover,
deadline: args.deadline,
nativeValue: args.nativeValue,
tokens: args.rewardTokens
}
};
}
};
// src/quotes/OpenQuotingClient.ts
var import_axios = __toESM(require("axios"), 1);
// src/quotes/types.ts
var OpenQuotingAPI;
((OpenQuotingAPI2) => {
let Endpoints;
((Endpoints2) => {
Endpoints2["Quotes"] = "/api/v1/quotes";
})(Endpoints = OpenQuotingAPI2.Endpoints || (OpenQuotingAPI2.Endpoints = {}));
})(OpenQuotingAPI || (OpenQuotingAPI = {}));
// src/quotes/OpenQuotingClient.ts
var OpenQuotingClient = class {
constructor({ dAppID, customBaseUrl }) {
this.MAX_RETRIES = 5;
this.dAppID = dAppID;
this.axiosInstance = import_axios.default.create({
baseURL: customBaseUrl || ECO_SDK_CONFIG.openQuotingBaseUrl
});
import("axios-retry").then(({ default: axiosRetry }) => {
axiosRetry(this.axiosInstance, { retries: this.MAX_RETRIES, retryDelay: axiosRetry.linearDelay(1e3) });
}).catch(() => {
const axiosRetry = require("axios-retry");
axiosRetry(this.axiosInstance, { retries: this.MAX_RETRIES, retryDelay: axiosRetry.linearDelay(1e3) });
}).catch(() => {
console.warn("Failed to import axios-retry, OpenQuotingClient requests will only be attempted once");
});
}
/**
* Requests quotes for a given intent.
*
* @param intent - The intent for which quotes are being requested.
* @returns A promise that resolves to an `OpenQuotingClient_ApiResponse_Quotes` object containing the quotes.
* @throws An error if multiple requests fail.
*
* @remarks
* This method sends a POST request to the `/api/v1/quotes` endpoint with the provided intent information.
*/
async requestQuotesForIntent(intent) {
const payload = {
dAppID: this.dAppID,
intentData: {
routeData: {
originChainID: intent.route.source.toString(),
destinationChainID: intent.route.destination.toString(),
inboxContract: intent.route.inbox,
tokens: intent.route.tokens.map((token) => ({
token: token.token,
amount: token.amount.toString()
})),
calls: intent.route.calls.map((call) => ({
target: call.target,
data: call.data,
value: call.value.toString()
}))
},
rewardData: {
creator: intent.reward.creator,
proverContract: intent.reward.prover,
deadline: intent.reward.deadline.toString(),
nativeValue: intent.reward.nativeValue.toString(),
tokens: intent.reward.tokens.map((token) => ({
token: token.token,
amount: token.amount.toString()
}))
}
}
};
const response = await this.axiosInstance.post(OpenQuotingAPI.Endpoints.Quotes, payload);
return response.data.data;
}
};
// src/quotes/quoteSelectors.ts
function selectCheapestQuote(quotes) {
return quotes.reduce((cheapest, quote) => {
const cheapestSum = cheapest ? sum(cheapest.quoteData.tokens.map(({ amount }) => amount)) : BigInt(Infinity);
const quoteSum = sum(quote.quoteData.tokens.map(({ amount }) => amount));
return quoteSum < cheapestSum ? quote : cheapest;
});
}
function selectCheapestQuoteNativeSend(quotes) {
return quotes.reduce((cheapest, quote) => {
const cheapestNativeValue = cheapest ? BigInt(cheapest.quoteData.nativeValue) : BigInt(Infinity);
const quoteNativeValue = BigInt(quote.quoteData.nativeValue);
return quoteNativeValue < cheapestNativeValue ? quote : cheapest;
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
OpenQuotingClient,
RoutesService,
chainIds,
selectCheapestQuote,
selectCheapestQuoteNativeSend,
stableAddresses,
stables
});