Created
June 25, 2025 18:09
-
-
Save drichar/4f8432a6396df600007c9e47b0b596ac to your computer and use it in GitHub Desktop.
TypeScript client library for Deflex DEX aggregator API. Provides type-safe functions to fetch swap quotes and transaction data, plus strongly-typed classes for API responses including quotes, routes, transactions, and signatures.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Deflex API responses | |
interface QuoteResponse { | |
quote: string | number | |
profit: { | |
amount: number | |
asa: { | |
id: number | |
} | |
} | |
priceBaseline: number | |
route: { | |
percentage: number | |
path: { | |
name: string | |
in: { | |
id: number | |
} | |
out: { | |
id: number | |
} | |
}[] | |
}[] | |
quotes: { | |
name: string | |
value: string | |
}[] | |
requiredAppOptIns: number[] | |
txnPayload: Record<string, string> | null | |
protocolFees: { [key: string]: number } | |
flattenedRoute: { [key: string]: number } | |
} | |
interface SwapTxnsResponse { | |
txns: { | |
data: string | |
group: string | |
logicSigBlob: any | false | |
signature: | |
| { | |
type: 'logic_signature' | 'secret_key' | |
value: any | |
} | |
| false | |
}[] | |
} | |
/** | |
* Fetches a quote from the Deflex API for swapping between two assets | |
* @param fromAssetId - The ID of the asset to swap from | |
* @param toAssetId - The ID of the asset to swap to | |
* @param amount - The amount to swap in base units (microunits) | |
* @param type - The type of swap, either 'fixed-input' or 'fixed-output' | |
* @returns A promise that resolves to a DeflexQuote object | |
*/ | |
export async function fetchDeflexQuote({ | |
fromAssetId, | |
toAssetId, | |
amount, | |
type = 'fixed-input', | |
disabledProtocols = [], | |
feeBps, | |
}: { | |
fromAssetId: number | |
toAssetId: number | |
amount: number | |
type?: 'fixed-input' | 'fixed-output' | |
disabledProtocols?: string[] | |
feeBps?: number | |
}): Promise<DeflexQuote> { | |
// Determine network (mainnet or testnet) | |
const network = (process.env.EXPO_PUBLIC_ALGORAND_NETWORK || 'mainnet') as | |
| 'mainnet' | |
| 'testnet' | |
// Get algod client details | |
const algodServer = | |
process.env.EXPO_PUBLIC_ALGORAND_NODE_URL || | |
'https://mainnet-api.4160.nodely.dev/' | |
const algodToken = process.env.EXPO_PUBLIC_ALGORAND_NODE_TOKEN || '' | |
const algodPort = process.env.EXPO_PUBLIC_ALGORAND_NODE_PORT | |
? parseInt(process.env.EXPO_PUBLIC_ALGORAND_NODE_PORT) | |
: 443 | |
// Construct the URL for the Deflex API | |
const url = new URL( | |
`${process.env.EXPO_PUBLIC_DEFLEX_API_URL}/api/fetchQuote`, | |
) | |
// Add query parameters | |
url.searchParams.append('chain', network) | |
url.searchParams.append('algodUri', algodServer) | |
url.searchParams.append('algodToken', algodToken) | |
url.searchParams.append('algodPort', algodPort.toString()) | |
url.searchParams.append('fromASAID', fromAssetId.toString()) | |
url.searchParams.append('toASAID', toAssetId.toString()) | |
url.searchParams.append('amount', amount.toString()) | |
url.searchParams.append('type', type) | |
url.searchParams.append('atomicOnly', 'true') | |
url.searchParams.append('disabledProtocols', disabledProtocols.join(',')) | |
// Add fee basis points if provided | |
if (feeBps !== undefined) { | |
url.searchParams.append('feeBps', feeBps.toString()) | |
} | |
// Add API key if available | |
if (process.env.EXPO_PUBLIC_DEFLEX_API_KEY) { | |
url.searchParams.append('apiKey', process.env.EXPO_PUBLIC_DEFLEX_API_KEY) | |
} | |
// Add referrer address if available | |
// TODO: Register a referrer address with Deflex | |
// if (process.env.EXPO_PUBLIC_REFERRER_ADDRESS) { | |
// url.searchParams.append( | |
// 'referrerAddress', | |
// process.env.EXPO_PUBLIC_REFERRER_ADDRESS, | |
// ) | |
// } | |
// Make the API request | |
const response = await fetch(url.toString()) | |
// Handle errors | |
if (!response.ok) { | |
const errorText = await response.text() | |
throw new Error( | |
`Failed to fetch Deflex quote: ${response.status} ${response.statusText} - ${errorText}`, | |
) | |
} | |
// Parse the response as JSON | |
const quote = (await response.json()) as QuoteResponse | |
// Return the quote | |
return DeflexQuote.fromAPIResponse( | |
type, | |
fromAssetId, | |
toAssetId, | |
amount, | |
quote, | |
) | |
} | |
/** | |
* Fetches swap transactions from the Deflex API for executing a swap | |
* @param address - The Algorand address of the user | |
* @param txnPayloadJSON - The transaction payload JSON from a DeflexQuote | |
* @param slippage - The slippage tolerance as a percentage (e.g., 5 for 5%) | |
* @returns A promise that resolves to a DeflexTransactionGroup object | |
*/ | |
export async function fetchDeflexSwapTransactions({ | |
address, | |
txnPayloadJSON, | |
slippage = 5, | |
}: { | |
address: string | |
txnPayloadJSON: Record<string, string> | null | |
slippage?: number | |
}): Promise<DeflexTransactionGroup> { | |
// Construct the URL for the Deflex API | |
const url = new URL( | |
`${process.env.EXPO_PUBLIC_DEFLEX_API_URL}/api/fetchExecuteSwapTxns`, | |
) | |
// Prepare the request body | |
const body: { | |
address: string | |
txnPayloadJSON: Record<string, string> | null | |
slippage: number | |
apiKey?: string | |
} = { | |
address, | |
txnPayloadJSON, | |
slippage, | |
} | |
// Add API key if available | |
if (process.env.EXPO_PUBLIC_DEFLEX_API_KEY) { | |
body.apiKey = process.env.EXPO_PUBLIC_DEFLEX_API_KEY | |
} | |
// Make the API request | |
const response = await fetch(url, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify(body), | |
}) | |
// Handle errors | |
if (!response.ok) { | |
const errorText = await response.text() | |
throw new Error( | |
`Failed to fetch Deflex swap transactions: ${response.status} ${response.statusText} - ${errorText}`, | |
) | |
} | |
// Parse the response as JSON | |
const apiResponse = (await response.json()) as SwapTxnsResponse | |
// Return the transaction group | |
return DeflexTransactionGroup.fromApiResponse(apiResponse) | |
} | |
/** | |
* Represents a transaction signature from the Deflex API | |
*/ | |
export class DeflexTransactionSignature { | |
public type: 'logic_signature' | 'secret_key' | |
public value: Uint8Array | |
/** | |
* Creates a new DeflexTransactionSignature | |
* @param type - The type of signature | |
* @param value - The signature value as a Uint8Array | |
*/ | |
constructor(type: 'logic_signature' | 'secret_key', value: Uint8Array) { | |
this.type = type | |
this.value = value | |
} | |
/** | |
* Creates a DeflexTransactionSignature from an API response | |
* @param apiResponse - The API response object | |
* @returns A new DeflexTransactionSignature | |
*/ | |
static fromApiResponse(apiResponse: { | |
type: 'logic_signature' | 'secret_key' | |
value: any | |
}) { | |
return new DeflexTransactionSignature( | |
apiResponse.type, | |
Uint8Array.from(Object.values(apiResponse.value)), | |
) | |
} | |
} | |
/** | |
* Represents a transaction from the Deflex API | |
*/ | |
export class DeflexTransaction { | |
public data: string | |
public group: string | |
public logicSigBlob: Uint8Array | boolean | |
public signature: DeflexTransactionSignature | boolean | |
/** | |
* Creates a new DeflexTransaction | |
* @param data - The transaction data as a base64 string | |
* @param group - The group ID | |
* @param logicSigBlob - The logic signature blob or false if not present | |
* @param signature - The transaction signature or false if not present | |
*/ | |
constructor( | |
data: string, | |
group: string, | |
logicSigBlob: Uint8Array | boolean, | |
signature: DeflexTransactionSignature | boolean, | |
) { | |
this.data = data | |
this.group = group | |
this.logicSigBlob = logicSigBlob | |
this.signature = signature | |
} | |
/** | |
* Creates a DeflexTransaction from an API response | |
* @param apiResponse - The API response object | |
* @returns A new DeflexTransaction | |
*/ | |
static fromApiResponse(apiResponse: { | |
data: string | |
group: string | |
logicSigBlob: any | false | |
signature: { type: 'logic_signature' | 'secret_key'; value: any } | false | |
}) { | |
return new DeflexTransaction( | |
apiResponse.data, | |
apiResponse.group, | |
apiResponse.logicSigBlob !== false | |
? Uint8Array.from(Object.values(apiResponse.logicSigBlob)) | |
: false, | |
apiResponse.signature !== false | |
? DeflexTransactionSignature.fromApiResponse(apiResponse.signature) | |
: false, | |
) | |
} | |
} | |
/** | |
* Represents a group of transactions from the Deflex API | |
*/ | |
export class DeflexTransactionGroup { | |
public txns: DeflexTransaction[] | |
/** | |
* Creates a new DeflexTransactionGroup | |
* @param txns - An array of DeflexTransaction objects | |
*/ | |
constructor(txns: DeflexTransaction[]) { | |
this.txns = txns | |
} | |
/** | |
* Creates a DeflexTransactionGroup from an API response | |
* @param apiResponse - The API response object | |
* @returns A new DeflexTransactionGroup | |
*/ | |
static fromApiResponse(apiResponse: SwapTxnsResponse) { | |
return new DeflexTransactionGroup( | |
apiResponse.txns.map((txn) => DeflexTransaction.fromApiResponse(txn)), | |
) | |
} | |
} | |
/** | |
* Represents a quote from the Deflex API | |
*/ | |
export class DeflexQuote { | |
public quote: number | null | |
public fromASAID: number | |
public toASAID: number | |
public type: string | |
public amountIn: number | |
public profitAmount: number | null | |
public profitASAID: number | null | |
public priceBaseline: number | null | |
public route: DeflexRoute[] | |
public quotes: DexQuote[] | |
public requiredAppOptIns: number[] | |
public txnPayload: Record<string, string> | null | |
public protocolFees: { [key: string]: number } | |
public flattenedRoute: { [key: string]: number } | |
/** | |
* Creates a new DeflexQuote | |
* @param quote - The quote amount or null if not available | |
* @param fromASAID - The ID of the asset to swap from | |
* @param toASAID - The ID of the asset to swap to | |
* @param type - The type of swap | |
* @param amount - The amount to swap | |
* @param profitAmount - The profit amount or null if not available | |
* @param profitASAID - The ID of the profit asset or null if not available | |
* @param priceBaseline - The price baseline or null if not available | |
* @param route - The route for the swap | |
* @param quotes - The quotes from different DEXes | |
* @param requiredAppOptIns - The app IDs that need to be opted into | |
* @param txnPayload - The transaction payload or null if not available | |
* @param protocolFees - The protocol fees | |
* @param flattenedRoute - The flattened route | |
*/ | |
constructor( | |
quote: number | null, | |
fromASAID: number, | |
toASAID: number, | |
type: string, | |
amount: number, | |
profitAmount: number | null, | |
profitASAID: number | null, | |
priceBaseline: number | null, | |
route: DeflexRoute[], | |
quotes: DexQuote[], | |
requiredAppOptIns: number[], | |
txnPayload: Record<string, string> | null, | |
protocolFees: { [key: string]: number }, | |
flattenedRoute: { [key: string]: number }, | |
) { | |
this.quote = quote | |
this.fromASAID = fromASAID | |
this.toASAID = toASAID | |
this.type = type | |
this.amountIn = amount | |
this.profitAmount = profitAmount | |
this.profitASAID = profitASAID | |
this.priceBaseline = priceBaseline | |
this.route = route | |
this.quotes = quotes | |
this.requiredAppOptIns = requiredAppOptIns | |
this.txnPayload = txnPayload | |
this.protocolFees = protocolFees | |
this.flattenedRoute = flattenedRoute | |
} | |
/** | |
* Creates a DeflexQuote from an API response | |
* @param type - The type of swap | |
* @param fromASAID - The ID of the asset to swap from | |
* @param toASAID - The ID of the asset to swap to | |
* @param amount - The amount to swap | |
* @param apiResponse - The API response object | |
* @returns A new DeflexQuote | |
*/ | |
static fromAPIResponse( | |
type: string, | |
fromASAID: number, | |
toASAID: number, | |
amount: number, | |
apiResponse: QuoteResponse, | |
): DeflexQuote { | |
return new DeflexQuote( | |
apiResponse.quote === '' | |
? null | |
: parseFloat(apiResponse.quote.toString()), | |
fromASAID, | |
toASAID, | |
type, | |
amount, | |
apiResponse.profit.amount, | |
apiResponse.profit.asa.id, | |
apiResponse.priceBaseline, | |
apiResponse.route.map((_route: any) => | |
DeflexRoute.fromApiResponse(_route), | |
), | |
apiResponse.quotes.map((quote: any) => DexQuote.fromApiResponse(quote)), | |
apiResponse.requiredAppOptIns, | |
apiResponse.txnPayload, | |
apiResponse.protocolFees, | |
apiResponse.flattenedRoute, | |
) | |
} | |
} | |
/** | |
* Represents a route from the Deflex API | |
*/ | |
export class DeflexRoute { | |
public percent: number | |
public path: DeflexPathElement[] | |
/** | |
* Creates a new DeflexRoute | |
* @param percent - The percentage of the swap that uses this route | |
* @param path - The path elements for this route | |
*/ | |
constructor(percent: number, path: DeflexPathElement[]) { | |
this.percent = percent | |
this.path = path | |
} | |
/** | |
* Creates a DeflexRoute from an API response | |
* @param apiResponse - The API response object | |
* @returns A new DeflexRoute | |
*/ | |
static fromApiResponse(apiResponse: { | |
percentage: number | |
path: { | |
name: string | |
in: { id: number } | |
out: { id: number } | |
}[] | |
}): DeflexRoute { | |
return new DeflexRoute( | |
apiResponse.percentage, | |
apiResponse.path.map((pathElement) => | |
DeflexPathElement.fromAPIResponse(pathElement), | |
), | |
) | |
} | |
} | |
/** | |
* Represents a path element in a route from the Deflex API | |
*/ | |
export class DeflexPathElement { | |
public name: string | |
public inputASAID: number | |
public outputASAID: number | |
/** | |
* Creates a new DeflexPathElement | |
* @param name - The name of the DEX | |
* @param inputASAID - The ID of the input asset | |
* @param outputASAID - The ID of the output asset | |
*/ | |
constructor(name: string, inputASAID: number, outputASAID: number) { | |
this.name = name | |
this.inputASAID = inputASAID | |
this.outputASAID = outputASAID | |
} | |
/** | |
* Creates a DeflexPathElement from an API response | |
* @param apiResponse - The API response object | |
* @returns A new DeflexPathElement | |
*/ | |
static fromAPIResponse(apiResponse: { | |
name: string | |
in: { id: number } | |
out: { id: number } | |
}): DeflexPathElement { | |
return new DeflexPathElement( | |
apiResponse.name, | |
apiResponse.in.id, | |
apiResponse.out.id, | |
) | |
} | |
} | |
/** | |
* Represents a quote from a specific DEX in the Deflex API | |
*/ | |
export class DexQuote { | |
public name: string | |
public value: string | |
/** | |
* Creates a new DexQuote | |
* @param name - The name of the DEX | |
* @param value - The quote value | |
*/ | |
constructor(name: string, value: string) { | |
this.name = name | |
this.value = value | |
} | |
/** | |
* Creates a DexQuote from an API response | |
* @param apiResponse - The API response object | |
* @returns A new DexQuote | |
*/ | |
static fromApiResponse(apiResponse: { | |
name: string | |
value: string | |
}): DexQuote { | |
return new DexQuote(apiResponse.name, apiResponse.value) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment