Skip to main content

What are Smart Transactions?

Smart Transactions are MEV-resistant, context aware, and dynamically executed transactions.

Smart enough (to allow for programmatic assertions) to:

  • Resist MEV (slippage protection, rollback guarantees, gasless attempts)
  • Have context awareness in terms of the mempool context (what the other transactions are up to), and of the wider world (by way of oracles updated on the fly)
  • Be dynamically executed by determining their exact execution path dynamically at the very last moment, such as when exactly (with delays or specific conditions being true) to execute by whom, and which code can or cannot be run around them

Explore the concepts of the talk in detail here.

Core Features

Smart Transactions are useful for infrastructure development, for all prior blockchain use cases, and shiny new smart-transaction-only features

  • MEV-Time Logic: Unlocks conditional execution semantics in transactions on EVM. Allows invariant enforcement on bundled transactions and introspection on execution state. STXNs make MEV time secure through bundle protection.
  • Time-Travel: "Time travel" introduces a paradigm where transactions can be executed in a sequence, with later transactions depending on the outcome of earlier ones.
  • Solver-service: Solvers resolve your transactions at MEV-Time, allowing transactions to compute values at the time that the transaction is carried out instead of atomically.

Example of Smart Transactions

Token Exchange with Conditional Execution: Imagine a scenario where Alice wants to trade her tokens but only under specific conditions that she defines, such as achieving a certain exchange rate. To facilitate this, Alice can deploy a smart contract designed to enforce these conditions, ensuring that her tokens are only exchanged when her criteria are fully satisfied. This approach exemplifies how smart transactions can be tailored to meet individual trading strategies and preferences.

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.9.0;

import "openzeppelin/token/ERC20/IERC20.sol";
import "../../src/TimeTypes.sol";
import "../../src/timetravel/CallBreaker.sol";
import "../../src/timetravel/SmarterContract.sol";

contract SelfCheckout is SmarterContract {
address owner;
address callbreakerAddress;

IERC20 atoken;
IERC20 btoken;

// hardcoded exchange rate (btokens per atoken)
uint256 exchangeRate = 2;

// your debt to the protocol denominated in btoken
uint256 imbalance = 0;

// tracks if we've called checkBalance yet. if not it needs to be.
bool balanceScheduled = false;

event DebugAddress(string message, address value);
event DebugInfo(string message, string value);
event DebugUint(string message, uint256 value);

constructor(address _owner, address _atoken, address _btoken, address _callbreakerAddress)
SmarterContract(_callbreakerAddress)
{
owner = _owner;

atoken = IERC20(_atoken);
btoken = IERC20(_btoken);

callbreakerAddress = _callbreakerAddress;
}

modifier onlyOwner() {
require(msg.sender == owner, "Proxy: Not the owner");
_;
}

function getAtoken() public view returns (address) {
return address(atoken);
}

function getBtoken() public view returns (address) {
return address(btoken);
}

function getExchangeRate() public view returns (uint256) {
return exchangeRate;
}

function getCallBreaker() public view returns (address) {
return callbreakerAddress;
}

function getSwapPartner() public view returns (address) {
bytes32 swapPartnerKey = keccak256(abi.encodePacked("swapPartner"));
bytes memory swapPartnerBytes =
CallBreaker(payable(callbreakerAddress)).fetchFromAssociatedDataStore(swapPartnerKey);
return address(bytes20(swapPartnerBytes));
}

event LogCallObj(CallObject callObj);

function takeSomeAtokenFromOwner(uint256 atokenamount) public onlyOwner {
require(CallBreaker(payable(callbreakerAddress)).isPortalOpen(), "CallBreaker is not open");

if (!balanceScheduled) {
CallObject memory callObj = CallObject({
amount: 0,
addr: address(this),
gas: 1000000,
callvalue: abi.encodeWithSignature("checkBalance()")
});
emit LogCallObj(callObj);
assertFutureCallTo(callObj);

balanceScheduled = true;
}

imbalance += atokenamount * exchangeRate;
require(atoken.transferFrom(owner, getSwapPartner(), atokenamount), "AToken transfer failed");
}

function giveSomeBtokenToOwner(uint256 btokenamount) public {
btoken.transferFrom(getSwapPartner(), owner, btokenamount);

if (imbalance > btokenamount) {
imbalance -= btokenamount;
} else {
imbalance = 0;
}
}

function checkBalance() public {
require(imbalance == 0, "You still owe me some btoken!");
balanceScheduled = false;
}

}