Enable Flow EVM users to interact with Flow YieldVaults's Cadence-based yield protocol through an asynchronous cross-VM bridge.
EVM users deposit FLOW and submit requests to a Solidity contract. A Cadence worker periodically processes these requests, bridges funds via COA, and manages YieldVault positions on their behalf.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Flow EVM │
│ │
│ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ EVM User │────────▶│ FlowYieldVaultsRequests │ │
│ │ │ │ │ │
│ │ - Deposit │ │ - Request queue │ │
│ │ - Request │◀────────│ - Fund escrow │ │
│ │ - Cancel │ │ - Balance tracking │ │
│ └──────────────┘ └─────────────┬─────────────┘ │
│ │ │
└─────────────────────────────────────────┼───────────────────────────────────┘
│ COA calls:
│ - startProcessingBatch()
│ - completeProcessing()
┌─────────────────────────────────────────┼───────────────────────────────────┐
│ Flow Cadence │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ FlowYieldVaultsEVM │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Worker │ │ │
│ │ │ │ │ │
│ │ │ Capabilities: │ │ │
│ │ │ - coaCap (EVM.Call, EVM.Withdraw, EVM.Bridge) │ │ │
│ │ │ - yieldVaultManagerCap (FungibleToken.Withdraw) │ │ │
│ │ │ - betaBadgeCap (FlowYieldVaultsClosedBeta.Beta) │ │ │
│ │ │ - feeProviderCap (FungibleToken.Withdraw, FungibleToken.Provider)│ │ │
│ │ │ │ │ │
│ │ │ Functions: │ │ │
│ │ │ - processRequest() │ │ │
│ │ │ - preprocessRequests() │ │ │
│ │ │ - markRequestAsFailed() │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ State: │ │
│ │ - yieldVaultRegistry: {String: {UInt64: Bool}} │ │
│ │ - flowYieldVaultsRequestsAddress: EVM.EVMAddress? │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ triggers │
│ ┌───────────────────────────┴─────────────────────────────────────────┐ │
│ │ FlowYieldVaultsEVMWorkerOps │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │ │
│ │ │ SchedulerHandler │───▶│ WorkerHandler │ │ │
│ │ │ │ │ │ │ │
│ │ │ - Recurrent job │ │ - Processes single request │ │ │
│ │ │ - Schedules workers│ │ - Finalizes status on EVM │ │ │
│ │ │ - Crash recovery │ │ - Removes from scheduledRequests │ │ │
│ │ │ - Preprocessing │ └─────────────────────────────────────┘ │ │
│ │ └─────────────────────┘ │ │
│ │ │ │
│ │ State: scheduledRequests, isSchedulerPaused │ │
│ │ Config: schedulerWakeupInterval (1s), maxProcessingRequests (3) │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Request queue and fund escrow contract.
Responsibilities:
- Accept and queue user requests (CREATE_YIELDVAULT, DEPOSIT_TO_YIELDVAULT, WITHDRAW_FROM_YIELDVAULT, CLOSE_YIELDVAULT)
- Escrow deposited funds until processing
- Track user balances and pending request counts
- Enforce access control (allowlist/blocklist)
- Two-phase commit to coordinate cross-VM processing (non-atomic across VMs)
Key State:
// Request tracking
mapping(uint256 => Request) public requests;
uint256[] public pendingRequestIds;
mapping(address => uint256) public userPendingRequestCount;
mapping(address => uint256[]) public pendingRequestIdsByUser;
// Balance tracking
mapping(address => mapping(address => uint256)) public pendingUserBalances; // Escrowed for active requests
mapping(address => mapping(address => uint256)) public claimableRefunds; // Claimable from cancelled, dropped, failed, or success-path residual refunds
// Token configuration
mapping(address => TokenConfig) public allowedTokens;
uint256 public maxPendingRequestsPerUser;
bool public paused;
// YieldVault ownership (EVM-side mirror)
mapping(uint64 => bool) public validYieldVaultIds;
mapping(uint64 => address) public yieldVaultOwners;
mapping(address => uint64[]) public yieldVaultsByUser;
mapping(address => mapping(uint64 => bool)) public userOwnsYieldVault;
// Access control
address public authorizedCOA;
bool public allowlistEnabled;
bool public blocklistEnabled;
mapping(address => bool) public allowlisted;
mapping(address => bool) public blocklisted;Worker contract that processes EVM requests and manages YieldVault positions.
Responsibilities:
- Fetch pending requests from EVM via
getPendingRequestsUnpacked() - Execute two-phase commit (startProcessingBatch → operation → completeProcessing)
- Create, deposit to, withdraw from, and close YieldVaults
- Bridge funds between EVM and Cadence via COA
- Track YieldVault ownership by EVM address
Key State:
// YieldVault ownership tracking
access(all) let yieldVaultRegistry: {String: {UInt64: Bool}}
// Configuration (stored as contract-only vars; exposed via getters)
var flowYieldVaultsRequestsAddress: EVM.EVMAddress?
// Constants
access(all) let nativeFlowEVMAddress: EVM.EVMAddress // 0xFFfF...FfFFFfFWorker orchestration contract with auto-scheduling and crash recovery.
Resources:
- WorkerHandler: Processes individual requests. Scheduled by SchedulerHandler to handle one request at a time.
- SchedulerHandler: Recurrent job that checks for pending requests and schedules WorkerHandlers based on available capacity.
Responsibilities:
- Implement
FlowTransactionScheduler.TransactionHandlerinterface for both handlers - SchedulerHandler checks for pending requests at fixed intervals
- SchedulerHandler calls
preprocessRequests()which validates and transitions requests (PENDING → PROCESSING/FAILED) - SchedulerHandler schedules WorkerHandlers for valid requests returned by
preprocessRequests() - SchedulerHandler identifies panicked WorkerHandlers and marks requests as FAILED
- WorkerHandler processes a single request and updates EVM state on completion
- Sequential scheduling for same-user requests to avoid block ordering issues
- Pausable for maintenance
Key State:
// In-flight request tracking
access(self) var scheduledRequests: {UInt256: ScheduledEVMRequest} // request id → tracking info
access(self) var isSchedulerPaused: Bool
// Configuration
access(self) var schedulerWakeupInterval: UFix64 // Default: 1.0 seconds
access(self) var maxProcessingRequests: UInt8 // Default: 3 concurrent workers
access(all) let executionEffortConstants: {String: UInt64} // Configurable execution effort valuesScheduledEVMRequest:
access(all) struct ScheduledEVMRequest {
access(all) let request: FlowYieldVaultsEVM.EVMRequest
access(all) let workerTransactionId: UInt64
access(all) let workerScheduledTimestamp: UFix64
}Bridge account controlled by the Worker.
Capabilities Required:
EVM.Call- Call EVM contractsEVM.Withdraw- Withdraw native $FLOW from EVMEVM.Bridge- Bridge tokens between VMs
// Sentinel value for "no yieldvault" (type(uint64).max)
uint64 public constant NO_YIELDVAULT_ID = type(uint64).max;
struct Request {
uint256 id; // Auto-incrementing ID (starts at 1)
address user; // Request creator
RequestType requestType; // CREATE_YIELDVAULT | DEPOSIT_TO_YIELDVAULT | WITHDRAW_FROM_YIELDVAULT | CLOSE_YIELDVAULT
RequestStatus status; // PENDING | PROCESSING | COMPLETED | FAILED
address tokenAddress; // NATIVE_FLOW (0xFFfF...FfFFFfF) or ERC20 address
uint256 amount; // Amount in wei (0 for CLOSE_YIELDVAULT)
uint64 yieldVaultId; // Target YieldVault Id (NO_YIELDVAULT_ID for CREATE_YIELDVAULT until completed; for others set at request creation)
uint256 timestamp; // Block timestamp when created
string message; // Status message or error reason
string vaultIdentifier; // Cadence vault type (e.g., "A.xxx.FlowToken.Vault")
string strategyIdentifier; // Cadence strategy type (e.g., "A.xxx.Strategy.Type")
}
enum RequestType {
CREATE_YIELDVAULT, // 0
DEPOSIT_TO_YIELDVAULT, // 1
WITHDRAW_FROM_YIELDVAULT, // 2
CLOSE_YIELDVAULT // 3
}
enum RequestStatus {
PENDING, // 0 - Awaiting processing
PROCESSING, // 1 - Being processed (balance deducted)
COMPLETED, // 2 - Successfully processed
FAILED // 3 - Failed (escrowed CREATE/DEPOSIT may credit a claimable refund)
}
struct TokenConfig {
bool isSupported; // Whether the token is supported
uint256 minimumBalance; // Minimum balance required for deposits
bool isNative; // Whether this is native FLOW token
}access(all) struct EVMRequest {
access(all) let id: UInt256
access(all) let user: EVM.EVMAddress
access(all) let requestType: UInt8
access(all) let status: UInt8
access(all) let tokenAddress: EVM.EVMAddress
access(all) let amount: UInt256
access(all) let yieldVaultId: UInt64
access(all) let timestamp: UInt256
access(all) let message: String
access(all) let vaultIdentifier: String
access(all) let strategyIdentifier: String
}/// Sentinel value for "no yieldvault" (UInt64.max)
access(all) let noYieldVaultId: UInt64 = UInt64.max
access(all) struct ProcessResult {
access(all) let success: Bool
access(all) let yieldVaultId: UInt64 // Uses noYieldVaultId as sentinel for "no yieldvault"
access(all) let message: String
}┌─────────────┐ ┌─────────────────────-──┐ ┌──────────────────┐ ┌──────────────────┐
│ EVM User │ │ FlowYieldVaultsRequests│ │FlowYieldVaultsEVM│ │ Flow YieldVaults │
└──────┬──────┘ └───────────┬─────────-──┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │ │
│ createYieldVault( │ │ │
│ token, amount, │ │ │
│ vault, strategy) │ │ │
│────────────────────▶│ │ │
│ │ Escrow funds │ │
│ │ Create PENDING request │ │
│◀────────────────────│ │ │
│ requestId │ │ │
│ │ │ │
│ │ getPendingRequestsUnpacked │ │
│ │◀───────────────────────│ │
│ │ [EVMRequest] │ │
│ │───────────────────────▶│ │
│ │ │ │
│ │ startProcessingBatch([id], []) │ │
│ │◀───────────────────────│ │
│ │ Mark PROCESSING │ │
│ │ Deduct user balance │ │
│ │───────────────────────▶│ │
│ │ │ │
│ │ │ COA.withdraw(amount)│
│ │ │ or withdrawTokens() │
│ │ │────────────────────▶│
│ │ │ $FLOW / ERC20 │
│ │ │◀────────────────────│
│ │ │ │
│ │ │ createYieldVault() │
│ │ │────────────────────▶│
│ │ │ yieldVaultId │
│ │ │◀────────────────────│
│ │ │ │
│ │ │ Store yieldVaultId │
│ │ │ mapping │
│ │ │ │
│ │ completeProcessing( │ │
│ │ id, true, │ │
│ │ yieldVaultId, msg) │ │
│ │◀───────────────────────│ │
│ │ Mark COMPLETED │ │
│ │ Register YieldVault │ │
│ │───────────────────────▶│ │
1. User calls depositToYieldVault(yieldVaultId, token, amount)
2. Contract validates YieldVault exists (ownership not required)
3. Contract escrows funds, creates PENDING request
4. Worker fetches request via getPendingRequestsUnpacked()
5. Worker does not require ownership for deposits (permissionless)
6. Worker calls startProcessingBatch() → PROCESSING, balance deducted
7. COA withdraws funds from its balance
8. Worker deposits to YieldVault via YieldVaultManager
9. Worker calls completeProcessing() → COMPLETED
1. User calls withdrawFromYieldVault(yieldVaultId, amount)
2. Contract validates YieldVault ownership
3. Contract creates PENDING request (no escrow needed)
4. Worker fetches request via getPendingRequestsUnpacked()
5. Worker validates YieldVault ownership
6. Worker calls startProcessingBatch() → PROCESSING
7. Worker withdraws from YieldVault via YieldVaultManager
8. Worker bridges funds to EVM via COA.deposit()
9. COA transfers $FLOW directly to user's EVM address
10. Worker calls completeProcessing() → COMPLETED
1. User calls closeYieldVault(yieldVaultId)
2. Contract validates YieldVault ownership
3. Contract creates PENDING request (amount = 0)
4. Worker fetches request via getPendingRequestsUnpacked()
5. Worker validates YieldVault ownership
6. Worker calls startProcessingBatch() → PROCESSING
7. Worker closes YieldVault via YieldVaultManager, receives all funds
8. Worker bridges funds to EVM via COA.deposit()
9. COA transfers all $FLOW to user's EVM address
10. Worker removes YieldVault from ownership mappings
11. Worker calls completeProcessing() → COMPLETED
12. Contract unregisters YieldVault ownership
1. User or admin calls cancelRequest(requestId)
2. Contract validates ownership (or admin) and PENDING status
3. Contract marks request as FAILED
4. Contract moves escrowed funds from pendingUserBalances to claimableRefunds
5. Contract decrements pending request count
6. User calls claimRefund(tokenAddress) to withdraw funds
All refund scenarios use a pull pattern - funds are credited to claimableRefunds and must be withdrawn by the user via claimRefund(tokenAddress):
| Scenario | What Happens |
|---|---|
After startProcessingBatch() (failed CREATE/DEPOSIT) |
Funds credited to claimableRefunds |
| Successful CREATE/DEPOSIT with precision loss | Precision residual credited to claimableRefunds |
| User cancels request | Funds moved from pendingUserBalances to claimableRefunds |
| Admin drops request | Funds moved from pendingUserBalances to claimableRefunds |
| WITHDRAW/CLOSE | No escrowed funds on EVM side, so refunds are not applicable |
Important: claimRefund() only withdraws from claimableRefunds. It does NOT touch funds in pendingUserBalances (escrowed for active pending requests).
The bridge uses a two-phase commit pattern for atomic state management:
function startProcessingBatch(uint256[] calldata successfulRequestIds, uint256[] calldata rejectedRequestIds) external onlyAuthorizedCOA {
// 1. Mark rejectedRequestIds as FAILED
// 2. Validate each successful request exists and is PENDING
// 3. Mark successful requests as PROCESSING
// 4. For CREATE_YIELDVAULT/DEPOSIT_TO_YIELDVAULT: Deduct user balance and transfer to COA
// 5. Emit RequestProcessed events
}Purpose: Prevents double-spending by atomically deducting user balance before Cadence operations begin.
function completeProcessing(
uint256 requestId,
bool success,
uint64 yieldVaultId,
string calldata message,
uint256 refundAmount
) external onlyAuthorizedCOA {
// 1. Validate request is PROCESSING
// 2. Validate refundAmount against the request lifecycle
// 3. Mark as COMPLETED or FAILED
// 4. Credit claimableRefunds when a failed CREATE/DEPOSIT or success-path precision residual must stay on EVM
// 5. On CREATE_YIELDVAULT success: Register YieldVault ownership
// 6. On CLOSE_YIELDVAULT success: Unregister YieldVault ownership
// 7. Remove from pending queue
// 8. Emit RequestProcessed event
}Purpose: Finalizes the operation with proper cleanup. Refunds are credited for later claim when a failed CREATE/DEPOSIT must be returned on EVM or when a successful CREATE/DEPOSIT leaves a precision residual on EVM; cross-VM flow is not atomic.
The SchedulerHandler runs at a fixed interval (schedulerWakeupInterval, default 1 second) and performs the following:
- Check if paused - Skip scheduling if
isSchedulerPausedis true - Crash recovery - Identify WorkerHandlers that panicked and mark their requests as FAILED
- Check capacity - Calculate available slots:
maxProcessingRequests - scheduledRequests.length - Fetch pending requests - Get up to
capacitypending requests from EVM - Preprocess requests - Call
preprocessRequests()which validates each request, fails invalid ones, and transitions valid ones to PROCESSING - Schedule workers - Create WorkerHandler transactions for each valid request
- Auto-reschedule - Schedule next SchedulerHandler execution
Each WorkerHandler is scheduled to process a single request:
- Process request - Call
worker.processRequest(request)which handles the actual operation - Remove from tracking - Remove request from
scheduledRequestsdictionary - Finalize on EVM - The worker calls
completeProcessing()to mark COMPLETED or FAILED
When multiple requests from the same EVM user are pending, they are scheduled with sequential delays to ensure ordering:
// Track user request count for scheduling offset
let userScheduleOffset: {String: Int} = {}
for request in requests {
let key = request.user.toString()
if userScheduleOffset[key] == nil {
userScheduleOffset[key] = 0
}
userScheduleOffset[key] = userScheduleOffset[key]! + 1
// Offset delay by user request count
delay = delay + userScheduleOffset[key]! as! UFix64
// Schedule with computed delay
// ...
}The SchedulerHandler monitors scheduled WorkerHandlers for failures:
// For each scheduled request:
// 1. Check if scheduled timestamp has passed
// 2. Get transaction status from manager
// 3. If status is nil (cleaned up) or not Scheduled, the worker panicked
// 4. Mark request as FAILED with error message
// 5. Remove from scheduledRequests| Parameter | Default | Description |
|---|---|---|
schedulerWakeupInterval |
1.0s | Fixed interval between SchedulerHandler executions |
maxProcessingRequests |
3 | Maximum concurrent WorkerHandlers |
Execution effort values are configurable via the executionEffortConstants dictionary and can be updated by the Admin using setExecutionEffortConstants(key, value).
| Key | Default | Description |
|---|---|---|
schedulerBaseEffort |
700 | Base effort for SchedulerHandler execution |
schedulerPerRequestEffort |
1000 | Additional effort per request preprocessed |
workerCreateYieldVaultRequestEffort |
5000 | Effort for CREATE_YIELDVAULT requests |
workerDepositRequestEffort |
2000 | Effort for DEPOSIT_TO_YIELDVAULT requests |
workerWithdrawRequestEffort |
2000 | Effort for WITHDRAW_FROM_YIELDVAULT requests |
workerCloseYieldVaultRequestEffort |
5000 | Effort for CLOSE_YIELDVAULT requests |
Priority is dynamically determined based on execution effort:
- Low: effort ≤ 2500
- Medium: 2500 < effort < 7500
- High: effort ≥ 7500
// User's escrowed balance (funds tied to active pending requests)
function getUserPendingBalance(address user, address tokenAddress) returns (uint256);
// User's claimable refund (funds available to withdraw via claimRefund)
function getClaimableRefund(address user, address tokenAddress) returns (uint256);
// Pending request count
function getPendingRequestCount() returns (uint256);
// User's pending request count
function getUserPendingRequestCount(address user) returns (uint256);
// User's YieldVault Ids
function getYieldVaultIdsForUser(address user) returns (uint64[] memory);
// Ownership check (O(1))
function doesUserOwnYieldVault(address user, uint64 yieldVaultId) returns (bool);
// Get pending request IDs array
function getPendingRequestIds() returns (uint256[] memory);
// Get pending requests in unpacked arrays (pagination)
function getPendingRequestsUnpacked(uint256 startIndex, uint256 count) returns (...);
// Get pending requests for a user in unpacked arrays (includes tracked-token escrow+refund balances)
// Returns (..., address[] memory balanceTokens, uint256[] memory pendingBalances, uint256[] memory claimableRefundsArray)
function getPendingRequestsByUserUnpacked(address user) returns (...);
// Get single request by ID
function getRequest(uint256 requestId) returns (Request memory);
// Check if YieldVault Id is valid
function isYieldVaultIdValid(uint64 yieldVaultId) returns (bool);
// Check if token is native FLOW
function isNativeFlow(address tokenAddress) returns (bool);
// Claim refunded funds from claimableRefunds (does NOT touch pendingUserBalances)
function claimRefund(address tokenAddress) external;// YieldVault Ids by EVM address
access(all) view fun getYieldVaultIdsForEVMAddress(_ evmAddress: String): [UInt64]
// Ownership check (O(1))
access(all) view fun doesEVMAddressOwnYieldVault(evmAddress: String, yieldVaultId: UInt64): Bool
// Pending requests for a specific EVM address
access(all) fun getPendingRequestsForEVMAddress(_ evmAddressHex: String): PendingRequestsInfo
// Total pending request count (public query)
access(all) fun getPendingRequestCount(): Int
// Scheduler paused status (FlowYieldVaultsEVMWorkerOps)
access(all) view fun getIsSchedulerPaused(): Bool
// Execution effort constants (FlowYieldVaultsEVMWorkerOps)
access(all) let executionEffortConstants: {String: UInt64}| Component | Mechanism | Details |
|---|---|---|
| FlowYieldVaultsRequests | onlyAuthorizedCOA |
Only COA can call processing functions |
| FlowYieldVaultsRequests | onlyOwner |
Admin functions restricted to owner |
| FlowYieldVaultsRequests | onlyAllowlisted |
Optional whitelist for users |
| FlowYieldVaultsRequests | notBlocklisted |
Optional blacklist for users |
| FlowYieldVaultsRequests | whenNotPaused |
New request creation blocked when paused |
| FlowYieldVaultsEVM | Capability-based | Worker requires valid COA, YieldVaultManager, BetaBadge caps |
| FlowYieldVaultsEVMWorkerOps | Admin resource | Pause/unpause scheduler restricted to Admin holder |
Both EVM and Cadence maintain ownership state with O(1) lookup:
// Solidity
mapping(address => mapping(uint64 => bool)) public userOwnsYieldVault;// Cadence
access(all) let yieldVaultRegistry: {String: {UInt64: Bool}}Ownership is verified for WITHDRAW/CLOSE on both EVM and Cadence. Deposits are permissionless; CREATE only validates identifiers.
- Escrow Model: Funds held in contract until processing begins; failed CREATE/DEPOSIT and success-path precision residuals become claimable refunds
- Two-Phase Commit: Balance deducted before operation, then reconciled on completion via success/failure finalization plus any explicit refund amount
- Cross-VM Non-Atomicity: Funds can be in transit between EVM and Cadence; stuck PROCESSING is possible without admin recovery
- ReentrancyGuard: Solidity contract protected against reentrancy
// EVMRequest validation in constructor
pre {
requestType >= RequestType.CREATE_YIELDVAULT.rawValue &&
requestType <= RequestType.CLOSE_YIELDVAULT.rawValue:
"Invalid request type"
status >= RequestStatus.PENDING.rawValue &&
status <= RequestStatus.FAILED.rawValue:
"Invalid status"
requestType == RequestType.CLOSE_YIELDVAULT.rawValue || amount > 0:
"Amount must be greater than 0 for non-close operations"
}| Event | Description |
|---|---|
RequestCreated |
New request submitted |
RequestProcessed |
Request status changed |
RequestCancelled |
Request cancelled by user/admin; refund credited |
RefundCredited |
Refund became claimable (pull pattern) |
RefundClaimed |
User claimed a refund |
BalanceUpdated |
User's escrowed balance changed |
FundsWithdrawn |
Funds transferred out |
AuthorizedCOAUpdated |
COA address changed |
AllowlistEnabled |
Allowlist toggled |
BlocklistEnabled |
Blocklist toggled |
TokenConfigured |
Token configuration changed |
AddressesAddedToAllowlist |
Batch allowlist additions |
AddressesRemovedFromAllowlist |
Batch allowlist removals |
AddressesAddedToBlocklist |
Batch blocklist additions |
AddressesRemovedFromBlocklist |
Batch blocklist removals |
MaxPendingRequestsPerUserUpdated |
Config change |
Paused |
Contract paused |
Unpaused |
Contract unpaused |
YieldVaultIdRegistered |
New YieldVault registered |
YieldVaultIdUnregistered |
YieldVault unregistered (closed) |
RequestsDropped |
Admin dropped requests |
| Event | Description |
|---|---|
WorkerInitialized |
Worker created with COA |
FlowYieldVaultsRequestsAddressSet |
EVM contract address configured |
RequestsProcessed |
Batch processing completed |
YieldVaultCreatedForEVMUser |
New YieldVault created |
YieldVaultDepositedForEVMUser |
Deposit to YieldVault |
YieldVaultWithdrawnForEVMUser |
Withdrawal from YieldVault |
YieldVaultClosedForEVMUser |
YieldVault closed |
RequestFailed |
Request processing failed |
WithdrawFundsFromEVMFailed |
Failed to withdraw funds from EVM |
EVMAllowlistStatusChanged |
Allowlist status changed on EVM |
EVMAllowlistUpdated |
Addresses added/removed from allowlist on EVM |
EVMBlocklistStatusChanged |
Blocklist status changed on EVM |
EVMBlocklistUpdated |
Addresses added/removed from blocklist on EVM |
EVMTokenConfigured |
Token configuration changed on EVM |
EVMAuthorizedCOAUpdated |
Authorized COA updated on EVM |
EVMMaxPendingRequestsPerUserUpdated |
Max pending requests per user updated on EVM |
EVMRequestsDropped |
Requests dropped on EVM |
EVMRequestCancelled |
Request cancelled on EVM |
| Event | Description |
|---|---|
SchedulerPaused |
Scheduler paused - no new workers scheduled |
SchedulerUnpaused |
Scheduler resumed |
WorkerHandlerExecuted |
WorkerHandler processed a request (includes result) |
SchedulerHandlerExecuted |
SchedulerHandler completed execution cycle |
WorkerHandlerPanicDetected |
WorkerHandler panicked, request marked as FAILED |
WorkerHandlerScheduled |
WorkerHandler scheduled to process a request |
AllExecutionsStopped |
All scheduled executions cancelled and fees refunded |
| Error | Cause |
|---|---|
NotAuthorizedCOA |
Non-COA calling restricted function |
NotInAllowlist |
User not whitelisted |
Blocklisted |
User is blacklisted |
ContractPaused |
Contract is paused |
AmountMustBeGreaterThanZero |
Zero amount deposit |
TokenNotSupported |
Unsupported token |
RequestNotFound |
Invalid request ID |
NotRequestOwner |
Cancelling another user's request |
CanOnlyCancelPending |
Cancelling non-pending request |
InvalidRequestState |
Request is not in correct state |
InsufficientBalance |
Not enough funds |
BelowMinimumBalance |
Deposit below minimum |
TooManyPendingRequests |
User at limit |
InvalidYieldVaultId |
YieldVault not owned by user |
InvalidCOAAddress |
Invalid COA address provided |
EmptyAddressArray |
Empty array passed to batch functions |
CannotAllowlistZeroAddress |
Cannot add zero address to allowlist |
EmptyVaultIdentifier |
Empty vault identifier for CREATE |
EmptyStrategyIdentifier |
Empty strategy identifier for CREATE |
NoRefundAvailable |
No refund available to claim |
MsgValueMustEqualAmount |
msg.value must equal amount for native FLOW |
MsgValueMustBeZero |
msg.value must be zero for ERC20 tokens |
TransferFailed |
Token transfer failed |
Failed operations return ProcessResult with success: false and descriptive message. The Worker emits RequestFailed and calls completeProcessing(..., refundAmount) to credit any failed escrowed amount for later claimRefund. Successful CREATE/DEPOSIT requests may also credit a smaller precision-residual refund during completion.
function setAuthorizedCOA(address _coa) external onlyOwner;
function setAllowlistEnabled(bool _enabled) external onlyOwner;
function setBlocklistEnabled(bool _enabled) external onlyOwner;
function batchAddToAllowlist(address[] calldata _addresses) external onlyOwner;
function batchRemoveFromAllowlist(address[] calldata _addresses) external onlyOwner;
function batchAddToBlocklist(address[] calldata _addresses) external onlyOwner;
function batchRemoveFromBlocklist(address[] calldata _addresses) external onlyOwner;
function setTokenConfig(address token, bool supported, uint256 min, bool native) external onlyOwner;
function setMaxPendingRequestsPerUser(uint256 _max) external onlyOwner;
function pause() external onlyOwner;
function unpause() external onlyOwner;
function dropRequests(uint256[] calldata requestIds) external onlyOwner;// Admin resource functions
access(all) fun setFlowYieldVaultsRequestsAddress(_ address: EVM.EVMAddress)
access(all) fun updateFlowYieldVaultsRequestsAddress(_ address: EVM.EVMAddress)
access(all) fun createWorker(...): @Worker// Admin resource functions
access(all) fun pauseScheduler() // Stop scheduling new workers (in-flight workers continue)
access(all) fun unpauseScheduler() // Resume scheduling
access(all) fun setMaxProcessingRequests(maxProcessingRequests: UInt8) // Set max concurrent workers
access(all) fun setExecutionEffortConstants(key: String, value: UInt64) // Update execution effort
access(all) fun setSchedulerWakeupInterval(schedulerWakeupInterval: UFix64) // Set scheduler interval
access(all) fun createWorkerHandler(workerCap: ...) -> @WorkerHandler
access(all) fun createSchedulerHandler(workerCap: ...) -> @SchedulerHandler
access(all) fun stopAll() // Emergency: pause + cancel all scheduled executions with refunds- Address:
0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF - Minimum: 1 FLOW (configurable)
- Transfer:
msg.valuefor deposits,call{value}for withdrawals
- Onboarded via FlowEVMBridge
- Uses
FlowEVMBridgeConfig.getTypeAssociated()for type lookup - Transfer:
SafeERC20.safeTransferFrom/safeTransfer - Bridging:
coaRef.withdrawTokens()/depositTokens()
- Flow account with FLOW for deployment and fees
- FlowYieldVaultsClosedBeta.BetaBadge for YieldVault creation
- FlowYieldVaults.YieldVaultManager for managing positions
- COA with sufficient capabilities
- Deploy
FlowYieldVaultsRequestson EVM with COA and WFLOW addresses - Deploy
FlowYieldVaultsEVMon Cadence - Deploy
FlowYieldVaultsEVMWorkerOpson Cadence - Configure
FlowYieldVaultsEVMwith EVM contract address - Create Worker with required capabilities (COA, YieldVaultManager, BetaBadge, FeeProvider)
- Create WorkerHandler and SchedulerHandler via WorkerOps Admin
- Register handlers with FlowTransactionScheduler Manager
- Schedule initial WorkerHandler (for registration) and SchedulerHandler
| Version | Date | Changes |
|---|---|---|
| 1.0 | Initial | Basic request/response flow |
| 2.0 | - | Added two-phase commit |
| 3.0 | Nov 2025 | Adaptive scheduling, O(1) ownership lookup |
| 3.1 | Dec 2025 | Removed parallel processing, added dynamic execution effort calculation |
| 3.2 | Feb 2026 | Refactored preprocessing into preprocessRequests(), WorkerHandler fetches request by ID |