Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions contracts/usdfree/Interfaces.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.30;

struct TypedData {
uint8 typ;
bytes data;
}

// Defines sequence of substeps performed as a part of that step
enum StepType {
// 1. Alter user reqs
// Note. Alter user requirements can take many different forms. For example, an offchain auction authority can sign
// over some payload to bump up the user's balanceReq. Or a user can trust a submitter (e.g. RL submitter) to bump
// up balanceReq (effectively, this is the same as having auction authority == RL submitter). Alternatively, imagine
// an Alter substep that: takes token amount in (from mint on dst chain), takes onchain oracle price, takes user
// BPS discount / premium required, alters balanceReq: balanceReq = tokenIn * price * (1 + bps_disc_or_premium)
// 2. Submitter actions to meet user reqs (MulticallHandler instructions or weiroll)
// 3. User req checks and action/transfer (see UserReqsAndAction / UserReqsAndTransfer)
AlterSubmitterUser,
// only 2. and 3.
SubmitterUser,
// only 3.
User
}

struct RefundSettings {
bytes32 refundRecipient;
uint256 reverseDeadline;
}

enum AlterSubstepType {
OffchainAuction, // user specifies auction authority
DutchOnchainAuction, // user specifies dutch auction params
OffchainAuctionWithDutchOnchainFallback, // user specifies offchain auction authority + fallback deadline + onchain dutch auction params
PriceOraclePlusDelta, // user specifies oracle address plus a delta (discount or permium) that they expect
SimpleSubmitterAlter // user doesn't specify anything besides the type. Submitter (checked by submitterReq) has the option to bump the user balanceReq up (e.g. a trusted submitter like RL submitter)
}

enum UserSubstepType {
ReqPlusAction,
ReqPlusTransfer
}

enum UserReqType {
Deadline,
Submitter,
Staticcall
}

struct UserReqs {
TypedData balanceReq;
TypedData[] otherReqs;
}

struct UserReqsAndAction {
UserReqs reqs;
bytes32 target;
bytes message;
}

struct UserReqsAndTransfer {
UserReqs reqs;
bytes32 recipient;
}

struct Step {
StepType typ;
RefundSettings refundSettings;
TypedData userSubstep;
bytes[] parts; // user-defined settings (often guardrails) for other steps
}

struct MerkleOrder {
bytes32 salt;
bytes32 routesRoot;
}

struct TokenAmount {
address token;
uint256 amount;
}

enum NextStepsType {
PlainArray, // Step[]
PlainRecursive, // StepAndNext
Obfuscated // bytes32
}

struct StepAndNext {
Step curStep;
TypedData nextSteps;
}

struct MerkleRoute {
StepAndNext stepAndNext;
bytes32[] proof;
}

struct SubmitterData {
TokenAmount[] extraFunding;
bytes[] parts;
}

enum OrderFundingType {
Approval,
Permit2,
TransferWithAuthorization
}

interface IOrderGateway {
function submit(
MerkleOrder calldata order,
MerkleRoute calldata route,
// Funding by the party authorizing the order. Funding has the amount of a single ERC20 token
TypedData calldata orderFunding,
SubmitterData calldata submitterData
) external payable;
}

interface IExecutor {
function execute(
bytes32 orderId,
TokenAmount calldata userTokenAmount,
StepAndNext calldata stepAndNext,
address submitter,
bytes[] calldata submitterParts
) external payable; // onlyOrderGateway / onlyOrderStore
}

interface IUserActionExecutor {
function execute(
bytes32 orderId,
TokenAmount calldata tokenAmount,
bytes calldata actionParams, // params for the execution on the current chain
TypedData calldata nextSteps // steps to pass along
) external payable;
}

interface IOrderStore {
function handle(bytes32 orderId, TokenAmount calldata tokenAmount, TypedData calldata steps) external payable;

// handle + fill immediately
function handleAtomic(
bytes32 orderId,
TokenAmount calldata tokenAmount,
TypedData calldata steps,
// Note: submitter here is technically an untrusted argument. However, it's up to the user on src chain to ensure that they're
// setting a receiving contract on the DST chain that will only allow a valid submitter value here (think, DstCctpPeriphery
// that will allow to finalize cctp + handleAtomic and just pass in msg.sender). It's on the underlying bridge to
// ensure the validity of the message passed, and on the receiving periphery contract to ensure the correctness of
// submitter address passed
address submitter,
SubmitterData calldata submitterData
) external payable;

function fill(uint256 localOrderIndex, SubmitterData calldata submitterData) external payable;
}
101 changes: 101 additions & 0 deletions contracts/usdfree/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# USDFree

