Glossary

Crosschain

Crosschain API - Lamina’s forward-facing API interface. Protocols talk to the Crosschain API by sending transaction calldata and target information (chain, address, cost). Crosschain does the heavy lifting on the backend, wrapping userops and calculating the overall transaction cost, then returns data for the protocol to sign before forwarding said signed data as a blob to the Crosschain Client.

// UserOp Operation Structure: 
type UserOperation struct {
	Sender                string `json:"sender"`
	Nonce                 string `json:"nonce"`
	InitCode              string `json:"initCode"`
	CallData              string `json:"callData"`
	CallGasLimit          string `json:"callGasLimit"`
	VerificationGasLimit  string `json:"verificationGasLimit"`
	PreVerificationGas    string `json:"preVerificationGas"`
	MaxFeePerGas          string `json:"maxFeePerGas"`
	MaxPritorityFeePerGas string `json:"maxPriorityFeePerGas"`
	PaymasterAndData      string `json:"paymasterAndData"`
	Signature             string `json:"signature"`
}

// Execution Data Structure:
type ExecutionData struct {
	DestinationChain string `json:"destinationChain"`
	TargetAddress    string `json:"targetAddress"`
	Asset            string `json:"asset"`
	Amount           string `json:"amount"`
	Calldata         string `json:"calldata"`
}

// Protocol's Request Transaction Format:
type RequestTransaction struct {
	Signer           string        `json:"signer"`
	ExecutionRequest ExecutionData `json:"executionRequest"`
}

// Protocol's Request Transaction Response
// (calldata and userop are to be signed):
type RequestTransactionResponse struct {
	Signer   string        `json:"signer"`
	Escrow   string        `json:"escrow"`
	Deadline string        `json:"deadline"`
	Calldata string        `json:"calldata"`
	UserOp   UserOperation `json:"userop"`
}

// Protocol's Signed Format:
type SubmitTransaction struct {
	Signer    string        `json:"signer"`
	Escrow    string        `json:"escrow"`
	Deadline  string        `json:"deadline"`
	Calldata  string        `json:"calldata"`
	Signature string        `json:"signature"`
	Hash      string        `json:"hash"`
	UserOp    UserOperation `json:"userop"`
}

Crosschain Client - Lamina's backend infrastructure responsible for lock request execution and userop validation. This client currently comprises of a private bundler and relayer but is expected to scale by offering a reward-based model to bring new bundlers and relayers into the fold.

Crosschain Mempool - Lamina’s Crosschain Mempool is a currently a private mempool with custom blobs of data. These blobs include a proposed bid, execution cost, and userop. Solvers digest these blobs and execute an array of userops on the destination chain’s ERC4337 EntryPoint contract.

type ExecutableBlob struct {
	ExecutionRequest ExecutionData `json:"execution"`
	UserOp           UserOperation `json:"userop"`
}

Crosschain Paymaster - Lamina's userop paymaster. During preOp the paymaster validates the userop and digests the paymasterAndData field. Then constructs the Hyperlane message for later authentication on the origin chain. Extra data from the paymasterAndData field will be appended with the Solver's address. During postOp the paymaster will pay for the Hyperlane IGP and redeposit used funds to it's own stake and send residual funds back to the Solver.

// PaymasterAndData Field (generated by Crosschain API):
struct PaymasterAndData {
  address paymaster;
  address owner;
  uint256 chainId;
  address asset;
  uint256 amount;
}

