Smart Contract

SwapConnectors

A.e1a479f0cb911df9.SwapConnectors

Valid From

142,038,837

Deployed

2w ago
Feb 13, 2026, 02:42:46 AM UTC

Dependents

0 imports
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3
4import DeFiActions from 0x6d888f175c158410
5import DeFiActionsUtils from 0x6d888f175c158410
6
7/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
8/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
9/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10///
11/// SwapConnectors
12///
13/// This contract defines DeFi Actions Sink & Source connector implementations for use with DeFi protocols. These
14/// connectors can be used alone or in conjunction with other DeFi Actions connectors to create complex DeFi workflows.
15///
16access(all) contract SwapConnectors {
17
18    /// BasicQuote
19    ///
20    /// A simple implementation of DeFiActions.Quote allowing callers of Swapper.quoteIn() and .quoteOut() to cache quoted
21    /// amount in and/or out.
22    ///
23    access(all) struct BasicQuote : DeFiActions.Quote {
24        access(all) let inType: Type
25        access(all) let outType: Type
26        access(all) let inAmount: UFix64
27        access(all) let outAmount: UFix64
28
29        init(
30            inType: Type,
31            outType: Type,
32            inAmount: UFix64,
33            outAmount: UFix64
34        ) {
35            self.inType = inType
36            self.outType = outType
37            self.inAmount = inAmount
38            self.outAmount = outAmount
39        }
40    }
41
42    /// MultiSwapperQuote
43    ///
44    /// A MultiSwapper specific DeFiActions.Quote implementation allowing for callers to set the Swapper used in
45    /// MultiSwapper that should fulfill the Swap
46    ///
47    access(all) struct MultiSwapperQuote : DeFiActions.Quote {
48        access(all) let inType: Type
49        access(all) let outType: Type
50        access(all) let inAmount: UFix64
51        access(all) let outAmount: UFix64
52        access(all) let swapperIndex: Int
53
54        init(
55            inType: Type,
56            outType: Type,
57            inAmount: UFix64,
58            outAmount: UFix64,
59            swapperIndex: Int
60        ) {
61            pre {
62                swapperIndex >= 0: "Invalid swapperIndex - provided \(swapperIndex) is less than 0"
63            }
64            self.inType = inType
65            self.outType = outType
66            self.inAmount = inAmount
67            self.outAmount = outAmount
68            self.swapperIndex = swapperIndex
69        }
70    }
71
72    /// MultiSwapper
73    ///
74    /// A Swapper implementation routing swap requests to the optimal contained Swapper. Once constructed, this can
75    /// effectively be used as an aggregator across all contained Swapper implementations, though it is limited to the
76    /// routes and pools exposed by its inner Swappers as well as runtime computation limits.
77    ///
78    access(all) struct MultiSwapper : DeFiActions.Swapper {
79        access(all) let swappers: [{DeFiActions.Swapper}]
80        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
81        access(self) let inVault: Type
82        access(self) let outVault: Type
83
84        init(
85            inVault: Type,
86            outVault: Type,
87            swappers: [{DeFiActions.Swapper}],
88            uniqueID: DeFiActions.UniqueIdentifier?
89        ) {
90            pre {
91                inVault.isSubtype(of: Type<@{FungibleToken.Vault}>()):
92                "Invalid inVault type - \(inVault.identifier) is not a FungibleToken Vault implementation"
93                outVault.isSubtype(of: Type<@{FungibleToken.Vault}>()):
94                "Invalid outVault type - \(outVault.identifier) is not a FungibleToken Vault implementation"
95            }
96            for i in InclusiveRange(0, swappers.length - 1) {
97                let swapper = &swappers[i] as &{DeFiActions.Swapper}
98                assert(swapper.inType() == inVault,
99                    message: "Mismatched inVault \(inVault.identifier) - Swapper \(swapper.getType().identifier) accepts \(swapper.inType().identifier)")
100                assert(swapper.outType() == outVault,
101                    message: "Mismatched outVault \(outVault.identifier) - Swapper \(swapper.getType().identifier) accepts \(swapper.outType().identifier)")
102            }
103            self.inVault = inVault
104            self.outVault = outVault
105            self.uniqueID = uniqueID
106            self.swappers = swappers
107        }
108
109        /// Returns a ComponentInfo struct containing information about this MultiSwapper and its inner DFA components
110        ///
111        /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
112        ///     each inner component in the stack.
113        ///
114        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
115            let inner: [DeFiActions.ComponentInfo] = []
116            for swapper in self.swappers {
117                inner.append(swapper.getComponentInfo())
118            }
119            return DeFiActions.ComponentInfo(
120                type: self.getType(),
121                id: self.id(),
122                innerComponents: inner
123            )
124        }
125        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
126        /// a DeFiActions stack. See DeFiActions.align() for more information.
127        ///
128        /// @return a copy of the struct's UniqueIdentifier
129        ///
130        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
131            return self.uniqueID
132        }
133        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
134        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
135        ///
136        /// @param id: the UniqueIdentifier to set for this component
137        ///
138        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
139            self.uniqueID = id
140        }
141        /// The type of Vault this Swapper accepts when performing a swap
142        access(all) view fun inType(): Type {
143            return self.inVault
144        }
145        /// The type of Vault this Swapper provides when performing a swap
146        access(all) view fun outType(): Type  {
147            return self.outVault
148        }
149        /// The estimated amount required to provide a Vault with the desired output balance
150        access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
151            let estimate = self._estimate(amount: forDesired, out: false, reverse: reverse)
152            return MultiSwapperQuote(
153                inType: reverse ? self.outType() : self.inType(),
154                outType: reverse ? self.inType() : self.outType(),
155                inAmount: estimate[1],
156                outAmount: forDesired,
157                swapperIndex: Int(estimate[0])
158            )
159        }
160        /// The estimated amount delivered out for a provided input balance
161        access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
162            let estimate = self._estimate(amount: forProvided, out: true, reverse: reverse)
163            return MultiSwapperQuote(
164                inType: reverse ? self.outType() : self.inType(),
165                outType: reverse ? self.inType() : self.outType(),
166                inAmount: forProvided,
167                outAmount: estimate[1],
168                swapperIndex: Int(estimate[0])
169            )
170        }
171        /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. Implementations may choose
172        /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
173        /// to use multiple Flow swap protocols. If the provided quote is not a MultiSwapperQuote, a new quote is
174        /// requested and the optimal Swapper used to fulfill the swap.
175        /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
176        access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
177            return <-self._swap(quote: quote, from: <-inVault, reverse: false)
178        }
179        /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Implementations may choose
180        /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
181        /// to use multiple Flow swap protocols.
182        /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
183        access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
184            return <-self._swap(quote: quote, from: <-residual, reverse: true)
185        }
186        /// Returns the the index of the optimal Swapper (result[0]) and the associated amountOut or amountIn (result[0])
187        /// as a UFix64 array
188        access(self) fun _estimate(amount: UFix64, out: Bool, reverse: Bool): [UFix64; 2] {
189            var res: [UFix64; 2] = out ? [0.0, 0.0] : [0.0, UFix64.max] // maximizing for out, minimizing for in
190            for i in InclusiveRange(0, self.swappers.length - 1) {
191                let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
192                // call the appropriate estimator
193                let estimate = out
194                    ? swapper.quoteOut(forProvided: amount, reverse: reverse).outAmount
195                    : swapper.quoteIn(forDesired: amount, reverse: reverse).inAmount
196                // estimates of 0.0 are presumed to be graceful failures - skip them
197                if estimate > 0.0 && (out ? res[1] < estimate : estimate < res[1]) {
198                    // take minimum for in, maximum for out
199                    res = [UFix64(i), estimate]
200                }
201            }
202            return res
203        }
204        /// Swaps the provided Vault in the defined direction. If the quote is not a MultiSwapperQuote, a new quote is
205        /// requested and the current optimal Swapper used to fulfill the swap.
206        access(self) fun _swap(quote: {DeFiActions.Quote}?, from: @{FungibleToken.Vault}, reverse: Bool): @{FungibleToken.Vault} {
207            var multiQuote = quote as? MultiSwapperQuote
208            if multiQuote == nil || multiQuote!.swapperIndex >= self.swappers.length {
209                multiQuote = self.quoteOut(forProvided: from.balance, reverse: reverse) as! MultiSwapperQuote
210            }
211            let optimalSwapper = &self.swappers[multiQuote!.swapperIndex] as &{DeFiActions.Swapper}
212            if reverse {
213                return <- optimalSwapper.swapBack(quote: multiQuote, residual: <-from)
214            } else {
215                return <- optimalSwapper.swap(quote: multiQuote, inVault: <-from)
216            }
217        }
218    }
219
220    /// SwapSink
221    ///
222    /// A DeFiActions connector that deposits the resulting post-conversion currency of a token swap to an inner
223    /// DeFiActions Sink, sourcing funds from a deposited Vault of a pre-set Type.
224    ///
225    /// NOTE: If residual tokens remain after depositing to the inner sink, swapBack() is called on the swapper to
226    ///     return them to the original vault. One-way swappers (e.g., ERC4626SwapConnectors.Swapper) that do not
227    ///     support swapBack() can still be used safely if the inner sink always consumes all swapped tokens (i.e.,
228    ///     has sufficient capacity). If residuals occur with a one-way swapper, the transaction will revert.
229    ///
230    access(all) struct SwapSink : DeFiActions.Sink {
231        access(self) let swapper: {DeFiActions.Swapper}
232        access(self) let sink: {DeFiActions.Sink}
233        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
234
235        init(swapper: {DeFiActions.Swapper}, sink: {DeFiActions.Sink}, uniqueID: DeFiActions.UniqueIdentifier?) {
236            pre {
237                swapper.outType() == sink.getSinkType():
238                "Swapper outputs \(swapper.outType().identifier) but Sink takes \(sink.getSinkType().identifier) - "
239                    .concat("Ensure the provided Swapper outputs a Vault Type compatible with the provided Sink")
240            }
241            self.swapper = swapper
242            self.sink = sink
243            self.uniqueID = uniqueID
244        }
245
246        /// Returns a ComponentInfo struct containing information about this SwapSink and its inner DFA components
247        ///
248        /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
249        ///     each inner component in the stack.
250        ///
251        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
252            return DeFiActions.ComponentInfo(
253                type: self.getType(),
254                id: self.id(),
255                innerComponents: [
256                    self.swapper.getComponentInfo(),
257                    self.sink.getComponentInfo()
258                ]
259            )
260        }
261        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
262        /// a DeFiActions stack. See DeFiActions.align() for more information.
263        ///
264        /// @return a copy of the struct's UniqueIdentifier
265        ///
266        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
267            return self.uniqueID
268        }
269        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
270        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
271        ///
272        /// @param id: the UniqueIdentifier to set for this component
273        ///
274        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
275            self.uniqueID = id
276        }
277        /// Returns the type of Vault this Sink accepts when performing a swap
278        ///
279        /// @return the type of Vault this Sink accepts when performing a swap
280        ///
281        access(all) view fun getSinkType(): Type {
282            return self.swapper.inType()
283        }
284        /// Returns the minimum capacity required to deposit to this Sink
285        ///
286        /// @return the minimum capacity required to deposit to this Sink
287        ///
288        access(all) fun minimumCapacity(): UFix64 {
289            return self.swapper.quoteIn(forDesired: self.sink.minimumCapacity(), reverse: false).inAmount
290        }
291        /// Deposits the provided Vault to this Sink, swapping the provided Vault to the required type if necessary
292        ///
293        /// @param from: the Vault to source deposits from
294        ///
295        access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
296            // nothing to swap from, no capacity to ingest, invalid Vault type - do nothing
297            if from.balance == 0.0 || from.getType() != self.getSinkType() { return; }
298            let limit = self.sink.minimumCapacity()
299            if limit == 0.0 { return; }
300
301            // quote based on available funds & inner sink limit
302            var quote = self.swapper.quoteOut(forProvided: from.balance, reverse: false)
303            if limit <= quote.outAmount {
304                quote = self.swapper.quoteIn(forDesired: limit, reverse: false)
305            }
306            // error when quoting - in/out should not be 0.0
307            if quote.inAmount == 0.0 || quote.outAmount == 0.0 { return; }
308
309            // assign the amount to swap based on updated quote
310            let swapAmount = quote.inAmount <= from.balance ? quote.inAmount : from.balance
311            // swap then deposit to the inner sink
312            let swapVault <- from.withdraw(amount: swapAmount)
313            let swappedTokens <- self.swapper.swap(quote: quote, inVault: <-swapVault)
314            self.sink.depositCapacity(from: &swappedTokens as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
315
316            if swappedTokens.balance > 0.0 {
317                // swap back any residual to the originating vault
318                let residual <- self.swapper.swapBack(quote: nil, residual: <-swappedTokens)
319                from.deposit(from: <-residual)
320            } else {
321                Burner.burn(<-swappedTokens) // nothing left - burn & execute vault's burnCallback()
322            }
323        }
324    }
325
326    /// SequentialSwapper
327    ///
328    /// A Swapper implementation routing swap requests to a sequence of Swappers. This may be helpful when a single
329    /// path is not sufficient to meet the desired swap requirements - e.g. swapping from one token to the underlying
330    /// asset of an ERC4626 vault to then swap to the ERC4626 vault shares.
331    ///
332    access(all) struct SequentialSwapper : DeFiActions.Swapper {
333        access(all) let swappers: [{DeFiActions.Swapper}]
334        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
335        access(self) let inVault: Type
336        access(self) let outVault: Type
337
338        init(swappers: [{DeFiActions.Swapper}], uniqueID: DeFiActions.UniqueIdentifier?) {
339            pre {
340                swappers.length > 1:
341                "Provided swappers must have a length of at least 2 - provided \(swappers.length) swappers"
342            }
343            // ensure each swapper's inType matches the previous swapper's outType
344            for i in InclusiveRange(0, swappers.length - 1) {
345                let swapper = &swappers[i] as &{DeFiActions.Swapper}
346                if i < swappers.length - 1 {
347                    let nextSwapper = &swappers[i + 1] as &{DeFiActions.Swapper}
348                    assert(swapper.outType() == nextSwapper.inType(),
349                        message: "Mismatched inType \(nextSwapper.inType().identifier) - Swapper \(swapper.getType().identifier) accepts \(swapper.outType().identifier)")
350                    }
351            }
352            self.inVault = swappers[0].inType()
353            self.outVault = swappers[swappers.length - 1].outType()
354            self.swappers = swappers
355            self.uniqueID = uniqueID
356        }
357
358        /// Returns a ComponentInfo struct containing information about this SequentialSwapper and its inner DFA components
359        ///
360        /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
361        ///     each inner component in the stack.
362        ///
363        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
364            let inner: [DeFiActions.ComponentInfo] = []
365            for i in InclusiveRange(0, self.swappers.length - 1) {
366                let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
367                inner.append(swapper.getComponentInfo())
368            }
369            return DeFiActions.ComponentInfo(type: self.getType(), id: self.id(), innerComponents: inner)
370        }
371        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
372        /// a DeFiActions stack. See DeFiActions.align() for more information.
373        ///
374        /// @return a copy of the struct's UniqueIdentifier
375        ///
376        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
377            return self.uniqueID
378        }
379        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
380        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
381        ///
382        /// @param id: the UniqueIdentifier to set for this component
383        ///
384        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
385            self.uniqueID = id
386        }
387        /// The type of Vault this Swapper accepts when performing a swap
388        access(all) view fun inType(): Type {
389            return self.inVault
390        }
391        /// The type of Vault this Swapper provides when performing a swap
392        access(all) view fun outType(): Type {
393            return self.outVault
394        }
395        /// The estimated amount required to provide a Vault with the desired output balance
396        ///
397        /// @param forDesired: The amount in desired of the pre-conversion currency as a result of the swap
398        /// @param reverse: If false, the default inVault -> outVault is used, otherwise, the method estimates a swap
399        ///     in the opposite direction, outVault -> inVault
400        ///
401        /// @return a DeFiActions.Quote containing estimate data. In order to prevent upstream reversion,
402        ///     result.inAmount and result.outAmount will be 0.0 if an estimate is not available
403        ///
404        access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
405            var tmpAmount = forDesired
406            var finalOutAmount = forDesired
407
408            // walk through the swappers in the sequence starting with the output based on direction of swap
409            let range = reverse
410                ? InclusiveRange(0, self.swappers.length - 1)
411                : InclusiveRange(self.swappers.length - 1, 0, step: -1)
412            for i in range {
413                let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
414                let quote = swapper.quoteIn(forDesired: tmpAmount, reverse: reverse)
415                if quote.inAmount == 0.0 || quote.outAmount == 0.0 {
416                    return SwapConnectors.BasicQuote(
417                        inType: reverse ? self.outType() : self.inType(),
418                        outType: reverse ? self.inType() : self.outType(),
419                        inAmount: 0.0,
420                        outAmount: 0.0
421                    )
422                }
423                if i == range.start { finalOutAmount = quote.outAmount; }
424                tmpAmount = quote.inAmount
425            }
426
427            return SwapConnectors.BasicQuote(
428                inType: reverse ? self.outType() : self.inType(),
429                outType: reverse ? self.inType() : self.outType(),
430                inAmount: tmpAmount,
431                outAmount: finalOutAmount
432            )
433        }
434        /// The estimated amount delivered out for a provided input balance
435        ///
436        /// @param forProvided: The amount in provided of the pre-conversion currency as a result of the swap
437        /// @param reverse: If false, the default inVault -> outVault is used, otherwise, the method estimates a swap
438        ///     in the opposite direction, outVault -> inVault
439        ///
440        /// @return a DeFiActions.Quote containing estimate data. In order to prevent upstream reversion,
441        ///     result.inAmount and result.outAmount will be 0.0 if an estimate is not available
442        ///
443        access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
444            var tmpAmount = forProvided
445            var finalInAmount = forProvided
446
447            // walk through the swappers in the sequence starting with the input based on direction of swap
448            let range = reverse
449                ? InclusiveRange(self.swappers.length - 1, 0, step: -1)
450                : InclusiveRange(0, self.swappers.length - 1)
451            for i in range {
452                let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
453                let quote = swapper.quoteOut(forProvided: tmpAmount, reverse: reverse)
454                if quote.inAmount == 0.0 || quote.outAmount == 0.0 {
455                    return SwapConnectors.BasicQuote(
456                        inType: reverse ? self.outType() : self.inType(),
457                        outType: reverse ? self.inType() : self.outType(),
458                        inAmount: 0.0,
459                        outAmount: 0.0
460                    )
461                }
462                if i == range.start { finalInAmount = quote.inAmount; }
463                tmpAmount = quote.outAmount
464            }
465
466            return BasicQuote(
467                inType: reverse ? self.outType() : self.inType(),
468                outType: reverse ? self.inType() : self.outType(),
469                inAmount: finalInAmount,
470                outAmount: tmpAmount
471            )
472        }
473        /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. Callers should be careful
474        /// to ensure the resulting output meets their expectations given the provided inVault.balance.
475        /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
476        ///
477        /// @param quote: A `DeFiActions.Quote` data structure. This implementation ignores the quote parameter so callers
478        ///     should be sure to enforce outputs against the expected outVault.balance
479        /// @param inVault: The Vault to swap from
480        ///
481        /// @return a Vault of type `outVault` containing the swapped currency.
482        ///
483        access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
484            return <- self._swap(quote: quote, from: <-inVault, reverse: false)
485        }
486        /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Callers should be careful
487        /// to ensure the resulting output meets their expectations given the provided residual.balance.
488        /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
489        ///
490        /// @param quote: A `DeFiActions.Quote` data structure. This implementation ignores the quote parameter so callers
491        ///     should be sure to enforce outputs against the expected inVault.balance
492        /// @param residual: The Vault to swap back from
493        ///
494        /// @return a Vault of type `inVault` containing the swapped currency.
495        ///
496        access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
497            return <- self._swap(quote: quote, from: <-residual, reverse: true)
498        }
499        /// Internal swap function performing the swap along the defined path.
500        ///
501        /// @param quote: A `DeFiActions.Quote` data structure. If provided, quote.outAmount is used as the minimum amount out
502        ///     desired otherwise a new quote is generated from current state
503        /// @param from: The Vault to swap from
504        /// @param reverse: Whether to swap in the reverse direction
505        ///
506        /// @return a Vault of type `outVault` containing the swapped currency.
507        ///
508        access(self)
509        fun _swap(quote: {DeFiActions.Quote}?, from: @{FungibleToken.Vault}, reverse: Bool): @{FungibleToken.Vault} {
510            let range = reverse
511                ? InclusiveRange(self.swappers.length - 1, 0, step: -1)
512                : InclusiveRange(0, self.swappers.length - 1)
513            var vault <- from.withdraw(amount: from.balance)
514            Burner.burn(<-from)
515
516            for i in range {
517                let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
518                let swapFun = reverse ? swapper.swapBack : swapper.swap
519
520                let preSwap <- vault.withdraw(amount: vault.balance)
521                var swapped <- swapFun(quote: nil, <-preSwap)
522                vault <-> swapped
523                Burner.burn(<-swapped)
524            }
525            return <- vault
526        }
527    }
528
529    /// SwapSource
530    ///
531    /// A DeFiActions connector that returns post-conversion currency, sourcing pre-converted funds from an inner
532    /// DeFiActions Source
533    ///
534    access(all) struct SwapSource : DeFiActions.Source {
535        access(self) let swapper: {DeFiActions.Swapper}
536        access(self) let source: {DeFiActions.Source}
537        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
538
539        init(swapper: {DeFiActions.Swapper}, source: {DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier?) {
540            pre {
541                source.getSourceType() == swapper.inType():
542                "Source outputs \(source.getSourceType().identifier) but Swapper takes \(swapper.inType().identifier) - "
543                    .concat("Ensure the provided Source outputs a Vault Type compatible with the provided Swapper")
544            }
545            self.swapper = swapper
546            self.source = source
547            self.uniqueID = uniqueID
548        }
549
550        /// Returns a ComponentInfo struct containing information about this SwapSource and its inner DFA components
551        ///
552        /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
553        ///     each inner component in the stack.
554        ///
555        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
556            return DeFiActions.ComponentInfo(
557                type: self.getType(),
558                id: self.id(),
559                innerComponents: [
560                    self.swapper.getComponentInfo(),
561                    self.source.getComponentInfo()
562                ]
563            )
564        }
565        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
566        /// a DeFiActions stack. See DeFiActions.align() for more information.
567        ///
568        /// @return a copy of the struct's UniqueIdentifier
569        ///
570        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
571            return self.uniqueID
572        }
573        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
574        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
575        ///
576        /// @param id: the UniqueIdentifier to set for this component
577        ///
578        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
579            self.uniqueID = id
580        }
581        /// Returns the type of Vault this Source provides when performing a swap
582        ///
583        /// @return the type of Vault this Source provides when performing a swap
584        ///
585        access(all) view fun getSourceType(): Type {
586            return self.swapper.outType()
587        }
588        /// Returns the minimum amount of currency available to withdraw from this Source
589        ///
590        /// @return the minimum amount of currency available to withdraw from this Source
591        ///
592        access(all) fun minimumAvailable(): UFix64 {
593            // estimate post-conversion currency based on the source's pre-conversion balance available
594            let availableIn = self.source.minimumAvailable()
595            return availableIn > 0.0
596                ? self.swapper.quoteOut(forProvided: availableIn, reverse: false).outAmount
597                : 0.0
598        }
599        /// Withdraws the provided amount of currency from this Source, swapping the provided amount to the required type if necessary
600        ///
601        /// @param maxAmount: the maximum amount of currency to withdraw from this Source
602        ///
603        /// @return the Vault containing the withdrawn currency
604        ///
605        access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
606            let minimumAvail = self.minimumAvailable()
607            if minimumAvail == 0.0 || maxAmount == 0.0 {
608                return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
609            }
610
611            // expect output amount as the lesser between the amount available and the maximum amount
612            var quote = minimumAvail < maxAmount
613                ? self.swapper.quoteOut(forProvided: self.source.minimumAvailable(), reverse: false)
614                : self.swapper.quoteIn(forDesired: maxAmount, reverse: false)
615
616            let sourceLiquidity <- self.source.withdrawAvailable(maxAmount: quote.inAmount)
617            if sourceLiquidity.balance == 0.0 {
618                Burner.burn(<-sourceLiquidity)
619                return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
620            }
621            return <- self.swapper.swap(quote: quote, inVault: <-sourceLiquidity)
622        }
623    }
624}
625