Smart Contract
MultiTokenQuoteTypes
A.136a10c590912ef8.MultiTokenQuoteTypes
1/// Contract to hold types and events for multi-token swap quote transactions
2/// This is needed because structs and events cannot be declared at the top level of transaction files
3
4access(all) contract MultiTokenQuoteTypes {
5
6 access(all) struct SwapQuote {
7 access(all) let fromToken: String
8 access(all) let toToken: String
9 access(all) let protocol: String // "IncrementFi" or "Uniswap"
10 access(all) let inputAmount: UFix64
11 access(all) let outputAmount: UFix64
12 access(all) let rate: UFix64 // outputAmount / inputAmount
13 access(all) let pairExists: Bool
14 access(all) let error: String? // Error message if quote failed
15
16 init(fromToken: String, toToken: String, protocol: String, inputAmount: UFix64, outputAmount: UFix64, pairExists: Bool, error: String?) {
17 self.fromToken = fromToken
18 self.toToken = toToken
19 self.protocol = protocol
20 self.inputAmount = inputAmount
21 self.outputAmount = outputAmount
22 self.rate = inputAmount > 0.0 ? outputAmount / inputAmount : 0.0
23 self.pairExists = pairExists
24 self.error = error
25 }
26 }
27
28 access(all) struct PairComparison {
29 access(all) let fromToken: String
30 access(all) let toToken: String
31 access(all) let incrementFiQuote: SwapQuote
32 access(all) let uniswapQuote: SwapQuote
33 access(all) let bestProtocol: String? // Which protocol offers better rate
34 access(all) let rateDifference: UFix64 // Absolute difference in rates
35 access(all) let rateDifferencePercent: UFix64 // Percentage difference
36
37 init(incrementFiQuote: SwapQuote, uniswapQuote: SwapQuote) {
38 self.fromToken = incrementFiQuote.fromToken
39 self.toToken = incrementFiQuote.toToken
40 self.incrementFiQuote = incrementFiQuote
41 self.uniswapQuote = uniswapQuote
42
43 // Determine best protocol
44 if incrementFiQuote.pairExists && uniswapQuote.pairExists {
45 if incrementFiQuote.outputAmount > uniswapQuote.outputAmount {
46 self.bestProtocol = "IncrementFi"
47 } else if uniswapQuote.outputAmount > incrementFiQuote.outputAmount {
48 self.bestProtocol = "Uniswap"
49 } else {
50 self.bestProtocol = "Equal"
51 }
52 } else if incrementFiQuote.pairExists {
53 self.bestProtocol = "IncrementFi"
54 } else if uniswapQuote.pairExists {
55 self.bestProtocol = "Uniswap"
56 } else {
57 self.bestProtocol = nil
58 }
59
60 // Calculate rate difference
61 if incrementFiQuote.pairExists && uniswapQuote.pairExists {
62 self.rateDifference = incrementFiQuote.rate > uniswapQuote.rate
63 ? incrementFiQuote.rate - uniswapQuote.rate
64 : uniswapQuote.rate - incrementFiQuote.rate
65
66 let lowerRate = incrementFiQuote.rate < uniswapQuote.rate ? incrementFiQuote.rate : uniswapQuote.rate
67 self.rateDifferencePercent = lowerRate > 0.0 ? (self.rateDifference / lowerRate) * 100.0 : 0.0
68 } else {
69 self.rateDifference = 0.0
70 self.rateDifferencePercent = 0.0
71 }
72 }
73 }
74
75 access(all) struct MultiTokenQuoteResult {
76 access(all) let comparisons: [PairComparison]
77 access(all) let totalPairsTested: Int
78 access(all) let bothProtocolsAvailable: Int
79 access(all) let onlyIncrementFi: Int
80 access(all) let onlyUniswap: Int
81 access(all) let neitherAvailable: Int
82 access(all) let arbitrageOpportunities: Int // Pairs with >1% rate difference
83
84 init(comparisons: [PairComparison]) {
85 self.comparisons = comparisons
86 self.totalPairsTested = comparisons.length
87
88 var bothAvailable = 0
89 var onlyIncrement = 0
90 var onlyUni = 0
91 var neither = 0
92 var arbOps = 0
93
94 for comparison in comparisons {
95 if comparison.incrementFiQuote.pairExists && comparison.uniswapQuote.pairExists {
96 bothAvailable = bothAvailable + 1
97 if comparison.rateDifferencePercent > 1.0 {
98 arbOps = arbOps + 1
99 }
100 } else if comparison.incrementFiQuote.pairExists {
101 onlyIncrement = onlyIncrement + 1
102 } else if comparison.uniswapQuote.pairExists {
103 onlyUni = onlyUni + 1
104 } else {
105 neither = neither + 1
106 }
107 }
108
109 self.bothProtocolsAvailable = bothAvailable
110 self.onlyIncrementFi = onlyIncrement
111 self.onlyUniswap = onlyUni
112 self.neitherAvailable = neither
113 self.arbitrageOpportunities = arbOps
114 }
115 }
116
117 // Events for logging results
118 access(all) event QuoteResult(
119 fromToken: String,
120 toToken: String,
121 incrementFiOutput: UFix64,
122 uniswapOutput: UFix64,
123 bestProtocol: String?,
124 rateDifferencePercent: UFix64,
125 incrementFiExists: Bool,
126 uniswapExists: Bool
127 )
128
129 access(all) event SummaryResult(
130 totalPairsTested: Int,
131 bothProtocolsAvailable: Int,
132 onlyIncrementFi: Int,
133 onlyUniswap: Int,
134 neitherAvailable: Int,
135 arbitrageOpportunities: Int
136 )
137
138 // Cross-chain route structures
139 access(all) struct CrossChainRoute {
140 access(all) let routeName: String
141 access(all) let startToken: String
142 access(all) let steps: [String] // Description of each step
143 access(all) let amounts: [UFix64] // Amount at each step
144 access(all) let bridgeFees: [UFix64] // Bridge fees for each bridge operation
145 access(all) let swapFees: [UFix64] // Swap fees for each swap
146 access(all) let finalAmount: UFix64
147 access(all) let profit: UFix64
148 access(all) let profitPercentage: UFix64
149 access(all) let isProfitable: Bool
150 access(all) let totalFees: UFix64
151
152 init(
153 routeName: String,
154 startToken: String,
155 steps: [String],
156 amounts: [UFix64],
157 bridgeFees: [UFix64],
158 swapFees: [UFix64]
159 ) {
160 self.routeName = routeName
161 self.startToken = startToken
162 self.steps = steps
163 self.amounts = amounts
164 self.bridgeFees = bridgeFees
165 self.swapFees = swapFees
166 self.finalAmount = amounts[amounts.length - 1]
167
168 var totalFeesCalc = 0.0
169 for fee in bridgeFees {
170 totalFeesCalc = totalFeesCalc + fee
171 }
172 for fee in swapFees {
173 totalFeesCalc = totalFeesCalc + fee
174 }
175 self.totalFees = totalFeesCalc
176
177 let initialAmount = amounts[0]
178 if self.finalAmount >= initialAmount {
179 self.profit = self.finalAmount - initialAmount - self.totalFees
180 self.isProfitable = self.profit > 0.0
181 } else {
182 self.profit = 0.0
183 self.isProfitable = false
184 }
185
186 self.profitPercentage = initialAmount > 0.0 ? (self.profit / initialAmount) * 100.0 : 0.0
187 }
188 }
189
190 // Event for cross-chain route results
191 access(all) event CrossChainRouteResult(
192 routeName: String,
193 startToken: String,
194 steps: [String],
195 amounts: [UFix64],
196 bridgeFees: [UFix64],
197 swapFees: [UFix64],
198 finalAmount: UFix64,
199 profit: UFix64,
200 profitPercentage: UFix64,
201 isProfitable: Bool,
202 totalFees: UFix64
203 )
204
205 // Helper functions to emit events (called from transactions)
206 // These act as "log" functions that emit events for data capture
207 // Similar to how scripts return data, but using events since transactions can't return data
208
209 /// Emits a QuoteResult event for a pair comparison
210 /// This is like returning one element from an array in a script
211 access(all) fun emitQuoteResult(
212 fromToken: String,
213 toToken: String,
214 incrementFiOutput: UFix64,
215 uniswapOutput: UFix64,
216 bestProtocol: String?,
217 rateDifferencePercent: UFix64,
218 incrementFiExists: Bool,
219 uniswapExists: Bool
220 ) {
221 emit QuoteResult(
222 fromToken: fromToken,
223 toToken: toToken,
224 incrementFiOutput: incrementFiOutput,
225 uniswapOutput: uniswapOutput,
226 bestProtocol: bestProtocol,
227 rateDifferencePercent: rateDifferencePercent,
228 incrementFiExists: incrementFiExists,
229 uniswapExists: uniswapExists
230 )
231 }
232
233 /// Emits a SummaryResult event with overall statistics
234 /// This is like returning the final summary struct in a script
235 access(all) fun emitSummaryResult(
236 totalPairsTested: Int,
237 bothProtocolsAvailable: Int,
238 onlyIncrementFi: Int,
239 onlyUniswap: Int,
240 neitherAvailable: Int,
241 arbitrageOpportunities: Int
242 ) {
243 emit SummaryResult(
244 totalPairsTested: totalPairsTested,
245 bothProtocolsAvailable: bothProtocolsAvailable,
246 onlyIncrementFi: onlyIncrementFi,
247 onlyUniswap: onlyUniswap,
248 neitherAvailable: neitherAvailable,
249 arbitrageOpportunities: arbitrageOpportunities
250 )
251 }
252
253 /// Emits a CrossChainRouteResult event for a cross-chain arbitrage route
254 access(all) fun emitCrossChainRouteResult(
255 routeName: String,
256 startToken: String,
257 steps: [String],
258 amounts: [UFix64],
259 bridgeFees: [UFix64],
260 swapFees: [UFix64],
261 finalAmount: UFix64,
262 profit: UFix64,
263 profitPercentage: UFix64,
264 isProfitable: Bool,
265 totalFees: UFix64
266 ) {
267 emit CrossChainRouteResult(
268 routeName: routeName,
269 startToken: startToken,
270 steps: steps,
271 amounts: amounts,
272 bridgeFees: bridgeFees,
273 swapFees: swapFees,
274 finalAmount: finalAmount,
275 profit: profit,
276 profitPercentage: profitPercentage,
277 isProfitable: isProfitable,
278 totalFees: totalFees
279 )
280 }
281
282 init() {
283 // Contract initialization - no state needed
284 }
285}
286
287