📹Solver Network

Uniroll supports the integration of solver networks to allow solvers to leverage shared settlement. Solvers create and fill intents on the spoke contracts while offchain agents handle settlement.

Uniroll is still in active development. These interfaces are subject to change.

Introduction

This guide will walk you through the integration of an intent protocol into the clearing chain. The intent protocol used in this example has a network of solvers that will relay a greeting from one chain to another for a fee.

Creating an Intent

contract Greeter {
    // Storage
    IERC20 public token;
    IUnirollSpoke public uniroll;
    
    // Destination chain configuration for Greeter
    uint32 public destination;
    address public destinationToken;
    address public destinationGreeter;
    
    // Stored greeting
    string public greeting;
    
    // Events
    event GreetingDispatched(bytes32 indexed id, uin256 fee, string greeting);
    
    constructor(
        address _token,
        address _uniroll,
        uint32 _destination,
        address _destinationToken,
        address _destinationGreeter
    ) {
        token = IERC20(_token);
        uniroll = IUniroll(_uniroll);
        
        destination = _destination;
        destinationToken = _destinationToken;
        destinationGreeter = _destinationGreeter;
        
        greeting = "Pay me for change";
    }
    
    function setGreeting(string memory _greeting, uint256 _fee) public {
        // Approve token to connext
        token.approve(address(uniroll), _fee);
        
        // Create the intent data
        bytes memory data = bytes(_greeting);
        
        // Call new intent
        (bytes32 intentId, Intent memory intent) = uniroll.newIntent(
            destination, 
            destinationGreeter, 
            address(token), 
            destinationToken, 
            _fee, 
            data
        );
        emit GreetingDispatched(intentId, fee, greeting);
    }
}

Once the GreetingDispatched event is emitted, a message to the clearing chain is queued and solvers of the greeter network should competing to fill that intent. Uniroll makes no assumptions about solver validity or intent discovery.

Filling Intents

Once the solver is ready to execute, they can call fillIntent on the destination spoke contract. Before calling fillIntent, solvers should ensure they have sufficient balance of the output asset on the spoke contracts (this can be done in the same call):

Copy

contract GreeterSolverProxy {
    Greeter public greeter;
    
    event GreeterIntentFilled(address indexed caller, Intent intent);
    
    constructor(address _greeter) {
        greeter = Greeter(_greeter);
    }
    
    function depositAndFill(address _fundingSource, Intent memory _intent) public {
        // Pull tokens from funding source
        IERC20(_intent.outputAsset).transferFrom(_fundingSource, address(this), _intent.amount);
        
        // Approve spoke
        IERC20(_intent.outputAsset).approve(address(greeter), _intent.amount);
        
        // Deposit
        greeter.connext.deposit(_intent.outputAsset, _intent.amount);
        
        // Fill intent
        fill(_intent);
    }
    
    function fill(Intent memory _intent) public {
        greeter.connext.fill(intent);
        emit GreeterIntentFilled(msg.sender, _intent);
    }
}

Once the intent is filled, a message to the clearing chain is queued. As soon as the intent and fill messages arrive to the clearing chain, the intent is ready to be settled.

Settlement

Once intent and fill messages arrive on the clearing chain, they are ready to be settled. When grouping intents into settlements, you should do the following for each intent to be settled:

  1. Check the supported chain by the solver.

  2. Check the available liquidity on each chain.

  3. If there is sufficient liquidity to settle on the target chain (i.e. the chain that the solver executed on), settle there.

  4. Otherwise, settle to the chain that has the highest liquidity that the solver also supports.

Offchain agents will automatically process messages and settlements in batches. Solvers are welcome to expedite settlement by calling the following methods on the ConnextSpoke contracts:

If you choose to self-process your settlement, you will incur the costs of the other enqueued intents.

contract GreeterSolverProxy {
    ISpokeGateway gateway;
    
    event FillQueueProcessed(uint32 number, uint256 fee);
    event IntentQueueProcessed(uint32 number, uint256 fee);
    // ...
    
    // This function will require callers to pass in the appropriate messaging fee.
    // This fee can be estimated using `ISpokeGateway.quoteMessage`.
    // The queue size can be retrieved from the subgraphs. This is called on the destination
    // chain of the intent.
    function fillAndProcess(Intent memory _intent, uint32 _queueSize) public payable {
        fill(_intent);
        greeter.connext.processFillQueue{value: msg.value}(_queueSize + 1);
        emit FillQueueProcessed(_queueSize + 1, msg.value);
    }
    
    // Both message queues must arrive on the clearing chain for the intent to be
    // elligible for settlement. This function must be called on the intent origin.
    function processIntentQueue(uint32 _queueSize) public payable {
        greeter.connext.processIntentQueue{value:  msg.value}(_queueSize);
        emit IntentQueueProcessed(_queueSize, msg.value);
    }
}

These queue methods will dispatch messages to the clearing chain. Once both messages arrive, the intent can be settled by offchain agents.

The settlement functions are non-custodial, so it is possible to self-settle. At the moment, it is non-trivial to get the settlement payloads but SDK methods will be exposed in the future.

Monitoring Intent Status

Intents can be monitored using the subgraphs. See the playgrounds here to explore the entities!

Remember that the subgraphs are not natively crosschain, so if a destination intent exists the intent is filled. Similarly, if a hub intent exists the status can be used to determine which messages need to arrive or if the intent is ready to be settled.

Querying for Intents on Spokes

query SpokeIntents {
  _meta { block { number, timestamp } }
  
  destinationIntents(first: 5) {
    id
    status
    queueIdx
    fillEvent {
      filler
    }
    amount
    origin
    destination
    initiator
    receiver
    inputAsset
    outputAsset
  }
  
  originIntents(first: 5) {
    id
    status
    queueIdx
    addEvent {
      caller
    }
    amount
    destination
    receiver
    inputAsset
    outputAsset
  }
}

Querying for Intents on Hub

query HubIntents {
  _meta { block { number, timestamp } }
  
  hubIntents(first: 5) {
    id
    status
    queueIdx
    addEvent {
      id
      intent {
        queueIdx
      }
    }
    fillEvent {
      id
      intent {
        queueIdx
      }
    }
    settlement {
      id
      domain
      intentIds
      quote
    }
  }
}

Last updated