Skip to content

Instantly share code, notes, and snippets.

@drichar
Created June 25, 2025 18:09
Show Gist options
  • Save drichar/4f8432a6396df600007c9e47b0b596ac to your computer and use it in GitHub Desktop.
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.
// 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