@@ -24,6 +24,24 @@ import {
2424import { Bytes } from "../types" ;
2525import { Holding } from "./iface" ;
2626
27+ // Legacy per-holding shape used by the deprecated `*ByRecipient` positional fns. `recipient`
28+ // historically doubled as both swap executor (taker) and output destination (receiver) — that
29+ // conflation is the root of GS013-class bugs. New code should pass `HoldingWithSwapAddresses`
30+ // to the new wrappers below; this type stays for backwards compat. The optional
31+ // `receiverAddress` lets the new wrappers thread per-holding receiver through legacy code.
32+ export type HoldingWithRecipient = Holding & {
33+ recipient : Bytes ;
34+ receiverAddress ?: Bytes ;
35+ } ;
36+
37+ // Per-holding shape for the new wrappers. Both `takerAddress` and `receiverAddress` are
38+ // required — even when source-side has them equal today, keeping both explicit forces every
39+ // call site to acknowledge each role.
40+ export type HoldingWithSwapAddresses = Holding & {
41+ takerAddress : Bytes ;
42+ receiverAddress : Bytes ;
43+ } ;
44+
2745export class AutoSelectionError extends Error { }
2846const safetyMultiplier = new Decimal ( "1.025" ) ;
2947
@@ -143,6 +161,10 @@ cots = [(1 COT, 1), (1 COT, 4)]
1431615. return quotes and assets used.
144162*/
145163
164+ /**
165+ * @deprecated Use {@link autoSelectSources} (object args; per-holding `takerAddress` and
166+ * `receiverAddress` on each `HoldingWithSwapAddresses`).
167+ */
146168export async function autoSelectSourcesV2 (
147169 userAddress : Bytes ,
148170 holdings : Holding [ ] ,
@@ -157,6 +179,32 @@ export async function autoSelectSourcesV2(
157179 idx : number ;
158180 cur : Currency ;
159181 } [ ] ;
182+ } > {
183+ return autoSelectSourcesV2ByRecipient (
184+ holdings . map ( ( holding ) => ( { ...holding , recipient : userAddress } ) ) ,
185+ outputRequired ,
186+ aggregators ,
187+ commonCurrencyID ,
188+ ) ;
189+ }
190+
191+ /**
192+ * @deprecated Use {@link autoSelectSources} (object args; per-holding `takerAddress` and
193+ * `receiverAddress` on each `HoldingWithSwapAddresses`).
194+ */
195+ export async function autoSelectSourcesV2ByRecipient (
196+ holdings : HoldingWithRecipient [ ] ,
197+ outputRequired : Decimal ,
198+ aggregators : Aggregator [ ] ,
199+ commonCurrencyID : CurrencyID = CurrencyID . USDC ,
200+ ) : Promise < {
201+ quoteResponses : QuoteResponse [ ] ;
202+ usedCOTs : {
203+ originalHolding : Holding ;
204+ amountUsed : Decimal ;
205+ idx : number ;
206+ cur : Currency ;
207+ } [ ] ;
160208} > {
161209 // Assumption: Holding is already sorted in usage priority
162210 console . debug ( "XCS | SSV2:" , {
@@ -217,7 +265,10 @@ export async function autoSelectSourcesV2(
217265 } else {
218266 fullLiquidationQuotes . push ( {
219267 req : {
220- userAddress,
268+ userAddress : holding . recipient ,
269+ // New wrappers thread per-holding receiver via this field. Falls back to taker
270+ // (`recipient`) when absent — preserves legacy positional-call behavior.
271+ receiverAddress : holding . receiverAddress ,
221272 type : QuoteType . EXACT_IN ,
222273 chain : chain . ChainID ,
223274 inputToken : holding . tokenAddress ,
@@ -460,11 +511,16 @@ export async function autoSelectSourcesV2(
460511 return { quoteResponses : final , usedCOTs } ;
461512}
462513
514+ /**
515+ * @deprecated Use {@link getDestinationExactOutSwap} (object args; explicit `takerAddress` and
516+ * `receiverAddress`, both required).
517+ */
463518export async function determineDestinationSwaps (
464519 userAddress : Bytes ,
465520 requirement : Holding ,
466521 aggregators : Aggregator [ ] ,
467522 commonCurrencyID : CurrencyID = CurrencyID . USDC ,
523+ receiverAddress ?: Bytes ,
468524) : Promise < QuoteResponse > {
469525 const chaindata = ChaindataMap . get ( requirement . chainID ) ;
470526 if ( chaindata == null ) {
@@ -483,6 +539,7 @@ export async function determineDestinationSwaps(
483539 type : QuoteType . EXACT_IN ,
484540 chain : requirement . chainID ,
485541 userAddress,
542+ receiverAddress,
486543 inputToken : requirement . tokenAddress ,
487544 outputToken : COT . tokenAddress ,
488545 inputAmount : requirement . amountRaw ,
@@ -518,6 +575,7 @@ export async function determineDestinationSwaps(
518575 {
519576 type : QuoteType . EXACT_IN ,
520577 userAddress,
578+ receiverAddress,
521579 chain : requirement . chainID ,
522580 inputToken : COT . tokenAddress ,
523581 outputToken : requirement . tokenAddress ,
@@ -555,11 +613,34 @@ export async function determineDestinationSwaps(
555613 }
556614}
557615
616+ /**
617+ * @deprecated Use {@link liquidateSourceHoldings} (object args; per-holding `takerAddress`
618+ * and `receiverAddress` on each `HoldingWithSwapAddresses`).
619+ */
558620export async function liquidateInputHoldings (
559621 userAddress : Bytes ,
560622 holdings : Holding [ ] ,
561623 aggregators : Aggregator [ ] ,
562624 commonCurrencyID = CurrencyID . USDC ,
625+ receiverAddress ?: Bytes ,
626+ ) : Promise < QuoteResponse [ ] > {
627+ return liquidateInputHoldingsByRecipient (
628+ holdings . map ( ( holding ) => ( { ...holding , recipient : userAddress } ) ) ,
629+ aggregators ,
630+ commonCurrencyID ,
631+ receiverAddress ,
632+ ) ;
633+ }
634+
635+ /**
636+ * @deprecated Use {@link liquidateSourceHoldings} (object args; per-holding `takerAddress`
637+ * and `receiverAddress` on each `HoldingWithSwapAddresses`).
638+ */
639+ export async function liquidateInputHoldingsByRecipient (
640+ holdings : HoldingWithRecipient [ ] ,
641+ aggregators : Aggregator [ ] ,
642+ commonCurrencyID = CurrencyID . USDC ,
643+ receiverAddress ?: Bytes ,
563644) : Promise < QuoteResponse [ ] > {
564645 console . debug ( "XCS | LIH | Holdings:" , holdings ) ;
565646 const groupedByChainID = groupBy ( holdings , ( h ) =>
@@ -607,7 +688,10 @@ export async function liquidateInputHoldings(
607688 }
608689 fullLiquidationQuotes . push ( {
609690 req : {
610- userAddress,
691+ userAddress : holding . recipient ,
692+ // Per-holding receiver wins (set by the new wrappers); shared param is the legacy
693+ // positional-call fallback.
694+ receiverAddress : holding . receiverAddress ?? receiverAddress ,
611695 type : QuoteType . EXACT_IN ,
612696 chain : chain . ChainID ,
613697 inputToken : holding . tokenAddress ,
@@ -645,13 +729,18 @@ export async function liquidateInputHoldings(
645729 return quotes ;
646730}
647731
732+ /**
733+ * @deprecated Use {@link getDestinationExactInSwap} (object args; explicit `takerAddress`
734+ * and `receiverAddress`, both required).
735+ */
648736export async function destinationSwapWithExactIn (
649737 userAddress : Bytes ,
650738 omniChainID : OmniversalChainID ,
651739 inputAmount : bigint ,
652740 outputToken : Bytes ,
653741 aggregators : Aggregator [ ] ,
654742 inputCurrency : CurrencyID = CurrencyID . USDC ,
743+ receiverAddress ?: Bytes ,
655744) : Promise < QuoteResponse > {
656745 const chaindata = ChaindataMap . get ( omniChainID ) ;
657746 if ( chaindata == null ) {
@@ -669,6 +758,7 @@ export async function destinationSwapWithExactIn(
669758 type : QuoteType . EXACT_IN ,
670759 chain : omniChainID ,
671760 userAddress,
761+ receiverAddress,
672762 inputToken : COT . tokenAddress ,
673763 outputToken : outputToken ,
674764 inputAmount : inputAmount ,
@@ -700,3 +790,99 @@ export async function destinationSwapWithExactIn(
700790 } ,
701791 } ;
702792}
793+
794+ // =====================================================================================
795+ // Object-arg wrappers around the legacy positional functions above.
796+ //
797+ // Aggregator vocabulary:
798+ // takerAddress — on-chain executor of the swap (drives aggregator simulation /
799+ // permit / approval routing). On 7702 chains this is the ephemeral; on
800+ // non-Pectra chains it's the deployed Safe. Maps to the underlying
801+ // QuoteRequest's `userAddress`.
802+ // receiverAddress — recipient of the swap output. Maps to the underlying QuoteRequest's
803+ // `receiverAddress`. Required on all 4 wrappers — the GS013-class bug we
804+ // fixed came from forgetting this and silently defaulting to the wrong
805+ // address. Even on source side (where it equals the taker today), require
806+ // it explicitly so the type system forces every call site to acknowledge
807+ // both roles.
808+ //
809+ // Wrap-only: each wrapper delegates to the deprecated positional fn. No business logic added.
810+ // =====================================================================================
811+
812+ export async function getDestinationExactOutSwap ( args : {
813+ takerAddress : Bytes ;
814+ receiverAddress : Bytes ;
815+ requirement : Holding ;
816+ aggregators : Aggregator [ ] ;
817+ commonCurrencyID ?: CurrencyID ;
818+ } ) : Promise < QuoteResponse > {
819+ return determineDestinationSwaps (
820+ args . takerAddress ,
821+ args . requirement ,
822+ args . aggregators ,
823+ args . commonCurrencyID ,
824+ args . receiverAddress ,
825+ ) ;
826+ }
827+
828+ export async function getDestinationExactInSwap ( args : {
829+ takerAddress : Bytes ;
830+ receiverAddress : Bytes ;
831+ chain : OmniversalChainID ;
832+ inputAmount : bigint ;
833+ outputToken : Bytes ;
834+ aggregators : Aggregator [ ] ;
835+ inputCurrency ?: CurrencyID ;
836+ } ) : Promise < QuoteResponse > {
837+ return destinationSwapWithExactIn (
838+ args . takerAddress ,
839+ args . chain ,
840+ args . inputAmount ,
841+ args . outputToken ,
842+ args . aggregators ,
843+ args . inputCurrency ,
844+ args . receiverAddress ,
845+ ) ;
846+ }
847+
848+ export async function liquidateSourceHoldings ( args : {
849+ holdings : HoldingWithSwapAddresses [ ] ;
850+ aggregators : Aggregator [ ] ;
851+ commonCurrencyID ?: CurrencyID ;
852+ } ) : Promise < QuoteResponse [ ] > {
853+ return liquidateInputHoldingsByRecipient (
854+ args . holdings . map ( ( h ) => ( {
855+ ...h ,
856+ recipient : h . takerAddress ,
857+ receiverAddress : h . receiverAddress ,
858+ } ) ) ,
859+ args . aggregators ,
860+ args . commonCurrencyID ,
861+ ) ;
862+ }
863+
864+ export async function autoSelectSources ( args : {
865+ holdings : HoldingWithSwapAddresses [ ] ;
866+ outputRequired : Decimal ;
867+ aggregators : Aggregator [ ] ;
868+ commonCurrencyID ?: CurrencyID ;
869+ } ) : Promise < {
870+ quoteResponses : QuoteResponse [ ] ;
871+ usedCOTs : {
872+ originalHolding : Holding ;
873+ amountUsed : Decimal ;
874+ idx : number ;
875+ cur : Currency ;
876+ } [ ] ;
877+ } > {
878+ return autoSelectSourcesV2ByRecipient (
879+ args . holdings . map ( ( h ) => ( {
880+ ...h ,
881+ recipient : h . takerAddress ,
882+ receiverAddress : h . receiverAddress ,
883+ } ) ) ,
884+ args . outputRequired ,
885+ args . aggregators ,
886+ args . commonCurrencyID ,
887+ ) ;
888+ }
0 commit comments