A mechanism-agnostic cross-chain order system that unifies CCTP, OFT, and Across bridge flows into a single architecture.

## Goals

- Bridge tokens cross-chain regardless of underlying mechanism
- Single upgradeable entry point (no re-approvals needed)
- Support gasless flows and sponsorship
- Enable chained cross-chain operations (src → A → B → dst)
- Execute arbitrary user actions after token delivery

## Architecture

```
Source Chain:
User → OrderGateway → Executor → IUserActionExecutor
[Bridge Message with nextSteps]
Destination Chain:
BridgeHandler → OrderStore → Executor → IUserActionExecutor
```

### Components

**OrderGateway** - Entry point for all order submissions

- `submit()` calculates `orderId` and pulls tokens from user and submitter (gasless- or approval-based)
- Pushes all of token amounts to `Executor`. Pushes tokens one by one starting from the orderFunding token. If submitter
provided extraFunding and the first token in that array is the same as the orderFunding token, the amounts get pushed
via a single Transfer to reduce gas costs.

**Executor** - Executes a single step (a series of atomic substeps)

- Runs a series of substeps

_AlterSubmitterUser_ variant

- Run alter substep (produces `Changes` to modify user requirements in the later step):
alter user requirements may take many different forms. For example, an offchain auction authority can sign
over some payload to bump up the user's balanceReq. Or a user can trust a submitter (e.g. RL submitter) to bump
up balanceReq (effectively, this is the same as having auction authority == RL submitter). Alternatively, imagine
an Alter substep that: takes token amount in (from mint on dst chain), takes onchain oracle price, takes user
BPS discount / premium required, alters balanceReq: balanceReq = tokenIn _ price _ (1 + bps_disc_or_premium)
- Run submitter substep (e.g. DEX swaps, or taking a fee as long as it meets user balance requirement in the next step)
- Run user substep: check requirements and execute transfer or action (based on `UserSubstepType`). Action variant calls
`IUserActionExecutor`. Transfer is push-based, action is approve-tranferFrom-based.

_SubmitterUser_

- only submitter and user substeps from above

_User_

- only user substep

**IUserActionExecutor** - Final action interface

- Pull tokens from Executor using the provided tokenAmount
- Execute user-specified action. For example, talk to IOFT to bridge tokens over to the next chain. It's the responsibility
of the user action executor to propagate next steps correctly to the next execution leg (e.g. in `composeMsg` for OFT).
Other bridge options can include CCTP, SpokePool and others.

**OrderStore** - Destination-side order management

- `handle()` - Receive tokens from bridge, store for filling
- `handleAtomic()` - Receive and fill in one transaction
- `fill()` - Fill a stored order

## Design Decisions

**Order ID Scope**: Computed once at OrderGateway with domain separation (srcChainId). Propagated cross-chain, but can be
spoofed on destination chains. Crosschain tracking is done as described below.

**Cross-Chain Tracking**: Uses bridge-native identifiers (OFT guid, CCTP nonce, etc.). API/indexer correlates the full chain.

**Multi-Chain Orders**: `nextSteps` are passed with bridge messages, enabling multi-hop orders.

**Token Custody**: Tokens sit in OrderStore until fill. During step execution, temporarily in Executor. Executor should never have approvals.

**Refunds**: Permissionless after reverse deadline - anyone can trigger, tokens go to `refundSettings.refundRecipient`.

**Obfuscation**: Trust-based. If user needs to hide their action, they specify a trusted submitter via `submitterReq`.

## Dst chain paths

**CCTP/OFT flows**: Oft/CctpHandler → OrderStore.handle() → fill()
\*Cctp finalizer can call CctpHandler.handleAtomic → OrderStore.handleAtomic()

**SpokePool**: Uses `handleV3AcrossMessage` wrapper that routes to IUserActionExecutor directly (skips OrderStore since SpokePool already checks requirements).

## Sponsorship

Two models supported:

1. **Phase0-compatible**: Uses an Order with a single user action, which conatins all of the Phase0 parameters (including
the API signature). Essentially hands over execution to the Phase0 system after src chain's submitterActions-reqChecks-
-userAction sequence is done.
2. **Off-chain**: API reimburses relayers post-execution based on orderId and execution chain tracking by the Indexer. API
only sponsors orders it can track completion of.
Binary file added contracts/usdfree/diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading