@txnlab/haystack-router 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -9
- package/dist/{debug-8aRsrfhx.mjs → debug-CU-Ps4nm.mjs} +4 -4
- package/dist/debug-CU-Ps4nm.mjs.map +1 -0
- package/dist/index.d.mts +160 -57
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +214 -78
- package/dist/index.mjs.map +1 -1
- package/dist/{logger-DUVOA2Oo.mjs → logger-BelK6H0R.mjs} +17 -26
- package/dist/{logger-DUVOA2Oo.mjs.map → logger-BelK6H0R.mjs.map} +1 -1
- package/package.json +10 -10
- package/dist/debug-8aRsrfhx.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { t as Logger } from "./logger-
|
|
2
|
-
import { Algodv2, AtomicTransactionComposer, LogicSigAccount,
|
|
3
|
-
|
|
1
|
+
import { t as Logger } from "./logger-BelK6H0R.mjs";
|
|
2
|
+
import { Algodv2, AtomicTransactionComposer, LogicSigAccount, TransactionType, decodeUint64, decodeUnsignedTransaction, getApplicationAddress, isValidAddress, makeApplicationOptInTxnFromObject, makeAssetTransferTxnWithSuggestedParamsFromObject, msgpackRawDecode, signLogicSigTransactionObject, signTransaction } from "algosdk";
|
|
4
3
|
//#region src/constants.ts
|
|
5
4
|
/**
|
|
6
5
|
* Supported DEX protocols for swap routing
|
|
7
6
|
*/
|
|
8
|
-
let Protocol = /* @__PURE__ */ function(Protocol
|
|
9
|
-
Protocol
|
|
10
|
-
Protocol
|
|
11
|
-
Protocol
|
|
12
|
-
Protocol
|
|
13
|
-
Protocol
|
|
14
|
-
Protocol
|
|
15
|
-
return Protocol
|
|
7
|
+
let Protocol = /* @__PURE__ */ function(Protocol) {
|
|
8
|
+
Protocol["TinymanV2"] = "TinymanV2";
|
|
9
|
+
Protocol["Algofi"] = "Algofi";
|
|
10
|
+
Protocol["Algomint"] = "Algomint";
|
|
11
|
+
Protocol["Pact"] = "Pact";
|
|
12
|
+
Protocol["Folks"] = "Folks";
|
|
13
|
+
Protocol["TAlgo"] = "TAlgo";
|
|
14
|
+
return Protocol;
|
|
16
15
|
}({});
|
|
17
16
|
/**
|
|
18
17
|
* Deprecated protocols that are automatically excluded from routing
|
|
@@ -42,24 +41,65 @@ const DEFAULT_AUTO_OPT_IN = false;
|
|
|
42
41
|
const DEFAULT_DEBUG_LEVEL = "none";
|
|
43
42
|
/** Default number of rounds to wait for transaction confirmation */
|
|
44
43
|
const DEFAULT_CONFIRMATION_ROUNDS = 10;
|
|
45
|
-
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/launch.ts
|
|
46
|
+
/**
|
|
47
|
+
* Normalize a quote's raw launch object, coercing amounts to bigint and
|
|
48
|
+
* defaulting the fields the API omits when they don't apply
|
|
49
|
+
*/
|
|
50
|
+
function toSwapLaunchQuote(launch) {
|
|
51
|
+
return {
|
|
52
|
+
side: launch.side,
|
|
53
|
+
tokenNum: launch.tokenNum,
|
|
54
|
+
bondingTokenId: launch.bondingTokenId,
|
|
55
|
+
bondingSurplus: BigInt(launch.bondingSurplus),
|
|
56
|
+
refund: BigInt(launch.refund ?? 0),
|
|
57
|
+
graduates: launch.graduates ?? false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
46
60
|
//#endregion
|
|
47
61
|
//#region src/composer.ts
|
|
62
|
+
/** ABI selectors of the HayLaunch bonding-curve buy methods (buyWithLimit, buyWithAlgoWithLimit) */
|
|
63
|
+
const LAUNCH_BUY_SELECTORS = /* @__PURE__ */ new Set(["9068a8d9", "b0e30517"]);
|
|
64
|
+
/** ABI selector of the HayLaunch bonding-curve sell method (sellWithLimit) */
|
|
65
|
+
const LAUNCH_SELL_SELECTOR = "028a0647";
|
|
66
|
+
/** Prefix of the log that carries an ARC-4 method's return value */
|
|
67
|
+
const ARC4_RETURN_PREFIX = "151f7c75";
|
|
68
|
+
/**
|
|
69
|
+
* Returns which side of a HayLaunch bonding-curve trade an app call is, or
|
|
70
|
+
* undefined if it isn't a curve buy or sell call
|
|
71
|
+
*/
|
|
72
|
+
function launchCurveSide(txn) {
|
|
73
|
+
const selector = txn.applicationCall?.appArgs[0];
|
|
74
|
+
if (txn.type !== TransactionType.appl || !selector) return void 0;
|
|
75
|
+
const hex = Buffer.from(selector).toString("hex");
|
|
76
|
+
if (LAUNCH_BUY_SELECTORS.has(hex)) return "buy";
|
|
77
|
+
if (hex === LAUNCH_SELL_SELECTOR) return "sell";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Reads a uint64 ARC-4 return value from an app call's logs (the last log,
|
|
81
|
+
* prefixed with 151f7c75)
|
|
82
|
+
*/
|
|
83
|
+
function readUint64Return(logs) {
|
|
84
|
+
const log = logs?.[logs.length - 1];
|
|
85
|
+
if (!log || log.length !== 12 || Buffer.from(log.subarray(0, 4)).toString("hex") !== ARC4_RETURN_PREFIX) return;
|
|
86
|
+
return decodeUint64(log.subarray(4), "bigint");
|
|
87
|
+
}
|
|
48
88
|
/**
|
|
49
89
|
* Status of the SwapComposer transaction group lifecycle
|
|
50
90
|
*/
|
|
51
|
-
let SwapComposerStatus = /* @__PURE__ */ function(SwapComposerStatus
|
|
91
|
+
let SwapComposerStatus = /* @__PURE__ */ function(SwapComposerStatus) {
|
|
52
92
|
/** The atomic group is still under construction. */
|
|
53
|
-
SwapComposerStatus
|
|
93
|
+
SwapComposerStatus[SwapComposerStatus["BUILDING"] = 0] = "BUILDING";
|
|
54
94
|
/** The atomic group has been finalized, but not yet signed. */
|
|
55
|
-
SwapComposerStatus
|
|
95
|
+
SwapComposerStatus[SwapComposerStatus["BUILT"] = 1] = "BUILT";
|
|
56
96
|
/** The atomic group has been finalized and signed, but not yet submitted to the network. */
|
|
57
|
-
SwapComposerStatus
|
|
97
|
+
SwapComposerStatus[SwapComposerStatus["SIGNED"] = 2] = "SIGNED";
|
|
58
98
|
/** The atomic group has been finalized, signed, and submitted to the network. */
|
|
59
|
-
SwapComposerStatus
|
|
99
|
+
SwapComposerStatus[SwapComposerStatus["SUBMITTED"] = 3] = "SUBMITTED";
|
|
60
100
|
/** The atomic group has been finalized, signed, submitted, and successfully committed to a block. */
|
|
61
|
-
SwapComposerStatus
|
|
62
|
-
return SwapComposerStatus
|
|
101
|
+
SwapComposerStatus[SwapComposerStatus["COMMITTED"] = 4] = "COMMITTED";
|
|
102
|
+
return SwapComposerStatus;
|
|
63
103
|
}({});
|
|
64
104
|
/**
|
|
65
105
|
* Composer for building and executing atomic swap transaction groups
|
|
@@ -96,6 +136,7 @@ var SwapComposer = class SwapComposer {
|
|
|
96
136
|
note;
|
|
97
137
|
inputTransactionIndex;
|
|
98
138
|
outputTransactionIndex;
|
|
139
|
+
curveTransactionIndex;
|
|
99
140
|
/** Summary data built incrementally during swap composition */
|
|
100
141
|
summaryData;
|
|
101
142
|
/**
|
|
@@ -207,15 +248,16 @@ var SwapComposer = class SwapComposer {
|
|
|
207
248
|
*/
|
|
208
249
|
async addSwapTransactions() {
|
|
209
250
|
if (this.swapTransactionsAdded) throw new Error("Swap transactions have already been added");
|
|
210
|
-
if (this.getStatus() !==
|
|
251
|
+
if (this.getStatus() !== 0) throw new Error("Cannot add swap transactions when composer status is not BUILDING");
|
|
211
252
|
const beforeTxns = await this.executeMiddlewareHooks("beforeSwap");
|
|
212
253
|
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}`);
|
|
213
254
|
for (const txnWithSigner of beforeTxns) this.atc.addTransaction(txnWithSigner);
|
|
214
|
-
const { txns: processedTxns, inputTxnRelativeIndex, outputTxnRelativeIndex } = await this.processSwapTransactions();
|
|
255
|
+
const { txns: processedTxns, inputTxnRelativeIndex, outputTxnRelativeIndex, curveTxnRelativeIndex } = await this.processSwapTransactions();
|
|
215
256
|
const afterTxns = await this.executeMiddlewareHooks("afterSwap");
|
|
216
257
|
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}`);
|
|
217
258
|
if (inputTxnRelativeIndex !== void 0) this.inputTransactionIndex = this.atc.count() + inputTxnRelativeIndex;
|
|
218
259
|
if (outputTxnRelativeIndex !== void 0) this.outputTransactionIndex = this.atc.count() + outputTxnRelativeIndex;
|
|
260
|
+
if (curveTxnRelativeIndex !== void 0) this.curveTransactionIndex = this.atc.count() + curveTxnRelativeIndex;
|
|
219
261
|
for (const txnWithSigner of processedTxns) this.atc.addTransaction(txnWithSigner);
|
|
220
262
|
for (const txnWithSigner of afterTxns) this.atc.addTransaction(txnWithSigner);
|
|
221
263
|
this.swapTransactionsAdded = true;
|
|
@@ -261,7 +303,7 @@ var SwapComposer = class SwapComposer {
|
|
|
261
303
|
* ```
|
|
262
304
|
*/
|
|
263
305
|
async sign() {
|
|
264
|
-
if (this.getStatus() >=
|
|
306
|
+
if (this.getStatus() >= 2) return this.atc.gatherSignatures();
|
|
265
307
|
if (!this.swapTransactionsAdded) await this.addSwapTransactions();
|
|
266
308
|
return await this.atc.gatherSignatures();
|
|
267
309
|
}
|
|
@@ -302,13 +344,14 @@ var SwapComposer = class SwapComposer {
|
|
|
302
344
|
* console.log('Transaction IDs:', result.txIds)
|
|
303
345
|
* ```
|
|
304
346
|
*/
|
|
305
|
-
async execute(waitRounds =
|
|
347
|
+
async execute(waitRounds = 10) {
|
|
306
348
|
try {
|
|
307
349
|
if (!this.swapTransactionsAdded) await this.addSwapTransactions();
|
|
308
350
|
const { txIDs, ...result } = await this.atc.execute(this.algodClient, waitRounds);
|
|
309
351
|
if (this.summaryData) {
|
|
310
352
|
if (this.inputTransactionIndex !== void 0) this.summaryData.inputTxnId = txIDs[this.inputTransactionIndex];
|
|
311
353
|
if (this.outputTransactionIndex !== void 0) this.summaryData.outputTxnId = txIDs[this.outputTransactionIndex];
|
|
354
|
+
if (this.summaryData.launch && this.curveTransactionIndex !== void 0) this.summaryData.launch.curveTxnId = txIDs[this.curveTransactionIndex];
|
|
312
355
|
}
|
|
313
356
|
await this.extractActualOutputAmount();
|
|
314
357
|
return {
|
|
@@ -316,7 +359,7 @@ var SwapComposer = class SwapComposer {
|
|
|
316
359
|
txIds: txIDs
|
|
317
360
|
};
|
|
318
361
|
} catch (error) {
|
|
319
|
-
const { logSwapExecutionFailure } = await import("./debug-
|
|
362
|
+
const { logSwapExecutionFailure } = await import("./debug-CU-Ps4nm.mjs");
|
|
320
363
|
logSwapExecutionFailure({
|
|
321
364
|
quote: this.quote,
|
|
322
365
|
address: this.address,
|
|
@@ -357,7 +400,7 @@ var SwapComposer = class SwapComposer {
|
|
|
357
400
|
* ```
|
|
358
401
|
*/
|
|
359
402
|
getInputTransactionId() {
|
|
360
|
-
if (this.getStatus() <
|
|
403
|
+
if (this.getStatus() < 1) return;
|
|
361
404
|
if (this.inputTransactionIndex === void 0) return;
|
|
362
405
|
return (this.atc.buildGroup()[this.inputTransactionIndex]?.txn)?.txID();
|
|
363
406
|
}
|
|
@@ -388,6 +431,8 @@ var SwapComposer = class SwapComposer {
|
|
|
388
431
|
if (!this.summaryData || this.summaryData.outputAmount === void 0 || this.summaryData.inputTxnId === void 0 || this.summaryData.outputTxnId === void 0 || this.summaryData.outputSender === void 0) return;
|
|
389
432
|
const txns = this.atc.buildGroup();
|
|
390
433
|
const totalFees = txns.reduce((sum, tws) => sum + tws.txn.fee, 0n);
|
|
434
|
+
const launch = this.summaryData.launch;
|
|
435
|
+
const curveTxnId = launch?.curveTxnId;
|
|
391
436
|
return {
|
|
392
437
|
inputAssetId: this.summaryData.inputAssetId,
|
|
393
438
|
outputAssetId: this.summaryData.outputAssetId,
|
|
@@ -396,10 +441,17 @@ var SwapComposer = class SwapComposer {
|
|
|
396
441
|
type: this.quote.type,
|
|
397
442
|
totalFees,
|
|
398
443
|
transactionCount: txns.length,
|
|
399
|
-
inputTxnId: this.summaryData.inputTxnId,
|
|
444
|
+
inputTxnId: launch?.side === "sell" && curveTxnId ? curveTxnId : this.summaryData.inputTxnId,
|
|
400
445
|
outputTxnId: this.summaryData.outputTxnId,
|
|
401
446
|
inputSender: this.summaryData.inputSender,
|
|
402
|
-
outputSender: this.summaryData.outputSender
|
|
447
|
+
outputSender: this.summaryData.outputSender,
|
|
448
|
+
...launch && curveTxnId && { launch: {
|
|
449
|
+
side: launch.side,
|
|
450
|
+
tokenNum: launch.tokenNum,
|
|
451
|
+
curveTxnId,
|
|
452
|
+
bondingAmount: launch.bondingAmount,
|
|
453
|
+
refund: launch.refund
|
|
454
|
+
} }
|
|
403
455
|
};
|
|
404
456
|
}
|
|
405
457
|
/**
|
|
@@ -416,32 +468,25 @@ var SwapComposer = class SwapComposer {
|
|
|
416
468
|
*/
|
|
417
469
|
async processSwapTransactions() {
|
|
418
470
|
const appOptIns = await this.processRequiredAppOptIns();
|
|
419
|
-
const
|
|
420
|
-
let inputTxnRelativeIndex;
|
|
421
|
-
let inputTxn;
|
|
422
|
-
for (let i = 0; i < this.swapTxns.length; i++) {
|
|
423
|
-
const swapTxn = this.swapTxns[i];
|
|
424
|
-
if (!swapTxn) continue;
|
|
471
|
+
const decoded = this.swapTxns.map((swapTxn, i) => {
|
|
425
472
|
try {
|
|
426
473
|
const txn = decodeUnsignedTransaction(Buffer.from(swapTxn.data, "base64"));
|
|
427
474
|
delete txn.group;
|
|
428
|
-
|
|
475
|
+
return {
|
|
429
476
|
txn,
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
if (this.note !== void 0) txn.note = this.note;
|
|
434
|
-
inputTxnRelativeIndex = appOptIns.length + swapTxns.length;
|
|
435
|
-
inputTxn = txn;
|
|
436
|
-
swapTxns.push({
|
|
437
|
-
txn,
|
|
438
|
-
signer: this.defaultSigner
|
|
439
|
-
});
|
|
440
|
-
}
|
|
477
|
+
signature: swapTxn.signature
|
|
478
|
+
};
|
|
441
479
|
} catch (error) {
|
|
442
|
-
throw new Error(`Failed to process swap transaction at index ${i}: ${error instanceof Error ? error.message : String(error)}
|
|
480
|
+
throw new Error(`Failed to process swap transaction at index ${i}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
443
481
|
}
|
|
444
|
-
}
|
|
482
|
+
});
|
|
483
|
+
const inputIndex = this.findInputTransactionIndex(decoded);
|
|
484
|
+
const inputTxn = inputIndex !== void 0 ? decoded[inputIndex]?.txn : void 0;
|
|
485
|
+
if (inputTxn && this.note !== void 0) inputTxn.note = this.note;
|
|
486
|
+
const swapTxns = decoded.map(({ txn, signature }) => ({
|
|
487
|
+
txn,
|
|
488
|
+
signer: signature === false ? this.defaultSigner : this.createSwapSigner(signature)
|
|
489
|
+
}));
|
|
445
490
|
if (inputTxn) {
|
|
446
491
|
const paymentAmount = inputTxn.payment?.amount;
|
|
447
492
|
const assetTransferAmount = inputTxn.assetTransfer?.amount;
|
|
@@ -453,12 +498,63 @@ var SwapComposer = class SwapComposer {
|
|
|
453
498
|
inputSender: this.address
|
|
454
499
|
};
|
|
455
500
|
}
|
|
456
|
-
const
|
|
501
|
+
const curveIndex = this.initLaunchSummary(decoded);
|
|
502
|
+
const outputIndex = this.summaryData?.launch?.side === "buy" && curveIndex !== void 0 ? curveIndex : swapTxns.length - 1;
|
|
503
|
+
const outputTxnRelativeIndex = outputIndex >= 0 ? appOptIns.length + outputIndex : void 0;
|
|
457
504
|
return {
|
|
458
505
|
txns: [...appOptIns, ...swapTxns],
|
|
459
|
-
inputTxnRelativeIndex,
|
|
460
|
-
outputTxnRelativeIndex
|
|
506
|
+
inputTxnRelativeIndex: inputIndex !== void 0 ? appOptIns.length + inputIndex : void 0,
|
|
507
|
+
outputTxnRelativeIndex,
|
|
508
|
+
curveTxnRelativeIndex: curveIndex !== void 0 ? appOptIns.length + curveIndex : void 0
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Finds the swap's input transaction: the user's payment or asset transfer
|
|
513
|
+
* to the router app, whose address comes from the router's pre-signed app
|
|
514
|
+
* calls. Falls back to the last user-signed transaction for groups without
|
|
515
|
+
* a router app call.
|
|
516
|
+
*/
|
|
517
|
+
findInputTransactionIndex(txns) {
|
|
518
|
+
const routerAppId = txns.find(({ txn, signature }) => signature !== false && txn.type === TransactionType.appl)?.txn.applicationCall?.appIndex;
|
|
519
|
+
if (routerAppId !== void 0) {
|
|
520
|
+
const routerAddress = getApplicationAddress(routerAppId).toString();
|
|
521
|
+
const index = txns.findIndex(({ txn, signature }) => signature === false && (txn.payment?.receiver ?? txn.assetTransfer?.receiver)?.toString() === routerAddress);
|
|
522
|
+
if (index !== -1) return index;
|
|
523
|
+
}
|
|
524
|
+
for (let i = txns.length - 1; i >= 0; i--) if (txns[i]?.signature === false) return i;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* For a launch quote, finds the user-signed curve buy or sell call and
|
|
528
|
+
* records the curve leg in summaryData. On a sell, the input amount becomes
|
|
529
|
+
* the launch tokens sold.
|
|
530
|
+
*
|
|
531
|
+
* @returns The curve call's index within the swap transactions, if found
|
|
532
|
+
*/
|
|
533
|
+
initLaunchSummary(txns) {
|
|
534
|
+
const side = this.quote.launch?.side;
|
|
535
|
+
if (!side || !this.summaryData) return void 0;
|
|
536
|
+
const curveIndex = txns.findIndex(({ txn, signature }) => signature === false && launchCurveSide(txn) === side);
|
|
537
|
+
const curveCall = txns[curveIndex]?.txn.applicationCall;
|
|
538
|
+
const tokenNumArg = curveCall?.appArgs[1];
|
|
539
|
+
if (!curveCall || !tokenNumArg) return void 0;
|
|
540
|
+
const appAddress = getApplicationAddress(curveCall.appIndex).toString();
|
|
541
|
+
let bondingAmount;
|
|
542
|
+
if (side === "buy") {
|
|
543
|
+
const payment = txns[curveIndex - 1]?.txn;
|
|
544
|
+
if ((payment?.payment?.receiver ?? payment?.assetTransfer?.receiver)?.toString() === appAddress) bondingAmount = payment?.payment?.amount ?? payment?.assetTransfer?.amount;
|
|
545
|
+
} else {
|
|
546
|
+
const tokensToSell = curveCall.appArgs[2];
|
|
547
|
+
if (tokensToSell) this.summaryData.inputAmount = decodeUint64(tokensToSell, "bigint");
|
|
548
|
+
}
|
|
549
|
+
this.summaryData.launch = {
|
|
550
|
+
side,
|
|
551
|
+
tokenNum: decodeUint64(tokenNumArg, "bigint"),
|
|
552
|
+
bondingTokenId: BigInt(this.quote.launch?.bondingTokenId ?? 0),
|
|
553
|
+
appAddress,
|
|
554
|
+
bondingAmount,
|
|
555
|
+
refund: 0n
|
|
461
556
|
};
|
|
557
|
+
return curveIndex;
|
|
462
558
|
}
|
|
463
559
|
/**
|
|
464
560
|
* Creates opt-in transactions for apps the user hasn't opted into yet
|
|
@@ -502,16 +598,19 @@ var SwapComposer = class SwapComposer {
|
|
|
502
598
|
try {
|
|
503
599
|
if (signature.type === "logic_signature") {
|
|
504
600
|
const valueArray = signature.value;
|
|
505
|
-
const
|
|
601
|
+
const valueBytes = new Uint8Array(Object.values(valueArray));
|
|
602
|
+
const decoded = msgpackRawDecode(valueBytes);
|
|
506
603
|
if (!decoded.lsig) throw new Error("Logic signature structure missing lsig field");
|
|
507
604
|
const lsig = decoded.lsig;
|
|
508
|
-
|
|
605
|
+
const logicSigAccount = new LogicSigAccount(lsig.l, lsig.arg);
|
|
606
|
+
return signLogicSigTransactionObject(transaction, logicSigAccount).blob;
|
|
509
607
|
} else if (signature.type === "secret_key") {
|
|
510
608
|
const valueArray = signature.value;
|
|
511
|
-
|
|
609
|
+
const secretKey = new Uint8Array(Object.values(valueArray));
|
|
610
|
+
return signTransaction(transaction, secretKey).blob;
|
|
512
611
|
} else throw new Error(`Unsupported signature type: ${signature.type}`);
|
|
513
612
|
} catch (error) {
|
|
514
|
-
throw new Error(`Failed to re-sign transaction: ${error instanceof Error ? error.message : String(error)}
|
|
613
|
+
throw new Error(`Failed to re-sign transaction: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
515
614
|
}
|
|
516
615
|
}
|
|
517
616
|
/**
|
|
@@ -523,6 +622,7 @@ var SwapComposer = class SwapComposer {
|
|
|
523
622
|
...this.quote,
|
|
524
623
|
quote: this.quote.quote === "" ? 0n : BigInt(this.quote.quote),
|
|
525
624
|
amount: 0n,
|
|
625
|
+
launch: this.quote.launch && toSwapLaunchQuote(this.quote.launch),
|
|
526
626
|
createdAt: Date.now()
|
|
527
627
|
};
|
|
528
628
|
const quoteContext = {
|
|
@@ -554,16 +654,32 @@ var SwapComposer = class SwapComposer {
|
|
|
554
654
|
* Extract the actual output amount from the confirmed output transaction's inner transactions
|
|
555
655
|
*
|
|
556
656
|
* Analyzes only the output transaction (last app call in the swap) to find the
|
|
557
|
-
* inner transaction that transfers the output asset to the user.
|
|
657
|
+
* inner transaction that transfers the output asset to the user. On a launch
|
|
658
|
+
* buy the output is a balance on the HayLaunch app, so it is read from the
|
|
659
|
+
* curve buy call's return value instead.
|
|
558
660
|
*/
|
|
559
661
|
async extractActualOutputAmount() {
|
|
560
662
|
if (!this.summaryData?.outputTxnId) return;
|
|
561
663
|
const outputAssetId = this.summaryData.outputAssetId;
|
|
562
664
|
const userAddress = this.address;
|
|
665
|
+
const launch = this.summaryData.launch;
|
|
563
666
|
try {
|
|
564
|
-
const
|
|
565
|
-
if (
|
|
566
|
-
|
|
667
|
+
const pendingInfo = await this.algodClient.pendingTransactionInformation(this.summaryData.outputTxnId).do();
|
|
668
|
+
if (launch?.side === "buy") {
|
|
669
|
+
const tokensCredited = readUint64Return(pendingInfo.logs);
|
|
670
|
+
if (tokensCredited !== void 0) {
|
|
671
|
+
this.summaryData.outputAmount = tokensCredited;
|
|
672
|
+
this.summaryData.outputSender = launch.appAddress;
|
|
673
|
+
}
|
|
674
|
+
for (const innerTxn of pendingInfo.innerTxns ?? []) {
|
|
675
|
+
const txn = innerTxn.txn.txn;
|
|
676
|
+
if (launch.bondingTokenId === 0n) {
|
|
677
|
+
if (txn.payment?.receiver.toString() === userAddress) launch.refund += txn.payment.amount;
|
|
678
|
+
} else if (txn.assetTransfer?.receiver.toString() === userAddress && txn.assetTransfer.assetIndex === launch.bondingTokenId) launch.refund += txn.assetTransfer.amount;
|
|
679
|
+
}
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
for (const innerTxn of pendingInfo.innerTxns ?? []) {
|
|
567
683
|
const txn = innerTxn.txn.txn;
|
|
568
684
|
const payment = txn.payment;
|
|
569
685
|
const assetTransfer = txn.assetTransfer;
|
|
@@ -582,15 +698,20 @@ var SwapComposer = class SwapComposer {
|
|
|
582
698
|
}
|
|
583
699
|
}
|
|
584
700
|
} catch {}
|
|
701
|
+
if (launch?.side === "sell" && launch.curveTxnId) try {
|
|
702
|
+
launch.bondingAmount = readUint64Return((await this.algodClient.pendingTransactionInformation(launch.curveTxnId).do()).logs);
|
|
703
|
+
} catch {}
|
|
585
704
|
}
|
|
586
705
|
};
|
|
587
|
-
|
|
588
706
|
//#endregion
|
|
589
707
|
//#region src/utils.ts
|
|
590
708
|
/**
|
|
591
709
|
* HTTP error with status code and response data
|
|
592
710
|
*/
|
|
593
711
|
var HTTPError = class extends Error {
|
|
712
|
+
status;
|
|
713
|
+
statusText;
|
|
714
|
+
data;
|
|
594
715
|
constructor(status, statusText, data) {
|
|
595
716
|
super(`HTTP ${status} ${statusText}`);
|
|
596
717
|
this.status = status;
|
|
@@ -627,7 +748,6 @@ async function request(url, options) {
|
|
|
627
748
|
}
|
|
628
749
|
return response.json();
|
|
629
750
|
}
|
|
630
|
-
|
|
631
751
|
//#endregion
|
|
632
752
|
//#region src/client.ts
|
|
633
753
|
/**
|
|
@@ -640,14 +760,14 @@ async function request(url, options) {
|
|
|
640
760
|
* @example
|
|
641
761
|
* ```typescript
|
|
642
762
|
* const router = new RouterClient({
|
|
643
|
-
* apiKey: '
|
|
763
|
+
* apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
644
764
|
* })
|
|
645
765
|
* ```
|
|
646
766
|
*
|
|
647
767
|
* @example
|
|
648
768
|
* ```typescript
|
|
649
769
|
* const router = new RouterClient({
|
|
650
|
-
* apiKey: '
|
|
770
|
+
* apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
651
771
|
* apiBaseUrl: 'https://hayrouter.txnlab.dev',
|
|
652
772
|
* algodUri: 'https://mainnet-api.4160.nodely.dev/',
|
|
653
773
|
* algodToken: '',
|
|
@@ -678,17 +798,17 @@ var RouterClient = class {
|
|
|
678
798
|
* @param config.middleware - Array of middleware to apply to swaps (default: [])
|
|
679
799
|
*/
|
|
680
800
|
constructor(config) {
|
|
681
|
-
const debugLevel = config.debugLevel ??
|
|
801
|
+
const debugLevel = config.debugLevel ?? "none";
|
|
682
802
|
Logger.setLevel(debugLevel);
|
|
683
803
|
this.config = {
|
|
684
804
|
apiKey: this.validateApiKey(config.apiKey),
|
|
685
|
-
apiBaseUrl: config.apiBaseUrl ??
|
|
686
|
-
algodUri: config.algodUri ??
|
|
687
|
-
algodToken: config.algodToken ??
|
|
688
|
-
algodPort: config.algodPort ??
|
|
805
|
+
apiBaseUrl: config.apiBaseUrl ?? "https://hayrouter.txnlab.dev/api",
|
|
806
|
+
algodUri: config.algodUri ?? "https://mainnet-api.4160.nodely.dev/",
|
|
807
|
+
algodToken: config.algodToken ?? "",
|
|
808
|
+
algodPort: config.algodPort ?? 443,
|
|
689
809
|
referrerAddress: config.referrerAddress ? this.validateAddress(config.referrerAddress) : void 0,
|
|
690
|
-
feeBps: this.validateFeeBps(config.feeBps ??
|
|
691
|
-
autoOptIn: config.autoOptIn ??
|
|
810
|
+
feeBps: this.validateFeeBps(config.feeBps ?? 10),
|
|
811
|
+
autoOptIn: config.autoOptIn ?? false,
|
|
692
812
|
debugLevel
|
|
693
813
|
};
|
|
694
814
|
this.algodClient = new Algodv2(this.config.algodToken, this.config.algodUri, this.config.algodPort);
|
|
@@ -713,6 +833,7 @@ var RouterClient = class {
|
|
|
713
833
|
* - If true: API reduces maxGroupSize by 1 and includes opt-in (always included, even if not needed)
|
|
714
834
|
* - If false: No opt-in transaction included
|
|
715
835
|
* - If undefined: Falls back to `config.autoOptIn` behavior with account check (if `params.address` is provided)
|
|
836
|
+
* @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
|
|
716
837
|
* @returns A FetchQuoteResponse object with routing information
|
|
717
838
|
*
|
|
718
839
|
* @example
|
|
@@ -726,12 +847,15 @@ var RouterClient = class {
|
|
|
726
847
|
* ```
|
|
727
848
|
*/
|
|
728
849
|
async fetchQuote(params) {
|
|
729
|
-
const { fromASAID, toASAID, amount, type = "fixed-input", disabledProtocols = [], maxGroupSize =
|
|
850
|
+
const { fromASAID, toASAID, amount, type = "fixed-input", disabledProtocols = [], maxGroupSize = 16, maxDepth = 4, optIn, address, slippage, _allowNonComposableSwaps = false } = params;
|
|
851
|
+
if (slippage !== void 0) this.validateSlippage(slippage);
|
|
730
852
|
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.");
|
|
731
|
-
const allDisabledProtocols = _allowNonComposableSwaps ? [
|
|
853
|
+
const allDisabledProtocols = _allowNonComposableSwaps ? [.../* @__PURE__ */ new Set(["Humble", ...disabledProtocols])] : [.../* @__PURE__ */ new Set([...DEPRECATED_PROTOCOLS, ...disabledProtocols])];
|
|
732
854
|
let includeOptIn = optIn;
|
|
733
|
-
if (includeOptIn === void 0 && this.config.autoOptIn)
|
|
734
|
-
|
|
855
|
+
if (includeOptIn === void 0 && this.config.autoOptIn) {
|
|
856
|
+
if (address) includeOptIn = await this.needsAssetOptIn(this.validateAddress(address), toASAID);
|
|
857
|
+
else console.warn("autoOptIn is enabled but no address provided to fetchQuote(). Asset opt-in check skipped.");
|
|
858
|
+
}
|
|
735
859
|
const url = new URL(`${this.config.apiBaseUrl}/fetchQuote`);
|
|
736
860
|
url.searchParams.append("apiKey", this.config.apiKey);
|
|
737
861
|
url.searchParams.append("algodUri", this.config.algodUri);
|
|
@@ -746,6 +870,7 @@ var RouterClient = class {
|
|
|
746
870
|
url.searchParams.append("maxGroupSize", maxGroupSize.toString());
|
|
747
871
|
url.searchParams.append("maxDepth", maxDepth.toString());
|
|
748
872
|
if (typeof includeOptIn === "boolean") url.searchParams.append("optIn", String(includeOptIn));
|
|
873
|
+
if (slippage !== void 0) url.searchParams.append("slippage", String(slippage));
|
|
749
874
|
if (this.config.referrerAddress) url.searchParams.append("referrerAddress", this.config.referrerAddress);
|
|
750
875
|
return request(url.toString());
|
|
751
876
|
}
|
|
@@ -824,6 +949,7 @@ var RouterClient = class {
|
|
|
824
949
|
* @param params.maxDepth - The maximum depth (default: 4)
|
|
825
950
|
* @param params.address - The address of the account that will perform the swap (recommended when using `config.autoOptIn` or `params.optIn`)
|
|
826
951
|
* @param params.optIn - Whether to include asset opt-in transaction
|
|
952
|
+
* @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
|
|
827
953
|
* @returns A SwapQuote enhanced quote result
|
|
828
954
|
*
|
|
829
955
|
* @example
|
|
@@ -855,11 +981,14 @@ var RouterClient = class {
|
|
|
855
981
|
};
|
|
856
982
|
for (const mw of this.middleware) if (await mw.shouldApply(quoteContext) && mw.adjustQuoteParams) adjustedParams = await mw.adjustQuoteParams(adjustedParams);
|
|
857
983
|
const response = await this.fetchQuote(adjustedParams);
|
|
984
|
+
const { launch, ...rest } = response;
|
|
858
985
|
return {
|
|
859
|
-
...
|
|
986
|
+
...rest,
|
|
860
987
|
quote: response.quote === "" ? 0n : BigInt(response.quote),
|
|
861
988
|
amount: BigInt(params.amount),
|
|
862
989
|
address: params.address ?? void 0,
|
|
990
|
+
slippage: adjustedParams.slippage,
|
|
991
|
+
launch: launch && toSwapLaunchQuote(launch),
|
|
863
992
|
createdAt: Date.now()
|
|
864
993
|
};
|
|
865
994
|
}
|
|
@@ -913,6 +1042,7 @@ var RouterClient = class {
|
|
|
913
1042
|
*/
|
|
914
1043
|
async newSwap(config) {
|
|
915
1044
|
const { quote, address, slippage, signer, note } = config;
|
|
1045
|
+
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.`);
|
|
916
1046
|
return new SwapComposer({
|
|
917
1047
|
quote,
|
|
918
1048
|
swapTxns: (await this.fetchSwapTransactions({
|
|
@@ -943,14 +1073,20 @@ var RouterClient = class {
|
|
|
943
1073
|
return address;
|
|
944
1074
|
}
|
|
945
1075
|
/**
|
|
1076
|
+
* Validates a slippage percentage (0 <= slippage < 100)
|
|
1077
|
+
*/
|
|
1078
|
+
validateSlippage(slippage) {
|
|
1079
|
+
if (!Number.isFinite(slippage) || slippage < 0 || slippage >= 100) throw new Error(`Invalid slippage: ${slippage} (must be >= 0 and < 100)`);
|
|
1080
|
+
return slippage;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
946
1083
|
* Validates the fee in basis points (max 300 = 3.00%)
|
|
947
1084
|
*/
|
|
948
1085
|
validateFeeBps(feeBps) {
|
|
949
|
-
if (feeBps < 0 || feeBps >
|
|
1086
|
+
if (feeBps < 0 || feeBps > 300) throw new Error(`Invalid fee: ${feeBps} basis points (must be between 0 and 300)`);
|
|
950
1087
|
return feeBps;
|
|
951
1088
|
}
|
|
952
1089
|
};
|
|
953
|
-
|
|
954
1090
|
//#endregion
|
|
955
1091
|
//#region src/middleware.ts
|
|
956
1092
|
/**
|
|
@@ -966,7 +1102,7 @@ var RouterClient = class {
|
|
|
966
1102
|
* })
|
|
967
1103
|
*
|
|
968
1104
|
* const router = new RouterClient({
|
|
969
|
-
* apiKey: '
|
|
1105
|
+
* apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
970
1106
|
* middleware: [autoOptOut],
|
|
971
1107
|
* })
|
|
972
1108
|
*
|
|
@@ -1022,7 +1158,7 @@ var AutoOptOutMiddleware = class {
|
|
|
1022
1158
|
}];
|
|
1023
1159
|
}
|
|
1024
1160
|
};
|
|
1025
|
-
|
|
1026
1161
|
//#endregion
|
|
1027
1162
|
export { 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 };
|
|
1163
|
+
|
|
1028
1164
|
//# sourceMappingURL=index.mjs.map
|