// preOp execution:
function _validatePaymasterUserOp(
    UserOperation calldata userOp,
    bytes32,
    uint256 requiredPreFund
  ) internal override returns (bytes memory context, uint256 validationResult) {
    unchecked {
      bytes calldata data = userOp.paymasterAndData;

      uint256 paymasterAndDataLength = data.length;
      // 124 == crosschain non-payable
      if (paymasterAndDataLength != 124 && paymasterAndDataLength != 176) {
        revert InvalidDataLength(paymasterAndDataLength);
      }

      address paymaster_ = address(bytes20(data[:20]));
      address owner_ = address(bytes20(data[20:40]));
      uint256 chainId_ = uint256(bytes32(data[40:72]));
      address paymentAsset_ = address(bytes20(data[72:92]));
      uint256 paymentAmount_ = uint256(bytes32(data[92:124]));
      bytes32 messageId_;
      uint256 gasAmount_ = 100000;
      uint32 destinationDomain_ = uint32(chainId_);

      if (!acceptedChain[destinationDomain_]) {
        revert InvalidChainId(destinationDomain_);
      }

      // enabled only for MVP; tbh we don't care about the assets used, network is P2P
      if (!acceptedAsset[destinationDomain_][paymentAsset_]) {
        revert InvalidAsset(destinationDomain_, paymentAsset_);
      }

      // withdraw funds for oracle call to paymaster
      // nope, let it fail if the paymaster has insufficent funds, solver should know better
      // entryPoint.withdrawTo(payable(address(this)), 0.1 ether);

      // the bundler/ solver that submits the tx from the uomempool
      // in reality we don't care who executes, but they are burdened with refinding the paymaster and tx cost
      address receiver = tx.origin;
      bytes32 recipientAddress_ = bytes32(uint256(uint160(receiver)));
      IMailbox(hyperlane_mailbox).dispatch(
        destinationDomain_,
        recipientAddress_,
        abi.encode(userOp, receiver)
      );
      uint256 igpQuote_ = IIGP(hyperlane_igp).quoteGasPayment(
        destinationDomain_,
        gasAmount_
      );
      context = abi.encode(
        receiver,
        messageId_,
        destinationDomain_,
        gasAmount_,
        igpQuote_
      );
    }
  }

// postOp refund
function _postOp(
    PostOpMode mode,
    bytes calldata context,
    uint256 actualGasCost
  ) internal override {
    // don't care about the mode, solver fronts the gas
    (mode);
    bytes32 messageId_;
    uint32 destinationDomain_;
    uint256 gasAmount_;
    uint256 igpQuote_;
    address refundAddress_;
    (
      refundAddress_,
      messageId_,
      destinationDomain_,
      gasAmount_,
      igpQuote_
    ) = abi.decode(context, (address, bytes32, uint32, uint256, uint256));
    IIGP(hyperlane_igp).payForGas{value: address(this).balance}(
      messageId_,
      destinationDomain_,
      gasAmount_,
      address(this)
    );

    entryPoint{ value: gasAmount_ }.addStake(0);
     // shouldn't revert, but doesn't matter
    payable(refundAddress_).call{ value: address(this).balance }("");
  }

Hyperlane

Hyperlane Mailbox - The mailbox, for Hyperlane messages, acts as either the receiver contract on destination chains, or the sender contract on origin chains. Hyperlane messages transmitted or received include: target chain, target address, value, and message. When a valid message received on the origin chain it will be added to the Hyperlane network with a pending state until payment is accepted by the Hyperlane IGP. When a valid message is received on the destination chain it will use the message data to call the target contract. Learn more here.

Hyperlane IGP (Interchain Gas Paymaster) - The gas paymaster estimates this native currency cost required to process the submitted Hyperlane message. The gas requirements must be quoted after the Hyperlane message is submitted. Crosschain Paymaster quotes the cost of a Hyperlane message during preOp and submits payment during postOp. This approach ensures that the Hyperlane message is triggered only if the userop is executed successfully. Learn more here.

Hyperlane Validator - The Hyperlane network on any chain includes a series of validators that process transactions on destination chains. Learn more here.

Hyperlane Handler - The handle function on the target contract accepting a message from the Hyperlane Mailbox on the destination chain. Learn more here.


Escrow

Escrow - Every signer has a create2 escrow address. The Escrow is a lightweight ERC1967 proxy that supports tracking of any lock or unlocked token. Only the signers locked funds are used for paying solvers. When receiving a Hyperlane message to handle it will validate and then execute the Solver payout.

Escrow Factory - Factory contract for initializing a signer’s ERC1967 Escrow.

Copy

// ERC1967 Escrow Initialization
function createEscrow(bytes memory _initializer, bytes32 _salt) external returns (address proxy) {
        bytes memory deploymentData = abi.encodePacked(type(EscrowProxy).creationCode, _ESCROWIMPL);
        bytes32 salt = _calcSalt(_initializer, _salt);
        assembly ("memory-safe") {
            proxy := create2(0x0, add(deploymentData, 0x20), mload(deploymentData), salt)
        }
        if (proxy == address(0)) {
            revert();
        }
        assembly ("memory-safe") {
            let succ := call(gas(), proxy, 0, add(_initializer, 0x20), mload(_initializer), 0, 0)
            if eq(succ, 0) { revert(0, 0) }
        }
        return proxy;
    }

Escrow Simpleton - Escrow current logic contract deployment.

Last updated

© 2024 Lamina Labs. All Rights Reserved