Skip to content

Commit a214d95

Browse files
melisaguevaradohakiashwinrava
authored
EPIC: swap api embedded actions (#1676)
* feat: new `MulticallHandler` addresses (#1668) * feat: encode embedded actions to include in bridge message (#1670) * feat: encode embedded actions to include in bridge message * relocate destination action arg * feat: populate fields dynamically for actions involving erc20 tokens (#1675) * feat: encode embedded actions to include in bridge message * relocate destination action arg * feat: populate fields dynamically for actions involving erc20 tokens * feat: support native balance injection for embedded actions (#1683) * feat: support native balance injection for embedded actions * use boolean instead of boolStr * set populateDynamically as optional * optional value when populating dynamically * Update api/swap/approval/_service.ts Co-authored-by: Ashwin <[email protected]> * fix: add drain call for destination swaps with embedded actions (#1692) --------- Co-authored-by: Dong-Ha Kim <[email protected]> Co-authored-by: Ashwin <[email protected]>
1 parent 82c787f commit a214d95

File tree

8 files changed

+1063
-53
lines changed

8 files changed

+1063
-53
lines changed

api/_dexes/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TradeType } from "@uniswap/sdk-core";
33

44
import { getSuggestedFees } from "../_utils";
55
import { AmountType, CrossSwapType } from "./utils";
6+
import { Action } from "../swap/_utils";
67

78
export type { AmountType, CrossSwapType };
89

@@ -40,6 +41,7 @@ export type CrossSwap = {
4041
isOutputNative?: boolean;
4142
excludeSources?: string[];
4243
includeSources?: string[];
44+
embeddedActions: Action[];
4345
};
4446

4547
export type SupportedDex =

api/_dexes/utils.ts

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
} from "../_spoke-pool-periphery";
4040
import { getUniversalSwapAndBridgeAddress } from "../_swap-and-bridge";
4141
import axios, { AxiosRequestHeaders } from "axios";
42+
import { encodeActionCalls } from "../swap/_utils";
4243

