@@ -13,6 +13,7 @@ contract QuarkBuilder {
13
13
/* ===== Constants ===== */
14
14
15
15
string constant VERSION = "1.0.0 " ;
16
+ uint256 constant MAX_BRIDGE_ACTION = 1 ;
16
17
17
18
/* ===== Custom Errors ===== */
18
19
@@ -21,6 +22,7 @@ contract QuarkBuilder {
21
22
error InsufficientFunds ();
22
23
error InvalidInput ();
23
24
error MaxCostTooHigh ();
25
+ error TooManyBridgeOperations ();
24
26
25
27
/* ===== Input Types ===== */
26
28
@@ -93,42 +95,75 @@ contract QuarkBuilder {
93
95
new IQuarkWallet.QuarkOperation [](chainAccountsList.length );
94
96
95
97
if (needsBridgedFunds (transferIntent, chainAccountsList)) {
96
- // TODO: actually enumerate chain accounts other than the destination chain,
97
- // and check balances and choose amounts to send and from which.
98
- //
99
- // for now: simplify!
100
- // only check 8453 (Base mainnet);
101
- // check every account;
102
- // sum the balances and if there's enough to cover the gap,
103
- // bridge from each account in arbitrary order of appearance
104
- // until there is enough.
105
- if (payment.isToken) {
106
- // wrap around paycall
107
- // TODO: need to embed price feed addresses for known tokens before we can do paycall.
108
- // ^^^ look up USDC price feeds for each supported chain?
109
- // we only need USDC/USD and only on chains 1 (mainnet) and 8453 (base mainnet).
110
- } else {
111
- quarkOperations[actionIndex++ ] = Actions.bridgeUSDC (
112
- Actions.BridgeUSDC ({
113
- chainAccountsList: chainAccountsList,
114
- assetSymbol: transferIntent.assetSymbol,
115
- amount: transferIntent.amount,
116
- // where it comes from
117
- originChainId: 8453 , // FIXME: originChainId
118
- sender: address (0 ), // FIXME: sender
119
- // where it goes
120
- destinationChainId: transferIntent.chainId,
121
- recipient: transferIntent.recipient,
122
- blockTimestamp: transferIntent.blockTimestamp
123
- })
124
- );
125
- // TODO: also append a Actions.Action to the actions array.
126
- // See: BridgeUSDC TODO for returning a Actions.Action.
98
+ // Note: Assumes that the asset uses the same # of decimals on each chain
99
+ uint256 balanceOnDstChain =
100
+ Accounts.getBalanceOnChain (transferIntent.assetSymbol, transferIntent.chainId, chainAccountsList);
101
+ uint256 amountLeftToBridge = transferIntent.amount - balanceOnDstChain;
102
+
103
+ uint256 bridgeActionCount = 0 ;
104
+ // TODO: bridge routing logic (which bridge to prioritize, how many bridges?)
105
+ // Iterate chainAccountList and find upto 2 chains that can provide enough fund
106
+ // Backend can provide optimal routes by adjust the order in chainAccountList.
107
+ for (uint256 i = 0 ; i < chainAccountsList.length ; ++ i) {
108
+ if (amountLeftToBridge == 0 ) {
109
+ break ;
110
+ }
111
+
112
+ Accounts.ChainAccounts memory srcChainAccounts = chainAccountsList[i];
113
+ if (srcChainAccounts.chainId == transferIntent.chainId) {
114
+ continue ;
115
+ }
116
+
117
+ if (
118
+ ! BridgeRoutes.canBridge (srcChainAccounts.chainId, transferIntent.chainId, transferIntent.assetSymbol)
119
+ ) {
120
+ continue ;
121
+ }
122
+
123
+ Accounts.AssetPositions memory srcAssetPositions =
124
+ Accounts.findAssetPositions (transferIntent.assetSymbol, srcChainAccounts.assetPositionsList);
125
+ Accounts.AccountBalance[] memory srcAccountBalances = srcAssetPositions.accountBalances;
126
+ // TODO: Make logic smarter. Currently, this uses a greedy algorithm.
127
+ // e.g. Optimize by trying to bridge with the least amount of bridge operations
128
+ for (uint256 j = 0 ; j < srcAccountBalances.length ; ++ j) {
129
+ if (bridgeActionCount >= MAX_BRIDGE_ACTION) {
130
+ revert TooManyBridgeOperations ();
131
+ }
132
+
133
+ uint256 amountToBridge = srcAccountBalances[j].balance >= amountLeftToBridge
134
+ ? amountLeftToBridge
135
+ : srcAccountBalances[j].balance;
136
+ amountLeftToBridge -= amountToBridge;
137
+
138
+ if (payment.isToken) {
139
+ // TODO: wrap around paycall
140
+ } else {
141
+ (quarkOperations[actionIndex], actions[actionIndex]) = Actions.bridgeAsset (
142
+ Actions.BridgeAsset ({
143
+ chainAccountsList: chainAccountsList,
144
+ assetSymbol: transferIntent.assetSymbol,
145
+ amount: amountToBridge,
146
+ // where it comes from
147
+ srcChainId: srcChainAccounts.chainId,
148
+ sender: srcAccountBalances[j].account,
149
+ // where it goes
150
+ destinationChainId: transferIntent.chainId,
151
+ recipient: transferIntent.sender,
152
+ blockTimestamp: transferIntent.blockTimestamp
153
+ })
154
+ );
155
+ actionIndex++ ;
156
+ bridgeActionCount++ ;
157
+ }
158
+ }
159
+ }
160
+
161
+ if (amountLeftToBridge > 0 ) {
162
+ revert FundsUnavailable ();
127
163
}
128
164
}
129
165
130
166
// Then, transferIntent `amount` of `assetSymbol` to `recipient`
131
- // TODO: construct action contexts
132
167
if (payment.isToken) {
133
168
// wrap around paycall
134
169
} else {
@@ -221,9 +256,8 @@ contract QuarkBuilder {
221
256
pure
222
257
returns (bool )
223
258
{
224
- Accounts.AssetPositions memory localPositions =
225
- Accounts.findAssetPositions (transferIntent.assetSymbol, transferIntent.chainId, chainAccountsList);
226
- return Accounts.sumBalances (localPositions) < transferIntent.amount;
259
+ return Accounts.getBalanceOnChain (transferIntent.assetSymbol, transferIntent.chainId, chainAccountsList)
260
+ < transferIntent.amount;
227
261
}
228
262
229
263
// Assert that each chain has sufficient funds to cover the max cost for that chain.
0 commit comments