@lifi/sdk
Version:
LI.FI Any-to-Any Cross-Chain-Swap SDK
452 lines • 17.6 kB
JavaScript
import { ChainId, isContractCallsRequestWithFromAmount, isContractCallsRequestWithToAmount, } from '@lifi/types';
import { config } from '../config.js';
import { BaseError } from '../errors/baseError.js';
import { ErrorName } from '../errors/constants.js';
import { ValidationError } from '../errors/errors.js';
import { SDKError } from '../errors/SDKError.js';
import { request } from '../request.js';
import { isRoutesRequest, isStep } from '../typeguards.js';
import { decodeTaskId } from '../utils/decode.js';
import { withDedupe } from '../utils/withDedupe.js';
export async function getQuote(params, options) {
const requiredParameters = [
'fromChain',
'fromToken',
'fromAddress',
'toChain',
'toToken',
];
for (const requiredParameter of requiredParameters) {
if (!params[requiredParameter]) {
throw new SDKError(new ValidationError(`Required parameter "${requiredParameter}" is missing.`));
}
}
const isFromAmountRequest = 'fromAmount' in params && params.fromAmount !== undefined;
const isToAmountRequest = 'toAmount' in params && params.toAmount !== undefined;
if (!isFromAmountRequest && !isToAmountRequest) {
throw new SDKError(new ValidationError('Required parameter "fromAmount" or "toAmount" is missing.'));
}
if (isFromAmountRequest && isToAmountRequest) {
throw new SDKError(new ValidationError('Cannot provide both "fromAmount" and "toAmount" parameters.'));
}
const _config = config.get();
// apply defaults
params.integrator ??= _config.integrator;
params.order ??= _config.routeOptions?.order;
params.slippage ??= _config.routeOptions?.slippage;
params.referrer ??= _config.routeOptions?.referrer;
params.fee ??= _config.routeOptions?.fee;
params.allowBridges ??= _config.routeOptions?.bridges?.allow;
params.denyBridges ??= _config.routeOptions?.bridges?.deny;
params.preferBridges ??= _config.routeOptions?.bridges?.prefer;
params.allowExchanges ??= _config.routeOptions?.exchanges?.allow;
params.denyExchanges ??= _config.routeOptions?.exchanges?.deny;
params.preferExchanges ??= _config.routeOptions?.exchanges?.prefer;
for (const key of Object.keys(params)) {
if (!params[key]) {
delete params[key];
}
}
return await request(`${_config.apiUrl}/${isFromAmountRequest ? 'quote' : 'quote/toAmount'}?${new URLSearchParams(params)}`, {
signal: options?.signal,
});
}
/**
* Get a set of routes for a request that describes a transfer of tokens.
* @param params - A description of the transfer.
* @param options - Request options
* @returns The resulting routes that can be used to realize the described transfer of tokens.
* @throws {LiFiError} Throws a LiFiError if request fails.
*/
export const getRoutes = async (params, options) => {
if (!isRoutesRequest(params)) {
throw new SDKError(new ValidationError('Invalid routes request.'));
}
const _config = config.get();
// apply defaults
params.options = {
integrator: _config.integrator,
..._config.routeOptions,
...params.options,
};
return await request(`${_config.apiUrl}/advanced/routes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
signal: options?.signal,
});
};
/**
* Get a quote for a destination contract call
* @param params - The configuration of the requested destination call
* @param options - Request options
* @throws {LiFiError} - Throws a LiFiError if request fails
* @returns - Returns step.
*/
export const getContractCallsQuote = async (params, options) => {
// validation
const requiredParameters = [
'fromChain',
'fromToken',
'fromAddress',
'toChain',
'toToken',
'contractCalls',
];
for (const requiredParameter of requiredParameters) {
if (!params[requiredParameter]) {
throw new SDKError(new ValidationError(`Required parameter "${requiredParameter}" is missing.`));
}
}
if (!isContractCallsRequestWithFromAmount(params) &&
!isContractCallsRequestWithToAmount(params)) {
throw new SDKError(new ValidationError(`Required parameter "fromAmount" or "toAmount" is missing.`));
}
const _config = config.get();
// apply defaults
// option.order is not used in this endpoint
params.integrator ??= _config.integrator;
params.slippage ??= _config.routeOptions?.slippage;
params.referrer ??= _config.routeOptions?.referrer;
params.fee ??= _config.routeOptions?.fee;
params.allowBridges ??= _config.routeOptions?.bridges?.allow;
params.denyBridges ??= _config.routeOptions?.bridges?.deny;
params.preferBridges ??= _config.routeOptions?.bridges?.prefer;
params.allowExchanges ??= _config.routeOptions?.exchanges?.allow;
params.denyExchanges ??= _config.routeOptions?.exchanges?.deny;
params.preferExchanges ??= _config.routeOptions?.exchanges?.prefer;
// send request
return await request(`${_config.apiUrl}/quote/contractCalls`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
signal: options?.signal,
});
};
/**
* Get the transaction data for a single step of a route
* @param step - The step object.
* @param options - Request options
* @returns The step populated with the transaction data.
* @throws {LiFiError} Throws a LiFiError if request fails.
*/
export const getStepTransaction = async (step, options) => {
if (!isStep(step)) {
// While the validation fails for some users we should not enforce it
console.warn('SDK Validation: Invalid Step', step);
}
const _config = config.get();
let requestUrl = `${_config.apiUrl}/advanced/stepTransaction`;
const isJitoBundleEnabled = Boolean(_config.routeOptions?.jitoBundle);
if (isJitoBundleEnabled && step.action.fromChainId === ChainId.SOL) {
// add jitoBundle param to url if from chain is SVM and jitoBundle is enabled in config
const queryParams = new URLSearchParams({ jitoBundle: 'true' });
requestUrl = `${requestUrl}?${queryParams}`;
}
return await request(requestUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(step),
signal: options?.signal,
});
};
/**
* Check the status of a transfer. For cross chain transfers, the "bridge" parameter is required.
* @param params - Configuration of the requested status
* @param options - Request options.
* @throws {LiFiError} - Throws a LiFiError if request fails
* @returns Returns status response.
*/
export const getStatus = async (params, options) => {
if (!('taskId' in params && params.taskId) &&
!('txHash' in params && params.txHash)) {
throw new SDKError(new ValidationError('Either "taskId" or "txHash" must be provided and non-empty.'));
}
const queryParams = new URLSearchParams(params);
return await request(`${config.get().apiUrl}/status?${queryParams}`, {
signal: options?.signal,
});
};
/**
* Get a relayer quote for a token transfer
* @param params - The configuration of the requested quote
* @param options - Request options
* @throws {LiFiError} - Throws a LiFiError if request fails
* @returns Relayer quote for a token transfer
*/
export const getRelayerQuote = async (params, options) => {
const requiredParameters = [
'fromChain',
'fromToken',
'fromAddress',
'fromAmount',
'toChain',
'toToken',
];
for (const requiredParameter of requiredParameters) {
if (!params[requiredParameter]) {
throw new SDKError(new ValidationError(`Required parameter "${requiredParameter}" is missing.`));
}
}
const _config = config.get();
// apply defaults
params.integrator ??= _config.integrator;
params.order ??= _config.routeOptions?.order;
params.slippage ??= _config.routeOptions?.slippage;
params.referrer ??= _config.routeOptions?.referrer;
params.fee ??= _config.routeOptions?.fee;
params.allowBridges ??= _config.routeOptions?.bridges?.allow;
params.denyBridges ??= _config.routeOptions?.bridges?.deny;
params.preferBridges ??= _config.routeOptions?.bridges?.prefer;
params.allowExchanges ??= _config.routeOptions?.exchanges?.allow;
params.denyExchanges ??= _config.routeOptions?.exchanges?.deny;
params.preferExchanges ??= _config.routeOptions?.exchanges?.prefer;
for (const key of Object.keys(params)) {
if (!params[key]) {
delete params[key];
}
}
const result = await request(`${config.get().apiUrl}/relayer/quote?${new URLSearchParams(params)}`, {
signal: options?.signal,
});
if (result.status === 'error') {
throw new BaseError(ErrorName.ServerError, result.data.code, result.data.message);
}
return result.data;
};
/**
* Relay a transaction through the relayer service
* @param params - The configuration for the relay request
* @param options - Request options
* @throws {LiFiError} - Throws a LiFiError if request fails
* @returns Task ID for the relayed transaction
*/
export const relayTransaction = async (params, options) => {
const requiredParameters = ['typedData'];
for (const requiredParameter of requiredParameters) {
if (!params[requiredParameter]) {
throw new SDKError(new ValidationError(`Required parameter "${requiredParameter}" is missing.`));
}
}
// Determine if the request is for a gasless relayer service or advanced relayer service
// We will use the same endpoint for both after the gasless relayer service is deprecated
const relayerPath = params.typedData.some((t) => t.primaryType === 'PermitWitnessTransferFrom')
? '/relayer/relay'
: '/advanced/relay';
const result = await request(`${config.get().apiUrl}${relayerPath}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params, (_, value) => {
if (typeof value === 'bigint') {
return value.toString();
}
return value;
}),
signal: options?.signal,
});
if (result.status === 'error') {
throw new BaseError(ErrorName.ServerError, result.data.code, result.data.message);
}
return result.data;
};
/**
* Get the status of a relayed transaction
* @param params - Parameters for the relay status request
* @param options - Request options
* @throws {LiFiError} - Throws a LiFiError if request fails
* @returns Status of the relayed transaction
*/
export const getRelayedTransactionStatus = async (params, options) => {
if (!params.taskId) {
throw new SDKError(new ValidationError('Required parameter "taskId" is missing.'));
}
const { taskId, ...otherParams } = params;
const queryParams = new URLSearchParams(otherParams);
const decodedTaskId = decodeTaskId(taskId);
// Temporary solution during the transition between status endpoints
if (decodedTaskId.length === 3) {
return (await getStatus(params, options));
}
const result = await request(`${config.get().apiUrl}/relayer/status/${taskId}?${queryParams}`, {
signal: options?.signal,
});
if (result.status === 'error') {
throw new BaseError(ErrorName.ServerError, result.data.code, result.data.message);
}
return result.data;
};
/**
* Get all available chains
* @param params - The configuration of the requested chains
* @param options - Request options
* @returns A list of all available chains
* @throws {LiFiError} Throws a LiFiError if request fails.
*/
export const getChains = async (params, options) => {
if (params) {
for (const key of Object.keys(params)) {
if (!params[key]) {
delete params[key];
}
}
}
const urlSearchParams = new URLSearchParams(params).toString();
const response = await withDedupe(() => request(`${config.get().apiUrl}/chains?${urlSearchParams}`, {
signal: options?.signal,
}), { id: `${getChains.name}.${urlSearchParams}` });
return response.chains;
};
export async function getTokens(params, options) {
if (params) {
for (const key of Object.keys(params)) {
if (!params[key]) {
delete params[key];
}
}
}
const urlSearchParams = new URLSearchParams(params).toString();
const isExtended = params?.extended === true;
const response = await withDedupe(() => request(`${config.get().apiUrl}/tokens?${urlSearchParams}`, {
signal: options?.signal,
}), { id: `${getTokens.name}.${urlSearchParams}` });
return response;
}
/**
* Fetch information about a Token
* @param chain - Id or key of the chain that contains the token
* @param token - Address or symbol of the token on the requested chain
* @param options - Request options
* @throws {LiFiError} - Throws a LiFiError if request fails
* @returns Token information
*/
export const getToken = async (chain, token, options) => {
if (!chain) {
throw new SDKError(new ValidationError('Required parameter "chain" is missing.'));
}
if (!token) {
throw new SDKError(new ValidationError('Required parameter "token" is missing.'));
}
return await request(`${config.get().apiUrl}/token?${new URLSearchParams({
chain,
token,
})}`, {
signal: options?.signal,
});
};
/**
* Get the available tools to bridge and swap tokens.
* @param params - The configuration of the requested tools
* @param options - Request options
* @returns The tools that are available on the requested chains
*/
export const getTools = async (params, options) => {
if (params) {
for (const key of Object.keys(params)) {
if (!params[key]) {
delete params[key];
}
}
}
return await request(`${config.get().apiUrl}/tools?${new URLSearchParams(params)}`, {
signal: options?.signal,
});
};
/**
* Get gas recommendation for a certain chain
* @param params - Configuration of the requested gas recommendation.
* @param options - Request options
* @throws {LiFiError} Throws a LiFiError if request fails.
* @returns Gas recommendation response.
*/
export const getGasRecommendation = async (params, options) => {
if (!params.chainId) {
throw new SDKError(new ValidationError('Required parameter "chainId" is missing.'));
}
const url = new URL(`${config.get().apiUrl}/gas/suggestion/${params.chainId}`);
if (params.fromChain) {
url.searchParams.append('fromChain', params.fromChain);
}
if (params.fromToken) {
url.searchParams.append('fromToken', params.fromToken);
}
return await request(url.toString(), {
signal: options?.signal,
});
};
/**
* Get all the available connections for swap/bridging tokens
* @param connectionRequest ConnectionsRequest
* @param options - Request options
* @returns ConnectionsResponse
*/
export const getConnections = async (connectionRequest, options) => {
const url = new URL(`${config.get().apiUrl}/connections`);
const { fromChain, fromToken, toChain, toToken } = connectionRequest;
if (fromChain) {
url.searchParams.append('fromChain', fromChain);
}
if (fromToken) {
url.searchParams.append('fromToken', fromToken);
}
if (toChain) {
url.searchParams.append('toChain', toChain);
}
if (toToken) {
url.searchParams.append('toToken', toToken);
}
const connectionRequestArrayParams = [
'allowBridges',
'denyBridges',
'preferBridges',
'allowExchanges',
'denyExchanges',
'preferExchanges',
];
for (const parameter of connectionRequestArrayParams) {
const connectionRequestArrayParam = connectionRequest[parameter];
if (connectionRequestArrayParam?.length) {
for (const value of connectionRequestArrayParam) {
url.searchParams.append(parameter, value);
}
}
}
return await request(url, options);
};
export const getTransactionHistory = async ({ wallet, status, fromTimestamp, toTimestamp }, options) => {
if (!wallet) {
throw new SDKError(new ValidationError('Required parameter "wallet" is missing.'));
}
const _config = config.get();
const url = new URL(`${_config.apiUrl}/analytics/transfers`);
url.searchParams.append('integrator', _config.integrator);
url.searchParams.append('wallet', wallet);
if (status) {
url.searchParams.append('status', status);
}
if (fromTimestamp) {
url.searchParams.append('fromTimestamp', fromTimestamp.toString());
}
if (toTimestamp) {
url.searchParams.append('toTimestamp', toTimestamp.toString());
}
return await request(url, options);
};
export const patchContractCalls = async (params, options) => {
return await request(`${config.get().apiUrl}/patcher`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
signal: options?.signal,
});
};
//# sourceMappingURL=api.js.map