Skip to content
This repository was archived by the owner on Jan 14, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/deflex/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {
DEFAULT_ALGOD_URI,
DEFAULT_API_BASE_URL,
DEFAULT_AUTO_OPT_IN,
DEFAULT_DEBUG_LEVEL,
DEFAULT_FEE_BPS,
DEFAULT_MAX_DEPTH,
DEFAULT_MAX_GROUP_SIZE,
DEPRECATED_PROTOCOLS,
MAX_FEE_BPS,
} from './constants'
import { Logger } from './logger'
import { request } from './utils'
import type { SwapMiddleware } from './middleware'
import type {
Expand Down Expand Up @@ -70,9 +72,14 @@ export class DeflexClient {
* @param config.referrerAddress - Referrer address for fee sharing (receives 25% of swap fees)
* @param config.feeBps - Fee in basis points (default: 15 = 0.15%, max: 300 = 3.00%)
* @param config.autoOptIn - Automatically detect and add required opt-in transactions (default: false)
* @param config.debugLevel - Debug logging level (default: 'none')
* @param config.middleware - Array of middleware to apply to swaps (default: [])
*/
constructor(config: DeflexConfigParams & { middleware?: SwapMiddleware[] }) {
// Set logger level FIRST (before any operations)
const debugLevel = config.debugLevel ?? DEFAULT_DEBUG_LEVEL
Logger.setLevel(debugLevel)

// Validate and set config
this.config = {
apiKey: this.validateApiKey(config.apiKey),
Expand All @@ -85,6 +92,7 @@ export class DeflexClient {
: undefined,
feeBps: this.validateFeeBps(config.feeBps ?? DEFAULT_FEE_BPS),
autoOptIn: config.autoOptIn ?? DEFAULT_AUTO_OPT_IN,
debugLevel,
}

// Create Algodv2 client
Expand Down Expand Up @@ -429,6 +437,7 @@ export class DeflexClient {
signer,
middleware: this.middleware,
note,
debugLevel: this.config.debugLevel,
})

return composer
Expand Down
66 changes: 48 additions & 18 deletions packages/deflex/src/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type TransactionWithSigner,
} from 'algosdk'
import { DEFAULT_CONFIRMATION_ROUNDS } from './constants'
import { Logger } from './logger'
import type { SwapMiddleware, SwapContext, QuoteContext } from './middleware'
import type {
FetchQuoteResponse,
Expand Down Expand Up @@ -76,6 +77,8 @@ export interface SwapComposerConfig {
readonly middleware?: SwapMiddleware[]
/** Optional note field for the user-signed input transaction (payment or asset transfer) */
readonly note?: Uint8Array
/** Debug logging level (propagated from DeflexClient) */
readonly debugLevel?: 'none' | 'info' | 'debug' | 'trace'
}

/**
Expand Down Expand Up @@ -144,6 +147,11 @@ export class SwapComposer {
* @param config.middleware - Middleware to apply during swap composition
*/
constructor(config: SwapComposerConfig) {
// Set logger level from config
if (config.debugLevel) {
Logger.setLevel(config.debugLevel)
}

// Validate required parameters
if (!config.quote) {
throw new Error('Quote is required')
Expand Down Expand Up @@ -306,6 +314,7 @@ export class SwapComposer {
inputTxnRelativeIndex,
outputTxnRelativeIndex,
} = await this.processSwapTransactions()

const afterTxns = await this.executeMiddlewareHooks('afterSwap')

// Check total length before adding swap and afterSwap transactions
Expand Down Expand Up @@ -340,6 +349,7 @@ export class SwapComposer {
}

this.swapTransactionsAdded = true

return this
}

Expand Down Expand Up @@ -417,7 +427,7 @@ export class SwapComposer {
await this.addSwapTransactions()
}

return this.atc.submit(this.algodClient)
return await this.atc.submit(this.algodClient)
}

/**
Expand Down Expand Up @@ -448,27 +458,46 @@ export class SwapComposer {
await this.addSwapTransactions()
}

const { txIDs, ...result } = await this.atc.execute(
this.algodClient,
waitRounds,
)
try {
const { txIDs, ...result } = await this.atc.execute(
this.algodClient,
waitRounds,
)

// Store transaction IDs in summaryData
if (this.summaryData) {
if (this.inputTransactionIndex !== undefined) {
this.summaryData.inputTxnId = txIDs[this.inputTransactionIndex]
}
if (this.outputTransactionIndex !== undefined) {
this.summaryData.outputTxnId = txIDs[this.outputTransactionIndex]
// Store transaction IDs in summaryData
if (this.summaryData) {
if (this.inputTransactionIndex !== undefined) {
this.summaryData.inputTxnId = txIDs[this.inputTransactionIndex]
}
if (this.outputTransactionIndex !== undefined) {
this.summaryData.outputTxnId = txIDs[this.outputTransactionIndex]
}
}
}

// Extract actual output amount from confirmed transaction
await this.extractActualOutputAmount()
// Extract actual output amount from confirmed transaction
await this.extractActualOutputAmount()

return {
...result,
txIds: txIDs,
return {
...result,
txIds: txIDs,
}
} catch (error) {
// Log comprehensive failure context when swap execution fails
const { logSwapExecutionFailure } = await import('./debug')
logSwapExecutionFailure(
{
quote: this.quote,
address: this.address,
slippage: 0, // Not stored in composer, would need to be passed from client
transactionCount: this.count(),
middlewareCount: this.middleware.length,
groupSize: this.count(),
},
error,
)

// Re-throw the error
throw error
}
}

Expand Down Expand Up @@ -807,6 +836,7 @@ export class SwapComposer {
}

const txns = await mw[hookName](swapContext)

allTxns.push(...txns)
}

Expand Down
3 changes: 3 additions & 0 deletions packages/deflex/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@ export const DEFAULT_MAX_DEPTH = 4
/** Default auto opt-in setting (automatic asset/app opt-in detection) */
export const DEFAULT_AUTO_OPT_IN = false

/** Default debug level (no debug logging) */
export const DEFAULT_DEBUG_LEVEL = 'none' as const

/** Default number of rounds to wait for transaction confirmation */
export const DEFAULT_CONFIRMATION_ROUNDS = 10
50 changes: 50 additions & 0 deletions packages/deflex/src/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Debug utilities for swap execution failures
* @internal
*/

import { Logger } from './logger'
import type { DeflexQuote, FetchQuoteResponse } from './types'

/**
* Context for swap execution debugging
*/
export interface SwapExecutionContext {
quote: DeflexQuote | FetchQuoteResponse
address: string
slippage: number
transactionCount: number
middlewareCount: number
groupSize: number
}

/**
* Log detailed context when a swap execution fails
* Only logs when debug level is enabled
*/
export function logSwapExecutionFailure(
context: SwapExecutionContext,
error: unknown,
): void {
Logger.debug('Swap execution failed', {
error: error instanceof Error ? error.message : String(error),
quote: {
fromASAID: context.quote.fromASAID,
toASAID: context.quote.toASAID,
type: context.quote.type,
quote: context.quote.quote.toString(),
requiredAppOptIns: context.quote.requiredAppOptIns,
route: context.quote.route?.map((r) => ({
percentage: r.percentage,
path: r.path.map((p) => p.name).join(' → '),
})),
},
swap: {
address: context.address,
slippage: context.slippage,
transactionCount: context.transactionCount,
middlewareCount: context.middlewareCount,
groupSize: context.groupSize,
},
})
}
Loading