@txnlab/haystack-router 3.4.0 → 3.5.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/README.md CHANGED
@@ -17,9 +17,11 @@ TypeScript/JavaScript SDK for [Haystack Order Router](https://txnlab.gitbook.io/
17
17
 
18
18
  | Tier | Key | Rate Limit | Use Case |
19
19
  | --- | --- | --- | --- |
20
- | **Free** | `1b72df7e-1131-4449-8ce1-29b79dd3f51e` | 60 requests/min | Development, testing, low-volume integrations |
20
+ | **Free** | `hay_pk_4zhj33wahuxfplteyif5xpcgnm` | 60 requests/min | Development, testing, low-volume integrations |
21
21
  | **Production** | Request from [support@txnlab.dev](mailto:support@txnlab.dev) | Higher limits | Production applications |
22
22
 
23
+ The previous free key, `1b72df7e-1131-4449-8ce1-29b79dd3f51e`, is deprecated: its limit falls to 10 requests/min between December 1, 2026 and March 1, 2027, and it stops working on April 1, 2027.
24
+
23
25
  The SDK sends the key in the `X-API-Key` header, never in the URL, so it stays out of server logs, browser history and caches.
24
26
 
25
27
  ## Installation
@@ -40,7 +42,7 @@ const { activeAddress, transactionSigner } = useWallet()
40
42
 
41
43
  // Initialize the client
42
44
  const router = new RouterClient({
43
- apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
45
+ apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
44
46
  })
45
47
 
46
48
  // Get a quote
@@ -72,12 +74,12 @@ import { RouterClient } from '@txnlab/haystack-router'
72
74
 
73
75
  // Basic initialization
74
76
  const router = new RouterClient({
75
- apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
77
+ apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
76
78
  })
77
79
 
78
80
  // Custom Algod configuration
79
81
  const router = new RouterClient({
80
- apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
82
+ apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
81
83
  algodUri: 'https://mainnet-api.4160.nodely.dev/',
82
84
  algodToken: '',
83
85
  algodPort: 443,
@@ -86,7 +88,7 @@ const router = new RouterClient({
86
88
 
87
89
  // Earn fees with the referral program
88
90
  const router = new RouterClient({
89
- apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
91
+ apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
90
92
  referrerAddress: 'YOUR_ALGORAND_ADDRESS', // Earns 25% of swap fees
91
93
  feeBps: 15, // 0.15% fee (max: 300 = 3%)
92
94
  })
@@ -207,6 +209,36 @@ if (quote.firstStage) {
207
209
 
208
210
  Without `address`, the swap is built without First Stage's calls, which fails for a frozen holding. Don't use a First Stage middleware (such as `@firststage/deflex-middleware`) with the router: its calls and the router's would be added twice, and the group fails. This applies to ORDER_ROUTER v4 swaps, and is on for API keys by default; contact TxnLab if your key's integration still wraps these swaps itself.
209
211
 
212
+ ### Falcon (Post-Quantum) Accounts
213
+
214
+ A transaction signed with a Falcon-1024 key (passkey accounts, Pera and Lute quantum accounts) costs 3 fee units instead of 1, so the swap group's fees must cover 0.002 ALGO more for each transaction the account signs. Pass `signerType: 'falcon'`:
215
+
216
+ ```typescript
217
+ import { getSignatureType } from '@txnlab/use-wallet'
218
+
219
+ const quote = await router.newQuote({
220
+ fromASAID: 0,
221
+ toASAID: 31566704,
222
+ amount: 1_000_000,
223
+ address: activeAddress,
224
+ signerType:
225
+ getSignatureType(activeAccount.emptySignature) === 'pqsig'
226
+ ? 'falcon'
227
+ : 'ed25519',
228
+ })
229
+ ```
230
+
231
+ The router includes the extra in the fees of the transactions it builds, and the `SwapComposer` adds it for the ones it or your code adds: app opt-ins, middleware transactions, `addTransaction()` and `addMethodCall()` calls sent from `address`. It all goes on the fee of the swap's input transfer, which the account pays, before the group ID is assigned, so no transaction is added. `buildGroup()`'s fees and `getSummary().totalFees` include it.
232
+
233
+ `newSwap()` reads whether the router's transactions include it from the router's response to the swap request (its `signerType`), not from the quote. When they don't (a router that doesn't support it, or one mid-update), the composer adds it for those too.
234
+
235
+ With `fetchQuote()`, pass `signerType` to `newSwap()` as well. A router that doesn't support Falcon signers returns a quote that doesn't say it was requested for one, so the composer would otherwise compose for ed25519:
236
+
237
+ ```typescript
238
+ const raw = await router.fetchQuote({ ...params, signerType: 'falcon' })
239
+ const swap = await router.newSwap({ quote: raw, address, slippage, signer, signerType: 'falcon' })
240
+ ```
241
+
210
242
  ### Transaction Signing
211
243
 
212
244
  The SDK supports both standard `algosdk.TransactionSigner` and ARC-1 compliant signer functions.
@@ -332,7 +364,7 @@ const myAsset = new MyAssetMiddleware({
332
364
 
333
365
  // Pass middleware to RouterClient
334
366
  const router = new RouterClient({
335
- apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
367
+ apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
336
368
  middleware: [myAsset], // Middleware is applied automatically
337
369
  })
338
370
 
@@ -360,7 +392,7 @@ const autoOptOut = new AutoOptOutMiddleware({
360
392
  })
361
393
 
362
394
  const router = new RouterClient({
363
- apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
395
+ apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
364
396
  middleware: [autoOptOut],
365
397
  })
366
398
 
@@ -381,7 +413,7 @@ If you're not using `autoOptIn: true`, you can manually check if opt-in is neede
381
413
 
382
414
  ```typescript
383
415
  const router = new RouterClient({
384
- apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
416
+ apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
385
417
  autoOptIn: false, // Default if not provided
386
418
  })
387
419
 
@@ -470,6 +502,7 @@ async newQuote(params: FetchQuoteParams): Promise<SwapQuote>
470
502
  | `maxDepth` | Maximum swap hops | `number` | `4` |
471
503
  | `optIn` | Override auto opt-in behavior | `boolean` | `undefined` |
472
504
  | `slippage` | Slippage % you will execute with, in [0, 100); required for accurate launch token quotes | `number` | `undefined` |
505
+ | `signerType` | Key type `address` signs with; `'falcon'` adds a [Falcon signer's extra fees](#falcon-post-quantum-accounts) | `'ed25519' \| 'falcon'` | `'ed25519'` |
473
506
 
474
507
  #### RouterClient.newSwap()
475
508
 
@@ -486,6 +519,7 @@ async newSwap(config: SwapComposerConfig): Promise<SwapComposer>
486
519
  | `slippage` | Slippage tolerance as percentage (e.g., 1 for 1%) | `number` |
487
520
  | `signer` | Transaction signer function | `algosdk.TransactionSigner \| ((txnGroup: Transaction[], indexesToSign: number[]) => Promise<(Uint8Array \| null)[]>)` |
488
521
  | `note` | Optional note for the user-signed input transaction (for tracking purposes) | `Uint8Array` |
522
+ | `signerType` | Optional key type `address` signs with (default: the quote's, else `'ed25519'`). Pass it with a `fetchQuote()` response | `'ed25519' \| 'falcon'` |
489
523
 
490
524
  #### RouterClient.needsAssetOptIn()
491
525
 
@@ -513,6 +547,7 @@ Plain object returned by [`newQuote()`](#routerclientnewquote). Extends the raw
513
547
  | `address` | User address (if provided) | `string \| undefined` |
514
548
  | `slippage` | Slippage sent with the quote request (if provided) | `number \| undefined` |
515
549
  | `launch` | Bonding-curve details for a pre-bonding launch token (amounts as `bigint`) | `SwapLaunchQuote \| undefined` |
550
+ | `signerType` | Key type the quote was requested for (`'ed25519'` when none was) | `'ed25519' \| 'falcon' \| undefined` |
516
551
  | `createdAt` | Timestamp when quote was created | `number` |
517
552
 
518
553
  **All properties from API response:**
package/dist/index.d.mts CHANGED
@@ -39,6 +39,11 @@ export declare const DEFAULT_MAX_DEPTH = 4;
39
39
  export declare const DEFAULT_AUTO_OPT_IN = false;
40
40
  /** Default debug level (no debug logging) */
41
41
  export declare const DEFAULT_DEBUG_LEVEL: "none";
42
+ /**
43
+ * What a Falcon (post-quantum) signature adds to its transaction's fee, in
44
+ * microAlgos: algod charges a transaction it signs 3 fee units instead of 1
45
+ */
46
+ export declare const FALCON_EXTRA_FEE = 2000n;
42
47
  /** Default number of rounds to wait for transaction confirmation */
43
48
  export declare const DEFAULT_CONFIRMATION_ROUNDS = 10;
44
49
  /**
@@ -84,6 +89,19 @@ export type Config = Omit<Required<ConfigParams>, 'referrerAddress' | 'debugLeve
84
89
  * Swap quote type determining which amount is fixed
85
90
  */
86
91
  export type QuoteType = 'fixed-input' | 'fixed-output';
92
+ /**
93
+ * The kind of key the swapping account signs with
94
+ *
95
+ * - `'ed25519'`: an ordinary Algorand account (the default)
96
+ * - `'falcon'`: a Falcon-1024 (post-quantum) account, such as a passkey
97
+ * account or a Pera or Lute quantum account. Each transaction it signs costs
98
+ * 3 fee units (0.003 ALGO) instead of 1, which the swap group's fees must
99
+ * cover.
100
+ *
101
+ * A rekeyed account signs with its auth address's key. With use-wallet, an
102
+ * account is Falcon when `getSignatureType(account.emptySignature) === 'pqsig'`.
103
+ */
104
+ export type SignerType = 'ed25519' | 'falcon';
87
105
  /**
88
106
  * Parameters for requesting a swap quote from the Haystack Router API (raw API method)
89
107
  */
@@ -121,6 +139,21 @@ export interface FetchQuoteParams {
121
139
  * the slippage. Other quotes ignore it.
122
140
  */
123
141
  readonly slippage?: number;
142
+ /**
143
+ * The kind of key the swapping account signs with (default: `'ed25519'`)
144
+ *
145
+ * Pass `'falcon'` for a Falcon (post-quantum) account. The router then
146
+ * includes the extra fee of every transaction it builds for the account to
147
+ * sign, and the SwapComposer adds it for the transactions it adds (app
148
+ * opt-ins, middleware, `addTransaction()`, `addMethodCall()`), all in the
149
+ * fee of the swap's input transfer, which the account pays. The router
150
+ * can't tell from the address: an unused Falcon account looks like any
151
+ * other.
152
+ *
153
+ * With `fetchQuote()`, pass it to `newSwap()` too: a response from a router
154
+ * that doesn't support it doesn't say the quote was for a Falcon signer.
155
+ */
156
+ readonly signerType?: SignerType;
124
157
  /**
125
158
  * ⚠️ **ADVANCED USE ONLY - NON-COMPOSABLE SWAPS** ⚠️
126
159
  *
@@ -318,13 +351,21 @@ export interface FetchQuoteResponse {
318
351
  * holding keeps (fixed-input), or all the input holding sends (fixed-output).
319
352
  */
320
353
  readonly firstStage?: FirstStageQuote;
354
+ /**
355
+ * `'falcon'` when the quote was requested for a Falcon signer and the
356
+ * router supports it. Absent otherwise, including from a router that
357
+ * doesn't support Falcon signers yet. Whether the swap's transactions
358
+ * include the extra fees is the execute response's `signerType`
359
+ * ({@link FetchSwapTxnsResponse}), which `newSwap()` reads.
360
+ */
361
+ readonly signerType?: 'falcon';
321
362
  }
322
363
  /**
323
364
  * Enhanced quote result returned by RouterClient.newQuote()
324
365
  *
325
366
  * Extends the raw API response with additional metadata and type normalization.
326
367
  */
327
- export type SwapQuote = Omit<FetchQuoteResponse, 'quote' | 'launch' | 'firstStage'> & {
368
+ export type SwapQuote = Omit<FetchQuoteResponse, 'quote' | 'launch' | 'firstStage' | 'signerType'> & {
328
369
  /**
329
370
  * The quoted output amount or input amount (coerced to bigint)
330
371
  *
@@ -342,6 +383,12 @@ export type SwapQuote = Omit<FetchQuoteResponse, 'quote' | 'launch' | 'firstStag
342
383
  readonly launch?: SwapLaunchQuote;
343
384
  /** First Stage taxes, present only when the swap wraps a First Stage taxed token's frozen holding */
344
385
  readonly firstStage?: SwapFirstStageQuote;
386
+ /**
387
+ * The kind of key the quote's account signs with: what `newQuote()` was
388
+ * asked for (`'ed25519'` when nothing was), or `'falcon'` copied from a
389
+ * `fetchQuote()` response that echoes it
390
+ */
391
+ readonly signerType?: SignerType;
345
392
  /** Timestamp when the quote was created (in milliseconds) */
346
393
  readonly createdAt: number;
347
394
  };
@@ -397,6 +444,13 @@ export interface FetchSwapTxnsBody {
397
444
  export interface FetchSwapTxnsResponse {
398
445
  /** Array of transaction data */
399
446
  readonly txns: SwapTransaction[];
447
+ /**
448
+ * `'falcon'` when the transactions' fees include a Falcon signer's extra
449
+ * fees for every transaction the account signs. Absent otherwise, even when
450
+ * the quote echoed `signerType` (a router mid-update can build without
451
+ * them): a Falcon signer's group then needs them all added.
452
+ */
453
+ readonly signerType?: 'falcon';
400
454
  }
401
455
  /**
402
456
  * Bonding-curve leg of an executed pre-bonding HayLaunch swap
@@ -754,7 +808,7 @@ export interface AutoOptOutConfig {
754
808
  * })
755
809
  *
756
810
  * const router = new RouterClient({
757
- * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
811
+ * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
758
812
  * middleware: [autoOptOut],
759
813
  * })
760
814
  *
@@ -819,6 +873,20 @@ export interface SwapComposerConfig {
819
873
  readonly middleware?: SwapMiddleware[];
820
874
  /** Optional note field for the user-signed input transaction (payment or asset transfer) */
821
875
  readonly note?: Uint8Array;
876
+ /**
877
+ * The kind of key `address` signs with (default: the quote's `signerType`,
878
+ * else `'ed25519'`). For `'falcon'`, the composer adds the extra fee of each
879
+ * transaction the account sends to the swap's input transfer as it builds
880
+ * the group.
881
+ */
882
+ readonly signerType?: SignerType;
883
+ /**
884
+ * Whether `swapTxns`' fees already include a Falcon signer's extra fees:
885
+ * the `fetchSwapTransactions()` response's `signerType` is `'falcon'`. Then
886
+ * only the transactions the composer adds are counted. Default: false, and
887
+ * every transaction the account sends is.
888
+ */
889
+ readonly signerFeesIncluded?: boolean;
822
890
  /** Debug logging level (propagated from RouterClient) */
823
891
  readonly debugLevel?: 'none' | 'info' | 'debug' | 'trace';
824
892
  }
@@ -855,6 +923,15 @@ export declare class SwapComposer {
855
923
  private readonly signer;
856
924
  private readonly middleware;
857
925
  private readonly note?;
926
+ private readonly signerType;
927
+ private readonly signerFeesIncluded;
928
+ /** Transactions the account sends whose Falcon fees the composer adds */
929
+ private signerFeeTxns;
930
+ /** The account's first transaction in the group, which carries them when there's no input transfer */
931
+ private firstUserTxn?;
932
+ /** The swap's input transfer, whose fee carries a Falcon signer's extra fees */
933
+ private inputTxn?;
934
+ private signerFeesAdded;
858
935
  private inputTransactionIndex?;
859
936
  private outputTransactionIndex?;
860
937
  private curveTransactionIndex?;
@@ -1065,6 +1142,24 @@ export declare class SwapComposer {
1065
1142
  * ```
1066
1143
  */
1067
1144
  getSummary(): SwapSummary | undefined;
1145
+ /**
1146
+ * Counts a transaction from sender toward a Falcon signer's extra fees
1147
+ * (addSignerFees) when the account sends it, unless the router's fees
1148
+ * already include it (included). Every way of adding a transaction calls
1149
+ * it; txn is the transaction when the composer holds it (not a method call
1150
+ * the ATC builds), which can then carry the fees.
1151
+ */
1152
+ private trackSignerFee;
1153
+ /**
1154
+ * Adds a Falcon signer's extra fees before the group is built: a Falcon
1155
+ * signature costs 3 fee units where an ed25519 one costs 1, and fees pool
1156
+ * across the group, so the fee of the swap's input transfer (which already
1157
+ * carries the router's fee budget) rises by FALCON_EXTRA_FEE for each
1158
+ * transaction the account sends that the router's fees don't include. The
1159
+ * account pays it, like every fee of the group. Runs once, while the group
1160
+ * is still being built.
1161
+ */
1162
+ private addSignerFees;
1068
1163
  /**
1069
1164
  * Validates an Algorand address
1070
1165
  */
@@ -1147,14 +1242,14 @@ export declare class SwapComposer {
1147
1242
  * @example
1148
1243
  * ```typescript
1149
1244
  * const router = new RouterClient({
1150
- * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
1245
+ * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
1151
1246
  * })
1152
1247
  * ```
1153
1248
  *
1154
1249
  * @example
1155
1250
  * ```typescript
1156
1251
  * const router = new RouterClient({
1157
- * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
1252
+ * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
1158
1253
  * apiBaseUrl: 'https://hayrouter.txnlab.dev',
1159
1254
  * algodUri: 'https://mainnet-api.4160.nodely.dev/',
1160
1255
  * algodToken: '',
@@ -1358,6 +1453,15 @@ export declare class RouterClient {
1358
1453
  signer: TransactionSigner | SignerFunction;
1359
1454
  /** Optional note field for the user-signed input transaction (payment or asset transfer) */
1360
1455
  note?: Uint8Array;
1456
+ /**
1457
+ * The kind of key `address` signs with. Defaults to the quote's
1458
+ * `signerType`: what `newQuote()` was asked for, or `'falcon'` when a
1459
+ * `fetchQuote()` response echoes it. Pass it with a `fetchQuote()`
1460
+ * response, which a router that doesn't support Falcon signers leaves
1461
+ * without it. For `'falcon'`, the composer adds the extra fees the
1462
+ * router's transactions don't include.
1463
+ */
1464
+ signerType?: SignerType;
1361
1465
  }): Promise<SwapComposer>;
1362
1466
  /**
1363
1467
  * Validates the API key
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/types.ts","../src/middleware.ts","../src/composer.ts","../src/client.ts","../src/utils.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/types.ts","../src/middleware.ts","../src/composer.ts","../src/client.ts","../src/utils.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as Logger } from "./logger-CYA908f8.mjs";
2
- import { Algodv2, AtomicTransactionComposer, LogicSigAccount, TransactionType, decodeUint64, decodeUnsignedTransaction, encodeAddress, getApplicationAddress, isValidAddress, makeApplicationOptInTxnFromObject, makeAssetTransferTxnWithSuggestedParamsFromObject, msgpackRawDecode, signLogicSigTransactionObject, signTransaction } from "algosdk";
2
+ import { Algodv2, AtomicTransactionComposer, LogicSigAccount, TransactionType, decodeUint64, decodeUnsignedTransaction, encodeAddress, getApplicationAddress, isTransactionWithSigner, isValidAddress, makeApplicationOptInTxnFromObject, makeAssetTransferTxnWithSuggestedParamsFromObject, msgpackRawDecode, signLogicSigTransactionObject, signTransaction } from "algosdk";
3
3
  // src/constants.ts
4
4
  /**
5
5
  * Supported DEX protocols for swap routing
@@ -42,6 +42,11 @@ const DEFAULT_MAX_DEPTH = 4;
42
42
  const DEFAULT_AUTO_OPT_IN = false;
43
43
  /** Default debug level (no debug logging) */
44
44
  const DEFAULT_DEBUG_LEVEL = "none";
45
+ /**
46
+ * What a Falcon (post-quantum) signature adds to its transaction's fee, in
47
+ * microAlgos: algod charges a transaction it signs 3 fee units instead of 1
48
+ */
49
+ const FALCON_EXTRA_FEE = 2000n;
45
50
  /** Default number of rounds to wait for transaction confirmation */
46
51
  const DEFAULT_CONFIRMATION_ROUNDS = 10;
47
52
  // src/firststage.ts
@@ -170,6 +175,15 @@ var SwapComposer = class SwapComposer {
170
175
  signer;
171
176
  middleware;
172
177
  note;
178
+ signerType;
179
+ signerFeesIncluded;
180
+ /** Transactions the account sends whose Falcon fees the composer adds */
181
+ signerFeeTxns = 0;
182
+ /** The account's first transaction in the group, which carries them when there's no input transfer */
183
+ firstUserTxn;
184
+ /** The swap's input transfer, whose fee carries a Falcon signer's extra fees */
185
+ inputTxn;
186
+ signerFeesAdded = false;
173
187
  inputTransactionIndex;
174
188
  outputTransactionIndex;
175
189
  curveTransactionIndex;
@@ -204,6 +218,8 @@ var SwapComposer = class SwapComposer {
204
218
  this.signer = config.signer;
205
219
  this.middleware = config.middleware ?? [];
206
220
  this.note = config.note;
221
+ this.signerType = config.signerType ?? config.quote.signerType ?? "ed25519";
222
+ this.signerFeesIncluded = config.signerFeesIncluded ?? false;
207
223
  }
208
224
  /**
209
225
  * Get the status of this composer's transaction group
@@ -244,6 +260,7 @@ var SwapComposer = class SwapComposer {
244
260
  txn: transaction,
245
261
  signer
246
262
  });
263
+ this.trackSignerFee(transaction.sender, transaction);
247
264
  return this;
248
265
  }
249
266
  /**
@@ -264,6 +281,8 @@ var SwapComposer = class SwapComposer {
264
281
  ...methodCall,
265
282
  signer: methodCall.signer ?? signer
266
283
  });
284
+ for (const arg of methodCall.methodArgs ?? []) if (isTransactionWithSigner(arg)) this.trackSignerFee(arg.txn.sender, arg.txn);
285
+ this.trackSignerFee(methodCall.sender);
267
286
  return this;
268
287
  }
269
288
  /**
@@ -287,15 +306,25 @@ var SwapComposer = class SwapComposer {
287
306
  if (this.getStatus() !== 0) throw new Error("Cannot add swap transactions when composer status is not BUILDING");
288
307
  const beforeTxns = await this.executeMiddlewareHooks("beforeSwap");
289
308
  if (this.atc.count() + beforeTxns.length > SwapComposer.MAX_GROUP_SIZE) throw new Error(`Adding beforeSwap transactions exceeds the maximum atomic group size of ${SwapComposer.MAX_GROUP_SIZE}`);
290
- for (const txnWithSigner of beforeTxns) this.atc.addTransaction(txnWithSigner);
291
- const { txns: processedTxns, inputTxnRelativeIndex, outputTxnRelativeIndex, curveTxnRelativeIndex } = await this.processSwapTransactions();
309
+ for (const txnWithSigner of beforeTxns) {
310
+ this.atc.addTransaction(txnWithSigner);
311
+ this.trackSignerFee(txnWithSigner.txn.sender, txnWithSigner.txn);
312
+ }
313
+ const { txns: processedTxns, apiTxns, inputTxn, inputTxnRelativeIndex, outputTxnRelativeIndex, curveTxnRelativeIndex } = await this.processSwapTransactions();
292
314
  const afterTxns = await this.executeMiddlewareHooks("afterSwap");
293
315
  if (this.atc.count() + processedTxns.length + afterTxns.length > SwapComposer.MAX_GROUP_SIZE) throw new Error(`Adding swap transactions exceeds the maximum atomic group size of ${SwapComposer.MAX_GROUP_SIZE}`);
294
316
  if (inputTxnRelativeIndex !== void 0) this.inputTransactionIndex = this.atc.count() + inputTxnRelativeIndex;
295
317
  if (outputTxnRelativeIndex !== void 0) this.outputTransactionIndex = this.atc.count() + outputTxnRelativeIndex;
296
318
  if (curveTxnRelativeIndex !== void 0) this.curveTransactionIndex = this.atc.count() + curveTxnRelativeIndex;
297
- for (const txnWithSigner of processedTxns) this.atc.addTransaction(txnWithSigner);
298
- for (const txnWithSigner of afterTxns) this.atc.addTransaction(txnWithSigner);
319
+ for (const txnWithSigner of processedTxns) {
320
+ this.atc.addTransaction(txnWithSigner);
321
+ this.trackSignerFee(txnWithSigner.txn.sender, txnWithSigner.txn, this.signerFeesIncluded && apiTxns.has(txnWithSigner.txn));
322
+ }
323
+ this.inputTxn = inputTxn;
324
+ for (const txnWithSigner of afterTxns) {
325
+ this.atc.addTransaction(txnWithSigner);
326
+ this.trackSignerFee(txnWithSigner.txn.sender, txnWithSigner.txn);
327
+ }
299
328
  this.swapTransactionsAdded = true;
300
329
  return this;
301
330
  }
@@ -323,6 +352,7 @@ var SwapComposer = class SwapComposer {
323
352
  * ```
324
353
  */
325
354
  buildGroup() {
355
+ this.addSignerFees();
326
356
  return this.atc.buildGroup();
327
357
  }
328
358
  /**
@@ -341,6 +371,7 @@ var SwapComposer = class SwapComposer {
341
371
  async sign() {
342
372
  if (this.getStatus() >= 2) return this.atc.gatherSignatures();
343
373
  if (!this.swapTransactionsAdded) await this.addSwapTransactions();
374
+ this.addSignerFees();
344
375
  return await this.atc.gatherSignatures();
345
376
  }
346
377
  /**
@@ -360,6 +391,7 @@ var SwapComposer = class SwapComposer {
360
391
  */
361
392
  async submit() {
362
393
  if (!this.swapTransactionsAdded) await this.addSwapTransactions();
394
+ this.addSignerFees();
363
395
  return await this.atc.submit(this.algodClient);
364
396
  }
365
397
  /**
@@ -383,6 +415,7 @@ var SwapComposer = class SwapComposer {
383
415
  async execute(waitRounds = 10) {
384
416
  try {
385
417
  if (!this.swapTransactionsAdded) await this.addSwapTransactions();
418
+ this.addSignerFees();
386
419
  const { txIDs, ...result } = await this.atc.execute(this.algodClient, waitRounds);
387
420
  if (this.summaryData) {
388
421
  if (this.inputTransactionIndex !== void 0) this.summaryData.inputTxnId = txIDs[this.inputTransactionIndex];
@@ -491,6 +524,40 @@ var SwapComposer = class SwapComposer {
491
524
  };
492
525
  }
493
526
  /**
527
+ * Counts a transaction from sender toward a Falcon signer's extra fees
528
+ * (addSignerFees) when the account sends it, unless the router's fees
529
+ * already include it (included). Every way of adding a transaction calls
530
+ * it; txn is the transaction when the composer holds it (not a method call
531
+ * the ATC builds), which can then carry the fees.
532
+ */
533
+ trackSignerFee(sender, txn, included = false) {
534
+ if (sender.toString() !== this.address) return;
535
+ if (txn) this.firstUserTxn ??= txn;
536
+ if (!included) this.signerFeeTxns++;
537
+ }
538
+ /**
539
+ * Adds a Falcon signer's extra fees before the group is built: a Falcon
540
+ * signature costs 3 fee units where an ed25519 one costs 1, and fees pool
541
+ * across the group, so the fee of the swap's input transfer (which already
542
+ * carries the router's fee budget) rises by FALCON_EXTRA_FEE for each
543
+ * transaction the account sends that the router's fees don't include. The
544
+ * account pays it, like every fee of the group. Runs once, while the group
545
+ * is still being built.
546
+ */
547
+ addSignerFees() {
548
+ if (this.signerType !== "falcon" || this.signerFeesAdded || this.getStatus() !== 0) return;
549
+ if (this.signerFeeTxns > 0) {
550
+ const carrier = this.inputTxn ?? this.firstUserTxn;
551
+ if (!carrier) throw new Error(`No transaction from ${this.address} in the group to carry its Falcon signature fees`);
552
+ carrier.fee += FALCON_EXTRA_FEE * BigInt(this.signerFeeTxns);
553
+ Logger.debug("Added Falcon signature fees", {
554
+ transactions: this.signerFeeTxns,
555
+ fee: carrier.fee
556
+ });
557
+ }
558
+ this.signerFeesAdded = true;
559
+ }
560
+ /**
494
561
  * Validates an Algorand address
495
562
  */
496
563
  validateAddress(address) {
@@ -539,6 +606,8 @@ var SwapComposer = class SwapComposer {
539
606
  const outputTxnRelativeIndex = outputIndex >= 0 ? appOptIns.length + outputIndex : void 0;
540
607
  return {
541
608
  txns: [...appOptIns, ...swapTxns],
609
+ apiTxns: new Set(decoded.map(({ txn }) => txn)),
610
+ inputTxn,
542
611
  inputTxnRelativeIndex: inputIndex !== void 0 ? appOptIns.length + inputIndex : void 0,
543
612
  outputTxnRelativeIndex,
544
613
  curveTxnRelativeIndex: curveIndex !== void 0 ? appOptIns.length + curveIndex : void 0
@@ -678,12 +747,16 @@ var SwapComposer = class SwapComposer {
678
747
  */
679
748
  async executeMiddlewareHooks(hookName) {
680
749
  const allTxns = [];
681
- const quote = "createdAt" in this.quote ? this.quote : {
750
+ const quote = "createdAt" in this.quote ? {
751
+ ...this.quote,
752
+ signerType: this.signerType
753
+ } : {
682
754
  ...this.quote,
683
755
  quote: this.quote.quote === "" ? 0n : BigInt(this.quote.quote),
684
756
  amount: 0n,
685
757
  launch: this.quote.launch && toSwapLaunchQuote(this.quote.launch),
686
758
  firstStage: this.quote.firstStage && toSwapFirstStageQuote(this.quote.firstStage),
759
+ signerType: this.signerType,
687
760
  createdAt: Date.now()
688
761
  };
689
762
  const quoteContext = {
@@ -830,14 +903,14 @@ async function request(url, options) {
830
903
  * @example
831
904
  * ```typescript
832
905
  * const router = new RouterClient({
833
- * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
906
+ * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
834
907
  * })
835
908
  * ```
836
909
  *
837
910
  * @example
838
911
  * ```typescript
839
912
  * const router = new RouterClient({
840
- * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
913
+ * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
841
914
  * apiBaseUrl: 'https://hayrouter.txnlab.dev',
842
915
  * algodUri: 'https://mainnet-api.4160.nodely.dev/',
843
916
  * algodToken: '',
@@ -917,8 +990,9 @@ var RouterClient = class {
917
990
  * ```
918
991
  */
919
992
  async fetchQuote(params) {
920
- const { fromASAID, toASAID, amount, type = "fixed-input", disabledProtocols = [], maxGroupSize = 16, maxDepth = 4, optIn, address, slippage, _allowNonComposableSwaps = false } = params;
993
+ const { fromASAID, toASAID, amount, type = "fixed-input", disabledProtocols = [], maxGroupSize = 16, maxDepth = 4, optIn, address, slippage, signerType, _allowNonComposableSwaps = false } = params;
921
994
  if (slippage !== void 0) this.validateSlippage(slippage);
995
+ if (signerType !== void 0 && signerType !== "ed25519" && signerType !== "falcon") throw new Error(`Invalid signerType: ${String(signerType)}. Must be 'ed25519' or 'falcon'`);
922
996
  if (_allowNonComposableSwaps && (optIn === true || this.config.autoOptIn)) throw new Error("Cannot use _allowNonComposableSwaps with optIn or autoOptIn. When allowing non-composable swaps (Tinyman v1), you must handle asset opt-ins and opt-outs in separate transaction groups.");
923
997
  const allDisabledProtocols = _allowNonComposableSwaps ? [...new Set(disabledProtocols)] : [.../* @__PURE__ */ new Set([...DEPRECATED_PROTOCOLS, ...disabledProtocols])];
924
998
  let includeOptIn = optIn;
@@ -941,6 +1015,7 @@ var RouterClient = class {
941
1015
  if (typeof includeOptIn === "boolean") url.searchParams.append("optIn", String(includeOptIn));
942
1016
  if (address) url.searchParams.append("address", this.validateAddress(address));
943
1017
  if (slippage !== void 0) url.searchParams.append("slippage", String(slippage));
1018
+ if (signerType === "falcon") url.searchParams.append("signerType", signerType);
944
1019
  if (this.config.referrerAddress) url.searchParams.append("referrerAddress", this.config.referrerAddress);
945
1020
  return request(url.toString(), { headers: this.authHeaders() });
946
1021
  }
@@ -1070,6 +1145,7 @@ var RouterClient = class {
1070
1145
  slippage: adjustedParams.slippage,
1071
1146
  launch: launch && toSwapLaunchQuote(launch),
1072
1147
  firstStage: firstStage && toSwapFirstStageQuote(firstStage),
1148
+ signerType: adjustedParams.signerType ?? params.signerType ?? "ed25519",
1073
1149
  createdAt: Date.now()
1074
1150
  };
1075
1151
  }
@@ -1123,19 +1199,23 @@ var RouterClient = class {
1123
1199
  */
1124
1200
  async newSwap(config) {
1125
1201
  const { quote, address, slippage, signer, note } = config;
1202
+ const signerType = config.signerType ?? quote.signerType ?? "ed25519";
1126
1203
  if (quote.launch && "createdAt" in quote && quote.slippage !== slippage) console.warn(quote.slippage === void 0 ? `Launch token quote was fetched without slippage but the swap uses ${slippage}%. Pass the same slippage to newQuote() for an accurate quoted output.` : `Launch token quote was fetched with ${quote.slippage}% slippage but the swap uses ${slippage}%. The quoted output may not match what is delivered.`);
1204
+ const swapResponse = await this.fetchSwapTransactions({
1205
+ quote,
1206
+ address,
1207
+ slippage
1208
+ });
1127
1209
  return new SwapComposer({
1128
1210
  quote,
1129
- swapTxns: (await this.fetchSwapTransactions({
1130
- quote,
1131
- address,
1132
- slippage
1133
- })).txns,
1211
+ swapTxns: swapResponse.txns,
1134
1212
  algodClient: this.algodClient,
1135
1213
  address,
1136
1214
  signer,
1137
1215
  middleware: this.middleware,
1138
1216
  note,
1217
+ signerType,
1218
+ signerFeesIncluded: swapResponse.signerType === "falcon",
1139
1219
  debugLevel: this.config.debugLevel
1140
1220
  });
1141
1221
  }
@@ -1182,7 +1262,7 @@ var RouterClient = class {
1182
1262
  * })
1183
1263
  *
1184
1264
  * const router = new RouterClient({
1185
- * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
1265
+ * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)
1186
1266
  * middleware: [autoOptOut],
1187
1267
  * })
1188
1268
  *
@@ -1238,6 +1318,6 @@ var AutoOptOutMiddleware = class {
1238
1318
  }];
1239
1319
  }
1240
1320
  };
1241
- export { API_KEY_HEADER, AutoOptOutMiddleware, 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, HTTPError, MAX_FEE_BPS, Protocol, RouterClient, SwapComposer, SwapComposerStatus, request };
1321
+ export { API_KEY_HEADER, AutoOptOutMiddleware, 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, FALCON_EXTRA_FEE, HTTPError, MAX_FEE_BPS, Protocol, RouterClient, SwapComposer, SwapComposerStatus, request };
1242
1322
 
1243
1323
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/firststage.ts","../src/launch.ts","../src/composer.ts","../src/utils.ts","../src/client.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Supported DEX protocols for swap routing\n */\nexport enum Protocol {\n TinymanV2 = 'TinymanV2',\n Humble = 'Humble',\n Algofi = 'Algofi',\n Algomint = 'Algomint',\n Pact = 'Pact',\n Folks = 'Folks',\n TAlgo = 'TAlgo',\n}\n\n/**\n * Deprecated protocols that are automatically excluded from routing\n *\n * @internal\n */\nexport const DEPRECATED_PROTOCOLS = ['Tinyman'] as const\n\n/** Default Algod node URI for mainnet */\nexport const DEFAULT_ALGOD_URI = 'https://mainnet-api.4160.nodely.dev/'\n\n/** Default Algod node token (empty for public nodes) */\nexport const DEFAULT_ALGOD_TOKEN = ''\n\n/** Default Algod node port */\nexport const DEFAULT_ALGOD_PORT = 443\n\n/** Default Haystack Router API base URL */\nexport const DEFAULT_API_BASE_URL = 'https://hayrouter.txnlab.dev/api'\n\n/** HTTP header that carries the Haystack Router API key */\nexport const API_KEY_HEADER = 'X-API-Key'\n\n/** Default fee in basis points (0.10%) */\nexport const DEFAULT_FEE_BPS = 10\n\n/** Maximum allowed fee in basis points (3.00%) */\nexport const MAX_FEE_BPS = 300\n\n/** Default maximum transaction group size */\nexport const DEFAULT_MAX_GROUP_SIZE = 16\n\n/** Default maximum routing depth (number of hops) */\nexport const DEFAULT_MAX_DEPTH = 4\n\n/** Default auto opt-in setting (automatic asset/app opt-in detection) */\nexport const DEFAULT_AUTO_OPT_IN = false\n\n/** Default debug level (no debug logging) */\nexport const DEFAULT_DEBUG_LEVEL = 'none' as const\n\n/** Default number of rounds to wait for transaction confirmation */\nexport const DEFAULT_CONFIRMATION_ROUNDS = 10\n","/**\n * Helpers for First Stage taxed token quotes\n * @internal\n */\n\nimport type { FirstStageQuote, SwapFirstStageQuote } from './types'\n\n/**\n * Normalize a quote's raw First Stage object, coercing amounts to bigint\n */\nexport function toSwapFirstStageQuote(\n firstStage: FirstStageQuote,\n): SwapFirstStageQuote {\n return {\n fromTax: BigInt(firstStage.fromTax),\n toTax: BigInt(firstStage.toTax),\n wrapped: [...(firstStage.wrapped ?? [])],\n }\n}\n","/**\n * Helpers for pre-bonding HayLaunch token quotes\n * @internal\n */\n\nimport type { LaunchQuote, SwapLaunchQuote } from './types'\n\n/**\n * Normalize a quote's raw launch object, coercing amounts to bigint and\n * defaulting the fields the API omits when they don't apply\n */\nexport function toSwapLaunchQuote(launch: LaunchQuote): SwapLaunchQuote {\n return {\n side: launch.side,\n tokenNum: launch.tokenNum,\n bondingTokenId: launch.bondingTokenId,\n bondingSurplus: BigInt(launch.bondingSurplus),\n refund: BigInt(launch.refund ?? 0),\n graduates: launch.graduates ?? false,\n }\n}\n","import {\n AtomicTransactionComposer,\n decodeUint64,\n decodeUnsignedTransaction,\n encodeAddress,\n getApplicationAddress,\n isValidAddress,\n LogicSigAccount,\n makeApplicationOptInTxnFromObject,\n msgpackRawDecode,\n signLogicSigTransactionObject,\n signTransaction,\n Transaction,\n TransactionType,\n type ABIResult,\n type Algodv2,\n type TransactionSigner,\n type TransactionWithSigner,\n} from 'algosdk'\nimport { DEFAULT_CONFIRMATION_ROUNDS } from './constants'\nimport { toSwapFirstStageQuote } from './firststage'\nimport { toSwapLaunchQuote } from './launch'\nimport { Logger } from './logger'\nimport type { SwapMiddleware, SwapContext, QuoteContext } from './middleware'\nimport type {\n FetchQuoteResponse,\n SwapTransaction,\n Signature,\n SwapQuote,\n MethodCall,\n QuoteType,\n SwapSummary,\n} from './types'\n\n/** ABI selectors of the HayLaunch bonding-curve buy methods (buyWithLimit, buyWithAlgoWithLimit) */\nconst LAUNCH_BUY_SELECTORS = new Set(['9068a8d9', 'b0e30517'])\n\n/** ABI selector of the HayLaunch bonding-curve sell method (sellWithLimit) */\nconst LAUNCH_SELL_SELECTOR = '028a0647'\n\n/** Prefix of the log that carries an ARC-4 method's return value */\nconst ARC4_RETURN_PREFIX = '151f7c75'\n\n/**\n * Selector of ORDER_ROUTER v4's ARC-28 Swap event: the first 4 bytes of the\n * SHA-512/256 of\n * Swap(uint64,uint64,uint8,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,address,address,address)\n * (assetIn, assetOut, type, amount, amountIn, outDelta, delivered,\n * refundSwapped, refunded, algoReturned, treasuryFee, referrerFee, sender,\n * beneficiary, referrer)\n */\nconst SWAP_EVENT_SELECTOR = '6d221c27'\n\n/** Byte length of a Swap event log: the selector, 11 uint64s, a uint8 and 3 addresses */\nconst SWAP_EVENT_LENGTH = 4 + 11 * 8 + 1 + 3 * 32\n\n/** A swap transaction from the API, decoded */\ninterface DecodedSwapTransaction {\n txn: Transaction\n signature: Signature | false\n}\n\n/**\n * Returns which side of a HayLaunch bonding-curve trade an app call is, or\n * undefined if it isn't a curve buy or sell call\n */\nfunction launchCurveSide(txn: Transaction): 'buy' | 'sell' | undefined {\n const selector = txn.applicationCall?.appArgs[0]\n if (txn.type !== TransactionType.appl || !selector) return undefined\n\n const hex = Buffer.from(selector).toString('hex')\n if (LAUNCH_BUY_SELECTORS.has(hex)) return 'buy'\n if (hex === LAUNCH_SELL_SELECTOR) return 'sell'\n return undefined\n}\n\n/**\n * Reads a uint64 ARC-4 return value from an app call's logs (the last log,\n * prefixed with 151f7c75)\n */\nfunction readUint64Return(logs: Uint8Array[] | undefined): bigint | undefined {\n const log = logs?.[logs.length - 1]\n if (\n !log ||\n log.length !== 12 ||\n Buffer.from(log.subarray(0, 4)).toString('hex') !== ARC4_RETURN_PREFIX\n ) {\n return undefined\n }\n return decodeUint64(log.subarray(4), 'bigint')\n}\n\n/**\n * Reads the output delivery from the Swap event an ORDER_ROUTER v4 route call\n * logs at settlement. v3 logs none, so this returns undefined for it.\n */\nfunction readSwapEvent(\n logs: Uint8Array[] | undefined,\n): { assetOut: bigint; delivered: bigint; beneficiary: string } | undefined {\n const log = logs?.find(\n (l) =>\n l.length === SWAP_EVENT_LENGTH &&\n Buffer.from(l.subarray(0, 4)).toString('hex') === SWAP_EVENT_SELECTOR,\n )\n if (!log) return undefined\n return {\n assetOut: decodeUint64(log.subarray(12, 20), 'bigint'),\n delivered: decodeUint64(log.subarray(45, 53), 'bigint'),\n beneficiary: encodeAddress(log.subarray(125, 157)),\n }\n}\n\n/**\n * A transaction signer function that supports both standard algosdk.TransactionSigner\n * and ARC-1 compliant signers that may return null for unsigned transactions.\n *\n * @param txnGroup - The complete transaction group to sign\n * @param indexesToSign - Array of indexes indicating which transactions need signing\n * @returns Array of signed transactions (may include nulls for ARC-1 compliant wallets)\n */\nexport type SignerFunction = (\n txnGroup: Transaction[],\n indexesToSign: number[],\n) => Promise<(Uint8Array | null)[]>\n\n/**\n * Status of the SwapComposer transaction group lifecycle\n */\nexport enum SwapComposerStatus {\n /** The atomic group is still under construction. */\n BUILDING,\n\n /** The atomic group has been finalized, but not yet signed. */\n BUILT,\n\n /** The atomic group has been finalized and signed, but not yet submitted to the network. */\n SIGNED,\n\n /** The atomic group has been finalized, signed, and submitted to the network. */\n SUBMITTED,\n\n /** The atomic group has been finalized, signed, submitted, and successfully committed to a block. */\n COMMITTED,\n}\n\n/**\n * Configuration for creating a SwapComposer instance\n */\nexport interface SwapComposerConfig {\n /** The quote response from fetchQuote() or newQuote() */\n readonly quote: FetchQuoteResponse | SwapQuote\n /** The swap transactions from fetchSwapTransactions() */\n readonly swapTxns: SwapTransaction[]\n /** Algodv2 client instance */\n readonly algodClient: Algodv2\n /** The address of the account that will sign transactions */\n readonly address: string\n /** Transaction signer function */\n readonly signer: TransactionSigner | SignerFunction\n /** Middleware to apply during swap composition */\n readonly middleware?: SwapMiddleware[]\n /** Optional note field for the user-signed input transaction (payment or asset transfer) */\n readonly note?: Uint8Array\n /** Debug logging level (propagated from RouterClient) */\n readonly debugLevel?: 'none' | 'info' | 'debug' | 'trace'\n}\n\n/**\n * Composer for building and executing atomic swap transaction groups\n *\n * The SwapComposer allows you to build complex transaction groups by adding custom\n * transactions before and after swap transactions. It handles pre-signed transactions,\n * automatic app opt-ins, and provides a fluent API for transaction group construction.\n *\n * @example\n * ```typescript\n * const quote = await router.fetchQuote({ ... })\n * const composer = await router.newSwap({ quote, address, slippage, signer })\n *\n * await composer\n * .addTransaction(customTxn)\n * .addSwapTransactions()\n * .execute()\n * ```\n */\nexport class SwapComposer {\n /** The ATC used to compose the group */\n private atc = new AtomicTransactionComposer()\n\n /** The maximum size of an atomic transaction group. */\n static MAX_GROUP_SIZE: number = AtomicTransactionComposer.MAX_GROUP_SIZE\n\n /** Whether the swap transactions have been added to the atomic group. */\n private swapTransactionsAdded = false\n\n private readonly quote: FetchQuoteResponse | SwapQuote\n private readonly requiredAppOptIns: number[]\n private readonly swapTxns: SwapTransaction[]\n private readonly algodClient: Algodv2\n private readonly address: string\n private readonly signer: TransactionSigner | SignerFunction\n private readonly middleware: SwapMiddleware[]\n private readonly note?: Uint8Array\n private inputTransactionIndex?: number\n private outputTransactionIndex?: number\n private curveTransactionIndex?: number\n\n /** Summary data built incrementally during swap composition */\n private summaryData?: {\n inputAssetId: bigint\n outputAssetId: bigint\n inputAmount: bigint\n inputTxnId?: string\n outputTxnId?: string\n inputSender: string\n outputSender?: string\n outputAmount?: bigint\n /** Bonding-curve leg of a pre-bonding HayLaunch swap */\n launch?: {\n side: 'buy' | 'sell'\n tokenNum: bigint\n bondingTokenId: bigint\n appAddress: string\n curveTxnId?: string\n bondingAmount?: bigint\n refund: bigint\n }\n }\n\n /**\n * Create a new SwapComposer instance\n *\n * Note: Most developers should use RouterClient.newSwap() instead of constructing\n * this directly, as the factory method handles fetching swap transactions automatically.\n *\n * @param config - Configuration for the composer\n * @param config.quote - The quote response from fetchQuote()\n * @param config.swapTxns - The swap transactions from fetchSwapTransactions()\n * @param config.algodClient - Algodv2 client instance\n * @param config.address - The address of the account that will sign transactions\n * @param config.signer - Transaction signer function\n * @param config.middleware - Middleware to apply during swap composition\n */\n constructor(config: SwapComposerConfig) {\n // Set logger level from config\n if (config.debugLevel) {\n Logger.setLevel(config.debugLevel)\n }\n\n // Validate required parameters\n if (!config.quote) {\n throw new Error('Quote is required')\n }\n if (!config.swapTxns) {\n throw new Error('Swap transactions are required')\n }\n if (config.swapTxns.length === 0) {\n throw new Error('Swap transactions array cannot be empty')\n }\n if (!config.algodClient) {\n throw new Error('Algodv2 client instance is required')\n }\n if (!config.signer) {\n throw new Error('Signer is required')\n }\n\n this.quote = config.quote\n this.requiredAppOptIns = config.quote.requiredAppOptIns\n this.swapTxns = config.swapTxns\n this.algodClient = config.algodClient\n this.address = this.validateAddress(config.address)\n this.signer = config.signer\n this.middleware = config.middleware ?? []\n this.note = config.note\n }\n\n /**\n * Get the status of this composer's transaction group\n *\n * @returns The current status of the transaction group\n */\n getStatus(): SwapComposerStatus {\n return this.atc.getStatus() as unknown as SwapComposerStatus\n }\n\n /**\n * Get the number of transactions currently in this atomic group\n *\n * @returns The number of transactions in the group\n */\n count(): number {\n return this.atc.count()\n }\n\n /**\n * Add a transaction to the atomic group\n *\n * Transactions are added in the order methods are called. For example:\n * ```typescript\n * composer\n * .addTransaction(txn1) // Added first\n * .addSwapTransactions() // Added second\n * .addTransaction(txn2) // Added third\n * ```\n *\n * @param transaction - The transaction to add\n * @returns This composer instance for chaining\n * @throws Error if the composer is not in the BUILDING status\n * @throws Error if the maximum group size is exceeded\n * @throws Error if attempting to compose with a non-composable swap (Tinyman v1)\n */\n addTransaction(transaction: Transaction, signer = this.defaultSigner): this {\n // Check if the quote uses Tinyman v1 (non-composable protocol)\n const usesTinymanV1 =\n this.quote.flattenedRoute?.['Tinyman'] !== undefined &&\n this.quote.flattenedRoute['Tinyman'] > 0\n\n if (usesTinymanV1) {\n throw new Error(\n 'Cannot add transactions to a swap group that uses Tinyman v1. ' +\n 'Tinyman v1 produces non-composable transaction groups. ' +\n 'Asset opt-ins, opt-outs, and other transactions must be sent in separate groups. ' +\n 'If you need to support Tinyman v1 swaps, you must handle multi-group coordination manually.',\n )\n }\n\n this.atc.addTransaction({ txn: transaction, signer })\n return this\n }\n\n /**\n * Add a method call to the atomic group\n *\n * The `signer` property in the `methodCall` parameter is optional. If not provided,\n * the signer will default to the one passed as the second parameter, or the\n * configured signer from the constructor if no second parameter is provided.\n *\n * @param methodCall - The method call to add\n * @param signer - The signer to use for the method call (defaults to constructor signer)\n * @returns This composer instance for chaining\n * @throws Error if attempting to compose with a non-composable swap (Tinyman v1)\n */\n addMethodCall(methodCall: MethodCall, signer = this.defaultSigner): this {\n // Check if the quote uses Tinyman v1 (non-composable protocol)\n const usesTinymanV1 =\n this.quote.flattenedRoute?.['Tinyman'] !== undefined &&\n this.quote.flattenedRoute['Tinyman'] > 0\n\n if (usesTinymanV1) {\n throw new Error(\n 'Cannot add method calls to a swap group that uses Tinyman v1. ' +\n 'Tinyman v1 produces non-composable transaction groups. ' +\n 'Asset opt-ins, opt-outs, and other transactions must be sent in separate groups. ' +\n 'If you need to support Tinyman v1 swaps, you must handle multi-group coordination manually.',\n )\n }\n\n this.atc.addMethodCall({\n ...methodCall,\n signer: methodCall.signer ?? signer,\n })\n return this\n }\n\n /**\n * Add swap transactions to the atomic group\n *\n * This method automatically processes required app opt-ins, executes middleware hooks,\n * and adds all swap transactions from the quote. Can only be called once per composer instance.\n *\n * Middleware hooks are executed in this order:\n * 1. beforeSwap() - Add transactions before swap transactions\n * 2. Swap transactions (from API)\n * 3. afterSwap() - Add transactions after swap transactions\n *\n * @returns This composer instance for chaining\n * @throws Error if the swap transactions have already been added\n * @throws Error if the composer is not in the BUILDING status\n * @throws Error if the maximum group size is exceeded\n */\n async addSwapTransactions(): Promise<this> {\n if (this.swapTransactionsAdded) {\n throw new Error('Swap transactions have already been added')\n }\n\n if (this.getStatus() !== SwapComposerStatus.BUILDING) {\n throw new Error(\n 'Cannot add swap transactions when composer status is not BUILDING',\n )\n }\n\n // Execute beforeSwap middleware hooks\n const beforeTxns = await this.executeMiddlewareHooks('beforeSwap')\n\n // Check total length before adding beforeSwap transactions\n if (this.atc.count() + beforeTxns.length > SwapComposer.MAX_GROUP_SIZE) {\n throw new Error(\n `Adding beforeSwap transactions exceeds the maximum atomic group size of ${SwapComposer.MAX_GROUP_SIZE}`,\n )\n }\n\n for (const txnWithSigner of beforeTxns) {\n this.atc.addTransaction(txnWithSigner)\n }\n\n // Process swap transactions and execute afterSwap hooks\n const {\n txns: processedTxns,\n inputTxnRelativeIndex,\n outputTxnRelativeIndex,\n curveTxnRelativeIndex,\n } = await this.processSwapTransactions()\n\n const afterTxns = await this.executeMiddlewareHooks('afterSwap')\n\n // Check total length before adding swap and afterSwap transactions\n const totalLength =\n this.atc.count() + processedTxns.length + afterTxns.length\n\n if (totalLength > SwapComposer.MAX_GROUP_SIZE) {\n throw new Error(\n `Adding swap transactions exceeds the maximum atomic group size of ${SwapComposer.MAX_GROUP_SIZE}`,\n )\n }\n\n // Calculate the absolute index of the user-signed input transaction\n // This is: current ATC count (user txns + beforeSwap) + relative index within processed txns\n if (inputTxnRelativeIndex !== undefined) {\n this.inputTransactionIndex = this.atc.count() + inputTxnRelativeIndex\n }\n\n // Calculate the absolute index of the output transaction (last app call in swap)\n if (outputTxnRelativeIndex !== undefined) {\n this.outputTransactionIndex = this.atc.count() + outputTxnRelativeIndex\n }\n\n // Calculate the absolute index of a launch swap's curve buy or sell call\n if (curveTxnRelativeIndex !== undefined) {\n this.curveTransactionIndex = this.atc.count() + curveTxnRelativeIndex\n }\n\n // Add swap transactions\n for (const txnWithSigner of processedTxns) {\n this.atc.addTransaction(txnWithSigner)\n }\n\n // Add afterSwap middleware transactions\n for (const txnWithSigner of afterTxns) {\n this.atc.addTransaction(txnWithSigner)\n }\n\n this.swapTransactionsAdded = true\n\n return this\n }\n\n /**\n * Finalize the transaction group by assigning group IDs\n *\n * This method builds the atomic transaction group, assigning group IDs to all transactions\n * if there is more than one transaction. After calling this method, the composer's status\n * will be at least BUILT.\n *\n * @returns Array of transactions with their associated signers\n *\n * @throws Error if the group contains 0 transactions\n *\n * @example\n * ```typescript\n * const composer = await router.newSwap({ quote, address, slippage, signer })\n * composer.addTransaction(customTxn)\n *\n * // Build the group to inspect transactions before signing\n * const txnsWithSigners = composer.buildGroup()\n * console.log('Group ID:', txnsWithSigners[0].txn.group)\n * console.log('Group length:', txnsWithSigners.length)\n * console.log('Status:', composer.getStatus()) // BUILT\n * ```\n */\n buildGroup(): TransactionWithSigner[] {\n return this.atc.buildGroup()\n }\n\n /**\n * Sign the transaction group\n *\n * Automatically adds swap transactions if not already added, builds the atomic group,\n * and signs all transactions using the configured signer.\n *\n * @returns A promise that resolves to an array of signed transaction blobs\n *\n * @example\n * ```typescript\n * const signedTxns = await composer.sign()\n * ```\n */\n async sign(): Promise<Uint8Array[]> {\n if (this.getStatus() >= SwapComposerStatus.SIGNED) {\n return this.atc.gatherSignatures()\n }\n\n // Auto-add swap transactions if needed\n if (!this.swapTransactionsAdded) {\n await this.addSwapTransactions()\n }\n\n return await this.atc.gatherSignatures()\n }\n\n /**\n * Submit the signed transactions to the network\n *\n * This method signs the transaction group (if not already signed) and submits\n * it to the Algorand network. Does not wait for confirmation.\n *\n * @returns The transaction IDs\n * @throws Error if the transaction group has already been submitted\n *\n * @example\n * ```typescript\n * const txIds = await composer.submit()\n * console.log('Submitted transactions:', txIds)\n * ```\n */\n async submit(): Promise<string[]> {\n // Auto-add swap transactions if needed (maintains backward compatibility)\n if (!this.swapTransactionsAdded) {\n await this.addSwapTransactions()\n }\n\n return await this.atc.submit(this.algodClient)\n }\n\n /**\n * Execute the swap\n *\n * Signs the transaction group, submits it to the network, and waits for confirmation.\n * This is the primary method for executing swaps and combines sign(), submit(), and\n * waitForConfirmation() into a single call.\n *\n * @param waitRounds - The number of rounds to wait for confirmation (default: 10)\n * @returns Object containing the confirmed round and transaction IDs\n * @throws Error if the transaction group has already been committed\n *\n * @example\n * ```typescript\n * const result = await composer.execute()\n * console.log(`Confirmed in round ${result.confirmedRound}`)\n * console.log('Transaction IDs:', result.txIds)\n * ```\n */\n async execute(waitRounds: number = DEFAULT_CONFIRMATION_ROUNDS): Promise<{\n confirmedRound: bigint\n txIds: string[]\n methodResults: ABIResult[]\n }> {\n try {\n // Auto-add swap transactions if needed (maintains backward compatibility)\n if (!this.swapTransactionsAdded) {\n await this.addSwapTransactions()\n }\n\n const { txIDs, ...result } = await this.atc.execute(\n this.algodClient,\n waitRounds,\n )\n\n // Store transaction IDs in summaryData\n if (this.summaryData) {\n if (this.inputTransactionIndex !== undefined) {\n this.summaryData.inputTxnId = txIDs[this.inputTransactionIndex]\n }\n if (this.outputTransactionIndex !== undefined) {\n this.summaryData.outputTxnId = txIDs[this.outputTransactionIndex]\n }\n if (\n this.summaryData.launch &&\n this.curveTransactionIndex !== undefined\n ) {\n this.summaryData.launch.curveTxnId = txIDs[this.curveTransactionIndex]\n }\n }\n\n // Extract actual output amount from confirmed transaction\n await this.extractActualOutputAmount()\n\n return {\n ...result,\n txIds: txIDs,\n }\n } catch (error) {\n // Log comprehensive failure context when swap execution fails\n const { logSwapExecutionFailure } = await import('./debug')\n logSwapExecutionFailure(\n {\n quote: this.quote,\n address: this.address,\n slippage: 0, // Not stored in composer, would need to be passed from client\n transactionCount: this.count(),\n middlewareCount: this.middleware.length,\n groupSize: this.count(),\n },\n error,\n )\n\n // Re-throw the error\n throw error\n }\n }\n\n /**\n * Get the transaction ID of the user-signed input transaction\n *\n * Returns the transaction ID of the payment or asset transfer transaction\n * that sends the input asset. This is the transaction whose note field can\n * be customized via the `note` config option.\n *\n * The transaction ID is only available after the group has been built\n * (after calling buildGroup(), sign(), submit(), or execute()).\n *\n * @returns The transaction ID, or undefined if the group hasn't been built yet\n * or if the input transaction index couldn't be determined\n *\n * @example\n * ```typescript\n * const swap = await router.newSwap({\n * quote,\n * address,\n * slippage,\n * signer,\n * note: new TextEncoder().encode('tracking-123')\n * })\n *\n * await swap.execute()\n * const inputTxId = swap.getInputTransactionId()\n * console.log('Input transaction ID:', inputTxId)\n * ```\n */\n getInputTransactionId(): string | undefined {\n if (this.getStatus() < SwapComposerStatus.BUILT) {\n return undefined\n }\n if (this.inputTransactionIndex === undefined) {\n return undefined\n }\n const txns = this.atc.buildGroup()\n const txn = txns[this.inputTransactionIndex]?.txn\n return txn?.txID()\n }\n\n /**\n * Get a summary of the swap amounts and fees\n *\n * Returns the exact input and output amounts, total transaction fees,\n * and transaction IDs. This is useful for displaying a complete summary\n * after a swap has been executed.\n *\n * Only available after calling execute() - returns undefined before execution.\n *\n * @returns SwapSummary containing exact amounts and fees, or undefined if not yet executed\n *\n * @example\n * ```typescript\n * const swap = await router.newSwap({ quote, address, slippage, signer })\n * const result = await swap.execute()\n *\n * const summary = swap.getSummary()\n * if (summary) {\n * console.log('Sent:', summary.inputAmount, 'Received:', summary.outputAmount)\n * console.log('Total fees:', summary.totalFees, 'microAlgos')\n * }\n * ```\n */\n getSummary(): SwapSummary | undefined {\n // Only return summary after execution when we have all the data\n if (\n !this.summaryData ||\n this.summaryData.outputAmount === undefined ||\n this.summaryData.inputTxnId === undefined ||\n this.summaryData.outputTxnId === undefined ||\n this.summaryData.outputSender === undefined\n ) {\n return undefined\n }\n\n const txns = this.atc.buildGroup()\n const totalFees = txns.reduce((sum, tws) => sum + tws.txn.fee, 0n)\n\n const launch = this.summaryData.launch\n const curveTxnId = launch?.curveTxnId\n\n return {\n inputAssetId: this.summaryData.inputAssetId,\n outputAssetId: this.summaryData.outputAssetId,\n inputAmount: this.summaryData.inputAmount,\n outputAmount: this.summaryData.outputAmount,\n type: this.quote.type as QuoteType,\n totalFees,\n transactionCount: txns.length,\n // On a launch sell, the tokens sold leave the user in the curve sell call\n inputTxnId:\n launch?.side === 'sell' && curveTxnId\n ? curveTxnId\n : this.summaryData.inputTxnId,\n outputTxnId: this.summaryData.outputTxnId,\n inputSender: this.summaryData.inputSender,\n outputSender: this.summaryData.outputSender,\n ...(launch &&\n curveTxnId && {\n launch: {\n side: launch.side,\n tokenNum: launch.tokenNum,\n curveTxnId,\n bondingAmount: launch.bondingAmount,\n refund: launch.refund,\n },\n }),\n }\n }\n\n /**\n * Validates an Algorand address\n */\n private validateAddress(address: string): string {\n if (!isValidAddress(address)) {\n throw new Error(`Invalid Algorand address: ${address}`)\n }\n return address\n }\n\n /**\n * Processes app opt-ins and decodes swap transactions from API response\n *\n * Also initializes summaryData with input transaction details and output transaction ID\n */\n private async processSwapTransactions(): Promise<{\n txns: TransactionWithSigner[]\n inputTxnRelativeIndex?: number\n outputTxnRelativeIndex?: number\n curveTxnRelativeIndex?: number\n }> {\n const appOptIns = await this.processRequiredAppOptIns()\n\n const decoded: DecodedSwapTransaction[] = this.swapTxns.map(\n (swapTxn, i) => {\n try {\n const txn = decodeUnsignedTransaction(\n Buffer.from(swapTxn.data, 'base64'),\n )\n delete txn.group\n return { txn, signature: swapTxn.signature }\n } catch (error) {\n throw new Error(\n `Failed to process swap transaction at index ${i}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n )\n }\n },\n )\n\n const inputIndex = this.findInputTransactionIndex(decoded)\n const inputTxn =\n inputIndex !== undefined ? decoded[inputIndex]?.txn : undefined\n\n // Set the note on the input transaction only (using type assertion since\n // note is readonly but safe to modify before signing). Other user-signed\n // transactions can differ only by their notes, e.g. HayLaunch gas() calls.\n if (inputTxn && this.note !== undefined) {\n ;(inputTxn as { note: Uint8Array }).note = this.note\n }\n\n // Pre-signed transactions use the custom Haystack Router signer; the rest\n // are signed by the user\n const swapTxns: TransactionWithSigner[] = decoded.map(\n ({ txn, signature }) => ({\n txn,\n signer:\n signature === false\n ? this.defaultSigner\n : this.createSwapSigner(signature),\n }),\n )\n\n // Initialize summary data with input transaction details\n if (inputTxn) {\n // Extract input amount from payment or asset transfer\n const paymentAmount = inputTxn.payment?.amount\n const assetTransferAmount = inputTxn.assetTransfer?.amount\n const inputAmount = paymentAmount ?? assetTransferAmount ?? 0n\n\n this.summaryData = {\n inputAssetId: BigInt(this.quote.fromASAID),\n outputAssetId: BigInt(this.quote.toASAID),\n inputAmount,\n inputSender: this.address,\n }\n }\n\n const curveIndex = this.initLaunchSummary(decoded)\n\n // The router's last app call (v4's route, v3's finalize) holds the inner\n // transaction sending the output asset to the user. On a launch buy the\n // output comes from the curve buy call instead, wherever it is.\n // We'll get its ID after buildGroup() is called\n // Store the relative index so we can get the ID later\n const outputIndex =\n this.summaryData?.launch?.side === 'buy' && curveIndex !== undefined\n ? curveIndex\n : this.findOutputTransactionIndex(decoded)\n const outputTxnRelativeIndex =\n outputIndex >= 0 ? appOptIns.length + outputIndex : undefined\n\n return {\n txns: [...appOptIns, ...swapTxns],\n inputTxnRelativeIndex:\n inputIndex !== undefined ? appOptIns.length + inputIndex : undefined,\n outputTxnRelativeIndex,\n curveTxnRelativeIndex:\n curveIndex !== undefined ? appOptIns.length + curveIndex : undefined,\n }\n }\n\n /**\n * Finds the swap's input transaction among the API's transactions: the\n * first user-signed asset transfer to the router app, else the first\n * user-signed payment to it. The router's address comes from its pre-signed\n * app calls. Falls back to the last user-signed transaction for groups\n * without a router app call.\n *\n * The user may also pay the router ALGO for its fees and opt-ins, after the\n * input on ORDER_ROUTER v3 and before an ASA input on v4. The rule relies\n * on two invariants of the groups the router API builds: an ASA input is\n * the only asset transfer the user makes to the router, and no payment to\n * the router precedes an ALGO input.\n */\n private findInputTransactionIndex(\n txns: DecodedSwapTransaction[],\n ): number | undefined {\n const routerAppId = txns.find(\n ({ txn, signature }) =>\n signature !== false && txn.type === TransactionType.appl,\n )?.txn.applicationCall?.appIndex\n\n if (routerAppId !== undefined) {\n const routerAddress = getApplicationAddress(routerAppId).toString()\n const isUserTxnToRouter = ({ txn, signature }: DecodedSwapTransaction) =>\n signature === false &&\n (txn.payment?.receiver ?? txn.assetTransfer?.receiver)?.toString() ===\n routerAddress\n for (const type of [TransactionType.axfer, TransactionType.pay]) {\n const index = txns.findIndex(\n (t) => t.txn.type === type && isUserTxnToRouter(t),\n )\n if (index !== -1) return index\n }\n }\n\n for (let i = txns.length - 1; i >= 0; i--) {\n if (txns[i]?.signature === false) return i\n }\n return undefined\n }\n\n /**\n * Finds the router's last app call among the API's transactions: v4's\n * route call or v3's finalize, which pays the output. The router's calls\n * are the pre-signed ones. A First Stage swap ends with the user's own tax\n * payment and First Stage calls after it, so the last transaction isn't\n * always the router's. Falls back to the last transaction.\n */\n private findOutputTransactionIndex(txns: DecodedSwapTransaction[]): number {\n for (let i = txns.length - 1; i >= 0; i--) {\n const t = txns[i]\n if (t?.signature !== false && t?.txn.type === TransactionType.appl) {\n return i\n }\n }\n return txns.length - 1\n }\n\n /**\n * For a launch quote, finds the user-signed curve buy or sell call and\n * records the curve leg in summaryData. On a sell, the input amount becomes\n * the launch tokens sold.\n *\n * @returns The curve call's index within the swap transactions, if found\n */\n private initLaunchSummary(\n txns: DecodedSwapTransaction[],\n ): number | undefined {\n const side = this.quote.launch?.side\n if (!side || !this.summaryData) return undefined\n\n const curveIndex = txns.findIndex(\n ({ txn, signature }) =>\n signature === false && launchCurveSide(txn) === side,\n )\n const curveCall = txns[curveIndex]?.txn.applicationCall\n // buy: (tokenNum, minTokensOut); sell: (tokenNum, tokensToSell, minBondingOut)\n const tokenNumArg = curveCall?.appArgs[1]\n if (!curveCall || !tokenNumArg) return undefined\n\n const appAddress = getApplicationAddress(curveCall.appIndex).toString()\n\n let bondingAmount: bigint | undefined\n if (side === 'buy') {\n // ARC-4 transaction arguments directly precede the app call, and the\n // bonding payment to the curve is the buy method's last one\n const payment = txns[curveIndex - 1]?.txn\n const receiver =\n payment?.payment?.receiver ?? payment?.assetTransfer?.receiver\n if (receiver?.toString() === appAddress) {\n bondingAmount =\n payment?.payment?.amount ?? payment?.assetTransfer?.amount\n }\n } else {\n const tokensToSell = curveCall.appArgs[2]\n if (tokensToSell) {\n this.summaryData.inputAmount = decodeUint64(tokensToSell, 'bigint')\n }\n }\n\n this.summaryData.launch = {\n side,\n tokenNum: decodeUint64(tokenNumArg, 'bigint'),\n bondingTokenId: BigInt(this.quote.launch?.bondingTokenId ?? 0),\n appAddress,\n bondingAmount,\n refund: 0n,\n }\n\n return curveIndex\n }\n\n /**\n * Creates opt-in transactions for apps the user hasn't opted into yet\n */\n private async processRequiredAppOptIns(): Promise<TransactionWithSigner[]> {\n // Fetch account information\n const accountInfo = await this.algodClient\n .accountInformation(this.address)\n .do()\n\n // Check app opt-ins\n const userApps =\n accountInfo?.appsLocalState?.map((app) => Number(app.id)) || []\n const appsToOptIn = this.requiredAppOptIns.filter(\n (appId) => !userApps.includes(appId),\n )\n\n if (appsToOptIn.length === 0) return []\n\n const suggestedParams = await this.algodClient.getTransactionParams().do()\n\n return appsToOptIn.map((appId) => ({\n txn: makeApplicationOptInTxnFromObject({\n sender: this.address,\n appIndex: appId,\n suggestedParams,\n }),\n signer: this.defaultSigner,\n }))\n }\n\n /**\n * The default signer function that uses the configured signer\n */\n private defaultSigner: TransactionSigner = async (\n txnGroup: Transaction[],\n indexesToSign: number[],\n ) => {\n const result = await this.signer(txnGroup, indexesToSign)\n return result.filter((txn): txn is Uint8Array => txn !== null)\n }\n\n /**\n * Creates a TransactionSigner function for Haystack Router pre-signed transactions\n */\n private createSwapSigner(signature: Signature): TransactionSigner {\n return async (\n txnGroup: Transaction[],\n indexesToSign: number[],\n ): Promise<Uint8Array[]> => {\n return indexesToSign.map((i) => {\n const txn = txnGroup[i]\n if (!txn) throw new Error(`Transaction at index ${i} not found`)\n return this.signSwapTransaction(txn, signature)\n })\n }\n }\n\n /**\n * Re-signs a Haystack Router transaction using the provided logic signature or secret key\n */\n private signSwapTransaction(\n transaction: Transaction,\n signature: Signature,\n ): Uint8Array {\n try {\n if (signature.type === 'logic_signature') {\n // Decode the signature value to extract the logic signature\n const valueArray = signature.value as Record<string, number>\n const valueBytes = new Uint8Array(Object.values(valueArray))\n const decoded = msgpackRawDecode(valueBytes) as {\n lsig?: { l: Uint8Array; arg?: Uint8Array[] }\n }\n\n if (!decoded.lsig) {\n throw new Error('Logic signature structure missing lsig field')\n }\n\n const lsig = decoded.lsig\n const logicSigAccount = new LogicSigAccount(lsig.l, lsig.arg)\n\n const signedTxn = signLogicSigTransactionObject(\n transaction,\n logicSigAccount,\n )\n return signedTxn.blob\n } else if (signature.type === 'secret_key') {\n // Convert signature.value (Record<string, number>) to Uint8Array\n const valueArray = signature.value as Record<string, number>\n const secretKey = new Uint8Array(Object.values(valueArray))\n const signedTxn = signTransaction(transaction, secretKey)\n return signedTxn.blob\n } else {\n throw new Error(`Unsupported signature type: ${signature.type}`)\n }\n } catch (error) {\n throw new Error(\n `Failed to re-sign transaction: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n )\n }\n }\n\n /**\n * Execute middleware hooks (beforeSwap or afterSwap)\n */\n private async executeMiddlewareHooks(\n hookName: 'beforeSwap' | 'afterSwap',\n ): Promise<TransactionWithSigner[]> {\n const allTxns: TransactionWithSigner[] = []\n\n // Convert to SwapQuote if needed\n const quote: SwapQuote =\n 'createdAt' in this.quote\n ? this.quote\n : {\n ...this.quote,\n quote: this.quote.quote === '' ? 0n : BigInt(this.quote.quote),\n amount: 0n, // Not available in FetchQuoteResponse\n launch: this.quote.launch && toSwapLaunchQuote(this.quote.launch),\n firstStage:\n this.quote.firstStage &&\n toSwapFirstStageQuote(this.quote.firstStage),\n createdAt: Date.now(),\n }\n\n // Create quote context for middleware shouldApply checks\n const quoteContext: QuoteContext = {\n fromASAID: BigInt(this.quote.fromASAID),\n toASAID: BigInt(this.quote.toASAID),\n amount: quote.amount,\n type: this.quote.type as QuoteType,\n address: quote.address,\n algodClient: this.algodClient,\n }\n\n for (const mw of this.middleware) {\n const shouldApply = await mw.shouldApply(quoteContext)\n\n if (!shouldApply || !mw[hookName]) {\n continue\n }\n\n // Create swap context for middleware hooks (only when needed)\n const suggestedParams = await this.algodClient.getTransactionParams().do()\n const swapContext: SwapContext = {\n quote,\n address: this.address,\n algodClient: this.algodClient,\n suggestedParams,\n fromASAID: BigInt(this.quote.fromASAID),\n toASAID: BigInt(this.quote.toASAID),\n signer: this.defaultSigner,\n }\n\n const txns = await mw[hookName](swapContext)\n\n allTxns.push(...txns)\n }\n\n return allTxns\n }\n\n /**\n * Extract the actual output amount from the confirmed output transaction\n *\n * Analyzes only the output transaction (last app call in the swap). On\n * ORDER_ROUTER v4 that is the route call, whose Swap event gives the amount\n * delivered. v3 logs no event, so the output is its first inner transaction\n * that transfers the output asset to the user (it makes only one). On a\n * launch buy the output is a balance on the HayLaunch app, so it is read\n * from the curve buy call's return value instead.\n */\n private async extractActualOutputAmount(): Promise<void> {\n if (!this.summaryData?.outputTxnId) {\n return\n }\n\n const outputAssetId = this.summaryData.outputAssetId\n const userAddress = this.address\n const launch = this.summaryData.launch\n\n try {\n const pendingInfo = await this.algodClient\n .pendingTransactionInformation(this.summaryData.outputTxnId)\n .do()\n\n if (launch?.side === 'buy') {\n // The buy call returns the tokens credited to the buyer's balance\n const tokensCredited = readUint64Return(pendingInfo.logs)\n if (tokensCredited !== undefined) {\n this.summaryData.outputAmount = tokensCredited\n this.summaryData.outputSender = launch.appAddress\n }\n\n // A buy that completes bonding refunds the unused bonding asset\n for (const innerTxn of pendingInfo.innerTxns ?? []) {\n const txn = innerTxn.txn.txn\n if (launch.bondingTokenId === 0n) {\n if (txn.payment?.receiver.toString() === userAddress) {\n launch.refund += txn.payment.amount\n }\n } else if (\n txn.assetTransfer?.receiver.toString() === userAddress &&\n txn.assetTransfer.assetIndex === launch.bondingTokenId\n ) {\n launch.refund += txn.assetTransfer.amount\n }\n }\n return\n }\n\n const swapEvent = readSwapEvent(pendingInfo.logs)\n const routeCall = swapEvent && pendingInfo.txn.txn.applicationCall\n if (swapEvent && routeCall) {\n // v4: every payout goes to the event's beneficiary\n if (\n swapEvent.assetOut === outputAssetId &&\n swapEvent.beneficiary === userAddress\n ) {\n this.summaryData.outputAmount = swapEvent.delivered\n this.summaryData.outputSender = getApplicationAddress(\n routeCall.appIndex,\n ).toString()\n }\n } else {\n for (const innerTxn of pendingInfo.innerTxns ?? []) {\n const txn = innerTxn.txn.txn\n const payment = txn.payment\n const assetTransfer = txn.assetTransfer\n\n // Get receiver address based on transaction type\n const receiver = payment?.receiver ?? assetTransfer?.receiver\n if (!receiver) continue\n\n // Check if this transfer is to the user\n if (receiver.toString() !== userAddress) continue\n\n // Get sender address for outputSender\n const senderAddress = txn.sender.toString()\n\n if (outputAssetId === 0n && payment?.amount != null) {\n // ALGO output to user\n this.summaryData.outputAmount = payment.amount\n this.summaryData.outputSender = senderAddress\n break\n } else if (outputAssetId !== 0n && assetTransfer) {\n // ASA output - verify it's the right asset\n const assetId = assetTransfer.assetIndex\n if (assetId === outputAssetId && assetTransfer.amount != null) {\n this.summaryData.outputAmount = assetTransfer.amount\n this.summaryData.outputSender = senderAddress\n break\n }\n }\n }\n }\n } catch {\n // Silently fail - outputAmount will remain undefined\n }\n\n // On a launch sell, the curve call returns the bonding asset paid out after\n // fees. It's looked up separately so a failure here keeps the output above.\n if (launch?.side === 'sell' && launch.curveTxnId) {\n try {\n const curveInfo = await this.algodClient\n .pendingTransactionInformation(launch.curveTxnId)\n .do()\n launch.bondingAmount = readUint64Return(curveInfo.logs)\n } catch {\n // Silently fail - bondingAmount will remain undefined\n }\n }\n }\n}\n","/**\n * HTTP error with status code and response data\n */\nexport class HTTPError extends Error {\n constructor(\n public status: number,\n public statusText: string,\n public data: unknown,\n ) {\n super(`HTTP ${status} ${statusText}`)\n this.name = 'HTTPError'\n }\n}\n\n/**\n * Make an HTTP request and parse JSON response\n *\n * Simple wrapper around native fetch for API calls. Throws HTTPError for\n * non-2xx responses.\n *\n * @param url - The URL to request\n * @param options - Fetch options\n * @returns Parsed JSON response\n * @throws HTTPError if the response status is not ok\n */\nexport async function request<T>(\n url: string,\n options?: RequestInit,\n): Promise<T> {\n const response = await fetch(url, options)\n\n if (!response.ok) {\n let errorData: unknown\n\n try {\n errorData = await response.json()\n } catch {\n try {\n errorData = await response.text()\n } catch {\n errorData = 'Failed to parse error response'\n }\n }\n\n throw new HTTPError(\n response.status,\n response.statusText,\n JSON.stringify(errorData),\n )\n }\n\n return response.json() as Promise<T>\n}\n","import { Algodv2, isValidAddress, type TransactionSigner } from 'algosdk'\nimport { SwapComposer, type SignerFunction } from './composer'\nimport {\n API_KEY_HEADER,\n DEFAULT_ALGOD_PORT,\n DEFAULT_ALGOD_TOKEN,\n DEFAULT_ALGOD_URI,\n DEFAULT_API_BASE_URL,\n DEFAULT_AUTO_OPT_IN,\n DEFAULT_DEBUG_LEVEL,\n DEFAULT_FEE_BPS,\n DEFAULT_MAX_DEPTH,\n DEFAULT_MAX_GROUP_SIZE,\n DEPRECATED_PROTOCOLS,\n MAX_FEE_BPS,\n} from './constants'\nimport { toSwapFirstStageQuote } from './firststage'\nimport { toSwapLaunchQuote } from './launch'\nimport { Logger } from './logger'\nimport { request } from './utils'\nimport type { SwapMiddleware } from './middleware'\nimport type {\n FetchQuoteResponse,\n FetchSwapTxnsResponse,\n Config,\n ConfigParams,\n FetchSwapTxnsParams,\n FetchSwapTxnsBody,\n FetchQuoteParams,\n SwapQuote,\n} from './types'\n\n/**\n * Client for interacting with the Haystack order router API\n *\n * The RouterClient provides methods to fetch swap quotes and create transaction composers\n * for executing swaps on the Algorand blockchain. It handles API communication, transaction\n * validation, and automatic asset/app opt-in detection.\n *\n * @example\n * ```typescript\n * const router = new RouterClient({\n * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)\n * })\n * ```\n *\n * @example\n * ```typescript\n * const router = new RouterClient({\n * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)\n * apiBaseUrl: 'https://hayrouter.txnlab.dev',\n * algodUri: 'https://mainnet-api.4160.nodely.dev/',\n * algodToken: '',\n * algodPort: 443,\n * referrerAddress: 'REFERRER_ADDRESS...',\n * feeBps: 15,\n * autoOptIn: false,\n * })\n * ```\n */\nexport class RouterClient {\n private readonly config: Config\n private readonly algodClient: Algodv2\n private readonly middleware: SwapMiddleware[]\n\n /**\n * Create a new RouterClient instance\n *\n * @param config - Configuration parameters\n * @param config.apiKey - API key for Haystack Router API (required)\n * @param config.apiBaseUrl - Base URL for the Haystack Router API (default: https://hayrouter.txnlab.dev)\n * @param config.algodUri - Algod node URI (default: https://mainnet-api.4160.nodely.dev/)\n * @param config.algodToken - Algod node token (default: '')\n * @param config.algodPort - Algod node port (default: 443)\n * @param config.referrerAddress - Referrer address for fee sharing (receives 25% of swap fees)\n * @param config.feeBps - Fee in basis points (default: 15 = 0.15%, max: 300 = 3.00%)\n * @param config.autoOptIn - Automatically detect and add required opt-in transactions (default: false)\n * @param config.debugLevel - Debug logging level (default: 'none')\n * @param config.middleware - Array of middleware to apply to swaps (default: [])\n */\n constructor(config: ConfigParams & { middleware?: SwapMiddleware[] }) {\n // Set logger level FIRST (before any operations)\n const debugLevel = config.debugLevel ?? DEFAULT_DEBUG_LEVEL\n Logger.setLevel(debugLevel)\n\n // Validate and set config\n this.config = {\n apiKey: this.validateApiKey(config.apiKey),\n apiBaseUrl: config.apiBaseUrl ?? DEFAULT_API_BASE_URL,\n algodUri: config.algodUri ?? DEFAULT_ALGOD_URI,\n algodToken: config.algodToken ?? DEFAULT_ALGOD_TOKEN,\n algodPort: config.algodPort ?? DEFAULT_ALGOD_PORT,\n referrerAddress: config.referrerAddress\n ? this.validateAddress(config.referrerAddress)\n : undefined,\n feeBps: this.validateFeeBps(config.feeBps ?? DEFAULT_FEE_BPS),\n autoOptIn: config.autoOptIn ?? DEFAULT_AUTO_OPT_IN,\n debugLevel,\n }\n\n // Create Algodv2 client\n this.algodClient = new Algodv2(\n this.config.algodToken,\n this.config.algodUri,\n this.config.algodPort,\n )\n\n // Store middleware\n this.middleware = config.middleware ?? []\n }\n\n /**\n * Fetch a swap quote from the Haystack Router API\n *\n * Requests optimal swap routing from the Haystack Router API. The quote includes routing\n * information, price impact, required opt-ins, and an encrypted transaction payload.\n *\n * @param params - Parameters for the quote request\n * @param params.fromASAID - The ID of the asset to swap from\n * @param params.toASAID - The ID of the asset to swap to\n * @param params.amount - The amount of the asset to swap in base units\n * @param params.type - The type of the quote (default: 'fixed-input')\n * @param params.disabledProtocols - List of protocols to disable (default: [])\n * @param params.maxGroupSize - The maximum group size (default: 16)\n * @param params.maxDepth - The maximum depth (default: 4)\n * @param params.address - The address of the account that will perform the swap (recommended when using `config.autoOptIn` or `params.optIn`)\n * @param params.optIn - Whether to include asset opt-in transaction\n * - If true: API reduces maxGroupSize by 1 and includes opt-in (always included, even if not needed)\n * - If false: No opt-in transaction included\n * - If undefined: Falls back to `config.autoOptIn` behavior with account check (if `params.address` is provided)\n * @param params.slippage - Slippage tolerance as a percentage, in [0, 100). Pass the value you will execute with; required for accurate pre-bonding launch token quotes\n * @returns A FetchQuoteResponse object with routing information\n *\n * @example\n * ```typescript\n * const quote = await router.fetchQuote({\n * address: 'ABC...',\n * fromASAID: 0, // ALGO\n * toASAID: 31566704, // USDC\n * amount: 1_000_000, // 1 ALGO\n * })\n * ```\n */\n async fetchQuote(params: FetchQuoteParams): Promise<FetchQuoteResponse> {\n const {\n fromASAID,\n toASAID,\n amount,\n type = 'fixed-input',\n disabledProtocols = [],\n maxGroupSize = DEFAULT_MAX_GROUP_SIZE,\n maxDepth = DEFAULT_MAX_DEPTH,\n optIn,\n address,\n slippage,\n _allowNonComposableSwaps = false,\n } = params\n\n if (slippage !== undefined) {\n this.validateSlippage(slippage)\n }\n\n // Validate incompatible parameter combinations\n if (_allowNonComposableSwaps && (optIn === true || this.config.autoOptIn)) {\n throw new Error(\n 'Cannot use _allowNonComposableSwaps with optIn or autoOptIn. ' +\n 'When allowing non-composable swaps (Tinyman v1), you must handle ' +\n 'asset opt-ins and opt-outs in separate transaction groups.',\n )\n }\n\n // Always disable the deprecated protocols (Tinyman v1), unless\n // _allowNonComposableSwaps opts into Tinyman v1\n const allDisabledProtocols = _allowNonComposableSwaps\n ? [...new Set(disabledProtocols)]\n : [...new Set([...DEPRECATED_PROTOCOLS, ...disabledProtocols])]\n\n let includeOptIn = optIn\n if (includeOptIn === undefined && this.config.autoOptIn) {\n if (address) {\n includeOptIn = await this.needsAssetOptIn(\n this.validateAddress(address),\n toASAID,\n )\n } else {\n console.warn(\n 'autoOptIn is enabled but no address provided to fetchQuote(). Asset opt-in check skipped.',\n )\n }\n }\n\n const url = new URL(`${this.config.apiBaseUrl}/fetchQuote`)\n\n url.searchParams.append('algodUri', this.config.algodUri)\n url.searchParams.append('algodToken', this.config.algodToken)\n url.searchParams.append('algodPort', String(this.config.algodPort))\n url.searchParams.append('feeBps', this.config.feeBps.toString())\n url.searchParams.append('fromASAID', BigInt(fromASAID).toString())\n url.searchParams.append('toASAID', BigInt(toASAID).toString())\n url.searchParams.append('amount', BigInt(amount).toString())\n url.searchParams.append('type', type)\n url.searchParams.append('disabledProtocols', allDisabledProtocols.join(','))\n url.searchParams.append('maxGroupSize', maxGroupSize.toString())\n url.searchParams.append('maxDepth', maxDepth.toString())\n\n if (typeof includeOptIn === 'boolean') {\n url.searchParams.append('optIn', String(includeOptIn))\n }\n\n // Lets the router quote a First Stage taxed token's swap from the\n // account's own holding (wrapped only while it is frozen)\n if (address) {\n url.searchParams.append('address', this.validateAddress(address))\n }\n\n if (slippage !== undefined) {\n url.searchParams.append('slippage', String(slippage))\n }\n\n if (this.config.referrerAddress) {\n url.searchParams.append('referrerAddress', this.config.referrerAddress)\n }\n\n return request<FetchQuoteResponse>(url.toString(), {\n headers: this.authHeaders(),\n })\n }\n\n /**\n * Check if asset opt-in is required for the output asset\n *\n * Convenience method to determine if an address needs to opt into the output asset\n * of a swap. This is useful when you want to get a quote without requiring wallet\n * connection upfront, but need to know whether to set `optIn: true` in fetchQuote()\n * to ensure proper routing (as opt-ins reduce maxGroupSize by 1).\n *\n * Note: If you enable `config.autoOptIn`, this check is handled automatically when\n * an address is provided to fetchQuote().\n *\n * @param address - The address to check\n * @param assetId - The output asset ID to check\n * @returns True if asset opt-in is required, false otherwise (always false for ALGO)\n *\n * @example\n * ```typescript\n * // Check if opt-in needed for output asset before fetching quote\n * const needsOptIn = await router.needsAssetOptIn(userAddress, toAssetId)\n * const quote = await router.fetchQuote({\n * fromAssetId,\n * toAssetId,\n * amount,\n * optIn: needsOptIn,\n * })\n * ```\n */\n async needsAssetOptIn(\n address: string,\n assetId: number | bigint,\n ): Promise<boolean> {\n // Fetch account information\n const accountInfo = await this.algodClient.accountInformation(address).do()\n\n // Check if asset opt-in is required\n return (\n BigInt(assetId) !== 0n &&\n accountInfo?.assets?.find(\n (asset) => asset.assetId === BigInt(assetId),\n ) === undefined\n )\n }\n\n /**\n * Fetch swap transactions from the Haystack Router API\n *\n * Decrypts the quote payload and generates executable swap transactions for the\n * specified signer address with the given slippage tolerance.\n *\n * @param params - Parameters for the swap transaction request\n * @param params.quote - The quote response from fetchQuote()\n * @param params.address - The address of the signer\n * @param params.slippage - The slippage tolerance as a percentage (e.g., 1 for 1%)\n * @returns A FetchSwapTxnsResponse object with transaction data\n */\n async fetchSwapTransactions(\n params: FetchSwapTxnsParams,\n ): Promise<FetchSwapTxnsResponse> {\n const { quote, address, slippage } = params\n\n // Validate signer address\n this.validateAddress(address)\n\n const url = new URL(`${this.config.apiBaseUrl}/fetchExecuteSwapTxns`)\n\n const body: FetchSwapTxnsBody = {\n address,\n txnPayloadJSON: quote.txnPayload,\n slippage,\n }\n\n return request<FetchSwapTxnsResponse>(url.toString(), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.authHeaders(),\n },\n body: JSON.stringify(body),\n })\n }\n\n /**\n * Headers that authenticate a request with the API key. The key goes in the\n * `X-API-Key` header rather than the URL, where it would end up in logs, browser\n * history and CDN caches. The API refuses a secret key (`hay_sk_`) in the URL.\n */\n private authHeaders(): Record<string, string> {\n return { [API_KEY_HEADER]: this.config.apiKey }\n }\n\n /**\n * Fetch a quote and return an enhanced quote result\n *\n * This is the recommended way to fetch quotes. It returns an object that\n * extends the raw API response with additional metadata and type normalization.\n *\n * @param params - Parameters for the quote request\n * @param params.fromASAID - The ID of the asset to swap from\n * @param params.toASAID - The ID of the asset to swap to\n * @param params.amount - The amount of the asset to swap in base units\n * @param params.type - The type of the quote (default: 'fixed-input')\n * @param params.disabledProtocols - List of protocols to disable (default: [])\n * @param params.maxGroupSize - The maximum group size (default: 16)\n * @param params.maxDepth - The maximum depth (default: 4)\n * @param params.address - The address of the account that will perform the swap (recommended when using `config.autoOptIn` or `params.optIn`)\n * @param params.optIn - Whether to include asset opt-in transaction\n * @param params.slippage - Slippage tolerance as a percentage, in [0, 100). Pass the value you will execute with; required for accurate pre-bonding launch token quotes\n * @returns A SwapQuote enhanced quote result\n *\n * @example\n * ```typescript\n * const quote = await router.newQuote({\n * address: 'ABC...',\n * fromASAID: 0,\n * toASAID: 31566704,\n * amount: 1_000_000,\n * })\n *\n * console.log(quote.quote) // bigint - quoted amount\n * console.log(quote.fromASAID) // number - input asset ID\n * console.log(quote.toASAID) // number - output asset ID\n * console.log(quote.amount) // bigint - original request amount\n * console.log(quote.createdAt) // number - timestamp\n * ```\n */\n async newQuote(params: FetchQuoteParams): Promise<SwapQuote> {\n // Block usage of _allowNonComposableSwaps with newQuote (middleware path)\n if (params._allowNonComposableSwaps) {\n throw new Error(\n 'The _allowNonComposableSwaps parameter is not supported with newQuote(). ' +\n 'This parameter is only available with fetchQuote() for advanced use cases. ' +\n 'The newQuote() method is designed for composable swaps with middleware support.',\n )\n }\n\n // Apply middleware transformations to quote params\n let adjustedParams = { ...params }\n\n // Create quote context for middleware\n const quoteContext = {\n fromASAID: BigInt(params.fromASAID),\n toASAID: BigInt(params.toASAID),\n amount: BigInt(params.amount),\n type: params.type ?? ('fixed-input' as const),\n address: params.address ?? undefined,\n algodClient: this.algodClient,\n }\n\n for (const mw of this.middleware) {\n const shouldApply = await mw.shouldApply(quoteContext)\n\n if (shouldApply && mw.adjustQuoteParams) {\n adjustedParams = await mw.adjustQuoteParams(adjustedParams)\n }\n }\n\n const response = await this.fetchQuote(adjustedParams)\n const { launch, firstStage, ...rest } = response\n\n return {\n ...rest,\n quote: response.quote === '' ? 0n : BigInt(response.quote),\n amount: BigInt(params.amount),\n address: params.address ?? undefined,\n slippage: adjustedParams.slippage,\n launch: launch && toSwapLaunchQuote(launch),\n firstStage: firstStage && toSwapFirstStageQuote(firstStage),\n createdAt: Date.now(),\n }\n }\n\n /**\n * Create a SwapComposer instance\n *\n * This factory method creates a composer that allows you to add custom transactions\n * before and after the swap transactions, with automatic handling of pre-signed transactions\n * and opt-ins.\n *\n * @param config.quote - The quote result from newQuote() or raw API response from fetchQuote()\n * @param config.address - The address of the signer\n * @param config.slippage - The slippage tolerance\n * @param config.signer - Transaction signer function\n * @returns A SwapComposer instance ready for building transaction groups\n *\n * @example\n * ```typescript\n * // Basic swap\n * const quote = await router.newQuote({ ... })\n * await router.newSwap({ quote, address, slippage, signer })\n * .execute()\n * ```\n *\n * @example\n * ```typescript\n * // Advanced swap with custom transactions\n * const quote = await router.newQuote({ ... })\n * const swap = await router.newSwap({\n * quote,\n * address,\n * slippage,\n * signer,\n * })\n *\n * console.log(swap.getStatus()) // BUILDING\n *\n * const signedTxns = await swap\n * .addTransaction(beforeTxn)\n * .addSwapTransactions() // Adds swap transactions to the group\n * .addTransaction(afterTxn)\n * .sign()\n *\n * console.log(swap.getStatus()) // SIGNED\n *\n * const result = await swap.execute(waitRounds)\n * console.log(result.confirmedRound, result.txIds)\n *\n * console.log(swap.getStatus()) // COMMITTED\n * ```\n */\n async newSwap(config: {\n quote: SwapQuote | FetchQuoteResponse\n address: string\n slippage: number\n signer: TransactionSigner | SignerFunction\n /** Optional note field for the user-signed input transaction (payment or asset transfer) */\n note?: Uint8Array\n }): Promise<SwapComposer> {\n const { quote, address, slippage, signer, note } = config\n\n // A launch quote prices the curve on the slippage it was fetched with, so\n // executing at a different slippage makes the quoted output inaccurate\n if (quote.launch && 'createdAt' in quote && quote.slippage !== slippage) {\n console.warn(\n quote.slippage === undefined\n ? `Launch token quote was fetched without slippage but the swap uses ${slippage}%. ` +\n 'Pass the same slippage to newQuote() for an accurate quoted output.'\n : `Launch token quote was fetched with ${quote.slippage}% slippage but the swap uses ${slippage}%. ` +\n 'The quoted output may not match what is delivered.',\n )\n }\n\n const swapResponse = await this.fetchSwapTransactions({\n quote,\n address,\n slippage,\n })\n\n // Create the composer\n const composer = new SwapComposer({\n quote,\n swapTxns: swapResponse.txns,\n algodClient: this.algodClient,\n address,\n signer,\n middleware: this.middleware,\n note,\n debugLevel: this.config.debugLevel,\n })\n\n return composer\n }\n\n /**\n * Validates the API key\n */\n private validateApiKey(apiKey: string): string {\n if (!apiKey) {\n throw new Error('API key is required')\n }\n return apiKey\n }\n\n /**\n * Validates an Algorand address\n */\n private validateAddress(address: string): string {\n if (!isValidAddress(address)) {\n throw new Error(`Invalid Algorand address: ${address}`)\n }\n return address\n }\n\n /**\n * Validates a slippage percentage (0 <= slippage < 100)\n */\n private validateSlippage(slippage: number): number {\n if (!Number.isFinite(slippage) || slippage < 0 || slippage >= 100) {\n throw new Error(`Invalid slippage: ${slippage} (must be >= 0 and < 100)`)\n }\n return slippage\n }\n\n /**\n * Validates the fee in basis points (max 300 = 3.00%)\n */\n private validateFeeBps(feeBps: number): number {\n if (feeBps < 0 || feeBps > MAX_FEE_BPS) {\n throw new Error(\n `Invalid fee: ${feeBps} basis points (must be between 0 and ${MAX_FEE_BPS})`,\n )\n }\n return feeBps\n }\n}\n","import {\n makeAssetTransferTxnWithSuggestedParamsFromObject,\n type Algodv2,\n type SuggestedParams,\n type TransactionSigner,\n type TransactionWithSigner,\n} from 'algosdk'\nimport type { SwapQuote, FetchQuoteParams, QuoteType } from './types'\n\n/**\n * Context provided to middleware shouldApply hook\n */\nexport interface QuoteContext {\n /** Input asset ID (always bigint for precision and future-proofing) */\n readonly fromASAID: bigint\n /** Output asset ID (always bigint for precision and future-proofing) */\n readonly toASAID: bigint\n /** Amount to swap (always bigint for precision and future-proofing) */\n readonly amount: bigint\n /** Quote type */\n readonly type: QuoteType\n /** Address of the account that will perform the swap (if provided) */\n readonly address?: string\n /** Algodv2 client instance for making additional queries */\n readonly algodClient: Algodv2\n}\n\n/**\n * Context provided to middleware hooks during swap composition\n */\nexport interface SwapContext {\n /** The quote result from newQuote() */\n readonly quote: SwapQuote\n /** The address of the account performing the swap */\n readonly address: string\n /** Algodv2 client instance for making additional queries/transactions */\n readonly algodClient: Algodv2\n /** Suggested transaction parameters from the network */\n readonly suggestedParams: SuggestedParams\n /** Input asset ID (always bigint for precision and future-proofing) */\n readonly fromASAID: bigint\n /** Output asset ID (always bigint for precision and future-proofing) */\n readonly toASAID: bigint\n /** Transaction signer for transactions that need to be signed by the user */\n readonly signer: TransactionSigner\n}\n\n/**\n * Middleware interface for extending Haystack Router swap functionality\n *\n * Middleware allows you to modify quote parameters and inject additional transactions\n * into the atomic swap group. This is useful for assets that require special handling,\n * such as those with transfer restrictions, taxes, or custom smart contract logic.\n *\n * @example\n * ```typescript\n * class CustomAssetMiddleware implements SwapMiddleware {\n * readonly name = 'CustomAsset'\n * readonly version = '1.0.0'\n *\n * async shouldApply(context) {\n * return context.fromASAID === CUSTOM_ASSET_ID || context.toASAID === CUSTOM_ASSET_ID\n * }\n *\n * async adjustQuoteParams(params) {\n * // Reduce maxGroupSize to account for extra transactions\n * return { ...params, maxGroupSize: params.maxGroupSize - 3 }\n * }\n *\n * async beforeSwap(context) {\n * // Return transactions to add before the swap\n * return [unfreezeTransaction]\n * }\n *\n * async afterSwap(context) {\n * // Return transactions to add after the swap\n * return [taxTransaction, refreezeTransaction]\n * }\n * }\n * ```\n */\nexport interface SwapMiddleware {\n /** Unique identifier for the middleware */\n readonly name: string\n\n /** Semantic version of the middleware */\n readonly version: string\n\n /**\n * Determines if this middleware should be applied to the given swap\n *\n * Called during both quote and swap phases. Use this to check if either\n * the input or output asset requires special handling.\n *\n * @param context - Quote context with asset IDs, amount, type, address, and algod client\n * @returns True if middleware should be applied\n *\n * @example\n * ```typescript\n * async shouldApply(context) {\n * // Check if asset is registered in our smart contract\n * const assetInfo = await this.getAssetInfo(context.fromASAID)\n * return assetInfo !== null\n * }\n * ```\n */\n shouldApply(context: QuoteContext): Promise<boolean>\n\n /**\n * Modify quote parameters before fetching the quote\n *\n * **IMPORTANT**: If your middleware adds transactions via `beforeSwap` or `afterSwap`,\n * you MUST reduce `maxGroupSize` accordingly to prevent failures. The Haystack Router API may\n * return routes that use all 16 available transaction slots.\n *\n * Use this to adjust the quote request based on your asset's requirements.\n * Common adjustments include:\n * - Reducing `maxGroupSize` to account for additional transactions (REQUIRED if adding txns)\n * - Adjusting `amount` to account for fees/taxes\n * - Modifying `disabledProtocols` if certain DEXs are incompatible\n *\n * @param params - Original quote parameters\n * @returns Modified quote parameters\n *\n * @example\n * ```typescript\n * async adjustQuoteParams(params) {\n * const [fromTaxed, toTaxed] = await Promise.all([\n * this.isAssetTaxed(params.fromASAID),\n * this.isAssetTaxed(params.toASAID),\n * ])\n *\n * // 3 extra transactions per taxed asset\n * let maxGroupSize = params.maxGroupSize ?? 16\n * if (fromTaxed) maxGroupSize -= 3\n * if (toTaxed) maxGroupSize -= 3\n *\n * // Adjust amount for input tax\n * let amount = params.amount\n * if (fromTaxed) {\n * const taxRate = await this.getTaxRate(params.fromASAID)\n * amount = this.applyTax(amount, taxRate)\n * }\n *\n * return { ...params, maxGroupSize, amount }\n * }\n * ```\n */\n adjustQuoteParams?(params: FetchQuoteParams): Promise<FetchQuoteParams>\n\n /**\n * Add transactions before the swap transactions\n *\n * Called when building the swap transaction group. Transactions are added\n * to the group in the order they appear in the returned array.\n *\n * Transaction order in final group: [beforeSwap] → [swap txns] → [afterSwap]\n *\n * @param context - Swap context with quote, address, and algod client\n * @returns Array of transactions with signers to add before swap\n *\n * @example\n * ```typescript\n * async beforeSwap(context) {\n * const txns: TransactionWithSigner[] = []\n *\n * // Unfreeze user account before swap\n * if (await this.needsUnfreeze(context.fromASAID)) {\n * const unfreezeCall = makeApplicationNoOpTxn(\n * context.address,\n * this.appId,\n * ...,\n * context.suggestedParams\n * )\n *\n * txns.push({\n * txn: unfreezeCall,\n * signer: context.signer, // Use the signer from context\n * })\n * }\n *\n * return txns\n * }\n * ```\n */\n beforeSwap?(context: SwapContext): Promise<TransactionWithSigner[]>\n\n /**\n * Add transactions after the swap transactions\n *\n * Called when building the swap transaction group. Transactions are added\n * to the group in the order they appear in the returned array.\n *\n * Transaction order in final group: [beforeSwap] → [swap txns] → [afterSwap]\n *\n * @param context - Swap context with quote, address, and algod client\n * @returns Array of transactions with signers to add after swap\n *\n * @example\n * ```typescript\n * async afterSwap(context) {\n * const txns: TransactionWithSigner[] = []\n *\n * // Pay tax and refreeze account\n * if (await this.isTaxed(context.fromASAID)) {\n * const taxAmount = await this.calculateTax(context)\n * const taxPayment = makeAssetTransferTxn(\n * context.address,\n * this.taxReceiver,\n * taxAmount,\n * context.fromASAID,\n * context.suggestedParams\n * )\n * const refreezeCall = makeApplicationNoOpTxn(\n * context.address,\n * this.appId,\n * ...,\n * context.suggestedParams\n * )\n *\n * txns.push(\n * { txn: taxPayment, signer: context.signer },\n * { txn: refreezeCall, signer: context.signer },\n * )\n * }\n *\n * return txns\n * }\n * ```\n */\n afterSwap?(context: SwapContext): Promise<TransactionWithSigner[]>\n}\n\n/**\n * Configuration options for AutoOptOutMiddleware\n */\nexport interface AutoOptOutConfig {\n /**\n * Array of asset IDs that should be excluded from automatic opt-out behavior\n * @default []\n */\n readonly excludedAssets?: readonly (number | bigint)[]\n}\n\n/**\n * Middleware that automatically adds an asset opt-out transaction when swapping\n * the full balance of an input asset, leaving it with a zero balance.\n *\n * @example\n * ```typescript\n * import { RouterClient, AutoOptOutMiddleware } from '@txnlab/haystack-router'\n *\n * const autoOptOut = new AutoOptOutMiddleware({\n * excludedAssets: [31566704], // Don't auto-opt-out of USDC\n * })\n *\n * const router = new RouterClient({\n * apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)\n * middleware: [autoOptOut],\n * })\n *\n * // When swapping full balance, opt-out transaction is automatically added\n * const quote = await router.newQuote({\n * address: userAddress,\n * fromASAID: someAssetId,\n * toASAID: 0,\n * amount: fullBalance, // If this equals account's full balance, opt-out is added\n * type: 'fixed-input',\n * })\n * ```\n */\nexport class AutoOptOutMiddleware implements SwapMiddleware {\n readonly name = 'AutoOptOut'\n readonly version = '1.0.0'\n\n private readonly excludedAssets: Set<bigint>\n\n constructor(config: AutoOptOutConfig = {}) {\n this.excludedAssets = new Set(\n (config.excludedAssets ?? []).map((id) => BigInt(id)),\n )\n }\n\n async shouldApply(context: QuoteContext): Promise<boolean> {\n // Only apply for fixed-input swaps\n if (context.type !== 'fixed-input') {\n return false\n }\n\n // Must have an address to check balance\n if (!context.address) {\n return false\n }\n\n // Don't opt-out of ALGO (asset ID 0)\n if (context.fromASAID === 0n) {\n return false\n }\n\n // Check if asset is in excluded list\n if (this.excludedAssets.has(context.fromASAID)) {\n return false\n }\n\n try {\n // Get account info to check current balance\n const accountInfo = await context.algodClient\n .accountInformation(context.address)\n .do()\n\n // Find the asset in account's holdings\n const assetHolding = accountInfo.assets?.find(\n (asset) => asset.assetId === context.fromASAID,\n )\n\n // If asset not found, don't opt-out\n if (!assetHolding) {\n return false\n }\n\n // Check if swap amount equals current balance\n return assetHolding.amount === context.amount\n } catch (error) {\n // If we can't fetch account info, don't apply middleware\n console.warn(\n `AutoOptOutMiddleware: Failed to fetch account info for ${context.address}:`,\n error,\n )\n return false\n }\n }\n\n async adjustQuoteParams(params: FetchQuoteParams): Promise<FetchQuoteParams> {\n // Reduce maxGroupSize by 1 to make room for the opt-out transaction\n const maxGroupSize = (params.maxGroupSize ?? 16) - 1\n\n return {\n ...params,\n maxGroupSize,\n }\n }\n\n async afterSwap(context: SwapContext): Promise<TransactionWithSigner[]> {\n // Create asset opt-out transaction (send 0 amount with closeRemainderTo = sender)\n const optOutTxn = makeAssetTransferTxnWithSuggestedParamsFromObject({\n sender: context.address,\n receiver: context.address,\n amount: 0n,\n assetIndex: context.fromASAID,\n closeRemainderTo: context.address,\n suggestedParams: context.suggestedParams,\n })\n\n return [\n {\n txn: optOutTxn,\n signer: context.signer,\n },\n ]\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/firststage.ts","../src/launch.ts","../src/composer.ts","../src/utils.ts","../src/client.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Supported DEX protocols for swap routing\n */\nexport enum Protocol {\n TinymanV2 = 'TinymanV2',\n Humble = 'Humble',\n Algofi = 'Algofi',\n Algomint = 'Algomint',\n Pact = 'Pact',\n Folks = 'Folks',\n TAlgo = 'TAlgo',\n}\n\n/**\n * Deprecated protocols that are automatically excluded from routing\n *\n * @internal\n */\nexport const DEPRECATED_PROTOCOLS = ['Tinyman'] as const\n\n/** Default Algod node URI for mainnet */\nexport const DEFAULT_ALGOD_URI = 'https://mainnet-api.4160.nodely.dev/'\n\n/** Default Algod node token (empty for public nodes) */\nexport const DEFAULT_ALGOD_TOKEN = ''\n\n/** Default Algod node port */\nexport const DEFAULT_ALGOD_PORT = 443\n\n/** Default Haystack Router API base URL */\nexport const DEFAULT_API_BASE_URL = 'https://hayrouter.txnlab.dev/api'\n\n/** HTTP header that carries the Haystack Router API key */\nexport const API_KEY_HEADER = 'X-API-Key'\n\n/** Default fee in basis points (0.10%) */\nexport const DEFAULT_FEE_BPS = 10\n\n/** Maximum allowed fee in basis points (3.00%) */\nexport const MAX_FEE_BPS = 300\n\n/** Default maximum transaction group size */\nexport const DEFAULT_MAX_GROUP_SIZE = 16\n\n/** Default maximum routing depth (number of hops) */\nexport const DEFAULT_MAX_DEPTH = 4\n\n/** Default auto opt-in setting (automatic asset/app opt-in detection) */\nexport const DEFAULT_AUTO_OPT_IN = false\n\n/** Default debug level (no debug logging) */\nexport const DEFAULT_DEBUG_LEVEL = 'none' as const\n\n/**\n * What a Falcon (post-quantum) signature adds to its transaction's fee, in\n * microAlgos: algod charges a transaction it signs 3 fee units instead of 1\n */\nexport const FALCON_EXTRA_FEE = 2_000n\n\n/** Default number of rounds to wait for transaction confirmation */\nexport const DEFAULT_CONFIRMATION_ROUNDS = 10\n","/**\n * Helpers for First Stage taxed token quotes\n * @internal\n */\n\nimport type { FirstStageQuote, SwapFirstStageQuote } from './types'\n\n/**\n * Normalize a quote's raw First Stage object, coercing amounts to bigint\n */\nexport function toSwapFirstStageQuote(\n firstStage: FirstStageQuote,\n): SwapFirstStageQuote {\n return {\n fromTax: BigInt(firstStage.fromTax),\n toTax: BigInt(firstStage.toTax),\n wrapped: [...(firstStage.wrapped ?? [])],\n }\n}\n","/**\n * Helpers for pre-bonding HayLaunch token quotes\n * @internal\n */\n\nimport type { LaunchQuote, SwapLaunchQuote } from './types'\n\n/**\n * Normalize a quote's raw launch object, coercing amounts to bigint and\n * defaulting the fields the API omits when they don't apply\n */\nexport function toSwapLaunchQuote(launch: LaunchQuote): SwapLaunchQuote {\n return {\n side: launch.side,\n tokenNum: launch.tokenNum,\n bondingTokenId: launch.bondingTokenId,\n bondingSurplus: BigInt(launch.bondingSurplus),\n refund: BigInt(launch.refund ?? 0),\n graduates: launch.graduates ?? false,\n }\n}\n","import {\n AtomicTransactionComposer,\n decodeUint64,\n decodeUnsignedTransaction,\n encodeAddress,\n getApplicationAddress,\n isTransactionWithSigner,\n isValidAddress,\n LogicSigAccount,\n makeApplicationOptInTxnFromObject,\n msgpackRawDecode,\n signLogicSigTransactionObject,\n signTransaction,\n Transaction,\n TransactionType,\n type ABIResult,\n type Algodv2,\n type TransactionSigner,\n type TransactionWithSigner,\n} from 'algosdk'\nimport { DEFAULT_CONFIRMATION_ROUNDS, FALCON_EXTRA_FEE } from './constants'\nimport { toSwapFirstStageQuote } from './firststage'\nimport { toSwapLaunchQuote } from './launch'\nimport { Logger } from './logger'\nimport type { SwapMiddleware, SwapContext, QuoteContext } from './middleware'\nimport type {\n FetchQuoteResponse,\n SwapTransaction,\n Signature,\n SwapQuote,\n MethodCall,\n QuoteType,\n SignerType,\n SwapSummary,\n} from './types'\n\n/** ABI selectors of the HayLaunch bonding-curve buy methods (buyWithLimit, buyWithAlgoWithLimit) */\nconst LAUNCH_BUY_SELECTORS = new Set(['9068a8d9', 'b0e30517'])\n\n/** ABI selector of the HayLaunch bonding-curve sell method (sellWithLimit) */\nconst LAUNCH_SELL_SELECTOR = '028a0647'\n\n/** Prefix of the log that carries an ARC-4 method's return value */\nconst ARC4_RETURN_PREFIX = '151f7c75'\n\n/**\n * Selector of ORDER_ROUTER v4's ARC-28 Swap event: the first 4 bytes of the\n * SHA-512/256 of\n * Swap(uint64,uint64,uint8,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,address,address,address)\n * (assetIn, assetOut, type, amount, amountIn, outDelta, delivered,\n * refundSwapped, refunded, algoReturned, treasuryFee, referrerFee, sender,\n * beneficiary, referrer)\n */\nconst SWAP_EVENT_SELECTOR = '6d221c27'\n\n/** Byte length of a Swap event log: the selector, 11 uint64s, a uint8 and 3 addresses */\nconst SWAP_EVENT_LENGTH = 4 + 11 * 8 + 1 + 3 * 32\n\n/** A swap transaction from the API, decoded */\ninterface DecodedSwapTransaction {\n txn: Transaction\n signature: Signature | false\n}\n\n/**\n * Returns which side of a HayLaunch bonding-curve trade an app call is, or\n * undefined if it isn't a curve buy or sell call\n */\nfunction launchCurveSide(txn: Transaction): 'buy' | 'sell' | undefined {\n const selector = txn.applicationCall?.appArgs[0]\n if (txn.type !== TransactionType.appl || !selector) return undefined\n\n const hex = Buffer.from(selector).toString('hex')\n if (LAUNCH_BUY_SELECTORS.has(hex)) return 'buy'\n if (hex === LAUNCH_SELL_SELECTOR) return 'sell'\n return undefined\n}\n\n/**\n * Reads a uint64 ARC-4 return value from an app call's logs (the last log,\n * prefixed with 151f7c75)\n */\nfunction readUint64Return(logs: Uint8Array[] | undefined): bigint | undefined {\n const log = logs?.[logs.length - 1]\n if (\n !log ||\n log.length !== 12 ||\n Buffer.from(log.subarray(0, 4)).toString('hex') !== ARC4_RETURN_PREFIX\n ) {\n return undefined\n }\n return decodeUint64(log.subarray(4), 'bigint')\n}\n\n/**\n * Reads the output delivery from the Swap event an ORDER_ROUTER v4 route call\n * logs at settlement. v3 logs none, so this returns undefined for it.\n */\nfunction readSwapEvent(\n logs: Uint8Array[] | undefined,\n): { assetOut: bigint; delivered: bigint; beneficiary: string } | undefined {\n const log = logs?.find(\n (l) =>\n l.length === SWAP_EVENT_LENGTH &&\n Buffer.from(l.subarray(0, 4)).toString('hex') === SWAP_EVENT_SELECTOR,\n )\n if (!log) return undefined\n return {\n assetOut: decodeUint64(log.subarray(12, 20), 'bigint'),\n delivered: decodeUint64(log.subarray(45, 53), 'bigint'),\n beneficiary: encodeAddress(log.subarray(125, 157)),\n }\n}\n\n/**\n * A transaction signer function that supports both standard algosdk.TransactionSigner\n * and ARC-1 compliant signers that may return null for unsigned transactions.\n *\n * @param txnGroup - The complete transaction group to sign\n * @param indexesToSign - Array of indexes indicating which transactions need signing\n * @returns Array of signed transactions (may include nulls for ARC-1 compliant wallets)\n */\nexport type SignerFunction = (\n txnGroup: Transaction[],\n indexesToSign: number[],\n) => Promise<(Uint8Array | null)[]>\n\n/**\n * Status of the SwapComposer transaction group lifecycle\n */\nexport enum SwapComposerStatus {\n /** The atomic group is still under construction. */\n BUILDING,\n\n /** The atomic group has been finalized, but not yet signed. */\n BUILT,\n\n /** The atomic group has been finalized and signed, but not yet submitted to the network. */\n SIGNED,\n\n /** The atomic group has been finalized, signed, and submitted to the network. */\n SUBMITTED,\n\n /** The atomic group has been finalized, signed, submitted, and successfully committed to a block. */\n COMMITTED,\n}\n\n/**\n * Configuration for creating a SwapComposer instance\n */\nexport interface SwapComposerConfig {\n /** The quote response from fetchQuote() or newQuote() */\n readonly quote: FetchQuoteResponse | SwapQuote\n /** The swap transactions from fetchSwapTransactions() */\n readonly swapTxns: SwapTransaction[]\n /** Algodv2 client instance */\n readonly algodClient: Algodv2\n /** The address of the account that will sign transactions */\n readonly address: string\n /** Transaction signer function */\n readonly signer: TransactionSigner | SignerFunction\n /** Middleware to apply during swap composition */\n readonly middleware?: SwapMiddleware[]\n /** Optional note field for the user-signed input transaction (payment or asset transfer) */\n readonly note?: Uint8Array\n /**\n * The kind of key `address` signs with (default: the quote's `signerType`,\n * else `'ed25519'`). For `'falcon'`, the composer adds the extra fee of each\n * transaction the account sends to the swap's input transfer as it builds\n * the group.\n */\n readonly signerType?: SignerType\n /**\n * Whether `swapTxns`' fees already include a Falcon signer's extra fees:\n * the `fetchSwapTransactions()` response's `signerType` is `'falcon'`. Then\n * only the transactions the composer adds are counted. Default: false, and\n * every transaction the account sends is.\n */\n readonly signerFeesIncluded?: boolean\n /** Debug logging level (propagated from RouterClient) */\n readonly debugLevel?: 'none' | 'info' | 'debug' | 'trace'\n}\n\n/**\n * Composer for building and executing atomic swap transaction groups\n *\n * The SwapComposer allows you to build complex transaction groups by adding custom\n * transactions before and after swap transactions. It handles pre-signed transactions,\n * automatic app opt-ins, and provides a fluent API for transaction group construction.\n *\n * @example\n * ```typescript\n * const quote = await router.fetchQuote({ ... })\n * const composer = await router.newSwap({ quote, address, slippage, signer })\n *\n * await composer\n * .addTransaction(customTxn)\n * .addSwapTransactions()\n * .execute()\n * ```\n */\nexport class SwapComposer {\n /** The ATC used to compose the group */\n private atc = new AtomicTransactionComposer()\n\n /** The maximum size of an atomic transaction group. */\n static MAX_GROUP_SIZE: number = AtomicTransactionComposer.MAX_GROUP_SIZE\n\n /** Whether the swap transactions have been added to the atomic group. */\n private swapTransactionsAdded = false\n\n private readonly quote: FetchQuoteResponse | SwapQuote\n private readonly requiredAppOptIns: number[]\n private readonly swapTxns: SwapTransaction[]\n private readonly algodClient: Algodv2\n private readonly address: string\n private readonly signer: TransactionSigner | SignerFunction\n private readonly middleware: SwapMiddleware[]\n private readonly note?: Uint8Array\n private readonly signerType: SignerType\n private readonly signerFeesIncluded: boolean\n /** Transactions the account sends whose Falcon fees the composer adds */\n private signerFeeTxns = 0\n /** The account's first transaction in the group, which carries them when there's no input transfer */\n private firstUserTxn?: Transaction\n /** The swap's input transfer, whose fee carries a Falcon signer's extra fees */\n private inputTxn?: Transaction\n private signerFeesAdded = false\n private inputTransactionIndex?: number\n private outputTransactionIndex?: number\n private curveTransactionIndex?: number\n\n /** Summary data built incrementally during swap composition */\n private summaryData?: {\n inputAssetId: bigint\n outputAssetId: bigint\n inputAmount: bigint\n inputTxnId?: string\n outputTxnId?: string\n inputSender: string\n outputSender?: string\n outputAmount?: bigint\n /** Bonding-curve leg of a pre-bonding HayLaunch swap */\n launch?: {\n side: 'buy' | 'sell'\n tokenNum: bigint\n bondingTokenId: bigint\n appAddress: string\n curveTxnId?: string\n bondingAmount?: bigint\n refund: bigint\n }\n }\n\n /**\n * Create a new SwapComposer instance\n *\n * Note: Most developers should use RouterClient.newSwap() instead of constructing\n * this directly, as the factory method handles fetching swap transactions automatically.\n *\n * @param config - Configuration for the composer\n * @param config.quote - The quote response from fetchQuote()\n * @param config.swapTxns - The swap transactions from fetchSwapTransactions()\n * @param config.algodClient - Algodv2 client instance\n * @param config.address - The address of the account that will sign transactions\n * @param config.signer - Transaction signer function\n * @param config.middleware - Middleware to apply during swap composition\n */\n constructor(config: SwapComposerConfig) {\n // Set logger level from config\n if (config.debugLevel) {\n Logger.setLevel(config.debugLevel)\n }\n\n // Validate required parameters\n if (!config.quote) {\n throw new Error('Quote is required')\n }\n if (!config.swapTxns) {\n throw new Error('Swap transactions are required')\n }\n if (config.swapTxns.length === 0) {\n throw new Error('Swap transactions array cannot be empty')\n }\n if (!config.algodClient) {\n throw new Error('Algodv2 client instance is required')\n }\n if (!config.signer) {\n throw new Error('Signer is required')\n }\n\n this.quote = config.quote\n this.requiredAppOptIns = config.quote.requiredAppOptIns\n this.swapTxns = config.swapTxns\n this.algodClient = config.algodClient\n this.address = this.validateAddress(config.address)\n this.signer = config.signer\n this.middleware = config.middleware ?? []\n this.note = config.note\n this.signerType = config.signerType ?? config.quote.signerType ?? 'ed25519'\n this.signerFeesIncluded = config.signerFeesIncluded ?? false\n }\n\n /**\n * Get the status of this composer's transaction group\n *\n * @returns The current status of the transaction group\n */\n getStatus(): SwapComposerStatus {\n return this.atc.getStatus() as unknown as SwapComposerStatus\n }\n\n /**\n * Get the number of transactions currently in this atomic group\n *\n * @returns The number of transactions in the group\n */\n count(): number {\n return this.atc.count()\n }\n\n /**\n * Add a transaction to the atomic group\n *\n * Transactions are added in the order methods are called. For example:\n * ```typescript\n * composer\n * .addTransaction(txn1) // Added first\n * .addSwapTransactions() // Added second\n * .addTransaction(txn2) // Added third\n * ```\n *\n * @param transaction - The transaction to add\n * @returns This composer instance for chaining\n * @throws Error if the composer is not in the BUILDING status\n * @throws Error if the maximum group size is exceeded\n * @throws Error if attempting to compose with a non-composable swap (Tinyman v1)\n */\n addTransaction(transaction: Transaction, signer = this.defaultSigner): this {\n // Check if the quote uses Tinyman v1 (non-composable protocol)\n const usesTinymanV1 =\n this.quote.flattenedRoute?.['Tinyman'] !== undefined &&\n this.quote.flattenedRoute['Tinyman'] > 0\n\n if (usesTinymanV1) {\n throw new Error(\n 'Cannot add transactions to a swap group that uses Tinyman v1. ' +\n 'Tinyman v1 produces non-composable transaction groups. ' +\n 'Asset opt-ins, opt-outs, and other transactions must be sent in separate groups. ' +\n 'If you need to support Tinyman v1 swaps, you must handle multi-group coordination manually.',\n )\n }\n\n this.atc.addTransaction({ txn: transaction, signer })\n this.trackSignerFee(transaction.sender, transaction)\n return this\n }\n\n /**\n * Add a method call to the atomic group\n *\n * The `signer` property in the `methodCall` parameter is optional. If not provided,\n * the signer will default to the one passed as the second parameter, or the\n * configured signer from the constructor if no second parameter is provided.\n *\n * @param methodCall - The method call to add\n * @param signer - The signer to use for the method call (defaults to constructor signer)\n * @returns This composer instance for chaining\n * @throws Error if attempting to compose with a non-composable swap (Tinyman v1)\n */\n addMethodCall(methodCall: MethodCall, signer = this.defaultSigner): this {\n // Check if the quote uses Tinyman v1 (non-composable protocol)\n const usesTinymanV1 =\n this.quote.flattenedRoute?.['Tinyman'] !== undefined &&\n this.quote.flattenedRoute['Tinyman'] > 0\n\n if (usesTinymanV1) {\n throw new Error(\n 'Cannot add method calls to a swap group that uses Tinyman v1. ' +\n 'Tinyman v1 produces non-composable transaction groups. ' +\n 'Asset opt-ins, opt-outs, and other transactions must be sent in separate groups. ' +\n 'If you need to support Tinyman v1 swaps, you must handle multi-group coordination manually.',\n )\n }\n\n this.atc.addMethodCall({\n ...methodCall,\n signer: methodCall.signer ?? signer,\n })\n // Its transaction arguments go before it. The ATC builds the call itself,\n // so it can't carry the fees\n for (const arg of methodCall.methodArgs ?? []) {\n if (isTransactionWithSigner(arg))\n this.trackSignerFee(arg.txn.sender, arg.txn)\n }\n this.trackSignerFee(methodCall.sender)\n return this\n }\n\n /**\n * Add swap transactions to the atomic group\n *\n * This method automatically processes required app opt-ins, executes middleware hooks,\n * and adds all swap transactions from the quote. Can only be called once per composer instance.\n *\n * Middleware hooks are executed in this order:\n * 1. beforeSwap() - Add transactions before swap transactions\n * 2. Swap transactions (from API)\n * 3. afterSwap() - Add transactions after swap transactions\n *\n * @returns This composer instance for chaining\n * @throws Error if the swap transactions have already been added\n * @throws Error if the composer is not in the BUILDING status\n * @throws Error if the maximum group size is exceeded\n */\n async addSwapTransactions(): Promise<this> {\n if (this.swapTransactionsAdded) {\n throw new Error('Swap transactions have already been added')\n }\n\n if (this.getStatus() !== SwapComposerStatus.BUILDING) {\n throw new Error(\n 'Cannot add swap transactions when composer status is not BUILDING',\n )\n }\n\n // Execute beforeSwap middleware hooks\n const beforeTxns = await this.executeMiddlewareHooks('beforeSwap')\n\n // Check total length before adding beforeSwap transactions\n if (this.atc.count() + beforeTxns.length > SwapComposer.MAX_GROUP_SIZE) {\n throw new Error(\n `Adding beforeSwap transactions exceeds the maximum atomic group size of ${SwapComposer.MAX_GROUP_SIZE}`,\n )\n }\n\n for (const txnWithSigner of beforeTxns) {\n this.atc.addTransaction(txnWithSigner)\n this.trackSignerFee(txnWithSigner.txn.sender, txnWithSigner.txn)\n }\n\n // Process swap transactions and execute afterSwap hooks\n const {\n txns: processedTxns,\n apiTxns,\n inputTxn,\n inputTxnRelativeIndex,\n outputTxnRelativeIndex,\n curveTxnRelativeIndex,\n } = await this.processSwapTransactions()\n\n const afterTxns = await this.executeMiddlewareHooks('afterSwap')\n\n // Check total length before adding swap and afterSwap transactions\n const totalLength =\n this.atc.count() + processedTxns.length + afterTxns.length\n\n if (totalLength > SwapComposer.MAX_GROUP_SIZE) {\n throw new Error(\n `Adding swap transactions exceeds the maximum atomic group size of ${SwapComposer.MAX_GROUP_SIZE}`,\n )\n }\n\n // Calculate the absolute index of the user-signed input transaction\n // This is: current ATC count (user txns + beforeSwap) + relative index within processed txns\n if (inputTxnRelativeIndex !== undefined) {\n this.inputTransactionIndex = this.atc.count() + inputTxnRelativeIndex\n }\n\n // Calculate the absolute index of the output transaction (last app call in swap)\n if (outputTxnRelativeIndex !== undefined) {\n this.outputTransactionIndex = this.atc.count() + outputTxnRelativeIndex\n }\n\n // Calculate the absolute index of a launch swap's curve buy or sell call\n if (curveTxnRelativeIndex !== undefined) {\n this.curveTransactionIndex = this.atc.count() + curveTxnRelativeIndex\n }\n\n // Add swap transactions. The router's own (apiTxns) already include a\n // Falcon signer's extra fees when execute said so\n for (const txnWithSigner of processedTxns) {\n this.atc.addTransaction(txnWithSigner)\n this.trackSignerFee(\n txnWithSigner.txn.sender,\n txnWithSigner.txn,\n this.signerFeesIncluded && apiTxns.has(txnWithSigner.txn),\n )\n }\n // Only now in the group, so it can carry the fees\n this.inputTxn = inputTxn\n\n // Add afterSwap middleware transactions\n for (const txnWithSigner of afterTxns) {\n this.atc.addTransaction(txnWithSigner)\n this.trackSignerFee(txnWithSigner.txn.sender, txnWithSigner.txn)\n }\n\n this.swapTransactionsAdded = true\n\n return this\n }\n\n /**\n * Finalize the transaction group by assigning group IDs\n *\n * This method builds the atomic transaction group, assigning group IDs to all transactions\n * if there is more than one transaction. After calling this method, the composer's status\n * will be at least BUILT.\n *\n * @returns Array of transactions with their associated signers\n *\n * @throws Error if the group contains 0 transactions\n *\n * @example\n * ```typescript\n * const composer = await router.newSwap({ quote, address, slippage, signer })\n * composer.addTransaction(customTxn)\n *\n * // Build the group to inspect transactions before signing\n * const txnsWithSigners = composer.buildGroup()\n * console.log('Group ID:', txnsWithSigners[0].txn.group)\n * console.log('Group length:', txnsWithSigners.length)\n * console.log('Status:', composer.getStatus()) // BUILT\n * ```\n */\n buildGroup(): TransactionWithSigner[] {\n this.addSignerFees()\n return this.atc.buildGroup()\n }\n\n /**\n * Sign the transaction group\n *\n * Automatically adds swap transactions if not already added, builds the atomic group,\n * and signs all transactions using the configured signer.\n *\n * @returns A promise that resolves to an array of signed transaction blobs\n *\n * @example\n * ```typescript\n * const signedTxns = await composer.sign()\n * ```\n */\n async sign(): Promise<Uint8Array[]> {\n if (this.getStatus() >= SwapComposerStatus.SIGNED) {\n return this.atc.gatherSignatures()\n }\n\n // Auto-add swap transactions if needed\n if (!this.swapTransactionsAdded) {\n await this.addSwapTransactions()\n }\n\n this.addSignerFees()\n return await this.atc.gatherSignatures()\n }\n\n /**\n * Submit the signed transactions to the network\n *\n * This method signs the transaction group (if not already signed) and submits\n * it to the Algorand network. Does not wait for confirmation.\n *\n * @returns The transaction IDs\n * @throws Error if the transaction group has already been submitted\n *\n * @example\n * ```typescript\n * const txIds = await composer.submit()\n * console.log('Submitted transactions:', txIds)\n * ```\n */\n async submit(): Promise<string[]> {\n // Auto-add swap transactions if needed (maintains backward compatibility)\n if (!this.swapTransactionsAdded) {\n await this.addSwapTransactions()\n }\n\n this.addSignerFees()\n return await this.atc.submit(this.algodClient)\n }\n\n /**\n * Execute the swap\n *\n * Signs the transaction group, submits it to the network, and waits for confirmation.\n * This is the primary method for executing swaps and combines sign(), submit(), and\n * waitForConfirmation() into a single call.\n *\n * @param waitRounds - The number of rounds to wait for confirmation (default: 10)\n * @returns Object containing the confirmed round and transaction IDs\n * @throws Error if the transaction group has already been committed\n *\n * @example\n * ```typescript\n * const result = await composer.execute()\n * console.log(`Confirmed in round ${result.confirmedRound}`)\n * console.log('Transaction IDs:', result.txIds)\n * ```\n */\n async execute(waitRounds: number = DEFAULT_CONFIRMATION_ROUNDS): Promise<{\n confirmedRound: bigint\n txIds: string[]\n methodResults: ABIResult[]\n }> {\n try {\n // Auto-add swap transactions if needed (maintains backward compatibility)\n if (!this.swapTransactionsAdded) {\n await this.addSwapTransactions()\n }\n\n this.addSignerFees()\n const { txIDs, ...result } = await this.atc.execute(\n this.algodClient,\n waitRounds,\n )\n\n // Store transaction IDs in summaryData\n if (this.summaryData) {\n if (this.inputTransactionIndex !== undefined) {\n this.summaryData.inputTxnId = txIDs[this.inputTransactionIndex]\n }\n if (this.outputTransactionIndex !== undefined) {\n this.summaryData.outputTxnId = txIDs[this.outputTransactionIndex]\n }\n if (\n this.summaryData.launch &&\n this.curveTransactionIndex !== undefined\n ) {\n this.summaryData.launch.curveTxnId = txIDs[this.curveTransactionIndex]\n }\n }\n\n // Extract actual output amount from confirmed transaction\n await this.extractActualOutputAmount()\n\n return {\n ...result,\n txIds: txIDs,\n }\n } catch (error) {\n // Log comprehensive failure context when swap execution fails\n const { logSwapExecutionFailure } = await import('./debug')\n logSwapExecutionFailure(\n {\n quote: this.quote,\n address: this.address,\n slippage: 0, // Not stored in composer, would need to be passed from client\n transactionCount: this.count(),\n middlewareCount: this.middleware.length,\n groupSize: this.count(),\n },\n error,\n )\n\n // Re-throw the error\n throw error\n }\n }\n\n /**\n * Get the transaction ID of the user-signed input transaction\n *\n * Returns the transaction ID of the payment or asset transfer transaction\n * that sends the input asset. This is the transaction whose note field can\n * be customized via the `note` config option.\n *\n * The transaction ID is only available after the group has been built\n * (after calling buildGroup(), sign(), submit(), or execute()).\n *\n * @returns The transaction ID, or undefined if the group hasn't been built yet\n * or if the input transaction index couldn't be determined\n *\n * @example\n * ```typescript\n * const swap = await router.newSwap({\n * quote,\n * address,\n * slippage,\n * signer,\n * note: new TextEncoder().encode('tracking-123')\n * })\n *\n * await swap.execute()\n * const inputTxId = swap.getInputTransactionId()\n * console.log('Input transaction ID:', inputTxId)\n * ```\n */\n getInputTransactionId(): string | undefined {\n if (this.getStatus() < SwapComposerStatus.BUILT) {\n return undefined\n }\n if (this.inputTransactionIndex === undefined) {\n return undefined\n }\n const txns = this.atc.buildGroup()\n const txn = txns[this.inputTransactionIndex]?.txn\n return txn?.txID()\n }\n\n /**\n * Get a summary of the swap amounts and fees\n *\n * Returns the exact input and output amounts, total transaction fees,\n * and transaction IDs. This is useful for displaying a complete summary\n * after a swap has been executed.\n *\n * Only available after calling execute() - returns undefined before execution.\n *\n * @returns SwapSummary containing exact amounts and fees, or undefined if not yet executed\n *\n * @example\n * ```typescript\n * const swap = await router.newSwap({ quote, address, slippage, signer })\n * const result = await swap.execute()\n *\n * const summary = swap.getSummary()\n * if (summary) {\n * console.log('Sent:', summary.inputAmount, 'Received:', summary.outputAmount)\n * console.log('Total fees:', summary.totalFees, 'microAlgos')\n * }\n * ```\n */\n getSummary(): SwapSummary | undefined {\n // Only return summary after execution when we have all the data\n if (\n !this.summaryData ||\n this.summaryData.outputAmount === undefined ||\n this.summaryData.inputTxnId === undefined ||\n this.summaryData.outputTxnId === undefined ||\n this.summaryData.outputSender === undefined\n ) {\n return undefined\n }\n\n const txns = this.atc.buildGroup()\n const totalFees = txns.reduce((sum, tws) => sum + tws.txn.fee, 0n)\n\n const launch = this.summaryData.launch\n const curveTxnId = launch?.curveTxnId\n\n return {\n inputAssetId: this.summaryData.inputAssetId,\n outputAssetId: this.summaryData.outputAssetId,\n inputAmount: this.summaryData.inputAmount,\n outputAmount: this.summaryData.outputAmount,\n type: this.quote.type as QuoteType,\n totalFees,\n transactionCount: txns.length,\n // On a launch sell, the tokens sold leave the user in the curve sell call\n inputTxnId:\n launch?.side === 'sell' && curveTxnId\n ? curveTxnId\n : this.summaryData.inputTxnId,\n outputTxnId: this.summaryData.outputTxnId,\n inputSender: this.summaryData.inputSender,\n outputSender: this.summaryData.outputSender,\n ...(launch &&\n curveTxnId && {\n launch: {\n side: launch.side,\n tokenNum: launch.tokenNum,\n curveTxnId,\n bondingAmount: launch.bondingAmount,\n refund: launch.refund,\n },\n }),\n }\n }\n\n /**\n * Counts a transaction from sender toward a Falcon signer's extra fees\n * (addSignerFees) when the account sends it, unless the router's fees\n * already include it (included). Every way of adding a transaction calls\n * it; txn is the transaction when the composer holds it (not a method call\n * the ATC builds), which can then carry the fees.\n */\n private trackSignerFee(\n sender: { toString(): string },\n txn?: Transaction,\n included = false,\n ): void {\n if (sender.toString() !== this.address) return\n if (txn) this.firstUserTxn ??= txn\n if (!included) this.signerFeeTxns++\n }\n\n /**\n * Adds a Falcon signer's extra fees before the group is built: a Falcon\n * signature costs 3 fee units where an ed25519 one costs 1, and fees pool\n * across the group, so the fee of the swap's input transfer (which already\n * carries the router's fee budget) rises by FALCON_EXTRA_FEE for each\n * transaction the account sends that the router's fees don't include. The\n * account pays it, like every fee of the group. Runs once, while the group\n * is still being built.\n */\n private addSignerFees(): void {\n if (\n this.signerType !== 'falcon' ||\n this.signerFeesAdded ||\n this.getStatus() !== SwapComposerStatus.BUILDING\n ) {\n return\n }\n if (this.signerFeeTxns > 0) {\n const carrier = this.inputTxn ?? this.firstUserTxn\n if (!carrier) {\n throw new Error(\n `No transaction from ${this.address} in the group to carry its Falcon signature fees`,\n )\n }\n carrier.fee += FALCON_EXTRA_FEE * BigInt(this.signerFeeTxns)\n Logger.debug('Added Falcon signature fees', {\n transactions: this.signerFeeTxns,\n fee: carrier.fee,\n })\n }\n this.signerFeesAdded = true\n }\n\n /**\n * Validates an Algorand address\n */\n private validateAddress(address: string): string {\n if (!isValidAddress(address)) {\n throw new Error(`Invalid Algorand address: ${address}`)\n }\n return address\n }\n\n /**\n * Processes app opt-ins and decodes swap transactions from API response\n *\n * Also initializes summaryData with input transaction details and output transaction ID\n */\n private async processSwapTransactions(): Promise<{\n txns: TransactionWithSigner[]\n /** The API's transactions among txns, the app opt-ins left out */\n apiTxns: Set<Transaction>\n /** The swap's input transfer among txns */\n inputTxn?: Transaction\n inputTxnRelativeIndex?: number\n outputTxnRelativeIndex?: number\n curveTxnRelativeIndex?: number\n }> {\n const appOptIns = await this.processRequiredAppOptIns()\n\n const decoded: DecodedSwapTransaction[] = this.swapTxns.map(\n (swapTxn, i) => {\n try {\n const txn = decodeUnsignedTransaction(\n Buffer.from(swapTxn.data, 'base64'),\n )\n delete txn.group\n return { txn, signature: swapTxn.signature }\n } catch (error) {\n throw new Error(\n `Failed to process swap transaction at index ${i}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n )\n }\n },\n )\n\n const inputIndex = this.findInputTransactionIndex(decoded)\n const inputTxn =\n inputIndex !== undefined ? decoded[inputIndex]?.txn : undefined\n\n // Set the note on the input transaction only (using type assertion since\n // note is readonly but safe to modify before signing). Other user-signed\n // transactions can differ only by their notes, e.g. HayLaunch gas() calls.\n if (inputTxn && this.note !== undefined) {\n ;(inputTxn as { note: Uint8Array }).note = this.note\n }\n\n // Pre-signed transactions use the custom Haystack Router signer; the rest\n // are signed by the user\n const swapTxns: TransactionWithSigner[] = decoded.map(\n ({ txn, signature }) => ({\n txn,\n signer:\n signature === false\n ? this.defaultSigner\n : this.createSwapSigner(signature),\n }),\n )\n\n // Initialize summary data with input transaction details\n if (inputTxn) {\n // Extract input amount from payment or asset transfer\n const paymentAmount = inputTxn.payment?.amount\n const assetTransferAmount = inputTxn.assetTransfer?.amount\n const inputAmount = paymentAmount ?? assetTransferAmount ?? 0n\n\n this.summaryData = {\n inputAssetId: BigInt(this.quote.fromASAID),\n outputAssetId: BigInt(this.quote.toASAID),\n inputAmount,\n inputSender: this.address,\n }\n }\n\n const curveIndex = this.initLaunchSummary(decoded)\n\n // The router's last app call (v4's route, v3's finalize) holds the inner\n // transaction sending the output asset to the user. On a launch buy the\n // output comes from the curve buy call instead, wherever it is.\n // We'll get its ID after buildGroup() is called\n // Store the relative index so we can get the ID later\n const outputIndex =\n this.summaryData?.launch?.side === 'buy' && curveIndex !== undefined\n ? curveIndex\n : this.findOutputTransactionIndex(decoded)\n const outputTxnRelativeIndex =\n outputIndex >= 0 ? appOptIns.length + outputIndex : undefined\n\n return {\n txns: [...appOptIns, ...swapTxns],\n apiTxns: new Set(decoded.map(({ txn }) => txn)),\n inputTxn,\n inputTxnRelativeIndex:\n inputIndex !== undefined ? appOptIns.length + inputIndex : undefined,\n outputTxnRelativeIndex,\n curveTxnRelativeIndex:\n curveIndex !== undefined ? appOptIns.length + curveIndex : undefined,\n }\n }\n\n /**\n * Finds the swap's input transaction among the API's transactions: the\n * first user-signed asset transfer to the router app, else the first\n * user-signed payment to it. The router's address comes from its pre-signed\n * app calls. Falls back to the last user-signed transaction for groups\n * without a router app call.\n *\n * The user may also pay the router ALGO for its fees and opt-ins, after the\n * input on ORDER_ROUTER v3 and before an ASA input on v4. The rule relies\n * on two invariants of the groups the router API builds: an ASA input is\n * the only asset transfer the user makes to the router, and no payment to\n * the router precedes an ALGO input.\n */\n private findInputTransactionIndex(\n txns: DecodedSwapTransaction[],\n ): number | undefined {\n const routerAppId = txns.find(\n ({ txn, signature }) =>\n signature !== false && txn.type === TransactionType.appl,\n )?.txn.applicationCall?.appIndex\n\n if (routerAppId !== undefined) {\n const routerAddress = getApplicationAddress(routerAppId).toString()\n const isUserTxnToRouter = ({ txn, signature }: DecodedSwapTransaction) =>\n signature === false &&\n (txn.payment?.receiver ?? txn.assetTransfer?.receiver)?.toString() ===\n routerAddress\n for (const type of [TransactionType.axfer, TransactionType.pay]) {\n const index = txns.findIndex(\n (t) => t.txn.type === type && isUserTxnToRouter(t),\n )\n if (index !== -1) return index\n }\n }\n\n for (let i = txns.length - 1; i >= 0; i--) {\n if (txns[i]?.signature === false) return i\n }\n return undefined\n }\n\n /**\n * Finds the router's last app call among the API's transactions: v4's\n * route call or v3's finalize, which pays the output. The router's calls\n * are the pre-signed ones. A First Stage swap ends with the user's own tax\n * payment and First Stage calls after it, so the last transaction isn't\n * always the router's. Falls back to the last transaction.\n */\n private findOutputTransactionIndex(txns: DecodedSwapTransaction[]): number {\n for (let i = txns.length - 1; i >= 0; i--) {\n const t = txns[i]\n if (t?.signature !== false && t?.txn.type === TransactionType.appl) {\n return i\n }\n }\n return txns.length - 1\n }\n\n /**\n * For a launch quote, finds the user-signed curve buy or sell call and\n * records the curve leg in summaryData. On a sell, the input amount becomes\n * the launch tokens sold.\n *\n * @returns The curve call's index within the swap transactions, if found\n */\n private initLaunchSummary(\n txns: DecodedSwapTransaction[],\n ): number | undefined {\n const side = this.quote.launch?.side\n if (!side || !this.summaryData) return undefined\n\n const curveIndex = txns.findIndex(\n ({ txn, signature }) =>\n signature === false && launchCurveSide(txn) === side,\n )\n const curveCall = txns[curveIndex]?.txn.applicationCall\n // buy: (tokenNum, minTokensOut); sell: (tokenNum, tokensToSell, minBondingOut)\n const tokenNumArg = curveCall?.appArgs[1]\n if (!curveCall || !tokenNumArg) return undefined\n\n const appAddress = getApplicationAddress(curveCall.appIndex).toString()\n\n let bondingAmount: bigint | undefined\n if (side === 'buy') {\n // ARC-4 transaction arguments directly precede the app call, and the\n // bonding payment to the curve is the buy method's last one\n const payment = txns[curveIndex - 1]?.txn\n const receiver =\n payment?.payment?.receiver ?? payment?.assetTransfer?.receiver\n if (receiver?.toString() === appAddress) {\n bondingAmount =\n payment?.payment?.amount ?? payment?.assetTransfer?.amount\n }\n } else {\n const tokensToSell = curveCall.appArgs[2]\n if (tokensToSell) {\n this.summaryData.inputAmount = decodeUint64(tokensToSell, 'bigint')\n }\n }\n\n this.summaryData.launch = {\n side,\n tokenNum: decodeUint64(tokenNumArg, 'bigint'),\n bondingTokenId: BigInt(this.quote.launch?.bondingTokenId ?? 0),\n appAddress,\n bondingAmount,\n refund: 0n,\n }\n\n return curveIndex\n }\n\n /**\n * Creates opt-in transactions for apps the user hasn't opted into yet\n */\n private async processRequiredAppOptIns(): Promise<TransactionWithSigner[]> {\n // Fetch account information\n const accountInfo = await this.algodClient\n .accountInformation(this.address)\n .do()\n\n // Check app opt-ins\n const userApps =\n accountInfo?.appsLocalState?.map((app) => Number(app.id)) || []\n const appsToOptIn = this.requiredAppOptIns.filter(\n (appId) => !userApps.includes(appId),\n )\n\n if (appsToOptIn.length === 0) return []\n\n const suggestedParams = await this.algodClient.getTransactionParams().do()\n\n return appsToOptIn.map((appId) => ({\n txn: makeApplicationOptInTxnFromObject({\n sender: this.address,\n appIndex: appId,\n suggestedParams,\n }),\n signer: this.defaultSigner,\n }))\n }\n\n /**\n * The default signer function that uses the configured signer\n */\n private defaultSigner: TransactionSigner = async (\n txnGroup: Transaction[],\n indexesToSign: number[],\n ) => {\n const result = await this.signer(txnGroup, indexesToSign)\n return result.filter((txn): txn is Uint8Array => txn !== null)\n }\n\n /**\n * Creates a TransactionSigner function for Haystack Router pre-signed transactions\n */\n private createSwapSigner(signature: Signature): TransactionSigner {\n return async (\n txnGroup: Transaction[],\n indexesToSign: number[],\n ): Promise<Uint8Array[]> => {\n return indexesToSign.map((i) => {\n const txn = txnGroup[i]\n if (!txn) throw new Error(`Transaction at index ${i} not found`)\n return this.signSwapTransaction(txn, signature)\n })\n }\n }\n\n /**\n * Re-signs a Haystack Router transaction using the provided logic signature or secret key\n */\n private signSwapTransaction(\n transaction: Transaction,\n signature: Signature,\n ): Uint8Array {\n try {\n if (signature.type === 'logic_signature') {\n // Decode the signature value to extract the logic signature\n const valueArray = signature.value as Record<string, number>\n const valueBytes = new Uint8Array(Object.values(valueArray))\n const decoded = msgpackRawDecode(valueBytes) as {\n lsig?: { l: Uint8Array; arg?: Uint8Array[] }\n }\n\n if (!decoded.lsig) {\n throw new Error('Logic signature structure missing lsig field')\n }\n\n const lsig = decoded.lsig\n const logicSigAccount = new LogicSigAccount(lsig.l, lsig.arg)\n\n const signedTxn = signLogicSigTransactionObject(\n transaction,\n logicSigAccount,\n )\n return signedTxn.blob\n } else if (signature.type === 'secret_key') {\n // Convert signature.value (Record<string, number>) to Uint8Array\n const valueArray = signature.value as Record<string, number>\n const secretKey = new Uint8Array(Object.values(valueArray))\n const signedTxn = signTransaction(transaction, secretKey)\n return signedTxn.blob\n } else {\n throw new Error(`Unsupported signature type: ${signature.type}`)\n }\n } catch (error) {\n throw new Error(\n `Failed to re-sign transaction: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n )\n }\n }\n\n /**\n * Execute middleware hooks (beforeSwap or afterSwap)\n */\n private async executeMiddlewareHooks(\n hookName: 'beforeSwap' | 'afterSwap',\n ): Promise<TransactionWithSigner[]> {\n const allTxns: TransactionWithSigner[] = []\n\n // Convert to SwapQuote if needed, with the signer type the composer\n // composes for (a newSwap() override included)\n const quote: SwapQuote =\n 'createdAt' in this.quote\n ? { ...this.quote, signerType: this.signerType }\n : {\n ...this.quote,\n quote: this.quote.quote === '' ? 0n : BigInt(this.quote.quote),\n amount: 0n, // Not available in FetchQuoteResponse\n launch: this.quote.launch && toSwapLaunchQuote(this.quote.launch),\n firstStage:\n this.quote.firstStage &&\n toSwapFirstStageQuote(this.quote.firstStage),\n signerType: this.signerType,\n createdAt: Date.now(),\n }\n\n // Create quote context for middleware shouldApply checks\n const quoteContext: QuoteContext = {\n fromASAID: BigInt(this.quote.fromASAID),\n toASAID: BigInt(this.quote.toASAID),\n amount: quote.amount,\n type: this.quote.type as QuoteType,\n address: quote.address,\n algodClient: this.algodClient,\n }\n\n for (const mw of this.middleware) {\n const shouldApply = await mw.shouldApply(quoteContext)\n\n if (!shouldApply || !mw[hookName]) {\n continue\n }\n\n // Create swap context for middleware hooks (only when needed)\n const suggestedParams = await this.algodClient.getTransactionParams().do()\n const swapContext: SwapContext = {\n quote,\n address: this.address,\n algodClient: this.algodClient,\n suggestedParams,\n fromASAID: BigInt(this.quote.fromASAID),\n toASAID: BigInt(this.quote.toASAID),\n signer: this.defaultSigner,\n }\n\n const txns = await mw[hookName](swapContext)\n\n allTxns.push(...txns)\n }\n\n return allTxns\n }\n\n /**\n * Extract the actual output amount from the confirmed output transaction\n *\n * Analyzes only the output transaction (last app call in the swap). On\n * ORDER_ROUTER v4 that is the route call, whose Swap event gives the amount\n * delivered. v3 logs no event, so the output is its first inner transaction\n * that transfers the output asset to the user (it makes only one). On a\n * launch buy the output is a balance on the HayLaunch app, so it is read\n * from the curve buy call's return value instead.\n */\n private async extractActualOutputAmount(): Promise<void> {\n if (!this.summaryData?.outputTxnId) {\n return\n }\n\n const outputAssetId = this.summaryData.outputAssetId\n const userAddress = this.address\n const launch = this.summaryData.launch\n\n try {\n const pendingInfo = await this.algodClient\n .pendingTransactionInformation(this.summaryData.outputTxnId)\n .do()\n\n if (launch?.side === 'buy') {\n // The buy call returns the tokens credited to the buyer's balance\n const tokensCredited = readUint64Return(pendingInfo.logs)\n if (tokensCredited !== undefined) {\n this.summaryData.outputAmount = tokensCredited\n this.summaryData.outputSender = launch.appAddress\n }\n\n // A buy that completes bonding refunds the unused bonding asset\n for (const innerTxn of pendingInfo.innerTxns ?? []) {\n const txn = innerTxn.txn.txn\n if (launch.bondingTokenId === 0n) {\n if (txn.payment?.receiver.toString() === userAddress) {\n launch.refund += txn.payment.amount\n }\n } else if (\n txn.assetTransfer?.receiver.toString() === userAddress &&\n txn.assetTransfer.assetIndex === launch.bondingTokenId\n ) {\n launch.refund += txn.assetTransfer.amount\n }\n }\n return\n }\n\n const swapEvent = readSwapEvent(pendingInfo.logs)\n const routeCall = swapEvent && pendingInfo.txn.txn.applicationCall\n if (swapEvent && routeCall) {\n // v4: every payout goes to the event's beneficiary\n if (\n swapEvent.assetOut === outputAssetId &&\n swapEvent.beneficiary === userAddress\n ) {\n this.summaryData.outputAmount = swapEvent.delivered\n this.summaryData.outputSender = getApplicationAddress(\n routeCall.appIndex,\n ).toString()\n }\n } else {\n for (const innerTxn of pendingInfo.innerTxns ?? []) {\n const txn = innerTxn.txn.txn\n const payment = txn.payment\n const assetTransfer = txn.assetTransfer\n\n // Get receiver address based on transaction type\n const receiver = payment?.receiver ?? assetTransfer?.receiver\n if (!receiver) continue\n\n // Check if this transfer is to the user\n if (receiver.toString() !== userAddress) continue\n\n // Get sender address for outputSender\n const senderAddress = txn.sender.toString()\n\n if (outputAssetId === 0n && payment?.amount != null) {\n // ALGO output to user\n this.summaryData.outputAmount = payment.amount\n this.summaryData.outputSender = senderAddress\n break\n } else if (outputAssetId !== 0n && assetTransfer) {\n // ASA output - verify it's the right asset\n const assetId = assetTransfer.assetIndex\n if (assetId === outputAssetId && assetTransfer.amount != null) {\n this.summaryData.outputAmount = assetTransfer.amount\n this.summaryData.outputSender = senderAddress\n break\n }\n }\n }\n }\n } catch {\n // Silently fail - outputAmount will remain undefined\n }\n\n // On a launch sell, the curve call returns the bonding asset paid out after\n // fees. It's looked up separately so a failure here keeps the output above.\n if (launch?.side === 'sell' && launch.curveTxnId) {\n try {\n const curveInfo = await this.algodClient\n .pendingTransactionInformation(launch.curveTxnId)\n .do()\n launch.bondingAmount = readUint64Return(curveInfo.logs)\n } catch {\n // Silently fail - bondingAmount will remain undefined\n }\n }\n }\n}\n","/**\n * HTTP error with status code and response data\n */\nexport class HTTPError extends Error {\n constructor(\n public status: number,\n public statusText: string,\n public data: unknown,\n ) {\n super(`HTTP ${status} ${statusText}`)\n this.name = 'HTTPError'\n }\n}\n\n/**\n * Make an HTTP request and parse JSON response\n *\n * Simple wrapper around native fetch for API calls. Throws HTTPError for\n * non-2xx responses.\n *\n * @param url - The URL to request\n * @param options - Fetch options\n * @returns Parsed JSON response\n * @throws HTTPError if the response status is not ok\n */\nexport async function request<T>(\n url: string,\n options?: RequestInit,\n): Promise<T> {\n const response = await fetch(url, options)\n\n if (!response.ok) {\n let errorData: unknown\n\n try {\n errorData = await response.json()\n } catch {\n try {\n errorData = await response.text()\n } catch {\n errorData = 'Failed to parse error response'\n }\n }\n\n throw new HTTPError(\n response.status,\n response.statusText,\n JSON.stringify(errorData),\n )\n }\n\n return response.json() as Promise<T>\n}\n","import { Algodv2, isValidAddress, type TransactionSigner } from 'algosdk'\nimport { SwapComposer, type SignerFunction } from './composer'\nimport {\n API_KEY_HEADER,\n DEFAULT_ALGOD_PORT,\n DEFAULT_ALGOD_TOKEN,\n DEFAULT_ALGOD_URI,\n DEFAULT_API_BASE_URL,\n DEFAULT_AUTO_OPT_IN,\n DEFAULT_DEBUG_LEVEL,\n DEFAULT_FEE_BPS,\n DEFAULT_MAX_DEPTH,\n DEFAULT_MAX_GROUP_SIZE,\n DEPRECATED_PROTOCOLS,\n MAX_FEE_BPS,\n} from './constants'\nimport { toSwapFirstStageQuote } from './firststage'\nimport { toSwapLaunchQuote } from './launch'\nimport { Logger } from './logger'\nimport { request } from './utils'\nimport type { SwapMiddleware } from './middleware'\nimport type {\n FetchQuoteResponse,\n FetchSwapTxnsResponse,\n Config,\n ConfigParams,\n FetchSwapTxnsParams,\n FetchSwapTxnsBody,\n FetchQuoteParams,\n SignerType,\n SwapQuote,\n} from './types'\n\n/**\n * Client for interacting with the Haystack order router API\n *\n * The RouterClient provides methods to fetch swap quotes and create transaction composers\n * for executing swaps on the Algorand blockchain. It handles API communication, transaction\n * validation, and automatic asset/app opt-in detection.\n *\n * @example\n * ```typescript\n * const router = new RouterClient({\n * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)\n * })\n * ```\n *\n * @example\n * ```typescript\n * const router = new RouterClient({\n * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)\n * apiBaseUrl: 'https://hayrouter.txnlab.dev',\n * algodUri: 'https://mainnet-api.4160.nodely.dev/',\n * algodToken: '',\n * algodPort: 443,\n * referrerAddress: 'REFERRER_ADDRESS...',\n * feeBps: 15,\n * autoOptIn: false,\n * })\n * ```\n */\nexport class RouterClient {\n private readonly config: Config\n private readonly algodClient: Algodv2\n private readonly middleware: SwapMiddleware[]\n\n /**\n * Create a new RouterClient instance\n *\n * @param config - Configuration parameters\n * @param config.apiKey - API key for Haystack Router API (required)\n * @param config.apiBaseUrl - Base URL for the Haystack Router API (default: https://hayrouter.txnlab.dev)\n * @param config.algodUri - Algod node URI (default: https://mainnet-api.4160.nodely.dev/)\n * @param config.algodToken - Algod node token (default: '')\n * @param config.algodPort - Algod node port (default: 443)\n * @param config.referrerAddress - Referrer address for fee sharing (receives 25% of swap fees)\n * @param config.feeBps - Fee in basis points (default: 15 = 0.15%, max: 300 = 3.00%)\n * @param config.autoOptIn - Automatically detect and add required opt-in transactions (default: false)\n * @param config.debugLevel - Debug logging level (default: 'none')\n * @param config.middleware - Array of middleware to apply to swaps (default: [])\n */\n constructor(config: ConfigParams & { middleware?: SwapMiddleware[] }) {\n // Set logger level FIRST (before any operations)\n const debugLevel = config.debugLevel ?? DEFAULT_DEBUG_LEVEL\n Logger.setLevel(debugLevel)\n\n // Validate and set config\n this.config = {\n apiKey: this.validateApiKey(config.apiKey),\n apiBaseUrl: config.apiBaseUrl ?? DEFAULT_API_BASE_URL,\n algodUri: config.algodUri ?? DEFAULT_ALGOD_URI,\n algodToken: config.algodToken ?? DEFAULT_ALGOD_TOKEN,\n algodPort: config.algodPort ?? DEFAULT_ALGOD_PORT,\n referrerAddress: config.referrerAddress\n ? this.validateAddress(config.referrerAddress)\n : undefined,\n feeBps: this.validateFeeBps(config.feeBps ?? DEFAULT_FEE_BPS),\n autoOptIn: config.autoOptIn ?? DEFAULT_AUTO_OPT_IN,\n debugLevel,\n }\n\n // Create Algodv2 client\n this.algodClient = new Algodv2(\n this.config.algodToken,\n this.config.algodUri,\n this.config.algodPort,\n )\n\n // Store middleware\n this.middleware = config.middleware ?? []\n }\n\n /**\n * Fetch a swap quote from the Haystack Router API\n *\n * Requests optimal swap routing from the Haystack Router API. The quote includes routing\n * information, price impact, required opt-ins, and an encrypted transaction payload.\n *\n * @param params - Parameters for the quote request\n * @param params.fromASAID - The ID of the asset to swap from\n * @param params.toASAID - The ID of the asset to swap to\n * @param params.amount - The amount of the asset to swap in base units\n * @param params.type - The type of the quote (default: 'fixed-input')\n * @param params.disabledProtocols - List of protocols to disable (default: [])\n * @param params.maxGroupSize - The maximum group size (default: 16)\n * @param params.maxDepth - The maximum depth (default: 4)\n * @param params.address - The address of the account that will perform the swap (recommended when using `config.autoOptIn` or `params.optIn`)\n * @param params.optIn - Whether to include asset opt-in transaction\n * - If true: API reduces maxGroupSize by 1 and includes opt-in (always included, even if not needed)\n * - If false: No opt-in transaction included\n * - If undefined: Falls back to `config.autoOptIn` behavior with account check (if `params.address` is provided)\n * @param params.slippage - Slippage tolerance as a percentage, in [0, 100). Pass the value you will execute with; required for accurate pre-bonding launch token quotes\n * @returns A FetchQuoteResponse object with routing information\n *\n * @example\n * ```typescript\n * const quote = await router.fetchQuote({\n * address: 'ABC...',\n * fromASAID: 0, // ALGO\n * toASAID: 31566704, // USDC\n * amount: 1_000_000, // 1 ALGO\n * })\n * ```\n */\n async fetchQuote(params: FetchQuoteParams): Promise<FetchQuoteResponse> {\n const {\n fromASAID,\n toASAID,\n amount,\n type = 'fixed-input',\n disabledProtocols = [],\n maxGroupSize = DEFAULT_MAX_GROUP_SIZE,\n maxDepth = DEFAULT_MAX_DEPTH,\n optIn,\n address,\n slippage,\n signerType,\n _allowNonComposableSwaps = false,\n } = params\n\n if (slippage !== undefined) {\n this.validateSlippage(slippage)\n }\n\n if (\n signerType !== undefined &&\n signerType !== 'ed25519' &&\n signerType !== 'falcon'\n ) {\n throw new Error(\n `Invalid signerType: ${String(signerType)}. Must be 'ed25519' or 'falcon'`,\n )\n }\n\n // Validate incompatible parameter combinations\n if (_allowNonComposableSwaps && (optIn === true || this.config.autoOptIn)) {\n throw new Error(\n 'Cannot use _allowNonComposableSwaps with optIn or autoOptIn. ' +\n 'When allowing non-composable swaps (Tinyman v1), you must handle ' +\n 'asset opt-ins and opt-outs in separate transaction groups.',\n )\n }\n\n // Always disable the deprecated protocols (Tinyman v1), unless\n // _allowNonComposableSwaps opts into Tinyman v1\n const allDisabledProtocols = _allowNonComposableSwaps\n ? [...new Set(disabledProtocols)]\n : [...new Set([...DEPRECATED_PROTOCOLS, ...disabledProtocols])]\n\n let includeOptIn = optIn\n if (includeOptIn === undefined && this.config.autoOptIn) {\n if (address) {\n includeOptIn = await this.needsAssetOptIn(\n this.validateAddress(address),\n toASAID,\n )\n } else {\n console.warn(\n 'autoOptIn is enabled but no address provided to fetchQuote(). Asset opt-in check skipped.',\n )\n }\n }\n\n const url = new URL(`${this.config.apiBaseUrl}/fetchQuote`)\n\n url.searchParams.append('algodUri', this.config.algodUri)\n url.searchParams.append('algodToken', this.config.algodToken)\n url.searchParams.append('algodPort', String(this.config.algodPort))\n url.searchParams.append('feeBps', this.config.feeBps.toString())\n url.searchParams.append('fromASAID', BigInt(fromASAID).toString())\n url.searchParams.append('toASAID', BigInt(toASAID).toString())\n url.searchParams.append('amount', BigInt(amount).toString())\n url.searchParams.append('type', type)\n url.searchParams.append('disabledProtocols', allDisabledProtocols.join(','))\n url.searchParams.append('maxGroupSize', maxGroupSize.toString())\n url.searchParams.append('maxDepth', maxDepth.toString())\n\n if (typeof includeOptIn === 'boolean') {\n url.searchParams.append('optIn', String(includeOptIn))\n }\n\n // Lets the router quote a First Stage taxed token's swap from the\n // account's own holding (wrapped only while it is frozen)\n if (address) {\n url.searchParams.append('address', this.validateAddress(address))\n }\n\n if (slippage !== undefined) {\n url.searchParams.append('slippage', String(slippage))\n }\n\n // A Falcon signer's transactions cost 3 fee units, which the router then\n // plans for and includes in the fees of the transactions it builds\n if (signerType === 'falcon') {\n url.searchParams.append('signerType', signerType)\n }\n\n if (this.config.referrerAddress) {\n url.searchParams.append('referrerAddress', this.config.referrerAddress)\n }\n\n return request<FetchQuoteResponse>(url.toString(), {\n headers: this.authHeaders(),\n })\n }\n\n /**\n * Check if asset opt-in is required for the output asset\n *\n * Convenience method to determine if an address needs to opt into the output asset\n * of a swap. This is useful when you want to get a quote without requiring wallet\n * connection upfront, but need to know whether to set `optIn: true` in fetchQuote()\n * to ensure proper routing (as opt-ins reduce maxGroupSize by 1).\n *\n * Note: If you enable `config.autoOptIn`, this check is handled automatically when\n * an address is provided to fetchQuote().\n *\n * @param address - The address to check\n * @param assetId - The output asset ID to check\n * @returns True if asset opt-in is required, false otherwise (always false for ALGO)\n *\n * @example\n * ```typescript\n * // Check if opt-in needed for output asset before fetching quote\n * const needsOptIn = await router.needsAssetOptIn(userAddress, toAssetId)\n * const quote = await router.fetchQuote({\n * fromAssetId,\n * toAssetId,\n * amount,\n * optIn: needsOptIn,\n * })\n * ```\n */\n async needsAssetOptIn(\n address: string,\n assetId: number | bigint,\n ): Promise<boolean> {\n // Fetch account information\n const accountInfo = await this.algodClient.accountInformation(address).do()\n\n // Check if asset opt-in is required\n return (\n BigInt(assetId) !== 0n &&\n accountInfo?.assets?.find(\n (asset) => asset.assetId === BigInt(assetId),\n ) === undefined\n )\n }\n\n /**\n * Fetch swap transactions from the Haystack Router API\n *\n * Decrypts the quote payload and generates executable swap transactions for the\n * specified signer address with the given slippage tolerance.\n *\n * @param params - Parameters for the swap transaction request\n * @param params.quote - The quote response from fetchQuote()\n * @param params.address - The address of the signer\n * @param params.slippage - The slippage tolerance as a percentage (e.g., 1 for 1%)\n * @returns A FetchSwapTxnsResponse object with transaction data\n */\n async fetchSwapTransactions(\n params: FetchSwapTxnsParams,\n ): Promise<FetchSwapTxnsResponse> {\n const { quote, address, slippage } = params\n\n // Validate signer address\n this.validateAddress(address)\n\n const url = new URL(`${this.config.apiBaseUrl}/fetchExecuteSwapTxns`)\n\n const body: FetchSwapTxnsBody = {\n address,\n txnPayloadJSON: quote.txnPayload,\n slippage,\n }\n\n return request<FetchSwapTxnsResponse>(url.toString(), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.authHeaders(),\n },\n body: JSON.stringify(body),\n })\n }\n\n /**\n * Headers that authenticate a request with the API key. The key goes in the\n * `X-API-Key` header rather than the URL, where it would end up in logs, browser\n * history and CDN caches. The API refuses a secret key (`hay_sk_`) in the URL.\n */\n private authHeaders(): Record<string, string> {\n return { [API_KEY_HEADER]: this.config.apiKey }\n }\n\n /**\n * Fetch a quote and return an enhanced quote result\n *\n * This is the recommended way to fetch quotes. It returns an object that\n * extends the raw API response with additional metadata and type normalization.\n *\n * @param params - Parameters for the quote request\n * @param params.fromASAID - The ID of the asset to swap from\n * @param params.toASAID - The ID of the asset to swap to\n * @param params.amount - The amount of the asset to swap in base units\n * @param params.type - The type of the quote (default: 'fixed-input')\n * @param params.disabledProtocols - List of protocols to disable (default: [])\n * @param params.maxGroupSize - The maximum group size (default: 16)\n * @param params.maxDepth - The maximum depth (default: 4)\n * @param params.address - The address of the account that will perform the swap (recommended when using `config.autoOptIn` or `params.optIn`)\n * @param params.optIn - Whether to include asset opt-in transaction\n * @param params.slippage - Slippage tolerance as a percentage, in [0, 100). Pass the value you will execute with; required for accurate pre-bonding launch token quotes\n * @returns A SwapQuote enhanced quote result\n *\n * @example\n * ```typescript\n * const quote = await router.newQuote({\n * address: 'ABC...',\n * fromASAID: 0,\n * toASAID: 31566704,\n * amount: 1_000_000,\n * })\n *\n * console.log(quote.quote) // bigint - quoted amount\n * console.log(quote.fromASAID) // number - input asset ID\n * console.log(quote.toASAID) // number - output asset ID\n * console.log(quote.amount) // bigint - original request amount\n * console.log(quote.createdAt) // number - timestamp\n * ```\n */\n async newQuote(params: FetchQuoteParams): Promise<SwapQuote> {\n // Block usage of _allowNonComposableSwaps with newQuote (middleware path)\n if (params._allowNonComposableSwaps) {\n throw new Error(\n 'The _allowNonComposableSwaps parameter is not supported with newQuote(). ' +\n 'This parameter is only available with fetchQuote() for advanced use cases. ' +\n 'The newQuote() method is designed for composable swaps with middleware support.',\n )\n }\n\n // Apply middleware transformations to quote params\n let adjustedParams = { ...params }\n\n // Create quote context for middleware\n const quoteContext = {\n fromASAID: BigInt(params.fromASAID),\n toASAID: BigInt(params.toASAID),\n amount: BigInt(params.amount),\n type: params.type ?? ('fixed-input' as const),\n address: params.address ?? undefined,\n algodClient: this.algodClient,\n }\n\n for (const mw of this.middleware) {\n const shouldApply = await mw.shouldApply(quoteContext)\n\n if (shouldApply && mw.adjustQuoteParams) {\n adjustedParams = await mw.adjustQuoteParams(adjustedParams)\n }\n }\n\n const response = await this.fetchQuote(adjustedParams)\n const { launch, firstStage, ...rest } = response\n\n return {\n ...rest,\n quote: response.quote === '' ? 0n : BigInt(response.quote),\n amount: BigInt(params.amount),\n address: params.address ?? undefined,\n slippage: adjustedParams.slippage,\n launch: launch && toSwapLaunchQuote(launch),\n firstStage: firstStage && toSwapFirstStageQuote(firstStage),\n // The caller's, should a middleware's adjusted params leave it out: the\n // router then plans without it, and the composer adds every extra fee\n signerType: adjustedParams.signerType ?? params.signerType ?? 'ed25519',\n createdAt: Date.now(),\n }\n }\n\n /**\n * Create a SwapComposer instance\n *\n * This factory method creates a composer that allows you to add custom transactions\n * before and after the swap transactions, with automatic handling of pre-signed transactions\n * and opt-ins.\n *\n * @param config.quote - The quote result from newQuote() or raw API response from fetchQuote()\n * @param config.address - The address of the signer\n * @param config.slippage - The slippage tolerance\n * @param config.signer - Transaction signer function\n * @returns A SwapComposer instance ready for building transaction groups\n *\n * @example\n * ```typescript\n * // Basic swap\n * const quote = await router.newQuote({ ... })\n * await router.newSwap({ quote, address, slippage, signer })\n * .execute()\n * ```\n *\n * @example\n * ```typescript\n * // Advanced swap with custom transactions\n * const quote = await router.newQuote({ ... })\n * const swap = await router.newSwap({\n * quote,\n * address,\n * slippage,\n * signer,\n * })\n *\n * console.log(swap.getStatus()) // BUILDING\n *\n * const signedTxns = await swap\n * .addTransaction(beforeTxn)\n * .addSwapTransactions() // Adds swap transactions to the group\n * .addTransaction(afterTxn)\n * .sign()\n *\n * console.log(swap.getStatus()) // SIGNED\n *\n * const result = await swap.execute(waitRounds)\n * console.log(result.confirmedRound, result.txIds)\n *\n * console.log(swap.getStatus()) // COMMITTED\n * ```\n */\n async newSwap(config: {\n quote: SwapQuote | FetchQuoteResponse\n address: string\n slippage: number\n signer: TransactionSigner | SignerFunction\n /** Optional note field for the user-signed input transaction (payment or asset transfer) */\n note?: Uint8Array\n /**\n * The kind of key `address` signs with. Defaults to the quote's\n * `signerType`: what `newQuote()` was asked for, or `'falcon'` when a\n * `fetchQuote()` response echoes it. Pass it with a `fetchQuote()`\n * response, which a router that doesn't support Falcon signers leaves\n * without it. For `'falcon'`, the composer adds the extra fees the\n * router's transactions don't include.\n */\n signerType?: SignerType\n }): Promise<SwapComposer> {\n const { quote, address, slippage, signer, note } = config\n const signerType: SignerType =\n config.signerType ?? quote.signerType ?? 'ed25519'\n\n // A launch quote prices the curve on the slippage it was fetched with, so\n // executing at a different slippage makes the quoted output inaccurate\n if (quote.launch && 'createdAt' in quote && quote.slippage !== slippage) {\n console.warn(\n quote.slippage === undefined\n ? `Launch token quote was fetched without slippage but the swap uses ${slippage}%. ` +\n 'Pass the same slippage to newQuote() for an accurate quoted output.'\n : `Launch token quote was fetched with ${quote.slippage}% slippage but the swap uses ${slippage}%. ` +\n 'The quoted output may not match what is delivered.',\n )\n }\n\n const swapResponse = await this.fetchSwapTransactions({\n quote,\n address,\n slippage,\n })\n\n // Create the composer\n const composer = new SwapComposer({\n quote,\n swapTxns: swapResponse.txns,\n algodClient: this.algodClient,\n address,\n signer,\n middleware: this.middleware,\n note,\n signerType,\n // Execute's word, not the quote's: a router mid-update can quote with\n // the echo and build without the extra fees\n signerFeesIncluded: swapResponse.signerType === 'falcon',\n debugLevel: this.config.debugLevel,\n })\n\n return composer\n }\n\n /**\n * Validates the API key\n */\n private validateApiKey(apiKey: string): string {\n if (!apiKey) {\n throw new Error('API key is required')\n }\n return apiKey\n }\n\n /**\n * Validates an Algorand address\n */\n private validateAddress(address: string): string {\n if (!isValidAddress(address)) {\n throw new Error(`Invalid Algorand address: ${address}`)\n }\n return address\n }\n\n /**\n * Validates a slippage percentage (0 <= slippage < 100)\n */\n private validateSlippage(slippage: number): number {\n if (!Number.isFinite(slippage) || slippage < 0 || slippage >= 100) {\n throw new Error(`Invalid slippage: ${slippage} (must be >= 0 and < 100)`)\n }\n return slippage\n }\n\n /**\n * Validates the fee in basis points (max 300 = 3.00%)\n */\n private validateFeeBps(feeBps: number): number {\n if (feeBps < 0 || feeBps > MAX_FEE_BPS) {\n throw new Error(\n `Invalid fee: ${feeBps} basis points (must be between 0 and ${MAX_FEE_BPS})`,\n )\n }\n return feeBps\n }\n}\n","import {\n makeAssetTransferTxnWithSuggestedParamsFromObject,\n type Algodv2,\n type SuggestedParams,\n type TransactionSigner,\n type TransactionWithSigner,\n} from 'algosdk'\nimport type { SwapQuote, FetchQuoteParams, QuoteType } from './types'\n\n/**\n * Context provided to middleware shouldApply hook\n */\nexport interface QuoteContext {\n /** Input asset ID (always bigint for precision and future-proofing) */\n readonly fromASAID: bigint\n /** Output asset ID (always bigint for precision and future-proofing) */\n readonly toASAID: bigint\n /** Amount to swap (always bigint for precision and future-proofing) */\n readonly amount: bigint\n /** Quote type */\n readonly type: QuoteType\n /** Address of the account that will perform the swap (if provided) */\n readonly address?: string\n /** Algodv2 client instance for making additional queries */\n readonly algodClient: Algodv2\n}\n\n/**\n * Context provided to middleware hooks during swap composition\n */\nexport interface SwapContext {\n /** The quote result from newQuote() */\n readonly quote: SwapQuote\n /** The address of the account performing the swap */\n readonly address: string\n /** Algodv2 client instance for making additional queries/transactions */\n readonly algodClient: Algodv2\n /** Suggested transaction parameters from the network */\n readonly suggestedParams: SuggestedParams\n /** Input asset ID (always bigint for precision and future-proofing) */\n readonly fromASAID: bigint\n /** Output asset ID (always bigint for precision and future-proofing) */\n readonly toASAID: bigint\n /** Transaction signer for transactions that need to be signed by the user */\n readonly signer: TransactionSigner\n}\n\n/**\n * Middleware interface for extending Haystack Router swap functionality\n *\n * Middleware allows you to modify quote parameters and inject additional transactions\n * into the atomic swap group. This is useful for assets that require special handling,\n * such as those with transfer restrictions, taxes, or custom smart contract logic.\n *\n * @example\n * ```typescript\n * class CustomAssetMiddleware implements SwapMiddleware {\n * readonly name = 'CustomAsset'\n * readonly version = '1.0.0'\n *\n * async shouldApply(context) {\n * return context.fromASAID === CUSTOM_ASSET_ID || context.toASAID === CUSTOM_ASSET_ID\n * }\n *\n * async adjustQuoteParams(params) {\n * // Reduce maxGroupSize to account for extra transactions\n * return { ...params, maxGroupSize: params.maxGroupSize - 3 }\n * }\n *\n * async beforeSwap(context) {\n * // Return transactions to add before the swap\n * return [unfreezeTransaction]\n * }\n *\n * async afterSwap(context) {\n * // Return transactions to add after the swap\n * return [taxTransaction, refreezeTransaction]\n * }\n * }\n * ```\n */\nexport interface SwapMiddleware {\n /** Unique identifier for the middleware */\n readonly name: string\n\n /** Semantic version of the middleware */\n readonly version: string\n\n /**\n * Determines if this middleware should be applied to the given swap\n *\n * Called during both quote and swap phases. Use this to check if either\n * the input or output asset requires special handling.\n *\n * @param context - Quote context with asset IDs, amount, type, address, and algod client\n * @returns True if middleware should be applied\n *\n * @example\n * ```typescript\n * async shouldApply(context) {\n * // Check if asset is registered in our smart contract\n * const assetInfo = await this.getAssetInfo(context.fromASAID)\n * return assetInfo !== null\n * }\n * ```\n */\n shouldApply(context: QuoteContext): Promise<boolean>\n\n /**\n * Modify quote parameters before fetching the quote\n *\n * **IMPORTANT**: If your middleware adds transactions via `beforeSwap` or `afterSwap`,\n * you MUST reduce `maxGroupSize` accordingly to prevent failures. The Haystack Router API may\n * return routes that use all 16 available transaction slots.\n *\n * Use this to adjust the quote request based on your asset's requirements.\n * Common adjustments include:\n * - Reducing `maxGroupSize` to account for additional transactions (REQUIRED if adding txns)\n * - Adjusting `amount` to account for fees/taxes\n * - Modifying `disabledProtocols` if certain DEXs are incompatible\n *\n * @param params - Original quote parameters\n * @returns Modified quote parameters\n *\n * @example\n * ```typescript\n * async adjustQuoteParams(params) {\n * const [fromTaxed, toTaxed] = await Promise.all([\n * this.isAssetTaxed(params.fromASAID),\n * this.isAssetTaxed(params.toASAID),\n * ])\n *\n * // 3 extra transactions per taxed asset\n * let maxGroupSize = params.maxGroupSize ?? 16\n * if (fromTaxed) maxGroupSize -= 3\n * if (toTaxed) maxGroupSize -= 3\n *\n * // Adjust amount for input tax\n * let amount = params.amount\n * if (fromTaxed) {\n * const taxRate = await this.getTaxRate(params.fromASAID)\n * amount = this.applyTax(amount, taxRate)\n * }\n *\n * return { ...params, maxGroupSize, amount }\n * }\n * ```\n */\n adjustQuoteParams?(params: FetchQuoteParams): Promise<FetchQuoteParams>\n\n /**\n * Add transactions before the swap transactions\n *\n * Called when building the swap transaction group. Transactions are added\n * to the group in the order they appear in the returned array.\n *\n * Transaction order in final group: [beforeSwap] → [swap txns] → [afterSwap]\n *\n * @param context - Swap context with quote, address, and algod client\n * @returns Array of transactions with signers to add before swap\n *\n * @example\n * ```typescript\n * async beforeSwap(context) {\n * const txns: TransactionWithSigner[] = []\n *\n * // Unfreeze user account before swap\n * if (await this.needsUnfreeze(context.fromASAID)) {\n * const unfreezeCall = makeApplicationNoOpTxn(\n * context.address,\n * this.appId,\n * ...,\n * context.suggestedParams\n * )\n *\n * txns.push({\n * txn: unfreezeCall,\n * signer: context.signer, // Use the signer from context\n * })\n * }\n *\n * return txns\n * }\n * ```\n */\n beforeSwap?(context: SwapContext): Promise<TransactionWithSigner[]>\n\n /**\n * Add transactions after the swap transactions\n *\n * Called when building the swap transaction group. Transactions are added\n * to the group in the order they appear in the returned array.\n *\n * Transaction order in final group: [beforeSwap] → [swap txns] → [afterSwap]\n *\n * @param context - Swap context with quote, address, and algod client\n * @returns Array of transactions with signers to add after swap\n *\n * @example\n * ```typescript\n * async afterSwap(context) {\n * const txns: TransactionWithSigner[] = []\n *\n * // Pay tax and refreeze account\n * if (await this.isTaxed(context.fromASAID)) {\n * const taxAmount = await this.calculateTax(context)\n * const taxPayment = makeAssetTransferTxn(\n * context.address,\n * this.taxReceiver,\n * taxAmount,\n * context.fromASAID,\n * context.suggestedParams\n * )\n * const refreezeCall = makeApplicationNoOpTxn(\n * context.address,\n * this.appId,\n * ...,\n * context.suggestedParams\n * )\n *\n * txns.push(\n * { txn: taxPayment, signer: context.signer },\n * { txn: refreezeCall, signer: context.signer },\n * )\n * }\n *\n * return txns\n * }\n * ```\n */\n afterSwap?(context: SwapContext): Promise<TransactionWithSigner[]>\n}\n\n/**\n * Configuration options for AutoOptOutMiddleware\n */\nexport interface AutoOptOutConfig {\n /**\n * Array of asset IDs that should be excluded from automatic opt-out behavior\n * @default []\n */\n readonly excludedAssets?: readonly (number | bigint)[]\n}\n\n/**\n * Middleware that automatically adds an asset opt-out transaction when swapping\n * the full balance of an input asset, leaving it with a zero balance.\n *\n * @example\n * ```typescript\n * import { RouterClient, AutoOptOutMiddleware } from '@txnlab/haystack-router'\n *\n * const autoOptOut = new AutoOptOutMiddleware({\n * excludedAssets: [31566704], // Don't auto-opt-out of USDC\n * })\n *\n * const router = new RouterClient({\n * apiKey: 'hay_pk_4zhj33wahuxfplteyif5xpcgnm', // Free tier (60 requests/min)\n * middleware: [autoOptOut],\n * })\n *\n * // When swapping full balance, opt-out transaction is automatically added\n * const quote = await router.newQuote({\n * address: userAddress,\n * fromASAID: someAssetId,\n * toASAID: 0,\n * amount: fullBalance, // If this equals account's full balance, opt-out is added\n * type: 'fixed-input',\n * })\n * ```\n */\nexport class AutoOptOutMiddleware implements SwapMiddleware {\n readonly name = 'AutoOptOut'\n readonly version = '1.0.0'\n\n private readonly excludedAssets: Set<bigint>\n\n constructor(config: AutoOptOutConfig = {}) {\n this.excludedAssets = new Set(\n (config.excludedAssets ?? []).map((id) => BigInt(id)),\n )\n }\n\n async shouldApply(context: QuoteContext): Promise<boolean> {\n // Only apply for fixed-input swaps\n if (context.type !== 'fixed-input') {\n return false\n }\n\n // Must have an address to check balance\n if (!context.address) {\n return false\n }\n\n // Don't opt-out of ALGO (asset ID 0)\n if (context.fromASAID === 0n) {\n return false\n }\n\n // Check if asset is in excluded list\n if (this.excludedAssets.has(context.fromASAID)) {\n return false\n }\n\n try {\n // Get account info to check current balance\n const accountInfo = await context.algodClient\n .accountInformation(context.address)\n .do()\n\n // Find the asset in account's holdings\n const assetHolding = accountInfo.assets?.find(\n (asset) => asset.assetId === context.fromASAID,\n )\n\n // If asset not found, don't opt-out\n if (!assetHolding) {\n return false\n }\n\n // Check if swap amount equals current balance\n return assetHolding.amount === context.amount\n } catch (error) {\n // If we can't fetch account info, don't apply middleware\n console.warn(\n `AutoOptOutMiddleware: Failed to fetch account info for ${context.address}:`,\n error,\n )\n return false\n }\n }\n\n async adjustQuoteParams(params: FetchQuoteParams): Promise<FetchQuoteParams> {\n // Reduce maxGroupSize by 1 to make room for the opt-out transaction\n const maxGroupSize = (params.maxGroupSize ?? 16) - 1\n\n return {\n ...params,\n maxGroupSize,\n }\n }\n\n async afterSwap(context: SwapContext): Promise<TransactionWithSigner[]> {\n // Create asset opt-out transaction (send 0 amount with closeRemainderTo = sender)\n const optOutTxn = makeAssetTransferTxnWithSuggestedParamsFromObject({\n sender: context.address,\n receiver: context.address,\n amount: 0n,\n assetIndex: context.fromASAID,\n closeRemainderTo: context.address,\n suggestedParams: context.suggestedParams,\n })\n\n return [\n {\n txn: optOutTxn,\n signer: context.signer,\n },\n ]\n }\n}\n"],"mappings":";;;;;;AAGA,IAAY,WAAL,yBAAA,UAAA;CACL,SAAA,eAAA;CACA,SAAA,YAAA;CACA,SAAA,YAAA;CACA,SAAA,cAAA;CACA,SAAA,UAAA;CACA,SAAA,WAAA;CACA,SAAA,WAAA;;AACF,EAAA,CAAA,CAAA;;;;;;AAOA,MAAa,uBAAuB,CAAC,SAAS;;AAG9C,MAAa,oBAAoB;;AAGjC,MAAa,sBAAsB;;AAGnC,MAAa,qBAAqB;;AAGlC,MAAa,uBAAuB;;AAGpC,MAAa,iBAAiB;;AAG9B,MAAa,kBAAkB;;AAG/B,MAAa,cAAc;;AAG3B,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;AAGjC,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;;;;AAMnC,MAAa,mBAAmB;;AAGhC,MAAa,8BAA8B;;;;;AClD3C,SAAgB,sBACd,YACqB;CACrB,OAAO;EACL,SAAS,OAAO,WAAW,OAAO;EAClC,OAAO,OAAO,WAAW,KAAK;EAC9B,SAAS,CAAC,GAAI,WAAW,WAAW,CAAC,CAAE;CACzC;AACF;;;;;;ACPA,SAAgB,kBAAkB,QAAsC;CACtE,OAAO;EACL,MAAM,OAAO;EACb,UAAU,OAAO;EACjB,gBAAgB,OAAO;EACvB,gBAAgB,OAAO,OAAO,cAAc;EAC5C,QAAQ,OAAO,OAAO,UAAU,CAAC;EACjC,WAAW,OAAO,aAAa;CACjC;AACF;;;ACiBA,MAAM,uCAAuB,IAAI,IAAI,CAAC,YAAY,UAAU,CAAC;;AAG7D,MAAM,uBAAuB;;AAG7B,MAAM,qBAAqB;;;;;;;;;AAU3B,MAAM,sBAAsB;;AAG5B,MAAM,oBAAoB;;;;;AAY1B,SAAS,gBAAgB,KAA8C;CACrE,MAAM,WAAW,IAAI,iBAAiB,QAAQ;CAC9C,IAAI,IAAI,SAAS,gBAAgB,QAAQ,CAAC,UAAU,OAAO,KAAA;CAE3D,MAAM,MAAM,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,KAAK;CAChD,IAAI,qBAAqB,IAAI,GAAG,GAAG,OAAO;CAC1C,IAAI,QAAQ,sBAAsB,OAAO;AAE3C;;;;;AAMA,SAAS,iBAAiB,MAAoD;CAC5E,MAAM,MAAM,OAAO,KAAK,SAAS;CACjC,IACE,CAAC,OACD,IAAI,WAAW,MACf,OAAO,KAAK,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,oBAEpD;CAEF,OAAO,aAAa,IAAI,SAAS,CAAC,GAAG,QAAQ;AAC/C;;;;;AAMA,SAAS,cACP,MAC0E;CAC1E,MAAM,MAAM,MAAM,MACf,MACC,EAAE,WAAW,qBACb,OAAO,KAAK,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,mBACtD;CACA,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,OAAO;EACL,UAAU,aAAa,IAAI,SAAS,IAAI,EAAE,GAAG,QAAQ;EACrD,WAAW,aAAa,IAAI,SAAS,IAAI,EAAE,GAAG,QAAQ;EACtD,aAAa,cAAc,IAAI,SAAS,KAAK,GAAG,CAAC;CACnD;AACF;;;;AAkBA,IAAY,qBAAL,yBAAA,oBAAA;;CAEL,mBAAA,mBAAA,cAAA,KAAA;;CAGA,mBAAA,mBAAA,WAAA,KAAA;;CAGA,mBAAA,mBAAA,YAAA,KAAA;;CAGA,mBAAA,mBAAA,eAAA,KAAA;;CAGA,mBAAA,mBAAA,eAAA,KAAA;;AACF,EAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;AAwDA,IAAa,eAAb,MAAa,aAAa;;CAExB,MAAc,IAAI,0BAA0B;;CAG5C,OAAO,iBAAyB,0BAA0B;;CAG1D,wBAAgC;CAEhC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;CAEA,gBAAwB;;CAExB;;CAEA;CACA,kBAA0B;CAC1B;CACA;CACA;;CAGA;;;;;;;;;;;;;;;CAmCA,YAAY,QAA4B;EAEtC,IAAI,OAAO,YACT,OAAO,SAAS,OAAO,UAAU;EAInC,IAAI,CAAC,OAAO,OACV,MAAM,IAAI,MAAM,mBAAmB;EAErC,IAAI,CAAC,OAAO,UACV,MAAM,IAAI,MAAM,gCAAgC;EAElD,IAAI,OAAO,SAAS,WAAW,GAC7B,MAAM,IAAI,MAAM,yCAAyC;EAE3D,IAAI,CAAC,OAAO,aACV,MAAM,IAAI,MAAM,qCAAqC;EAEvD,IAAI,CAAC,OAAO,QACV,MAAM,IAAI,MAAM,oBAAoB;EAGtC,KAAK,QAAQ,OAAO;EACpB,KAAK,oBAAoB,OAAO,MAAM;EACtC,KAAK,WAAW,OAAO;EACvB,KAAK,cAAc,OAAO;EAC1B,KAAK,UAAU,KAAK,gBAAgB,OAAO,OAAO;EAClD,KAAK,SAAS,OAAO;EACrB,KAAK,aAAa,OAAO,cAAc,CAAC;EACxC,KAAK,OAAO,OAAO;EACnB,KAAK,aAAa,OAAO,cAAc,OAAO,MAAM,cAAc;EAClE,KAAK,qBAAqB,OAAO,sBAAsB;CACzD;;;;;;CAOA,YAAgC;EAC9B,OAAO,KAAK,IAAI,UAAU;CAC5B;;;;;;CAOA,QAAgB;EACd,OAAO,KAAK,IAAI,MAAM;CACxB;;;;;;;;;;;;;;;;;;CAmBA,eAAe,aAA0B,SAAS,KAAK,eAAqB;EAM1E,IAHE,KAAK,MAAM,iBAAiB,eAAe,KAAA,KAC3C,KAAK,MAAM,eAAe,aAAa,GAGvC,MAAM,IAAI,MACR,mSAIF;EAGF,KAAK,IAAI,eAAe;GAAE,KAAK;GAAa;EAAO,CAAC;EACpD,KAAK,eAAe,YAAY,QAAQ,WAAW;EACnD,OAAO;CACT;;;;;;;;;;;;;CAcA,cAAc,YAAwB,SAAS,KAAK,eAAqB;EAMvE,IAHE,KAAK,MAAM,iBAAiB,eAAe,KAAA,KAC3C,KAAK,MAAM,eAAe,aAAa,GAGvC,MAAM,IAAI,MACR,mSAIF;EAGF,KAAK,IAAI,cAAc;GACrB,GAAG;GACH,QAAQ,WAAW,UAAU;EAC/B,CAAC;EAGD,KAAK,MAAM,OAAO,WAAW,cAAc,CAAC,GAC1C,IAAI,wBAAwB,GAAG,GAC7B,KAAK,eAAe,IAAI,IAAI,QAAQ,IAAI,GAAG;EAE/C,KAAK,eAAe,WAAW,MAAM;EACrC,OAAO;CACT;;;;;;;;;;;;;;;;;CAkBA,MAAM,sBAAqC;EACzC,IAAI,KAAK,uBACP,MAAM,IAAI,MAAM,2CAA2C;EAG7D,IAAI,KAAK,UAAU,MAAA,GACjB,MAAM,IAAI,MACR,mEACF;EAIF,MAAM,aAAa,MAAM,KAAK,uBAAuB,YAAY;EAGjE,IAAI,KAAK,IAAI,MAAM,IAAI,WAAW,SAAS,aAAa,gBACtD,MAAM,IAAI,MACR,2EAA2E,aAAa,gBAC1F;EAGF,KAAK,MAAM,iBAAiB,YAAY;GACtC,KAAK,IAAI,eAAe,aAAa;GACrC,KAAK,eAAe,cAAc,IAAI,QAAQ,cAAc,GAAG;EACjE;EAGA,MAAM,EACJ,MAAM,eACN,SACA,UACA,uBACA,wBACA,0BACE,MAAM,KAAK,wBAAwB;EAEvC,MAAM,YAAY,MAAM,KAAK,uBAAuB,WAAW;EAM/D,IAFE,KAAK,IAAI,MAAM,IAAI,cAAc,SAAS,UAAU,SAEpC,aAAa,gBAC7B,MAAM,IAAI,MACR,qEAAqE,aAAa,gBACpF;EAKF,IAAI,0BAA0B,KAAA,GAC5B,KAAK,wBAAwB,KAAK,IAAI,MAAM,IAAI;EAIlD,IAAI,2BAA2B,KAAA,GAC7B,KAAK,yBAAyB,KAAK,IAAI,MAAM,IAAI;EAInD,IAAI,0BAA0B,KAAA,GAC5B,KAAK,wBAAwB,KAAK,IAAI,MAAM,IAAI;EAKlD,KAAK,MAAM,iBAAiB,eAAe;GACzC,KAAK,IAAI,eAAe,aAAa;GACrC,KAAK,eACH,cAAc,IAAI,QAClB,cAAc,KACd,KAAK,sBAAsB,QAAQ,IAAI,cAAc,GAAG,CAC1D;EACF;EAEA,KAAK,WAAW;EAGhB,KAAK,MAAM,iBAAiB,WAAW;GACrC,KAAK,IAAI,eAAe,aAAa;GACrC,KAAK,eAAe,cAAc,IAAI,QAAQ,cAAc,GAAG;EACjE;EAEA,KAAK,wBAAwB;EAE7B,OAAO;CACT;;;;;;;;;;;;;;;;;;;;;;;;CAyBA,aAAsC;EACpC,KAAK,cAAc;EACnB,OAAO,KAAK,IAAI,WAAW;CAC7B;;;;;;;;;;;;;;CAeA,MAAM,OAA8B;EAClC,IAAI,KAAK,UAAU,KAAA,GACjB,OAAO,KAAK,IAAI,iBAAiB;EAInC,IAAI,CAAC,KAAK,uBACR,MAAM,KAAK,oBAAoB;EAGjC,KAAK,cAAc;EACnB,OAAO,MAAM,KAAK,IAAI,iBAAiB;CACzC;;;;;;;;;;;;;;;;CAiBA,MAAM,SAA4B;EAEhC,IAAI,CAAC,KAAK,uBACR,MAAM,KAAK,oBAAoB;EAGjC,KAAK,cAAc;EACnB,OAAO,MAAM,KAAK,IAAI,OAAO,KAAK,WAAW;CAC/C;;;;;;;;;;;;;;;;;;;CAoBA,MAAM,QAAQ,aAAA,IAIX;EACD,IAAI;GAEF,IAAI,CAAC,KAAK,uBACR,MAAM,KAAK,oBAAoB;GAGjC,KAAK,cAAc;GACnB,MAAM,EAAE,OAAO,GAAG,WAAW,MAAM,KAAK,IAAI,QAC1C,KAAK,aACL,UACF;GAGA,IAAI,KAAK,aAAa;IACpB,IAAI,KAAK,0BAA0B,KAAA,GACjC,KAAK,YAAY,aAAa,MAAM,KAAK;IAE3C,IAAI,KAAK,2BAA2B,KAAA,GAClC,KAAK,YAAY,cAAc,MAAM,KAAK;IAE5C,IACE,KAAK,YAAY,UACjB,KAAK,0BAA0B,KAAA,GAE/B,KAAK,YAAY,OAAO,aAAa,MAAM,KAAK;GAEpD;GAGA,MAAM,KAAK,0BAA0B;GAErC,OAAO;IACL,GAAG;IACH,OAAO;GACT;EACF,SAAS,OAAO;GAEd,MAAM,EAAE,4BAA4B,MAAM,OAAO;GACjD,wBACE;IACE,OAAO,KAAK;IACZ,SAAS,KAAK;IACd,UAAU;IACV,kBAAkB,KAAK,MAAM;IAC7B,iBAAiB,KAAK,WAAW;IACjC,WAAW,KAAK,MAAM;GACxB,GACA,KACF;GAGA,MAAM;EACR;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BA,wBAA4C;EAC1C,IAAI,KAAK,UAAU,IAAA,GACjB;EAEF,IAAI,KAAK,0BAA0B,KAAA,GACjC;EAIF,QAFa,KAAK,IAAI,WACP,CAAC,CAAC,KAAK,sBAAsB,EAAE,IAAA,EAClC,KAAK;CACnB;;;;;;;;;;;;;;;;;;;;;;;;CAyBA,aAAsC;EAEpC,IACE,CAAC,KAAK,eACN,KAAK,YAAY,iBAAiB,KAAA,KAClC,KAAK,YAAY,eAAe,KAAA,KAChC,KAAK,YAAY,gBAAgB,KAAA,KACjC,KAAK,YAAY,iBAAiB,KAAA,GAElC;EAGF,MAAM,OAAO,KAAK,IAAI,WAAW;EACjC,MAAM,YAAY,KAAK,QAAQ,KAAK,QAAQ,MAAM,IAAI,IAAI,KAAK,EAAE;EAEjE,MAAM,SAAS,KAAK,YAAY;EAChC,MAAM,aAAa,QAAQ;EAE3B,OAAO;GACL,cAAc,KAAK,YAAY;GAC/B,eAAe,KAAK,YAAY;GAChC,aAAa,KAAK,YAAY;GAC9B,cAAc,KAAK,YAAY;GAC/B,MAAM,KAAK,MAAM;GACjB;GACA,kBAAkB,KAAK;GAEvB,YACE,QAAQ,SAAS,UAAU,aACvB,aACA,KAAK,YAAY;GACvB,aAAa,KAAK,YAAY;GAC9B,aAAa,KAAK,YAAY;GAC9B,cAAc,KAAK,YAAY;GAC/B,GAAI,UACF,cAAc,EACZ,QAAQ;IACN,MAAM,OAAO;IACb,UAAU,OAAO;IACjB;IACA,eAAe,OAAO;IACtB,QAAQ,OAAO;GACjB,EACF;EACJ;CACF;;;;;;;;CASA,eACE,QACA,KACA,WAAW,OACL;EACN,IAAI,OAAO,SAAS,MAAM,KAAK,SAAS;EACxC,IAAI,KAAK,KAAK,iBAAiB;EAC/B,IAAI,CAAC,UAAU,KAAK;CACtB;;;;;;;;;;CAWA,gBAA8B;EAC5B,IACE,KAAK,eAAe,YACpB,KAAK,mBACL,KAAK,UAAU,MAAA,GAEf;EAEF,IAAI,KAAK,gBAAgB,GAAG;GAC1B,MAAM,UAAU,KAAK,YAAY,KAAK;GACtC,IAAI,CAAC,SACH,MAAM,IAAI,MACR,uBAAuB,KAAK,QAAQ,iDACtC;GAEF,QAAQ,OAAO,mBAAmB,OAAO,KAAK,aAAa;GAC3D,OAAO,MAAM,+BAA+B;IAC1C,cAAc,KAAK;IACnB,KAAK,QAAQ;GACf,CAAC;EACH;EACA,KAAK,kBAAkB;CACzB;;;;CAKA,gBAAwB,SAAyB;EAC/C,IAAI,CAAC,eAAe,OAAO,GACzB,MAAM,IAAI,MAAM,6BAA6B,SAAS;EAExD,OAAO;CACT;;;;;;CAOA,MAAc,0BASX;EACD,MAAM,YAAY,MAAM,KAAK,yBAAyB;EAEtD,MAAM,UAAoC,KAAK,SAAS,KACrD,SAAS,MAAM;GACd,IAAI;IACF,MAAM,MAAM,0BACV,OAAO,KAAK,QAAQ,MAAM,QAAQ,CACpC;IACA,OAAO,IAAI;IACX,OAAO;KAAE;KAAK,WAAW,QAAQ;IAAU;GAC7C,SAAS,OAAO;IACd,MAAM,IAAI,MACR,+CAA+C,EAAE,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAC1G,EAAE,OAAO,MAAM,CACjB;GACF;EACF,CACF;EAEA,MAAM,aAAa,KAAK,0BAA0B,OAAO;EACzD,MAAM,WACJ,eAAe,KAAA,IAAY,QAAQ,WAAW,EAAE,MAAM,KAAA;EAKxD,IAAI,YAAY,KAAK,SAAS,KAAA,GAC3B,SAAmC,OAAO,KAAK;EAKlD,MAAM,WAAoC,QAAQ,KAC/C,EAAE,KAAK,iBAAiB;GACvB;GACA,QACE,cAAc,QACV,KAAK,gBACL,KAAK,iBAAiB,SAAS;EACvC,EACF;EAGA,IAAI,UAAU;GAEZ,MAAM,gBAAgB,SAAS,SAAS;GACxC,MAAM,sBAAsB,SAAS,eAAe;GACpD,MAAM,cAAc,iBAAiB,uBAAuB;GAE5D,KAAK,cAAc;IACjB,cAAc,OAAO,KAAK,MAAM,SAAS;IACzC,eAAe,OAAO,KAAK,MAAM,OAAO;IACxC;IACA,aAAa,KAAK;GACpB;EACF;EAEA,MAAM,aAAa,KAAK,kBAAkB,OAAO;EAOjD,MAAM,cACJ,KAAK,aAAa,QAAQ,SAAS,SAAS,eAAe,KAAA,IACvD,aACA,KAAK,2BAA2B,OAAO;EAC7C,MAAM,yBACJ,eAAe,IAAI,UAAU,SAAS,cAAc,KAAA;EAEtD,OAAO;GACL,MAAM,CAAC,GAAG,WAAW,GAAG,QAAQ;GAChC,SAAS,IAAI,IAAI,QAAQ,KAAK,EAAE,UAAU,GAAG,CAAC;GAC9C;GACA,uBACE,eAAe,KAAA,IAAY,UAAU,SAAS,aAAa,KAAA;GAC7D;GACA,uBACE,eAAe,KAAA,IAAY,UAAU,SAAS,aAAa,KAAA;EAC/D;CACF;;;;;;;;;;;;;;CAeA,0BACE,MACoB;EACpB,MAAM,cAAc,KAAK,MACtB,EAAE,KAAK,gBACN,cAAc,SAAS,IAAI,SAAS,gBAAgB,IACxD,CAAC,EAAE,IAAI,iBAAiB;EAExB,IAAI,gBAAgB,KAAA,GAAW;GAC7B,MAAM,gBAAgB,sBAAsB,WAAW,CAAC,CAAC,SAAS;GAClE,MAAM,qBAAqB,EAAE,KAAK,gBAChC,cAAc,UACb,IAAI,SAAS,YAAY,IAAI,eAAe,SAAA,EAAW,SAAS,MAC/D;GACJ,KAAK,MAAM,QAAQ,CAAC,gBAAgB,OAAO,gBAAgB,GAAG,GAAG;IAC/D,MAAM,QAAQ,KAAK,WAChB,MAAM,EAAE,IAAI,SAAS,QAAQ,kBAAkB,CAAC,CACnD;IACA,IAAI,UAAU,IAAI,OAAO;GAC3B;EACF;EAEA,KAAK,IAAI,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KACpC,IAAI,KAAK,EAAE,EAAE,cAAc,OAAO,OAAO;CAG7C;;;;;;;;CASA,2BAAmC,MAAwC;EACzE,KAAK,IAAI,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;GACzC,MAAM,IAAI,KAAK;GACf,IAAI,GAAG,cAAc,SAAS,GAAG,IAAI,SAAS,gBAAgB,MAC5D,OAAO;EAEX;EACA,OAAO,KAAK,SAAS;CACvB;;;;;;;;CASA,kBACE,MACoB;EACpB,MAAM,OAAO,KAAK,MAAM,QAAQ;EAChC,IAAI,CAAC,QAAQ,CAAC,KAAK,aAAa,OAAO,KAAA;EAEvC,MAAM,aAAa,KAAK,WACrB,EAAE,KAAK,gBACN,cAAc,SAAS,gBAAgB,GAAG,MAAM,IACpD;EACA,MAAM,YAAY,KAAK,WAAW,EAAE,IAAI;EAExC,MAAM,cAAc,WAAW,QAAQ;EACvC,IAAI,CAAC,aAAa,CAAC,aAAa,OAAO,KAAA;EAEvC,MAAM,aAAa,sBAAsB,UAAU,QAAQ,CAAC,CAAC,SAAS;EAEtE,IAAI;EACJ,IAAI,SAAS,OAAO;GAGlB,MAAM,UAAU,KAAK,aAAa,EAAE,EAAE;GAGtC,KADE,SAAS,SAAS,YAAY,SAAS,eAAe,SAAA,EAC1C,SAAS,MAAM,YAC3B,gBACE,SAAS,SAAS,UAAU,SAAS,eAAe;EAE1D,OAAO;GACL,MAAM,eAAe,UAAU,QAAQ;GACvC,IAAI,cACF,KAAK,YAAY,cAAc,aAAa,cAAc,QAAQ;EAEtE;EAEA,KAAK,YAAY,SAAS;GACxB;GACA,UAAU,aAAa,aAAa,QAAQ;GAC5C,gBAAgB,OAAO,KAAK,MAAM,QAAQ,kBAAkB,CAAC;GAC7D;GACA;GACA,QAAQ;EACV;EAEA,OAAO;CACT;;;;CAKA,MAAc,2BAA6D;EAOzE,MAAM,YACJ,MANwB,KAAK,YAC5B,mBAAmB,KAAK,OAAO,CAAC,CAChC,GAAG,EAAA,EAIS,gBAAgB,KAAK,QAAQ,OAAO,IAAI,EAAE,CAAC,KAAK,CAAC;EAChE,MAAM,cAAc,KAAK,kBAAkB,QACxC,UAAU,CAAC,SAAS,SAAS,KAAK,CACrC;EAEA,IAAI,YAAY,WAAW,GAAG,OAAO,CAAC;EAEtC,MAAM,kBAAkB,MAAM,KAAK,YAAY,qBAAqB,CAAC,CAAC,GAAG;EAEzE,OAAO,YAAY,KAAK,WAAW;GACjC,KAAK,kCAAkC;IACrC,QAAQ,KAAK;IACb,UAAU;IACV;GACF,CAAC;GACD,QAAQ,KAAK;EACf,EAAE;CACJ;;;;CAKA,gBAA2C,OACzC,UACA,kBACG;EAEH,QAAO,MADc,KAAK,OAAO,UAAU,aAAa,EAAA,CAC1C,QAAQ,QAA2B,QAAQ,IAAI;CAC/D;;;;CAKA,iBAAyB,WAAyC;EAChE,OAAO,OACL,UACA,kBAC0B;GAC1B,OAAO,cAAc,KAAK,MAAM;IAC9B,MAAM,MAAM,SAAS;IACrB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,wBAAwB,EAAE,WAAW;IAC/D,OAAO,KAAK,oBAAoB,KAAK,SAAS;GAChD,CAAC;EACH;CACF;;;;CAKA,oBACE,aACA,WACY;EACZ,IAAI;GACF,IAAI,UAAU,SAAS,mBAAmB;IAExC,MAAM,aAAa,UAAU;IAC7B,MAAM,aAAa,IAAI,WAAW,OAAO,OAAO,UAAU,CAAC;IAC3D,MAAM,UAAU,iBAAiB,UAAU;IAI3C,IAAI,CAAC,QAAQ,MACX,MAAM,IAAI,MAAM,8CAA8C;IAGhE,MAAM,OAAO,QAAQ;IACrB,MAAM,kBAAkB,IAAI,gBAAgB,KAAK,GAAG,KAAK,GAAG;IAM5D,OAJkB,8BAChB,aACA,eAEa,CAAC,CAAC;GACnB,OAAO,IAAI,UAAU,SAAS,cAAc;IAE1C,MAAM,aAAa,UAAU;IAC7B,MAAM,YAAY,IAAI,WAAW,OAAO,OAAO,UAAU,CAAC;IAE1D,OADkB,gBAAgB,aAAa,SAChC,CAAC,CAAC;GACnB,OACE,MAAM,IAAI,MAAM,+BAA+B,UAAU,MAAM;EAEnE,SAAS,OAAO;GACd,MAAM,IAAI,MACR,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KACvF,EAAE,OAAO,MAAM,CACjB;EACF;CACF;;;;CAKA,MAAc,uBACZ,UACkC;EAClC,MAAM,UAAmC,CAAC;EAI1C,MAAM,QACJ,eAAe,KAAK,QAChB;GAAE,GAAG,KAAK;GAAO,YAAY,KAAK;EAAW,IAC7C;GACE,GAAG,KAAK;GACR,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK,OAAO,KAAK,MAAM,KAAK;GAC7D,QAAQ;GACR,QAAQ,KAAK,MAAM,UAAU,kBAAkB,KAAK,MAAM,MAAM;GAChE,YACE,KAAK,MAAM,cACX,sBAAsB,KAAK,MAAM,UAAU;GAC7C,YAAY,KAAK;GACjB,WAAW,KAAK,IAAI;EACtB;EAGN,MAAM,eAA6B;GACjC,WAAW,OAAO,KAAK,MAAM,SAAS;GACtC,SAAS,OAAO,KAAK,MAAM,OAAO;GAClC,QAAQ,MAAM;GACd,MAAM,KAAK,MAAM;GACjB,SAAS,MAAM;GACf,aAAa,KAAK;EACpB;EAEA,KAAK,MAAM,MAAM,KAAK,YAAY;GAGhC,IAAI,CAAC,MAFqB,GAAG,YAAY,YAAY,KAEjC,CAAC,GAAG,WACtB;GAIF,MAAM,kBAAkB,MAAM,KAAK,YAAY,qBAAqB,CAAC,CAAC,GAAG;GACzE,MAAM,cAA2B;IAC/B;IACA,SAAS,KAAK;IACd,aAAa,KAAK;IAClB;IACA,WAAW,OAAO,KAAK,MAAM,SAAS;IACtC,SAAS,OAAO,KAAK,MAAM,OAAO;IAClC,QAAQ,KAAK;GACf;GAEA,MAAM,OAAO,MAAM,GAAG,SAAS,CAAC,WAAW;GAE3C,QAAQ,KAAK,GAAG,IAAI;EACtB;EAEA,OAAO;CACT;;;;;;;;;;;CAYA,MAAc,4BAA2C;EACvD,IAAI,CAAC,KAAK,aAAa,aACrB;EAGF,MAAM,gBAAgB,KAAK,YAAY;EACvC,MAAM,cAAc,KAAK;EACzB,MAAM,SAAS,KAAK,YAAY;EAEhC,IAAI;GACF,MAAM,cAAc,MAAM,KAAK,YAC5B,8BAA8B,KAAK,YAAY,WAAW,CAAC,CAC3D,GAAG;GAEN,IAAI,QAAQ,SAAS,OAAO;IAE1B,MAAM,iBAAiB,iBAAiB,YAAY,IAAI;IACxD,IAAI,mBAAmB,KAAA,GAAW;KAChC,KAAK,YAAY,eAAe;KAChC,KAAK,YAAY,eAAe,OAAO;IACzC;IAGA,KAAK,MAAM,YAAY,YAAY,aAAa,CAAC,GAAG;KAClD,MAAM,MAAM,SAAS,IAAI;KACzB,IAAI,OAAO,mBAAmB,IACxB;UAAA,IAAI,SAAS,SAAS,SAAS,MAAM,aACvC,OAAO,UAAU,IAAI,QAAQ;KAAA,OAE1B,IACL,IAAI,eAAe,SAAS,SAAS,MAAM,eAC3C,IAAI,cAAc,eAAe,OAAO,gBAExC,OAAO,UAAU,IAAI,cAAc;IAEvC;IACA;GACF;GAEA,MAAM,YAAY,cAAc,YAAY,IAAI;GAChD,MAAM,YAAY,aAAa,YAAY,IAAI,IAAI;GACnD,IAAI,aAAa,WAGb;QAAA,UAAU,aAAa,iBACvB,UAAU,gBAAgB,aAC1B;KACA,KAAK,YAAY,eAAe,UAAU;KAC1C,KAAK,YAAY,eAAe,sBAC9B,UAAU,QACZ,CAAC,CAAC,SAAS;IACb;UAEA,KAAK,MAAM,YAAY,YAAY,aAAa,CAAC,GAAG;IAClD,MAAM,MAAM,SAAS,IAAI;IACzB,MAAM,UAAU,IAAI;IACpB,MAAM,gBAAgB,IAAI;IAG1B,MAAM,WAAW,SAAS,YAAY,eAAe;IACrD,IAAI,CAAC,UAAU;IAGf,IAAI,SAAS,SAAS,MAAM,aAAa;IAGzC,MAAM,gBAAgB,IAAI,OAAO,SAAS;IAE1C,IAAI,kBAAkB,MAAM,SAAS,UAAU,MAAM;KAEnD,KAAK,YAAY,eAAe,QAAQ;KACxC,KAAK,YAAY,eAAe;KAChC;IACF,OAAO,IAAI,kBAAkB,MAAM,eAEjB;SAAA,cAAc,eACd,iBAAiB,cAAc,UAAU,MAAM;MAC7D,KAAK,YAAY,eAAe,cAAc;MAC9C,KAAK,YAAY,eAAe;MAChC;KACF;;GAEJ;EAEJ,QAAQ,CAER;EAIA,IAAI,QAAQ,SAAS,UAAU,OAAO,YACpC,IAAI;GAIF,OAAO,gBAAgB,kBAAiB,MAHhB,KAAK,YAC1B,8BAA8B,OAAO,UAAU,CAAC,CAChD,GAAG,EAAA,CAC4C,IAAI;EACxD,QAAQ,CAER;CAEJ;AACF;;;;;ACjyCA,IAAa,YAAb,cAA+B,MAAM;CAE1B;CACA;CACA;CAHT,YACE,QACA,YACA,MACA;EACA,MAAM,QAAQ,OAAO,GAAG,YAAY;EAJ7B,KAAA,SAAA;EACA,KAAA,aAAA;EACA,KAAA,OAAA;EAGP,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;AAaA,eAAsB,QACpB,KACA,SACY;CACZ,MAAM,WAAW,MAAM,MAAM,KAAK,OAAO;CAEzC,IAAI,CAAC,SAAS,IAAI;EAChB,IAAI;EAEJ,IAAI;GACF,YAAY,MAAM,SAAS,KAAK;EAClC,QAAQ;GACN,IAAI;IACF,YAAY,MAAM,SAAS,KAAK;GAClC,QAAQ;IACN,YAAY;GACd;EACF;EAEA,MAAM,IAAI,UACR,SAAS,QACT,SAAS,YACT,KAAK,UAAU,SAAS,CAC1B;CACF;CAEA,OAAO,SAAS,KAAK;AACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACSA,IAAa,eAAb,MAA0B;CACxB;CACA;CACA;;;;;;;;;;;;;;;;CAiBA,YAAY,QAA0D;EAEpE,MAAM,aAAa,OAAO,cAAA;EAC1B,OAAO,SAAS,UAAU;EAG1B,KAAK,SAAS;GACZ,QAAQ,KAAK,eAAe,OAAO,MAAM;GACzC,YAAY,OAAO,cAAA;GACnB,UAAU,OAAO,YAAA;GACjB,YAAY,OAAO,cAAA;GACnB,WAAW,OAAO,aAAA;GAClB,iBAAiB,OAAO,kBACpB,KAAK,gBAAgB,OAAO,eAAe,IAC3C,KAAA;GACJ,QAAQ,KAAK,eAAe,OAAO,UAAA,EAAyB;GAC5D,WAAW,OAAO,aAAA;GAClB;EACF;EAGA,KAAK,cAAc,IAAI,QACrB,KAAK,OAAO,YACZ,KAAK,OAAO,UACZ,KAAK,OAAO,SACd;EAGA,KAAK,aAAa,OAAO,cAAc,CAAC;CAC1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCA,MAAM,WAAW,QAAuD;EACtE,MAAM,EACJ,WACA,SACA,QACA,OAAO,eACP,oBAAoB,CAAC,GACrB,eAAA,IACA,WAAA,GACA,OACA,SACA,UACA,YACA,2BAA2B,UACzB;EAEJ,IAAI,aAAa,KAAA,GACf,KAAK,iBAAiB,QAAQ;EAGhC,IACE,eAAe,KAAA,KACf,eAAe,aACf,eAAe,UAEf,MAAM,IAAI,MACR,uBAAuB,OAAO,UAAU,EAAE,gCAC5C;EAIF,IAAI,6BAA6B,UAAU,QAAQ,KAAK,OAAO,YAC7D,MAAM,IAAI,MACR,0LAGF;EAKF,MAAM,uBAAuB,2BACzB,CAAC,GAAG,IAAI,IAAI,iBAAiB,CAAC,IAC9B,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,sBAAsB,GAAG,iBAAiB,CAAC,CAAC;EAEhE,IAAI,eAAe;EACnB,IAAI,iBAAiB,KAAA,KAAa,KAAK,OAAO,WAAW;GACvD,IAAI,SACF,eAAe,MAAM,KAAK,gBACxB,KAAK,gBAAgB,OAAO,GAC5B,OACF;QAEA,QAAQ,KACN,2FACF;EAEJ;EAEA,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,WAAW,YAAY;EAE1D,IAAI,aAAa,OAAO,YAAY,KAAK,OAAO,QAAQ;EACxD,IAAI,aAAa,OAAO,cAAc,KAAK,OAAO,UAAU;EAC5D,IAAI,aAAa,OAAO,aAAa,OAAO,KAAK,OAAO,SAAS,CAAC;EAClE,IAAI,aAAa,OAAO,UAAU,KAAK,OAAO,OAAO,SAAS,CAAC;EAC/D,IAAI,aAAa,OAAO,aAAa,OAAO,SAAS,CAAC,CAAC,SAAS,CAAC;EACjE,IAAI,aAAa,OAAO,WAAW,OAAO,OAAO,CAAC,CAAC,SAAS,CAAC;EAC7D,IAAI,aAAa,OAAO,UAAU,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC;EAC3D,IAAI,aAAa,OAAO,QAAQ,IAAI;EACpC,IAAI,aAAa,OAAO,qBAAqB,qBAAqB,KAAK,GAAG,CAAC;EAC3E,IAAI,aAAa,OAAO,gBAAgB,aAAa,SAAS,CAAC;EAC/D,IAAI,aAAa,OAAO,YAAY,SAAS,SAAS,CAAC;EAEvD,IAAI,OAAO,iBAAiB,WAC1B,IAAI,aAAa,OAAO,SAAS,OAAO,YAAY,CAAC;EAKvD,IAAI,SACF,IAAI,aAAa,OAAO,WAAW,KAAK,gBAAgB,OAAO,CAAC;EAGlE,IAAI,aAAa,KAAA,GACf,IAAI,aAAa,OAAO,YAAY,OAAO,QAAQ,CAAC;EAKtD,IAAI,eAAe,UACjB,IAAI,aAAa,OAAO,cAAc,UAAU;EAGlD,IAAI,KAAK,OAAO,iBACd,IAAI,aAAa,OAAO,mBAAmB,KAAK,OAAO,eAAe;EAGxE,OAAO,QAA4B,IAAI,SAAS,GAAG,EACjD,SAAS,KAAK,YAAY,EAC5B,CAAC;CACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BA,MAAM,gBACJ,SACA,SACkB;EAElB,MAAM,cAAc,MAAM,KAAK,YAAY,mBAAmB,OAAO,CAAC,CAAC,GAAG;EAG1E,OACE,OAAO,OAAO,MAAM,MACpB,aAAa,QAAQ,MAClB,UAAU,MAAM,YAAY,OAAO,OAAO,CAC7C,MAAM,KAAA;CAEV;;;;;;;;;;;;;CAcA,MAAM,sBACJ,QACgC;EAChC,MAAM,EAAE,OAAO,SAAS,aAAa;EAGrC,KAAK,gBAAgB,OAAO;EAE5B,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,WAAW,sBAAsB;EAEpE,MAAM,OAA0B;GAC9B;GACA,gBAAgB,MAAM;GACtB;EACF;EAEA,OAAO,QAA+B,IAAI,SAAS,GAAG;GACpD,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,GAAG,KAAK,YAAY;GACtB;GACA,MAAM,KAAK,UAAU,IAAI;EAC3B,CAAC;CACH;;;;;;CAOA,cAA8C;EAC5C,OAAO,GAAG,iBAAiB,KAAK,OAAO,OAAO;CAChD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCA,MAAM,SAAS,QAA8C;EAE3D,IAAI,OAAO,0BACT,MAAM,IAAI,MACR,qOAGF;EAIF,IAAI,iBAAiB,EAAE,GAAG,OAAO;EAGjC,MAAM,eAAe;GACnB,WAAW,OAAO,OAAO,SAAS;GAClC,SAAS,OAAO,OAAO,OAAO;GAC9B,QAAQ,OAAO,OAAO,MAAM;GAC5B,MAAM,OAAO,QAAS;GACtB,SAAS,OAAO,WAAW,KAAA;GAC3B,aAAa,KAAK;EACpB;EAEA,KAAK,MAAM,MAAM,KAAK,YAGpB,IAAI,MAFsB,GAAG,YAAY,YAAY,KAElC,GAAG,mBACpB,iBAAiB,MAAM,GAAG,kBAAkB,cAAc;EAI9D,MAAM,WAAW,MAAM,KAAK,WAAW,cAAc;EACrD,MAAM,EAAE,QAAQ,YAAY,GAAG,SAAS;EAExC,OAAO;GACL,GAAG;GACH,OAAO,SAAS,UAAU,KAAK,KAAK,OAAO,SAAS,KAAK;GACzD,QAAQ,OAAO,OAAO,MAAM;GAC5B,SAAS,OAAO,WAAW,KAAA;GAC3B,UAAU,eAAe;GACzB,QAAQ,UAAU,kBAAkB,MAAM;GAC1C,YAAY,cAAc,sBAAsB,UAAU;GAG1D,YAAY,eAAe,cAAc,OAAO,cAAc;GAC9D,WAAW,KAAK,IAAI;EACtB;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDA,MAAM,QAAQ,QAgBY;EACxB,MAAM,EAAE,OAAO,SAAS,UAAU,QAAQ,SAAS;EACnD,MAAM,aACJ,OAAO,cAAc,MAAM,cAAc;EAI3C,IAAI,MAAM,UAAU,eAAe,SAAS,MAAM,aAAa,UAC7D,QAAQ,KACN,MAAM,aAAa,KAAA,IACf,qEAAqE,SAAS,0EAE9E,uCAAuC,MAAM,SAAS,+BAA+B,SAAS,sDAEpG;EAGF,MAAM,eAAe,MAAM,KAAK,sBAAsB;GACpD;GACA;GACA;EACF,CAAC;EAkBD,OAAO,IAfc,aAAa;GAChC;GACA,UAAU,aAAa;GACvB,aAAa,KAAK;GAClB;GACA;GACA,YAAY,KAAK;GACjB;GACA;GAGA,oBAAoB,aAAa,eAAe;GAChD,YAAY,KAAK,OAAO;EAC1B,CAEc;CAChB;;;;CAKA,eAAuB,QAAwB;EAC7C,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,qBAAqB;EAEvC,OAAO;CACT;;;;CAKA,gBAAwB,SAAyB;EAC/C,IAAI,CAAC,eAAe,OAAO,GACzB,MAAM,IAAI,MAAM,6BAA6B,SAAS;EAExD,OAAO;CACT;;;;CAKA,iBAAyB,UAA0B;EACjD,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,WAAW,KAAK,YAAY,KAC5D,MAAM,IAAI,MAAM,qBAAqB,SAAS,0BAA0B;EAE1E,OAAO;CACT;;;;CAKA,eAAuB,QAAwB;EAC7C,IAAI,SAAS,KAAK,SAAA,KAChB,MAAM,IAAI,MACR,gBAAgB,OAAO,0CACzB;EAEF,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxSA,IAAa,uBAAb,MAA4D;CAC1D,OAAgB;CAChB,UAAmB;CAEnB;CAEA,YAAY,SAA2B,CAAC,GAAG;EACzC,KAAK,iBAAiB,IAAI,KACvB,OAAO,kBAAkB,CAAC,EAAA,CAAG,KAAK,OAAO,OAAO,EAAE,CAAC,CACtD;CACF;CAEA,MAAM,YAAY,SAAyC;EAEzD,IAAI,QAAQ,SAAS,eACnB,OAAO;EAIT,IAAI,CAAC,QAAQ,SACX,OAAO;EAIT,IAAI,QAAQ,cAAc,IACxB,OAAO;EAIT,IAAI,KAAK,eAAe,IAAI,QAAQ,SAAS,GAC3C,OAAO;EAGT,IAAI;GAOF,MAAM,gBAAe,MALK,QAAQ,YAC/B,mBAAmB,QAAQ,OAAO,CAAC,CACnC,GAAG,EAAA,CAG2B,QAAQ,MACtC,UAAU,MAAM,YAAY,QAAQ,SACvC;GAGA,IAAI,CAAC,cACH,OAAO;GAIT,OAAO,aAAa,WAAW,QAAQ;EACzC,SAAS,OAAO;GAEd,QAAQ,KACN,0DAA0D,QAAQ,QAAQ,IAC1E,KACF;GACA,OAAO;EACT;CACF;CAEA,MAAM,kBAAkB,QAAqD;EAE3E,MAAM,gBAAgB,OAAO,gBAAgB,MAAM;EAEnD,OAAO;GACL,GAAG;GACH;EACF;CACF;CAEA,MAAM,UAAU,SAAwD;EAWtE,OAAO,CACL;GACE,KAXc,kDAAkD;IAClE,QAAQ,QAAQ;IAChB,UAAU,QAAQ;IAClB,QAAQ;IACR,YAAY,QAAQ;IACpB,kBAAkB,QAAQ;IAC1B,iBAAiB,QAAQ;GAC3B,CAIiB;GACb,QAAQ,QAAQ;EAClB,CACF;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@txnlab/haystack-router",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "TypeScript/JavaScript SDK for Haystack Order Router - smart order routing and DEX aggregation on Algorand",
5
5
  "keywords": [
6
6
  "algorand",