Submit an Order

Openflow SDK provides an encapsulated easy-to-use interface for executing swaps and managing orders. Below we demonstrate SDK usage.

Basic Swap

For convenience several aliases have been built to make swapping as seamless as possible. To execute a basic swap with default swap options use swap(address,address). All swaps return a unique order UID which can be used later to invalidate/cancel orders if desired. To execute a swap you must allow your fromToken to be spent by your SDK instance.

IERC20(fromToken).approve(address(sdk), type(uint256).max);
bytes memory orderUid = sdk.swap(fromToken, toToken);

Conditional swap

All swaps regardless of authentication type support user-defined conditions. The condition configuration is as follows:

struct Condition {
    address target;
    bytes data;
}

In order for a swap to succeed the condition check must pass. The condition check is a non-state altering (staticcall) method which is called against user-defined condition.target and condition.data.

Conditional swaps unlock a wide variety of configurable/programmable swaps for all users.

The SDK comes with a basic conditional swap alias:

Condition memory condition = IOpenflowSdk.Condition({
   target: conditionChecker,
   data: abi.encodeWithSignature("checkCondition()")
});
bytes memory orderUid = sdk.conditionalSwap(fromToken, toToken, condition);

Good after time (GAT) swap

An SDK alias exists for GAT swaps. The syntax to execute a simple GAT is as follows:

uint32 validFrom = uint32(block.timestamp + (60*5)); // Order is valid in 5 minutes
bytes memory orderUid = sdk.gatSwap(fromToken, toToken, validFrom);

Pre-swap and post-swap hooks

One unique feature of Openflow is that all order submitters are allowed to define completely user-defined pre-swap and post-swap hooks.

User-defined hooks enable some unique/interesting use cases. For example:

  • Hooks allow users to zap in and zap out of a protocol

    • In the traditional solver landscape if a user wishes to zap, say, into a yvToken (vault token) only solvers that support this type of zap can compete

    • By allowing users to execute the zap logic themselves in hooks, this allows the entire pool of solvers to compete in the underlying swap auction rather than just the one or two solvers who support this type of zap natively. This means better and more competitive pricing for end-users. This also means higher reliability and decentralization, because in the current landscape if one of the few solvers who support esoteric zaps has downtown this affect can ripple downstream to your customers, either affecting the user's ability to zap entirely or potentially rugging users entirely (if a malicious solver offers a low bid and all the other bidders are offline). Openflow believes one of the major strengths of the protocol is that unlike other protocols it is not pay-to-play. Anyone can become a solver.

  • Hooks allow EOAs and smart contracts to require custom setup/tear-down logic, essentially enabling an entire keep3r like architecture to exist by incentivizing solvers with any token of the users' choice.

    • A user could submit a swap request where fromAmount is any amount (sufficient to cover gas + some incentive) and fromToken is any token of their choice, and toAmount is zero. In this example the user would also need to turn on the skipOracle feature. The user could then define any actions they wish to be executed at all, with conditional logic, that is valid after a specific time or for a duration of a time. The user could even create more orders based on the current post-swap state in a post-swap hook essentially enabling a completely programmable cascading keep3r-like system, supported on the majority of chains.

  • Hooks allow a variety of other features where, your imagination is the limit. Pre-swap hooks are actually used by the SDK itself to transfer tokens from sender to settlement before executing an order. This allows the SDK to act as an order delegator on behalf of sender (usually the SDK instance initiator).

  • For more information on hooks take a look at the hooks integration tests.

Zap-in Example

ISettlement.Hooks memory hooks;
hooks.postHooks = new ISettlement.Interaction[](1);
hooks.postHooks[0] = ISettlement.Interaction({
    target: vaultInteractions,
    value: 0,
    data: abi.encodeWithSignature(
        "deposit(address,address)",
        toToken,
        userA
    )
});

ISettlement.Payload memory swap;
swap.fromToken = fromToken;
swaptoToken = toToken;
swap.hooks = hooks;
sdk.submitOrder(swap);

Zap-out Example

ISettlement.Hooks memory hooks;
hooks.preHooks = new ISettlement.Interaction[](1);
preHooks[0] = ISettlement.Interaction({
    target: vaultInteractions,
    value: 0,
    data: abi.encodeWithSignature("withdraw(address)", address(vault))
});
ISettlement.Payload memory swap;
swap.fromToken = fromToken;
swaptoToken = toToken;
swap.hooks = hooks;
sdk.submitOrder(swap);

Hooks Implementation Details

Under the hood hooks are defined by the user in the swap payload. Hooks are executed during the execution cycle of a swap. Hooks are always executed from the context of the execution proxy. The reason this is done is to de-couple hook execution from Settlement to limit any potential surface area of attack. It is not recommended to allow execution proxy to spend your tokens! Instead, utilize the built in authentication mechanism which validates that the external call made by the execution proxy is authenticated by sender in the order's signed payload.

Authenticated sender is appended to the transaction data of each hook. To recover sender do as follows in your hook target if authentication is required:

require(msg.sender == executionProxy, "Only execution proxy");
address signatory;
assembly {
    signatory := shr(96, calldataload(sub(calldatasize(), 20)))
}
require(
    signatory == address(this),
    "Transfer must be initiated from SDK"
);

For a real-world example of this see the SDK Order Delegator contract.

Completely Customized Swaps

The SDK supports additional types of swaps (interval swaps, DCA swaps, stop-loss pattern, etc) which will be documented at a later date.

Finally to submit a completely custom swap do as follows:

ISettlement.Payload memory customSwap;
customSwap.fromToken = fromToken; // Required
customSwap.toToken = toToken; // Required
customSwap.fromAmount = fromAmount; // Default is fromToken.balanceOf(sender)
customSwap.toAmount = toAmount; // Default comes from fromAmount, slippage settings and oracle
customSwap.sender = sender; // Default is options.sender
customSwap.recipient = recipient; // Default is options.recipient
customSwap.validFrom = validFrom; // Default is block.timestamp
customSwap.validTo = validTo; // Default is validFrom + options.auctionDuration
customSwap.driver = driver; // Default is options.driver
customSwap.condition = condition; // Default is no condition
customSwap.hooks = hooks; // Default is no hooks
sdk.submitOrder(customSwap);

Questions

If you have any questions or comments please feel free to reach out. Join our discord server!

Last updated