4344
export type CrossSwapType =
4445
(typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE];
@@ -190,14 +191,19 @@ export function buildExactInputBridgeTokenMessage(
190191
crossSwap: CrossSwap,
191192
outputAmount: BigNumber
192193
) {
193-
const transferActions = crossSwap.isOutputNative
194+
const unwrapActions = crossSwap.isOutputNative
194195
? // WETH unwrap to ETH
195196
[
196197
{
197198
target: crossSwap.outputToken.address,
198199
callData: encodeWethWithdrawCalldata(outputAmount),
199200
value: "0",
200201
},
202+
]
203+
: [];
204+
const transferActions = crossSwap.isOutputNative
205+
? // ETH transfer
206+
[
201207
{
202208
target: crossSwap.recipient,
203209
callData: "0x",
@@ -212,10 +218,20 @@ export function buildExactInputBridgeTokenMessage(
212218
value: "0",
213219
},
214220
];
221+
const embeddedActions = crossSwap.embeddedActions
222+
? encodeActionCalls(
223+
crossSwap.embeddedActions,
224+
crossSwap.outputToken.chainId
225+
)
226+
: [];
227+
215228
return buildMulticallHandlerMessage({
216229
fallbackRecipient: getFallbackRecipient(crossSwap),
217230
actions: [
218-
...transferActions,
231+
// unwrap weth if output token is native
232+
...unwrapActions,
233+
// execute destination actions or transfer output tokens
234+
...(embeddedActions.length > 0 ? embeddedActions : transferActions),
219235
// drain remaining bridgeable output tokens from MultiCallHandler contract
220236
{
221237
target: getMultiCallHandlerAddress(crossSwap.outputToken.chainId),
@@ -235,14 +251,19 @@ export function buildExactInputBridgeTokenMessage(
235251
* tokens are refunded to the depositor.
236252
*/
237253
export function buildExactOutputBridgeTokenMessage(crossSwap: CrossSwap) {
238-
const transferActions = crossSwap.isOutputNative
254+
const unwrapActions = crossSwap.isOutputNative
239255
? // WETH unwrap to ETH
240256
[
241257
{
242258
target: crossSwap.outputToken.address,
243259
callData: encodeWethWithdrawCalldata(crossSwap.amount),
244260
value: "0",
245261
},
262+
]
263+
: [];
264+
const transferActions = crossSwap.isOutputNative
265+
? // ETH transfer
266+
[
246267
{
247268
target: crossSwap.recipient,
248269
callData: "0x",
@@ -260,10 +281,20 @@ export function buildExactOutputBridgeTokenMessage(crossSwap: CrossSwap) {
260281
value: "0",
261282
},
262283
];
284+
const embeddedActions = crossSwap.embeddedActions
285+
? encodeActionCalls(
286+
crossSwap.embeddedActions,
287+
crossSwap.outputToken.chainId
288+
)
289+
: [];
290+
263291
return buildMulticallHandlerMessage({
264292
fallbackRecipient: getFallbackRecipient(crossSwap),
265293
actions: [
266-
...transferActions,
294+
// unwrap weth if output token is native
295+
...unwrapActions,
296+
// execute destination actions or transfer output tokens
297+
...(embeddedActions.length > 0 ? embeddedActions : transferActions),
267298
// drain remaining bridgeable output tokens from MultiCallHandler contract
268299
{
269300
target: getMultiCallHandlerAddress(crossSwap.outputToken.chainId),
@@ -285,7 +316,7 @@ export function buildMinOutputBridgeTokenMessage(
285316
crossSwap: CrossSwap,
286317
unwrapAmount?: BigNumber
287318
) {
288-
const transferActions = crossSwap.isOutputNative
319+
const unwrapActions = crossSwap.isOutputNative
289320
? // WETH unwrap to ETH
290321
[
291322
{
@@ -295,18 +326,42 @@ export function buildMinOutputBridgeTokenMessage(
295326
),
296327
value: "0",
297328
},
329+
]
330+
: [];
331+
const transferActions = crossSwap.isOutputNative
332+
? // ETH transfer
333+
[
298334
{
299335
target: crossSwap.recipient,
300336
callData: "0x",
301337
value: (unwrapAmount || crossSwap.amount).toString(),
302338
},
303339
]
304340
: // ERC-20 token transfer
305-
[];
341+
[
342+
{
343+
target: crossSwap.outputToken.address,
344+
callData: encodeTransferCalldata(
345+
crossSwap.recipient,
346+
unwrapAmount || crossSwap.amount
347+
),
348+
value: "0",
349+
},
350+
];
351+
352+
const embeddedActions = crossSwap.embeddedActions
353+
? encodeActionCalls(
354+
crossSwap.embeddedActions,
355+
crossSwap.outputToken.chainId
356+
)
357+
: [];
306358
return buildMulticallHandlerMessage({
307359
fallbackRecipient: getFallbackRecipient(crossSwap),
308360
actions: [
309-
...transferActions,
361+
// unwrap weth if output token is native
362+
...unwrapActions,
363+
// execute destination actions or transfer output tokens
364+
...(embeddedActions.length > 0 ? embeddedActions : transferActions),
310365
// drain remaining bridgeable output tokens from MultiCallHandler contract
311366
{
312367
target: getMultiCallHandlerAddress(crossSwap.outputToken.chainId),
@@ -454,11 +509,14 @@ export function buildDestinationSwapCrossChainMessage({
454509
swapTxn.to === "0x0" && swapTxn.data === "0x0" && swapTxn.value === "0x0"
455510
);
456511

457-
let transferActions: {
512+
type Action = {
458513
target: string;
459514
callData: string;
460515
value: string;
461-
}[] = [];
516+
};
517+
518+
let transferActions: Action[] = [];
519+
let unwrapActions: Action[] = [];
462520

463521
// If output token is native, we need to unwrap WETH before sending it to the
464522
// recipient. This is because we only handle WETH in the destination swap.
@@ -467,12 +525,14 @@ export function buildDestinationSwapCrossChainMessage({
467525
(crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT ||
468526
crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT)
469527
) {
470-
transferActions = [
528+
unwrapActions = [
471529
{
472530
target: crossSwap.outputToken.address,
473531
callData: encodeWethWithdrawCalldata(crossSwap.amount),
474532
value: "0",
475533
},
534+
];
535+
transferActions = [
476536
{
477537
target: crossSwap.recipient,
478538
callData: "0x",
@@ -515,14 +575,16 @@ export function buildDestinationSwapCrossChainMessage({
515575
];
516576
} else if (crossSwap.type === AMOUNT_TYPE.EXACT_INPUT) {
517577
if (crossSwap.isOutputNative) {
518-
transferActions = [
578+
unwrapActions = [
519579
{
520580
target: crossSwap.outputToken.address,
521581
callData: encodeWethWithdrawCalldata(
522582
destinationSwapQuote.minAmountOut
523583
),
524584
value: "0",
525585
},
586+
];
587+
transferActions = [
526588
{
527589
target: crossSwap.recipient,
528590
callData: "0x",
@@ -551,6 +613,11 @@ export function buildDestinationSwapCrossChainMessage({
551613
value: swapTxn.value,
552614
}));
553615

616+
const embeddedActions =
617+
crossSwap.embeddedActions && !isIndicativeQuote
618+
? encodeActionCalls(crossSwap.embeddedActions, destinationSwapChainId)
619+
: [];
620+
554621
return buildMulticallHandlerMessage({
555622
fallbackRecipient: getFallbackRecipient(crossSwap),
556623
actions: [
@@ -565,8 +632,10 @@ export function buildDestinationSwapCrossChainMessage({
565632
},
566633
// swap bridgeable output token -> cross swap output token
567634
...swapActions,
568-
// transfer output tokens to recipient
569-
...transferActions,
635+
// unwrap weth if output token is native
636+
...unwrapActions,
637+
// transfer output tokens to recipient or execute destination actions
638+
...(embeddedActions.length > 0 ? embeddedActions : transferActions),
570639
// drain remaining bridgeable output tokens from MultiCallHandler contract
571640
{
572641
target: getMultiCallHandlerAddress(destinationSwapChainId),
@@ -576,6 +645,20 @@ export function buildDestinationSwapCrossChainMessage({
576645
),
577646
value: "0",
578647
},
648+
// drain remaining swap output tokens from MultiCallHandler contract
649+
// (only needed when there's embedded actions, otherwise the drain call is already part of transferActions)
650+
...(embeddedActions.length > 0
651+
? [
652+
{
653+
target: getMultiCallHandlerAddress(destinationSwapChainId),
654+
callData: encodeDrainCalldata(
655+
crossSwap.outputToken.address,
656+
crossSwap.refundAddress ?? crossSwap.depositor
657+
),
658+
value: "0",
659+
},
660+
]
661+
: []),
579662
],
580663
});
581664
}

api/_multicall-handler.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import { BigNumber, ethers } from "ethers";
2-
import { utils } from "@across-protocol/sdk";
32
import { CHAIN_IDs } from "./_constants";
43

54
export function getMultiCallHandlerAddress(chainId: number) {
6-
// @todo: use API to source addresses?
7-
const defaultAddress =
8-
utils.getDeployedAddress("MulticallHandler", chainId) ||
9-
"0x924a9f036260DdD5808007E1AA95f08eD08aA569";
5+
const defaultAddress = "0x0F7Ae28dE1C8532170AD4ee566B5801485c13a0E";
106
switch (chainId) {
117
case CHAIN_IDs.LENS:
12-
return "0xc5939F59b3c9662377DdA53A08D5085b2d52b719";
8+
return "0x1Ed0D59019a52870337b51DEe8190486a8663037";
139
case CHAIN_IDs.ZK_SYNC:
14-
return "0x863859ef502F0Ee9676626ED5B418037252eFeb2";
10+
return "0x68d3806E57148D6c6793C78EbDDbc272fE605dbf";
1511
case CHAIN_IDs.LINEA:
16-
return "0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB";
12+
return "0xdF1C940487574EEfa79989a79a4936A0F979cDa2";
1713
default:
1814
return defaultAddress;
1915
}
@@ -83,3 +79,22 @@ export function encodeDrainCalldata(token: string, destination: string) {
8379
destination,
8480
]);
8581
}
82+
83+
export function encodeMakeCallWithBalanceCalldata(
84+
target: string,
85+
callData: string,
86+
value: ethers.BigNumberish,
87+
replacements: Array<{ token: string; offset: ethers.BigNumberish }>
88+
) {
89+
const makeCallWithBalanceFunction =
90+
"function makeCallWithBalance(address target, bytes callData, uint256 value, (address token, uint256 offset)[] replacement)";
91+
const multicallHandlerInterface = new ethers.utils.Interface([
92+
makeCallWithBalanceFunction,
93+
]);
94+
return multicallHandlerInterface.encodeFunctionData("makeCallWithBalance", [
95+
target,
96+
callData,
97+
value,
98+
replacements,
99+
]);
100+
}

0 commit comments

Comments
 (0)