@txnlab/haystack-router 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +498 -0
- package/dist/debug-8aRsrfhx.mjs +38 -0
- package/dist/debug-8aRsrfhx.mjs.map +1 -0
- package/dist/index.d.mts +1246 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1028 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger-DUVOA2Oo.mjs +117 -0
- package/dist/logger-DUVOA2Oo.mjs.map +1 -0
- package/package.json +67 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,1246 @@
|
|
|
1
|
+
import { ABIArgument, ABIMethod, ABIResult, Address, Algodv2, BoxReference, OnApplicationComplete, ResourceReference, SuggestedParams, Transaction, TransactionSigner, TransactionWithSigner } from "algosdk";
|
|
2
|
+
|
|
3
|
+
//#region src/constants.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Supported DEX protocols for swap routing
|
|
6
|
+
*/
|
|
7
|
+
declare enum Protocol {
|
|
8
|
+
TinymanV2 = "TinymanV2",
|
|
9
|
+
Algofi = "Algofi",
|
|
10
|
+
Algomint = "Algomint",
|
|
11
|
+
Pact = "Pact",
|
|
12
|
+
Folks = "Folks",
|
|
13
|
+
TAlgo = "TAlgo",
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Deprecated protocols that are automatically excluded from routing
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
declare const DEPRECATED_PROTOCOLS: readonly ["Humble", "Tinyman"];
|
|
21
|
+
/** Default Algod node URI for mainnet */
|
|
22
|
+
declare const DEFAULT_ALGOD_URI = "https://mainnet-api.4160.nodely.dev/";
|
|
23
|
+
/** Default Algod node token (empty for public nodes) */
|
|
24
|
+
declare const DEFAULT_ALGOD_TOKEN = "";
|
|
25
|
+
/** Default Algod node port */
|
|
26
|
+
declare const DEFAULT_ALGOD_PORT = 443;
|
|
27
|
+
/** Default Haystack Router API base URL */
|
|
28
|
+
declare const DEFAULT_API_BASE_URL = "https://hayrouter.txnlab.dev/api";
|
|
29
|
+
/** Default fee in basis points (0.10%) */
|
|
30
|
+
declare const DEFAULT_FEE_BPS = 10;
|
|
31
|
+
/** Maximum allowed fee in basis points (3.00%) */
|
|
32
|
+
declare const MAX_FEE_BPS = 300;
|
|
33
|
+
/** Default maximum transaction group size */
|
|
34
|
+
declare const DEFAULT_MAX_GROUP_SIZE = 16;
|
|
35
|
+
/** Default maximum routing depth (number of hops) */
|
|
36
|
+
declare const DEFAULT_MAX_DEPTH = 4;
|
|
37
|
+
/** Default auto opt-in setting (automatic asset/app opt-in detection) */
|
|
38
|
+
declare const DEFAULT_AUTO_OPT_IN = false;
|
|
39
|
+
/** Default debug level (no debug logging) */
|
|
40
|
+
declare const DEFAULT_DEBUG_LEVEL: "none";
|
|
41
|
+
/** Default number of rounds to wait for transaction confirmation */
|
|
42
|
+
declare const DEFAULT_CONFIRMATION_ROUNDS = 10;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/types.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* Configuration parameters for initializing RouterClient
|
|
47
|
+
*/
|
|
48
|
+
interface ConfigParams {
|
|
49
|
+
/** API key for Haystack Router API (required) */
|
|
50
|
+
readonly apiKey: string;
|
|
51
|
+
/** Base URL for the Haystack Router API (default: https://hayrouter.txnlab.dev) */
|
|
52
|
+
readonly apiBaseUrl?: string;
|
|
53
|
+
/** Algod node URI (default: https://mainnet-api.4160.nodely.dev/) */
|
|
54
|
+
readonly algodUri?: string;
|
|
55
|
+
/** Algod node token (can be empty string for public nodes) */
|
|
56
|
+
readonly algodToken?: string;
|
|
57
|
+
/** Algod node port (default: 443) */
|
|
58
|
+
readonly algodPort?: string | number;
|
|
59
|
+
/** Referrer address for fee sharing (receives 25% of swap fees) */
|
|
60
|
+
readonly referrerAddress?: string;
|
|
61
|
+
/** Fee in basis points (default: 15 = 0.15%, max: 300 = 3.00%) */
|
|
62
|
+
readonly feeBps?: number;
|
|
63
|
+
/** Automatically detect and add required opt-in transactions (default: false) */
|
|
64
|
+
readonly autoOptIn?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Debug logging level (default: 'none')
|
|
67
|
+
*
|
|
68
|
+
* - 'none': No debug logging (default)
|
|
69
|
+
* - 'info': High-level operations (quote fetched, swap created, submitted)
|
|
70
|
+
* - 'debug': Detailed flow (middleware applied, validation, status transitions)
|
|
71
|
+
* - 'trace': Everything including request/response payloads, transaction details
|
|
72
|
+
*/
|
|
73
|
+
readonly debugLevel?: 'none' | 'info' | 'debug' | 'trace';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Internal configuration with all required fields validated
|
|
77
|
+
*
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
type Config = Omit<Required<ConfigParams>, 'referrerAddress' | 'debugLevel'> & {
|
|
81
|
+
readonly referrerAddress: string | undefined;
|
|
82
|
+
readonly debugLevel: 'none' | 'info' | 'debug' | 'trace';
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Swap quote type determining which amount is fixed
|
|
86
|
+
*/
|
|
87
|
+
type QuoteType = 'fixed-input' | 'fixed-output';
|
|
88
|
+
/**
|
|
89
|
+
* Parameters for requesting a swap quote from the Haystack Router API (raw API method)
|
|
90
|
+
*/
|
|
91
|
+
interface FetchQuoteParams {
|
|
92
|
+
/** Input asset ID */
|
|
93
|
+
readonly fromASAID: bigint | number;
|
|
94
|
+
/** Output asset ID */
|
|
95
|
+
readonly toASAID: bigint | number;
|
|
96
|
+
/** Amount to swap (in base units) */
|
|
97
|
+
readonly amount: bigint | number;
|
|
98
|
+
/** Quote type (default: 'fixed-input') */
|
|
99
|
+
readonly type?: QuoteType;
|
|
100
|
+
/** Protocols to exclude from routing (default: []) */
|
|
101
|
+
readonly disabledProtocols?: readonly Protocol[];
|
|
102
|
+
/** Maximum transaction group size (default: 16) */
|
|
103
|
+
readonly maxGroupSize?: number;
|
|
104
|
+
/** Maximum depth of the route (default: 4) */
|
|
105
|
+
readonly maxDepth?: number;
|
|
106
|
+
/** Whether to include asset opt-in transaction (overrides config.autoOptIn if set) */
|
|
107
|
+
readonly optIn?: boolean;
|
|
108
|
+
/** Address of the account that will perform the swap (required if autoOptIn is enabled) */
|
|
109
|
+
readonly address?: string | null;
|
|
110
|
+
/**
|
|
111
|
+
* ⚠️ **ADVANCED USE ONLY - NON-COMPOSABLE SWAPS** ⚠️
|
|
112
|
+
*
|
|
113
|
+
* When set to `true`, removes the automatic exclusion of Tinyman v1 from routing,
|
|
114
|
+
* allowing the API to route swaps through protocols that produce **non-composable**
|
|
115
|
+
* transaction groups.
|
|
116
|
+
*
|
|
117
|
+
* **Critical Limitations:**
|
|
118
|
+
* - You CANNOT add transactions before/after swap transactions in the atomic group
|
|
119
|
+
* - Asset opt-ins and opt-outs MUST be sent in separate transaction groups
|
|
120
|
+
* - The SwapComposer will throw errors if you attempt to compose additional transactions
|
|
121
|
+
* - This parameter CANNOT be used with `optIn: true` or `config.autoOptIn: true`
|
|
122
|
+
* - This parameter is NOT supported in `newQuote()` (only `fetchQuote()`)
|
|
123
|
+
*
|
|
124
|
+
* **When to use this:**
|
|
125
|
+
* Only use this if you have built custom infrastructure to:
|
|
126
|
+
* 1. Detect when the route uses non-composable protocols (Tinyman v1)
|
|
127
|
+
* 2. Handle opt-in/opt-out transactions separately from the swap group
|
|
128
|
+
* 3. Coordinate multiple transaction groups instead of composing a single atomic group
|
|
129
|
+
*
|
|
130
|
+
* **This breaks the standard SDK workflow.** The default behavior of always excluding
|
|
131
|
+
* Tinyman v1 ensures all swaps are composable, which is the recommended approach.
|
|
132
|
+
*
|
|
133
|
+
* @default false
|
|
134
|
+
* @internal This is not a supported public API feature
|
|
135
|
+
*/
|
|
136
|
+
readonly _allowNonComposableSwaps?: boolean;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Asset information from the Haystack Router API
|
|
140
|
+
*/
|
|
141
|
+
interface Asset {
|
|
142
|
+
/** Asset ID */
|
|
143
|
+
readonly id: number;
|
|
144
|
+
/** Number of decimal places */
|
|
145
|
+
readonly decimals: number;
|
|
146
|
+
/** Unit name */
|
|
147
|
+
readonly unit_name: string;
|
|
148
|
+
/** Full asset name */
|
|
149
|
+
readonly name: string;
|
|
150
|
+
/** Price in ALGO */
|
|
151
|
+
readonly price_algo: number;
|
|
152
|
+
/** Price in USD */
|
|
153
|
+
readonly price_usd: number;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Profit information for a swap
|
|
157
|
+
*/
|
|
158
|
+
interface Profit {
|
|
159
|
+
/** Profit amount in base units */
|
|
160
|
+
readonly amount: number;
|
|
161
|
+
/** The asset in which profit is denominated */
|
|
162
|
+
readonly asa: Asset;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* A single step in a swap route path
|
|
166
|
+
*/
|
|
167
|
+
interface PathElement {
|
|
168
|
+
/** Protocol name and fee tier */
|
|
169
|
+
readonly name: string;
|
|
170
|
+
/** Protocol class identifier */
|
|
171
|
+
readonly class: string[][];
|
|
172
|
+
/** Input asset */
|
|
173
|
+
readonly in: Asset;
|
|
174
|
+
/** Output asset */
|
|
175
|
+
readonly out: Asset;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* A route with its percentage of the total swap amount
|
|
179
|
+
*/
|
|
180
|
+
interface Route {
|
|
181
|
+
/** Percentage of total swap amount routed through this path */
|
|
182
|
+
readonly percentage: number;
|
|
183
|
+
/** The sequence of swaps in this route */
|
|
184
|
+
readonly path: PathElement[];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Quote from a specific DEX protocol
|
|
188
|
+
*/
|
|
189
|
+
interface DexQuote {
|
|
190
|
+
/** Protocol name and fee tier */
|
|
191
|
+
readonly name: string;
|
|
192
|
+
/** Protocol class */
|
|
193
|
+
readonly class: string;
|
|
194
|
+
/** Quoted output value */
|
|
195
|
+
readonly value: number;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Encrypted transaction payload from the quote
|
|
199
|
+
*/
|
|
200
|
+
interface TxnPayload {
|
|
201
|
+
/** Initialization vector for decryption */
|
|
202
|
+
readonly iv: string;
|
|
203
|
+
/** Encrypted transaction data */
|
|
204
|
+
readonly data: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Quote response from the Haystack Router API
|
|
208
|
+
*/
|
|
209
|
+
interface FetchQuoteResponse {
|
|
210
|
+
/** The quoted output amount or input amount (depending on quote type) */
|
|
211
|
+
readonly quote: string | number;
|
|
212
|
+
/** Profit information for the swap */
|
|
213
|
+
readonly profit: Profit;
|
|
214
|
+
/** Baseline price without fees */
|
|
215
|
+
readonly priceBaseline: number;
|
|
216
|
+
/** Price impact for the user */
|
|
217
|
+
readonly userPriceImpact?: number;
|
|
218
|
+
/** Overall market price impact */
|
|
219
|
+
readonly marketPriceImpact?: number;
|
|
220
|
+
/** USD value of input amount */
|
|
221
|
+
readonly usdIn: number;
|
|
222
|
+
/** USD value of output amount */
|
|
223
|
+
readonly usdOut: number;
|
|
224
|
+
/** The routing path(s) for the swap */
|
|
225
|
+
readonly route: Route[];
|
|
226
|
+
/** Flattened view of routing percentages by protocol */
|
|
227
|
+
readonly flattenedRoute: Record<string, number>;
|
|
228
|
+
/** Individual quotes from each DEX */
|
|
229
|
+
readonly quotes: DexQuote[];
|
|
230
|
+
/** App IDs that require opt-in for this swap */
|
|
231
|
+
readonly requiredAppOptIns: number[];
|
|
232
|
+
/** Encrypted transaction payload for generating swap transactions */
|
|
233
|
+
readonly txnPayload: TxnPayload | null;
|
|
234
|
+
/** Fees charged by each protocol */
|
|
235
|
+
readonly protocolFees: Record<string, number>;
|
|
236
|
+
/** Input asset ID */
|
|
237
|
+
readonly fromASAID: number;
|
|
238
|
+
/** Output asset ID */
|
|
239
|
+
readonly toASAID: number;
|
|
240
|
+
/** Quote type */
|
|
241
|
+
readonly type: string;
|
|
242
|
+
/** Performance timing data */
|
|
243
|
+
readonly timing?: unknown;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Enhanced quote result returned by RouterClient.newQuote()
|
|
247
|
+
*
|
|
248
|
+
* Extends the raw API response with additional metadata and type normalization.
|
|
249
|
+
*/
|
|
250
|
+
type SwapQuote = Omit<FetchQuoteResponse, 'quote'> & {
|
|
251
|
+
/**
|
|
252
|
+
* The quoted output amount or input amount (coerced to bigint)
|
|
253
|
+
*
|
|
254
|
+
* For fixed-input swaps: This is the output amount you'll receive
|
|
255
|
+
* For fixed-output swaps: This is the input amount you'll need to provide
|
|
256
|
+
*/
|
|
257
|
+
readonly quote: bigint;
|
|
258
|
+
/** The original amount from the quote request (in base units) */
|
|
259
|
+
readonly amount: bigint;
|
|
260
|
+
/** The address parameter from the quote request (if provided) */
|
|
261
|
+
readonly address?: string;
|
|
262
|
+
/** Timestamp when the quote was created (in milliseconds) */
|
|
263
|
+
readonly createdAt: number;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Transaction signature from the Haystack Router API
|
|
267
|
+
*/
|
|
268
|
+
interface Signature {
|
|
269
|
+
/** Signature type */
|
|
270
|
+
readonly type: 'logic_signature' | 'secret_key';
|
|
271
|
+
/** Signature data */
|
|
272
|
+
readonly value: unknown;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Transaction data from the Haystack Router API
|
|
276
|
+
*/
|
|
277
|
+
interface SwapTransaction {
|
|
278
|
+
/** Base64-encoded transaction data */
|
|
279
|
+
readonly data: string;
|
|
280
|
+
/** Group ID for the transaction */
|
|
281
|
+
readonly group: string;
|
|
282
|
+
/** Logic signature blob (if applicable) */
|
|
283
|
+
readonly logicSigBlob: unknown | false;
|
|
284
|
+
/** Signature data (false if user must sign) */
|
|
285
|
+
readonly signature: Signature | false;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Parameters for fetching executable swap transactions
|
|
289
|
+
*/
|
|
290
|
+
interface FetchSwapTxnsParams {
|
|
291
|
+
/** Quote response from fetchQuote() or newQuote() */
|
|
292
|
+
readonly quote: FetchQuoteResponse | SwapQuote;
|
|
293
|
+
/** Algorand address that will sign the transactions */
|
|
294
|
+
readonly address: string;
|
|
295
|
+
/** Slippage tolerance as a percentage (e.g., 1 for 1%) */
|
|
296
|
+
readonly slippage: number;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Request body for fetching swap transactions
|
|
300
|
+
*
|
|
301
|
+
* @internal
|
|
302
|
+
*/
|
|
303
|
+
interface FetchSwapTxnsBody {
|
|
304
|
+
/** API key for authentication */
|
|
305
|
+
readonly apiKey: string;
|
|
306
|
+
/** Address of the account that will sign the transactions */
|
|
307
|
+
readonly address: string;
|
|
308
|
+
/** Encrypted transaction payload from the quote */
|
|
309
|
+
readonly txnPayloadJSON: TxnPayload | null;
|
|
310
|
+
/** Slippage tolerance as a percentage */
|
|
311
|
+
readonly slippage: number;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Response from fetching swap transactions
|
|
315
|
+
*/
|
|
316
|
+
interface FetchSwapTxnsResponse {
|
|
317
|
+
/** Array of transaction data */
|
|
318
|
+
readonly txns: SwapTransaction[];
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Summary of a swap transaction group after execution
|
|
322
|
+
*
|
|
323
|
+
* Contains the exact input/output amounts and total transaction fees.
|
|
324
|
+
* Only available after calling execute() on a SwapComposer instance.
|
|
325
|
+
*/
|
|
326
|
+
interface SwapSummary {
|
|
327
|
+
/** Input asset ID (0 for ALGO) */
|
|
328
|
+
inputAssetId: bigint;
|
|
329
|
+
/** Output asset ID (0 for ALGO) */
|
|
330
|
+
outputAssetId: bigint;
|
|
331
|
+
/** Exact input amount in base units, from the user-signed transaction */
|
|
332
|
+
inputAmount: bigint;
|
|
333
|
+
/** Exact output amount received in base units, from the inner transaction */
|
|
334
|
+
outputAmount: bigint;
|
|
335
|
+
/** Quote type */
|
|
336
|
+
type: QuoteType;
|
|
337
|
+
/** Total ALGO transaction fees in microAlgos */
|
|
338
|
+
totalFees: bigint;
|
|
339
|
+
/** Number of transactions in the group */
|
|
340
|
+
transactionCount: number;
|
|
341
|
+
/** Transaction ID of the user-signed input transaction (payment/asset transfer) */
|
|
342
|
+
inputTxnId: string;
|
|
343
|
+
/** Transaction ID of the app call containing the output transfer as an inner transaction */
|
|
344
|
+
outputTxnId: string;
|
|
345
|
+
/** Address that sent the input asset (the user) */
|
|
346
|
+
inputSender: string;
|
|
347
|
+
/** Address that sent the output asset to the user */
|
|
348
|
+
outputSender: string;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Method call to be executed as part of the swap
|
|
352
|
+
*/
|
|
353
|
+
interface MethodCall {
|
|
354
|
+
/** The ID of the smart contract to call. Set this to 0 to indicate an application creation call. */
|
|
355
|
+
appID: number | bigint;
|
|
356
|
+
/** The method to call on the smart contract */
|
|
357
|
+
method: ABIMethod;
|
|
358
|
+
/** The arguments to include in the method call. If omitted, no arguments will be passed to the method. */
|
|
359
|
+
methodArgs?: ABIArgument[];
|
|
360
|
+
/** The address of the sender of this application call */
|
|
361
|
+
sender: string | Address;
|
|
362
|
+
/** Transactions params to use for this application call */
|
|
363
|
+
suggestedParams: SuggestedParams;
|
|
364
|
+
/** The OnComplete action to take for this application call. If omitted, OnApplicationComplete.NoOpOC will be used. */
|
|
365
|
+
onComplete?: OnApplicationComplete;
|
|
366
|
+
/** The approval program for this application call. Only set this if this is an application creation call, or if onComplete is OnApplicationComplete.UpdateApplicationOC */
|
|
367
|
+
approvalProgram?: Uint8Array;
|
|
368
|
+
/** The clear program for this application call. Only set this if this is an application creation call, or if onComplete is OnApplicationComplete.UpdateApplicationOC */
|
|
369
|
+
clearProgram?: Uint8Array;
|
|
370
|
+
/** The global integer schema size. Only set this if this is an application creation call. */
|
|
371
|
+
numGlobalInts?: number;
|
|
372
|
+
/** The global byte slice schema size. Only set this if this is an application creation call. */
|
|
373
|
+
numGlobalByteSlices?: number;
|
|
374
|
+
/** The local integer schema size. Only set this if this is an application creation call. */
|
|
375
|
+
numLocalInts?: number;
|
|
376
|
+
/** The local byte slice schema size. Only set this if this is an application creation call. */
|
|
377
|
+
numLocalByteSlices?: number;
|
|
378
|
+
/** The number of extra pages to allocate for the application's programs. Only set this if this is an application creation call. If omitted, defaults to 0. */
|
|
379
|
+
extraPages?: number;
|
|
380
|
+
/** Array of Address strings that represent external accounts supplied to this application. If accounts are provided here, the accounts specified in the method args will appear after these. */
|
|
381
|
+
appAccounts?: Array<string | Address>;
|
|
382
|
+
/** Array of App ID numbers that represent external apps supplied to this application. If apps are provided here, the apps specified in the method args will appear after these. */
|
|
383
|
+
appForeignApps?: Array<number | bigint>;
|
|
384
|
+
/** Array of Asset ID numbers that represent external assets supplied to this application. If assets are provided here, the assets specified in the method args will appear after these. */
|
|
385
|
+
appForeignAssets?: Array<number | bigint>;
|
|
386
|
+
/** The box references for this application call */
|
|
387
|
+
boxes?: BoxReference[];
|
|
388
|
+
/** The resource references for this application call */
|
|
389
|
+
access?: ResourceReference[];
|
|
390
|
+
/** The note value for this application call */
|
|
391
|
+
note?: Uint8Array;
|
|
392
|
+
/** The lease value for this application call */
|
|
393
|
+
lease?: Uint8Array;
|
|
394
|
+
/** If provided, the address that the sender will be rekeyed to at the conclusion of this application call */
|
|
395
|
+
rekeyTo?: string | Address;
|
|
396
|
+
/** The lowest application version for which this transaction should immediately fail. 0 indicates that no version check should be performed. */
|
|
397
|
+
rejectVersion?: number | bigint;
|
|
398
|
+
/** A transaction signer that can authorize this application call from sender */
|
|
399
|
+
signer?: TransactionSigner;
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/middleware.d.ts
|
|
403
|
+
/**
|
|
404
|
+
* Context provided to middleware shouldApply hook
|
|
405
|
+
*/
|
|
406
|
+
interface QuoteContext {
|
|
407
|
+
/** Input asset ID (always bigint for precision and future-proofing) */
|
|
408
|
+
readonly fromASAID: bigint;
|
|
409
|
+
/** Output asset ID (always bigint for precision and future-proofing) */
|
|
410
|
+
readonly toASAID: bigint;
|
|
411
|
+
/** Amount to swap (always bigint for precision and future-proofing) */
|
|
412
|
+
readonly amount: bigint;
|
|
413
|
+
/** Quote type */
|
|
414
|
+
readonly type: QuoteType;
|
|
415
|
+
/** Address of the account that will perform the swap (if provided) */
|
|
416
|
+
readonly address?: string;
|
|
417
|
+
/** Algodv2 client instance for making additional queries */
|
|
418
|
+
readonly algodClient: Algodv2;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Context provided to middleware hooks during swap composition
|
|
422
|
+
*/
|
|
423
|
+
interface SwapContext {
|
|
424
|
+
/** The quote result from newQuote() */
|
|
425
|
+
readonly quote: SwapQuote;
|
|
426
|
+
/** The address of the account performing the swap */
|
|
427
|
+
readonly address: string;
|
|
428
|
+
/** Algodv2 client instance for making additional queries/transactions */
|
|
429
|
+
readonly algodClient: Algodv2;
|
|
430
|
+
/** Suggested transaction parameters from the network */
|
|
431
|
+
readonly suggestedParams: SuggestedParams;
|
|
432
|
+
/** Input asset ID (always bigint for precision and future-proofing) */
|
|
433
|
+
readonly fromASAID: bigint;
|
|
434
|
+
/** Output asset ID (always bigint for precision and future-proofing) */
|
|
435
|
+
readonly toASAID: bigint;
|
|
436
|
+
/** Transaction signer for transactions that need to be signed by the user */
|
|
437
|
+
readonly signer: TransactionSigner;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Middleware interface for extending Deflex swap functionality
|
|
441
|
+
*
|
|
442
|
+
* Middleware allows you to modify quote parameters and inject additional transactions
|
|
443
|
+
* into the atomic swap group. This is useful for assets that require special handling,
|
|
444
|
+
* such as those with transfer restrictions, taxes, or custom smart contract logic.
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```typescript
|
|
448
|
+
* class CustomAssetMiddleware implements SwapMiddleware {
|
|
449
|
+
* readonly name = 'CustomAsset'
|
|
450
|
+
* readonly version = '1.0.0'
|
|
451
|
+
*
|
|
452
|
+
* async shouldApply(context) {
|
|
453
|
+
* return context.fromASAID === CUSTOM_ASSET_ID || context.toASAID === CUSTOM_ASSET_ID
|
|
454
|
+
* }
|
|
455
|
+
*
|
|
456
|
+
* async adjustQuoteParams(params) {
|
|
457
|
+
* // Reduce maxGroupSize to account for extra transactions
|
|
458
|
+
* return { ...params, maxGroupSize: params.maxGroupSize - 3 }
|
|
459
|
+
* }
|
|
460
|
+
*
|
|
461
|
+
* async beforeSwap(context) {
|
|
462
|
+
* // Return transactions to add before the swap
|
|
463
|
+
* return [unfreezeTransaction]
|
|
464
|
+
* }
|
|
465
|
+
*
|
|
466
|
+
* async afterSwap(context) {
|
|
467
|
+
* // Return transactions to add after the swap
|
|
468
|
+
* return [taxTransaction, refreezeTransaction]
|
|
469
|
+
* }
|
|
470
|
+
* }
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
interface SwapMiddleware {
|
|
474
|
+
/** Unique identifier for the middleware */
|
|
475
|
+
readonly name: string;
|
|
476
|
+
/** Semantic version of the middleware */
|
|
477
|
+
readonly version: string;
|
|
478
|
+
/**
|
|
479
|
+
* Determines if this middleware should be applied to the given swap
|
|
480
|
+
*
|
|
481
|
+
* Called during both quote and swap phases. Use this to check if either
|
|
482
|
+
* the input or output asset requires special handling.
|
|
483
|
+
*
|
|
484
|
+
* @param context - Quote context with asset IDs, amount, type, address, and algod client
|
|
485
|
+
* @returns True if middleware should be applied
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```typescript
|
|
489
|
+
* async shouldApply(context) {
|
|
490
|
+
* // Check if asset is registered in our smart contract
|
|
491
|
+
* const assetInfo = await this.getAssetInfo(context.fromASAID)
|
|
492
|
+
* return assetInfo !== null
|
|
493
|
+
* }
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
shouldApply(context: QuoteContext): Promise<boolean>;
|
|
497
|
+
/**
|
|
498
|
+
* Modify quote parameters before fetching the quote
|
|
499
|
+
*
|
|
500
|
+
* **IMPORTANT**: If your middleware adds transactions via `beforeSwap` or `afterSwap`,
|
|
501
|
+
* you MUST reduce `maxGroupSize` accordingly to prevent failures. The Haystack Router API may
|
|
502
|
+
* return routes that use all 16 available transaction slots.
|
|
503
|
+
*
|
|
504
|
+
* Use this to adjust the quote request based on your asset's requirements.
|
|
505
|
+
* Common adjustments include:
|
|
506
|
+
* - Reducing `maxGroupSize` to account for additional transactions (REQUIRED if adding txns)
|
|
507
|
+
* - Adjusting `amount` to account for fees/taxes
|
|
508
|
+
* - Modifying `disabledProtocols` if certain DEXs are incompatible
|
|
509
|
+
*
|
|
510
|
+
* @param params - Original quote parameters
|
|
511
|
+
* @returns Modified quote parameters
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```typescript
|
|
515
|
+
* async adjustQuoteParams(params) {
|
|
516
|
+
* const [fromTaxed, toTaxed] = await Promise.all([
|
|
517
|
+
* this.isAssetTaxed(params.fromASAID),
|
|
518
|
+
* this.isAssetTaxed(params.toASAID),
|
|
519
|
+
* ])
|
|
520
|
+
*
|
|
521
|
+
* // 3 extra transactions per taxed asset
|
|
522
|
+
* let maxGroupSize = params.maxGroupSize ?? 16
|
|
523
|
+
* if (fromTaxed) maxGroupSize -= 3
|
|
524
|
+
* if (toTaxed) maxGroupSize -= 3
|
|
525
|
+
*
|
|
526
|
+
* // Adjust amount for input tax
|
|
527
|
+
* let amount = params.amount
|
|
528
|
+
* if (fromTaxed) {
|
|
529
|
+
* const taxRate = await this.getTaxRate(params.fromASAID)
|
|
530
|
+
* amount = this.applyTax(amount, taxRate)
|
|
531
|
+
* }
|
|
532
|
+
*
|
|
533
|
+
* return { ...params, maxGroupSize, amount }
|
|
534
|
+
* }
|
|
535
|
+
* ```
|
|
536
|
+
*/
|
|
537
|
+
adjustQuoteParams?(params: FetchQuoteParams): Promise<FetchQuoteParams>;
|
|
538
|
+
/**
|
|
539
|
+
* Add transactions before the swap transactions
|
|
540
|
+
*
|
|
541
|
+
* Called when building the swap transaction group. Transactions are added
|
|
542
|
+
* to the group in the order they appear in the returned array.
|
|
543
|
+
*
|
|
544
|
+
* Transaction order in final group: [beforeSwap] → [swap txns] → [afterSwap]
|
|
545
|
+
*
|
|
546
|
+
* @param context - Swap context with quote, address, and algod client
|
|
547
|
+
* @returns Array of transactions with signers to add before swap
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* ```typescript
|
|
551
|
+
* async beforeSwap(context) {
|
|
552
|
+
* const txns: TransactionWithSigner[] = []
|
|
553
|
+
*
|
|
554
|
+
* // Unfreeze user account before swap
|
|
555
|
+
* if (await this.needsUnfreeze(context.fromASAID)) {
|
|
556
|
+
* const unfreezeCall = makeApplicationNoOpTxn(
|
|
557
|
+
* context.address,
|
|
558
|
+
* this.appId,
|
|
559
|
+
* ...,
|
|
560
|
+
* context.suggestedParams
|
|
561
|
+
* )
|
|
562
|
+
*
|
|
563
|
+
* txns.push({
|
|
564
|
+
* txn: unfreezeCall,
|
|
565
|
+
* signer: context.signer, // Use the signer from context
|
|
566
|
+
* })
|
|
567
|
+
* }
|
|
568
|
+
*
|
|
569
|
+
* return txns
|
|
570
|
+
* }
|
|
571
|
+
* ```
|
|
572
|
+
*/
|
|
573
|
+
beforeSwap?(context: SwapContext): Promise<TransactionWithSigner[]>;
|
|
574
|
+
/**
|
|
575
|
+
* Add transactions after the swap transactions
|
|
576
|
+
*
|
|
577
|
+
* Called when building the swap transaction group. Transactions are added
|
|
578
|
+
* to the group in the order they appear in the returned array.
|
|
579
|
+
*
|
|
580
|
+
* Transaction order in final group: [beforeSwap] → [swap txns] → [afterSwap]
|
|
581
|
+
*
|
|
582
|
+
* @param context - Swap context with quote, address, and algod client
|
|
583
|
+
* @returns Array of transactions with signers to add after swap
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```typescript
|
|
587
|
+
* async afterSwap(context) {
|
|
588
|
+
* const txns: TransactionWithSigner[] = []
|
|
589
|
+
*
|
|
590
|
+
* // Pay tax and refreeze account
|
|
591
|
+
* if (await this.isTaxed(context.fromASAID)) {
|
|
592
|
+
* const taxAmount = await this.calculateTax(context)
|
|
593
|
+
* const taxPayment = makeAssetTransferTxn(
|
|
594
|
+
* context.address,
|
|
595
|
+
* this.taxReceiver,
|
|
596
|
+
* taxAmount,
|
|
597
|
+
* context.fromASAID,
|
|
598
|
+
* context.suggestedParams
|
|
599
|
+
* )
|
|
600
|
+
* const refreezeCall = makeApplicationNoOpTxn(
|
|
601
|
+
* context.address,
|
|
602
|
+
* this.appId,
|
|
603
|
+
* ...,
|
|
604
|
+
* context.suggestedParams
|
|
605
|
+
* )
|
|
606
|
+
*
|
|
607
|
+
* txns.push(
|
|
608
|
+
* { txn: taxPayment, signer: context.signer },
|
|
609
|
+
* { txn: refreezeCall, signer: context.signer },
|
|
610
|
+
* )
|
|
611
|
+
* }
|
|
612
|
+
*
|
|
613
|
+
* return txns
|
|
614
|
+
* }
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
afterSwap?(context: SwapContext): Promise<TransactionWithSigner[]>;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Configuration options for AutoOptOutMiddleware
|
|
621
|
+
*/
|
|
622
|
+
interface AutoOptOutConfig {
|
|
623
|
+
/**
|
|
624
|
+
* Array of asset IDs that should be excluded from automatic opt-out behavior
|
|
625
|
+
* @default []
|
|
626
|
+
*/
|
|
627
|
+
readonly excludedAssets?: readonly (number | bigint)[];
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Middleware that automatically adds an asset opt-out transaction when swapping
|
|
631
|
+
* the full balance of an input asset, leaving it with a zero balance.
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```typescript
|
|
635
|
+
* import { RouterClient, AutoOptOutMiddleware } from '@txnlab/haystack-router'
|
|
636
|
+
*
|
|
637
|
+
* const autoOptOut = new AutoOptOutMiddleware({
|
|
638
|
+
* excludedAssets: [31566704], // Don't auto-opt-out of USDC
|
|
639
|
+
* })
|
|
640
|
+
*
|
|
641
|
+
* const router = new RouterClient({
|
|
642
|
+
* apiKey: 'your-api-key',
|
|
643
|
+
* middleware: [autoOptOut],
|
|
644
|
+
* })
|
|
645
|
+
*
|
|
646
|
+
* // When swapping full balance, opt-out transaction is automatically added
|
|
647
|
+
* const quote = await router.newQuote({
|
|
648
|
+
* address: userAddress,
|
|
649
|
+
* fromASAID: someAssetId,
|
|
650
|
+
* toASAID: 0,
|
|
651
|
+
* amount: fullBalance, // If this equals account's full balance, opt-out is added
|
|
652
|
+
* type: 'fixed-input',
|
|
653
|
+
* })
|
|
654
|
+
* ```
|
|
655
|
+
*/
|
|
656
|
+
declare class AutoOptOutMiddleware implements SwapMiddleware {
|
|
657
|
+
readonly name = "AutoOptOut";
|
|
658
|
+
readonly version = "1.0.0";
|
|
659
|
+
private readonly excludedAssets;
|
|
660
|
+
constructor(config?: AutoOptOutConfig);
|
|
661
|
+
shouldApply(context: QuoteContext): Promise<boolean>;
|
|
662
|
+
adjustQuoteParams(params: FetchQuoteParams): Promise<FetchQuoteParams>;
|
|
663
|
+
afterSwap(context: SwapContext): Promise<TransactionWithSigner[]>;
|
|
664
|
+
}
|
|
665
|
+
//#endregion
|
|
666
|
+
//#region src/composer.d.ts
|
|
667
|
+
/**
|
|
668
|
+
* A transaction signer function that supports both standard algosdk.TransactionSigner
|
|
669
|
+
* and ARC-1 compliant signers that may return null for unsigned transactions.
|
|
670
|
+
*
|
|
671
|
+
* @param txnGroup - The complete transaction group to sign
|
|
672
|
+
* @param indexesToSign - Array of indexes indicating which transactions need signing
|
|
673
|
+
* @returns Array of signed transactions (may include nulls for ARC-1 compliant wallets)
|
|
674
|
+
*/
|
|
675
|
+
type SignerFunction = (txnGroup: Transaction[], indexesToSign: number[]) => Promise<(Uint8Array | null)[]>;
|
|
676
|
+
/**
|
|
677
|
+
* Status of the SwapComposer transaction group lifecycle
|
|
678
|
+
*/
|
|
679
|
+
declare enum SwapComposerStatus {
|
|
680
|
+
/** The atomic group is still under construction. */
|
|
681
|
+
BUILDING = 0,
|
|
682
|
+
/** The atomic group has been finalized, but not yet signed. */
|
|
683
|
+
BUILT = 1,
|
|
684
|
+
/** The atomic group has been finalized and signed, but not yet submitted to the network. */
|
|
685
|
+
SIGNED = 2,
|
|
686
|
+
/** The atomic group has been finalized, signed, and submitted to the network. */
|
|
687
|
+
SUBMITTED = 3,
|
|
688
|
+
/** The atomic group has been finalized, signed, submitted, and successfully committed to a block. */
|
|
689
|
+
COMMITTED = 4,
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Configuration for creating a SwapComposer instance
|
|
693
|
+
*/
|
|
694
|
+
interface SwapComposerConfig {
|
|
695
|
+
/** The quote response from fetchQuote() or newQuote() */
|
|
696
|
+
readonly quote: FetchQuoteResponse | SwapQuote;
|
|
697
|
+
/** The swap transactions from fetchSwapTransactions() */
|
|
698
|
+
readonly swapTxns: SwapTransaction[];
|
|
699
|
+
/** Algodv2 client instance */
|
|
700
|
+
readonly algodClient: Algodv2;
|
|
701
|
+
/** The address of the account that will sign transactions */
|
|
702
|
+
readonly address: string;
|
|
703
|
+
/** Transaction signer function */
|
|
704
|
+
readonly signer: TransactionSigner | SignerFunction;
|
|
705
|
+
/** Middleware to apply during swap composition */
|
|
706
|
+
readonly middleware?: SwapMiddleware[];
|
|
707
|
+
/** Optional note field for the user-signed input transaction (payment or asset transfer) */
|
|
708
|
+
readonly note?: Uint8Array;
|
|
709
|
+
/** Debug logging level (propagated from RouterClient) */
|
|
710
|
+
readonly debugLevel?: 'none' | 'info' | 'debug' | 'trace';
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Composer for building and executing atomic swap transaction groups
|
|
714
|
+
*
|
|
715
|
+
* The SwapComposer allows you to build complex transaction groups by adding custom
|
|
716
|
+
* transactions before and after swap transactions. It handles pre-signed transactions,
|
|
717
|
+
* automatic app opt-ins, and provides a fluent API for transaction group construction.
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```typescript
|
|
721
|
+
* const quote = await router.fetchQuote({ ... })
|
|
722
|
+
* const composer = await router.newSwap({ quote, address, slippage, signer })
|
|
723
|
+
*
|
|
724
|
+
* await composer
|
|
725
|
+
* .addTransaction(customTxn)
|
|
726
|
+
* .addSwapTransactions()
|
|
727
|
+
* .execute()
|
|
728
|
+
* ```
|
|
729
|
+
*/
|
|
730
|
+
declare class SwapComposer {
|
|
731
|
+
/** The ATC used to compose the group */
|
|
732
|
+
private atc;
|
|
733
|
+
/** The maximum size of an atomic transaction group. */
|
|
734
|
+
static MAX_GROUP_SIZE: number;
|
|
735
|
+
/** Whether the swap transactions have been added to the atomic group. */
|
|
736
|
+
private swapTransactionsAdded;
|
|
737
|
+
private readonly quote;
|
|
738
|
+
private readonly requiredAppOptIns;
|
|
739
|
+
private readonly swapTxns;
|
|
740
|
+
private readonly algodClient;
|
|
741
|
+
private readonly address;
|
|
742
|
+
private readonly signer;
|
|
743
|
+
private readonly middleware;
|
|
744
|
+
private readonly note?;
|
|
745
|
+
private inputTransactionIndex?;
|
|
746
|
+
private outputTransactionIndex?;
|
|
747
|
+
/** Summary data built incrementally during swap composition */
|
|
748
|
+
private summaryData?;
|
|
749
|
+
/**
|
|
750
|
+
* Create a new SwapComposer instance
|
|
751
|
+
*
|
|
752
|
+
* Note: Most developers should use RouterClient.newSwap() instead of constructing
|
|
753
|
+
* this directly, as the factory method handles fetching swap transactions automatically.
|
|
754
|
+
*
|
|
755
|
+
* @param config - Configuration for the composer
|
|
756
|
+
* @param config.quote - The quote response from fetchQuote()
|
|
757
|
+
* @param config.swapTxns - The swap transactions from fetchSwapTransactions()
|
|
758
|
+
* @param config.algodClient - Algodv2 client instance
|
|
759
|
+
* @param config.address - The address of the account that will sign transactions
|
|
760
|
+
* @param config.signer - Transaction signer function
|
|
761
|
+
* @param config.middleware - Middleware to apply during swap composition
|
|
762
|
+
*/
|
|
763
|
+
constructor(config: SwapComposerConfig);
|
|
764
|
+
/**
|
|
765
|
+
* Get the status of this composer's transaction group
|
|
766
|
+
*
|
|
767
|
+
* @returns The current status of the transaction group
|
|
768
|
+
*/
|
|
769
|
+
getStatus(): SwapComposerStatus;
|
|
770
|
+
/**
|
|
771
|
+
* Get the number of transactions currently in this atomic group
|
|
772
|
+
*
|
|
773
|
+
* @returns The number of transactions in the group
|
|
774
|
+
*/
|
|
775
|
+
count(): number;
|
|
776
|
+
/**
|
|
777
|
+
* Add a transaction to the atomic group
|
|
778
|
+
*
|
|
779
|
+
* Transactions are added in the order methods are called. For example:
|
|
780
|
+
* ```typescript
|
|
781
|
+
* composer
|
|
782
|
+
* .addTransaction(txn1) // Added first
|
|
783
|
+
* .addSwapTransactions() // Added second
|
|
784
|
+
* .addTransaction(txn2) // Added third
|
|
785
|
+
* ```
|
|
786
|
+
*
|
|
787
|
+
* @param transaction - The transaction to add
|
|
788
|
+
* @returns This composer instance for chaining
|
|
789
|
+
* @throws Error if the composer is not in the BUILDING status
|
|
790
|
+
* @throws Error if the maximum group size is exceeded
|
|
791
|
+
* @throws Error if attempting to compose with a non-composable swap (Tinyman v1)
|
|
792
|
+
*/
|
|
793
|
+
addTransaction(transaction: Transaction, signer?: TransactionSigner): this;
|
|
794
|
+
/**
|
|
795
|
+
* Add a method call to the atomic group
|
|
796
|
+
*
|
|
797
|
+
* The `signer` property in the `methodCall` parameter is optional. If not provided,
|
|
798
|
+
* the signer will default to the one passed as the second parameter, or the
|
|
799
|
+
* configured signer from the constructor if no second parameter is provided.
|
|
800
|
+
*
|
|
801
|
+
* @param methodCall - The method call to add
|
|
802
|
+
* @param signer - The signer to use for the method call (defaults to constructor signer)
|
|
803
|
+
* @returns This composer instance for chaining
|
|
804
|
+
* @throws Error if attempting to compose with a non-composable swap (Tinyman v1)
|
|
805
|
+
*/
|
|
806
|
+
addMethodCall(methodCall: MethodCall, signer?: TransactionSigner): this;
|
|
807
|
+
/**
|
|
808
|
+
* Add swap transactions to the atomic group
|
|
809
|
+
*
|
|
810
|
+
* This method automatically processes required app opt-ins, executes middleware hooks,
|
|
811
|
+
* and adds all swap transactions from the quote. Can only be called once per composer instance.
|
|
812
|
+
*
|
|
813
|
+
* Middleware hooks are executed in this order:
|
|
814
|
+
* 1. beforeSwap() - Add transactions before swap transactions
|
|
815
|
+
* 2. Swap transactions (from API)
|
|
816
|
+
* 3. afterSwap() - Add transactions after swap transactions
|
|
817
|
+
*
|
|
818
|
+
* @returns This composer instance for chaining
|
|
819
|
+
* @throws Error if the swap transactions have already been added
|
|
820
|
+
* @throws Error if the composer is not in the BUILDING status
|
|
821
|
+
* @throws Error if the maximum group size is exceeded
|
|
822
|
+
*/
|
|
823
|
+
addSwapTransactions(): Promise<this>;
|
|
824
|
+
/**
|
|
825
|
+
* Finalize the transaction group by assigning group IDs
|
|
826
|
+
*
|
|
827
|
+
* This method builds the atomic transaction group, assigning group IDs to all transactions
|
|
828
|
+
* if there is more than one transaction. After calling this method, the composer's status
|
|
829
|
+
* will be at least BUILT.
|
|
830
|
+
*
|
|
831
|
+
* @returns Array of transactions with their associated signers
|
|
832
|
+
*
|
|
833
|
+
* @throws Error if the group contains 0 transactions
|
|
834
|
+
*
|
|
835
|
+
* @example
|
|
836
|
+
* ```typescript
|
|
837
|
+
* const composer = await router.newSwap({ quote, address, slippage, signer })
|
|
838
|
+
* composer.addTransaction(customTxn)
|
|
839
|
+
*
|
|
840
|
+
* // Build the group to inspect transactions before signing
|
|
841
|
+
* const txnsWithSigners = composer.buildGroup()
|
|
842
|
+
* console.log('Group ID:', txnsWithSigners[0].txn.group)
|
|
843
|
+
* console.log('Group length:', txnsWithSigners.length)
|
|
844
|
+
* console.log('Status:', composer.getStatus()) // BUILT
|
|
845
|
+
* ```
|
|
846
|
+
*/
|
|
847
|
+
buildGroup(): TransactionWithSigner[];
|
|
848
|
+
/**
|
|
849
|
+
* Sign the transaction group
|
|
850
|
+
*
|
|
851
|
+
* Automatically adds swap transactions if not already added, builds the atomic group,
|
|
852
|
+
* and signs all transactions using the configured signer.
|
|
853
|
+
*
|
|
854
|
+
* @returns A promise that resolves to an array of signed transaction blobs
|
|
855
|
+
*
|
|
856
|
+
* @example
|
|
857
|
+
* ```typescript
|
|
858
|
+
* const signedTxns = await composer.sign()
|
|
859
|
+
* ```
|
|
860
|
+
*/
|
|
861
|
+
sign(): Promise<Uint8Array[]>;
|
|
862
|
+
/**
|
|
863
|
+
* Submit the signed transactions to the network
|
|
864
|
+
*
|
|
865
|
+
* This method signs the transaction group (if not already signed) and submits
|
|
866
|
+
* it to the Algorand network. Does not wait for confirmation.
|
|
867
|
+
*
|
|
868
|
+
* @returns The transaction IDs
|
|
869
|
+
* @throws Error if the transaction group has already been submitted
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* ```typescript
|
|
873
|
+
* const txIds = await composer.submit()
|
|
874
|
+
* console.log('Submitted transactions:', txIds)
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
submit(): Promise<string[]>;
|
|
878
|
+
/**
|
|
879
|
+
* Execute the swap
|
|
880
|
+
*
|
|
881
|
+
* Signs the transaction group, submits it to the network, and waits for confirmation.
|
|
882
|
+
* This is the primary method for executing swaps and combines sign(), submit(), and
|
|
883
|
+
* waitForConfirmation() into a single call.
|
|
884
|
+
*
|
|
885
|
+
* @param waitRounds - The number of rounds to wait for confirmation (default: 10)
|
|
886
|
+
* @returns Object containing the confirmed round and transaction IDs
|
|
887
|
+
* @throws Error if the transaction group has already been committed
|
|
888
|
+
*
|
|
889
|
+
* @example
|
|
890
|
+
* ```typescript
|
|
891
|
+
* const result = await composer.execute()
|
|
892
|
+
* console.log(`Confirmed in round ${result.confirmedRound}`)
|
|
893
|
+
* console.log('Transaction IDs:', result.txIds)
|
|
894
|
+
* ```
|
|
895
|
+
*/
|
|
896
|
+
execute(waitRounds?: number): Promise<{
|
|
897
|
+
confirmedRound: bigint;
|
|
898
|
+
txIds: string[];
|
|
899
|
+
methodResults: ABIResult[];
|
|
900
|
+
}>;
|
|
901
|
+
/**
|
|
902
|
+
* Get the transaction ID of the user-signed input transaction
|
|
903
|
+
*
|
|
904
|
+
* Returns the transaction ID of the payment or asset transfer transaction
|
|
905
|
+
* that sends the input asset. This is the transaction whose note field can
|
|
906
|
+
* be customized via the `note` config option.
|
|
907
|
+
*
|
|
908
|
+
* The transaction ID is only available after the group has been built
|
|
909
|
+
* (after calling buildGroup(), sign(), submit(), or execute()).
|
|
910
|
+
*
|
|
911
|
+
* @returns The transaction ID, or undefined if the group hasn't been built yet
|
|
912
|
+
* or if the input transaction index couldn't be determined
|
|
913
|
+
*
|
|
914
|
+
* @example
|
|
915
|
+
* ```typescript
|
|
916
|
+
* const swap = await router.newSwap({
|
|
917
|
+
* quote,
|
|
918
|
+
* address,
|
|
919
|
+
* slippage,
|
|
920
|
+
* signer,
|
|
921
|
+
* note: new TextEncoder().encode('tracking-123')
|
|
922
|
+
* })
|
|
923
|
+
*
|
|
924
|
+
* await swap.execute()
|
|
925
|
+
* const inputTxId = swap.getInputTransactionId()
|
|
926
|
+
* console.log('Input transaction ID:', inputTxId)
|
|
927
|
+
* ```
|
|
928
|
+
*/
|
|
929
|
+
getInputTransactionId(): string | undefined;
|
|
930
|
+
/**
|
|
931
|
+
* Get a summary of the swap amounts and fees
|
|
932
|
+
*
|
|
933
|
+
* Returns the exact input and output amounts, total transaction fees,
|
|
934
|
+
* and transaction IDs. This is useful for displaying a complete summary
|
|
935
|
+
* after a swap has been executed.
|
|
936
|
+
*
|
|
937
|
+
* Only available after calling execute() - returns undefined before execution.
|
|
938
|
+
*
|
|
939
|
+
* @returns SwapSummary containing exact amounts and fees, or undefined if not yet executed
|
|
940
|
+
*
|
|
941
|
+
* @example
|
|
942
|
+
* ```typescript
|
|
943
|
+
* const swap = await router.newSwap({ quote, address, slippage, signer })
|
|
944
|
+
* const result = await swap.execute()
|
|
945
|
+
*
|
|
946
|
+
* const summary = swap.getSummary()
|
|
947
|
+
* if (summary) {
|
|
948
|
+
* console.log('Sent:', summary.inputAmount, 'Received:', summary.outputAmount)
|
|
949
|
+
* console.log('Total fees:', summary.totalFees, 'microAlgos')
|
|
950
|
+
* }
|
|
951
|
+
* ```
|
|
952
|
+
*/
|
|
953
|
+
getSummary(): SwapSummary | undefined;
|
|
954
|
+
/**
|
|
955
|
+
* Validates an Algorand address
|
|
956
|
+
*/
|
|
957
|
+
private validateAddress;
|
|
958
|
+
/**
|
|
959
|
+
* Processes app opt-ins and decodes swap transactions from API response
|
|
960
|
+
*
|
|
961
|
+
* Also initializes summaryData with input transaction details and output transaction ID
|
|
962
|
+
*/
|
|
963
|
+
private processSwapTransactions;
|
|
964
|
+
/**
|
|
965
|
+
* Creates opt-in transactions for apps the user hasn't opted into yet
|
|
966
|
+
*/
|
|
967
|
+
private processRequiredAppOptIns;
|
|
968
|
+
/**
|
|
969
|
+
* The default signer function that uses the configured signer
|
|
970
|
+
*/
|
|
971
|
+
private defaultSigner;
|
|
972
|
+
/**
|
|
973
|
+
* Creates a TransactionSigner function for Deflex pre-signed transactions
|
|
974
|
+
*/
|
|
975
|
+
private createSwapSigner;
|
|
976
|
+
/**
|
|
977
|
+
* Re-signs a Deflex transaction using the provided logic signature or secret key
|
|
978
|
+
*/
|
|
979
|
+
private signSwapTransaction;
|
|
980
|
+
/**
|
|
981
|
+
* Execute middleware hooks (beforeSwap or afterSwap)
|
|
982
|
+
*/
|
|
983
|
+
private executeMiddlewareHooks;
|
|
984
|
+
/**
|
|
985
|
+
* Extract the actual output amount from the confirmed output transaction's inner transactions
|
|
986
|
+
*
|
|
987
|
+
* Analyzes only the output transaction (last app call in the swap) to find the
|
|
988
|
+
* inner transaction that transfers the output asset to the user.
|
|
989
|
+
*/
|
|
990
|
+
private extractActualOutputAmount;
|
|
991
|
+
}
|
|
992
|
+
//#endregion
|
|
993
|
+
//#region src/client.d.ts
|
|
994
|
+
/**
|
|
995
|
+
* Client for interacting with the Haystack order router API
|
|
996
|
+
*
|
|
997
|
+
* The RouterClient provides methods to fetch swap quotes and create transaction composers
|
|
998
|
+
* for executing swaps on the Algorand blockchain. It handles API communication, transaction
|
|
999
|
+
* validation, and automatic asset/app opt-in detection.
|
|
1000
|
+
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* ```typescript
|
|
1003
|
+
* const router = new RouterClient({
|
|
1004
|
+
* apiKey: 'your-api-key',
|
|
1005
|
+
* })
|
|
1006
|
+
* ```
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* ```typescript
|
|
1010
|
+
* const router = new RouterClient({
|
|
1011
|
+
* apiKey: 'your-api-key',
|
|
1012
|
+
* apiBaseUrl: 'https://hayrouter.txnlab.dev',
|
|
1013
|
+
* algodUri: 'https://mainnet-api.4160.nodely.dev/',
|
|
1014
|
+
* algodToken: '',
|
|
1015
|
+
* algodPort: 443,
|
|
1016
|
+
* referrerAddress: 'REFERRER_ADDRESS...',
|
|
1017
|
+
* feeBps: 15,
|
|
1018
|
+
* autoOptIn: false,
|
|
1019
|
+
* })
|
|
1020
|
+
* ```
|
|
1021
|
+
*/
|
|
1022
|
+
declare class RouterClient {
|
|
1023
|
+
private readonly config;
|
|
1024
|
+
private readonly algodClient;
|
|
1025
|
+
private readonly middleware;
|
|
1026
|
+
/**
|
|
1027
|
+
* Create a new RouterClient instance
|
|
1028
|
+
*
|
|
1029
|
+
* @param config - Configuration parameters
|
|
1030
|
+
* @param config.apiKey - API key for Haystack Router API (required)
|
|
1031
|
+
* @param config.apiBaseUrl - Base URL for the Haystack Router API (default: https://hayrouter.txnlab.dev)
|
|
1032
|
+
* @param config.algodUri - Algod node URI (default: https://mainnet-api.4160.nodely.dev/)
|
|
1033
|
+
* @param config.algodToken - Algod node token (default: '')
|
|
1034
|
+
* @param config.algodPort - Algod node port (default: 443)
|
|
1035
|
+
* @param config.referrerAddress - Referrer address for fee sharing (receives 25% of swap fees)
|
|
1036
|
+
* @param config.feeBps - Fee in basis points (default: 15 = 0.15%, max: 300 = 3.00%)
|
|
1037
|
+
* @param config.autoOptIn - Automatically detect and add required opt-in transactions (default: false)
|
|
1038
|
+
* @param config.debugLevel - Debug logging level (default: 'none')
|
|
1039
|
+
* @param config.middleware - Array of middleware to apply to swaps (default: [])
|
|
1040
|
+
*/
|
|
1041
|
+
constructor(config: ConfigParams & {
|
|
1042
|
+
middleware?: SwapMiddleware[];
|
|
1043
|
+
});
|
|
1044
|
+
/**
|
|
1045
|
+
* Fetch a swap quote from the Haystack Router API
|
|
1046
|
+
*
|
|
1047
|
+
* Requests optimal swap routing from the Haystack Router API. The quote includes routing
|
|
1048
|
+
* information, price impact, required opt-ins, and an encrypted transaction payload.
|
|
1049
|
+
*
|
|
1050
|
+
* @param params - Parameters for the quote request
|
|
1051
|
+
* @param params.fromASAID - The ID of the asset to swap from
|
|
1052
|
+
* @param params.toASAID - The ID of the asset to swap to
|
|
1053
|
+
* @param params.amount - The amount of the asset to swap in base units
|
|
1054
|
+
* @param params.type - The type of the quote (default: 'fixed-input')
|
|
1055
|
+
* @param params.disabledProtocols - List of protocols to disable (default: [])
|
|
1056
|
+
* @param params.maxGroupSize - The maximum group size (default: 16)
|
|
1057
|
+
* @param params.maxDepth - The maximum depth (default: 4)
|
|
1058
|
+
* @param params.address - The address of the account that will perform the swap (recommended when using `config.autoOptIn` or `params.optIn`)
|
|
1059
|
+
* @param params.optIn - Whether to include asset opt-in transaction
|
|
1060
|
+
* - If true: API reduces maxGroupSize by 1 and includes opt-in (always included, even if not needed)
|
|
1061
|
+
* - If false: No opt-in transaction included
|
|
1062
|
+
* - If undefined: Falls back to `config.autoOptIn` behavior with account check (if `params.address` is provided)
|
|
1063
|
+
* @returns A FetchQuoteResponse object with routing information
|
|
1064
|
+
*
|
|
1065
|
+
* @example
|
|
1066
|
+
* ```typescript
|
|
1067
|
+
* const quote = await router.fetchQuote({
|
|
1068
|
+
* address: 'ABC...',
|
|
1069
|
+
* fromASAID: 0, // ALGO
|
|
1070
|
+
* toASAID: 31566704, // USDC
|
|
1071
|
+
* amount: 1_000_000, // 1 ALGO
|
|
1072
|
+
* })
|
|
1073
|
+
* ```
|
|
1074
|
+
*/
|
|
1075
|
+
fetchQuote(params: FetchQuoteParams): Promise<FetchQuoteResponse>;
|
|
1076
|
+
/**
|
|
1077
|
+
* Check if asset opt-in is required for the output asset
|
|
1078
|
+
*
|
|
1079
|
+
* Convenience method to determine if an address needs to opt into the output asset
|
|
1080
|
+
* of a swap. This is useful when you want to get a quote without requiring wallet
|
|
1081
|
+
* connection upfront, but need to know whether to set `optIn: true` in fetchQuote()
|
|
1082
|
+
* to ensure proper routing (as opt-ins reduce maxGroupSize by 1).
|
|
1083
|
+
*
|
|
1084
|
+
* Note: If you enable `config.autoOptIn`, this check is handled automatically when
|
|
1085
|
+
* an address is provided to fetchQuote().
|
|
1086
|
+
*
|
|
1087
|
+
* @param address - The address to check
|
|
1088
|
+
* @param assetId - The output asset ID to check
|
|
1089
|
+
* @returns True if asset opt-in is required, false otherwise (always false for ALGO)
|
|
1090
|
+
*
|
|
1091
|
+
* @example
|
|
1092
|
+
* ```typescript
|
|
1093
|
+
* // Check if opt-in needed for output asset before fetching quote
|
|
1094
|
+
* const needsOptIn = await router.needsAssetOptIn(userAddress, toAssetId)
|
|
1095
|
+
* const quote = await router.fetchQuote({
|
|
1096
|
+
* fromAssetId,
|
|
1097
|
+
* toAssetId,
|
|
1098
|
+
* amount,
|
|
1099
|
+
* optIn: needsOptIn,
|
|
1100
|
+
* })
|
|
1101
|
+
* ```
|
|
1102
|
+
*/
|
|
1103
|
+
needsAssetOptIn(address: string, assetId: number | bigint): Promise<boolean>;
|
|
1104
|
+
/**
|
|
1105
|
+
* Fetch swap transactions from the Haystack Router API
|
|
1106
|
+
*
|
|
1107
|
+
* Decrypts the quote payload and generates executable swap transactions for the
|
|
1108
|
+
* specified signer address with the given slippage tolerance.
|
|
1109
|
+
*
|
|
1110
|
+
* @param params - Parameters for the swap transaction request
|
|
1111
|
+
* @param params.quote - The quote response from fetchQuote()
|
|
1112
|
+
* @param params.address - The address of the signer
|
|
1113
|
+
* @param params.slippage - The slippage tolerance as a percentage (e.g., 1 for 1%)
|
|
1114
|
+
* @returns A FetchSwapTxnsResponse object with transaction data
|
|
1115
|
+
*/
|
|
1116
|
+
fetchSwapTransactions(params: FetchSwapTxnsParams): Promise<FetchSwapTxnsResponse>;
|
|
1117
|
+
/**
|
|
1118
|
+
* Fetch a quote and return an enhanced quote result
|
|
1119
|
+
*
|
|
1120
|
+
* This is the recommended way to fetch quotes. It returns an object that
|
|
1121
|
+
* extends the raw API response with additional metadata and type normalization.
|
|
1122
|
+
*
|
|
1123
|
+
* @param params - Parameters for the quote request
|
|
1124
|
+
* @param params.fromASAID - The ID of the asset to swap from
|
|
1125
|
+
* @param params.toASAID - The ID of the asset to swap to
|
|
1126
|
+
* @param params.amount - The amount of the asset to swap in base units
|
|
1127
|
+
* @param params.type - The type of the quote (default: 'fixed-input')
|
|
1128
|
+
* @param params.disabledProtocols - List of protocols to disable (default: [])
|
|
1129
|
+
* @param params.maxGroupSize - The maximum group size (default: 16)
|
|
1130
|
+
* @param params.maxDepth - The maximum depth (default: 4)
|
|
1131
|
+
* @param params.address - The address of the account that will perform the swap (recommended when using `config.autoOptIn` or `params.optIn`)
|
|
1132
|
+
* @param params.optIn - Whether to include asset opt-in transaction
|
|
1133
|
+
* @returns A SwapQuote enhanced quote result
|
|
1134
|
+
*
|
|
1135
|
+
* @example
|
|
1136
|
+
* ```typescript
|
|
1137
|
+
* const quote = await router.newQuote({
|
|
1138
|
+
* address: 'ABC...',
|
|
1139
|
+
* fromASAID: 0,
|
|
1140
|
+
* toASAID: 31566704,
|
|
1141
|
+
* amount: 1_000_000,
|
|
1142
|
+
* })
|
|
1143
|
+
*
|
|
1144
|
+
* console.log(quote.quote) // bigint - quoted amount
|
|
1145
|
+
* console.log(quote.fromASAID) // number - input asset ID
|
|
1146
|
+
* console.log(quote.toASAID) // number - output asset ID
|
|
1147
|
+
* console.log(quote.amount) // bigint - original request amount
|
|
1148
|
+
* console.log(quote.createdAt) // number - timestamp
|
|
1149
|
+
* ```
|
|
1150
|
+
*/
|
|
1151
|
+
newQuote(params: FetchQuoteParams): Promise<SwapQuote>;
|
|
1152
|
+
/**
|
|
1153
|
+
* Create a SwapComposer instance
|
|
1154
|
+
*
|
|
1155
|
+
* This factory method creates a composer that allows you to add custom transactions
|
|
1156
|
+
* before and after the swap transactions, with automatic handling of pre-signed transactions
|
|
1157
|
+
* and opt-ins.
|
|
1158
|
+
*
|
|
1159
|
+
* @param config.quote - The quote result from newQuote() or raw API response from fetchQuote()
|
|
1160
|
+
* @param config.address - The address of the signer
|
|
1161
|
+
* @param config.slippage - The slippage tolerance
|
|
1162
|
+
* @param config.signer - Transaction signer function
|
|
1163
|
+
* @returns A SwapComposer instance ready for building transaction groups
|
|
1164
|
+
*
|
|
1165
|
+
* @example
|
|
1166
|
+
* ```typescript
|
|
1167
|
+
* // Basic swap
|
|
1168
|
+
* const quote = await router.newQuote({ ... })
|
|
1169
|
+
* await router.newSwap({ quote, address, slippage, signer })
|
|
1170
|
+
* .execute()
|
|
1171
|
+
* ```
|
|
1172
|
+
*
|
|
1173
|
+
* @example
|
|
1174
|
+
* ```typescript
|
|
1175
|
+
* // Advanced swap with custom transactions
|
|
1176
|
+
* const quote = await router.newQuote({ ... })
|
|
1177
|
+
* const swap = await router.newSwap({
|
|
1178
|
+
* quote,
|
|
1179
|
+
* address,
|
|
1180
|
+
* slippage,
|
|
1181
|
+
* signer,
|
|
1182
|
+
* })
|
|
1183
|
+
*
|
|
1184
|
+
* console.log(swap.getStatus()) // BUILDING
|
|
1185
|
+
*
|
|
1186
|
+
* const signedTxns = await swap
|
|
1187
|
+
* .addTransaction(beforeTxn)
|
|
1188
|
+
* .addSwapTransactions() // Adds swap transactions to the group
|
|
1189
|
+
* .addTransaction(afterTxn)
|
|
1190
|
+
* .sign()
|
|
1191
|
+
*
|
|
1192
|
+
* console.log(swap.getStatus()) // SIGNED
|
|
1193
|
+
*
|
|
1194
|
+
* const result = await swap.execute(waitRounds)
|
|
1195
|
+
* console.log(result.confirmedRound, result.txIds)
|
|
1196
|
+
*
|
|
1197
|
+
* console.log(swap.getStatus()) // COMMITTED
|
|
1198
|
+
* ```
|
|
1199
|
+
*/
|
|
1200
|
+
newSwap(config: {
|
|
1201
|
+
quote: SwapQuote | FetchQuoteResponse;
|
|
1202
|
+
address: string;
|
|
1203
|
+
slippage: number;
|
|
1204
|
+
signer: TransactionSigner | SignerFunction;
|
|
1205
|
+
/** Optional note field for the user-signed input transaction (payment or asset transfer) */
|
|
1206
|
+
note?: Uint8Array;
|
|
1207
|
+
}): Promise<SwapComposer>;
|
|
1208
|
+
/**
|
|
1209
|
+
* Validates the API key
|
|
1210
|
+
*/
|
|
1211
|
+
private validateApiKey;
|
|
1212
|
+
/**
|
|
1213
|
+
* Validates an Algorand address
|
|
1214
|
+
*/
|
|
1215
|
+
private validateAddress;
|
|
1216
|
+
/**
|
|
1217
|
+
* Validates the fee in basis points (max 300 = 3.00%)
|
|
1218
|
+
*/
|
|
1219
|
+
private validateFeeBps;
|
|
1220
|
+
}
|
|
1221
|
+
//#endregion
|
|
1222
|
+
//#region src/utils.d.ts
|
|
1223
|
+
/**
|
|
1224
|
+
* HTTP error with status code and response data
|
|
1225
|
+
*/
|
|
1226
|
+
declare class HTTPError extends Error {
|
|
1227
|
+
status: number;
|
|
1228
|
+
statusText: string;
|
|
1229
|
+
data: unknown;
|
|
1230
|
+
constructor(status: number, statusText: string, data: unknown);
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Make an HTTP request and parse JSON response
|
|
1234
|
+
*
|
|
1235
|
+
* Simple wrapper around native fetch for API calls. Throws HTTPError for
|
|
1236
|
+
* non-2xx responses.
|
|
1237
|
+
*
|
|
1238
|
+
* @param url - The URL to request
|
|
1239
|
+
* @param options - Fetch options
|
|
1240
|
+
* @returns Parsed JSON response
|
|
1241
|
+
* @throws HTTPError if the response status is not ok
|
|
1242
|
+
*/
|
|
1243
|
+
declare function request<T>(url: string, options?: RequestInit): Promise<T>;
|
|
1244
|
+
//#endregion
|
|
1245
|
+
export { Asset, AutoOptOutConfig, AutoOptOutMiddleware, Config, ConfigParams, DEFAULT_ALGOD_PORT, DEFAULT_ALGOD_TOKEN, DEFAULT_ALGOD_URI, DEFAULT_API_BASE_URL, DEFAULT_AUTO_OPT_IN, DEFAULT_CONFIRMATION_ROUNDS, DEFAULT_DEBUG_LEVEL, DEFAULT_FEE_BPS, DEFAULT_MAX_DEPTH, DEFAULT_MAX_GROUP_SIZE, DEPRECATED_PROTOCOLS, DexQuote, FetchQuoteParams, FetchQuoteResponse, FetchSwapTxnsBody, FetchSwapTxnsParams, FetchSwapTxnsResponse, HTTPError, MAX_FEE_BPS, MethodCall, PathElement, Profit, Protocol, QuoteContext, QuoteType, Route, RouterClient, Signature, SignerFunction, SwapComposer, SwapComposerConfig, SwapComposerStatus, SwapContext, SwapMiddleware, SwapQuote, SwapSummary, SwapTransaction, TxnPayload, request };
|
|
1246
|
+
//# sourceMappingURL=index.d.mts.map
|