LLM Notice: This documentation site supports content negotiation for AI agents. Request any page with Accept: text/markdown or Accept: text/plain header to receive Markdown instead of HTML. Alternatively, append ?format=md to any URL. All markdown files are available at /md/ prefix paths. For all content in one file, visit /llms-full.txt
Skip to main content

DeFi Actions

DeFi Actions are composable primitives that enable complex DeFi operations through simple, reusable components. FYV leverages DeFi Actions to build sophisticated yield strategies from modular building blocks. This document explains the DeFi Actions framework and how it powers FYV's composability.

What are DeFi Actions?

DeFi Actions is a framework of composable smart contract components that implement common DeFi operations as standardized interfaces. Rather than building monolithic strategies, developers compose Actions like building blocks to create complex flows.

Key principles:

  • Single Responsibility: Each Action does one thing well
  • Composability: Actions can be chained and combined
  • Standardized Interfaces: Consistent APIs across implementations
  • Reusability: Same Actions used across multiple strategies

Core Action Types

FYV uses three main categories of DeFi Actions:

1. Swap Actions (SwapConnectors)

Convert one token type to another via decentralized exchanges.

Interface:


_13
pub resource interface SwapConnector {
_13
// Swap input tokens for output tokens
_13
pub fun swap(
_13
vaultIn: @FungibleToken.Vault,
_13
amountOutMin: UFix64
_13
): @FungibleToken.Vault
_13
_13
// Get expected output for given input
_13
pub fun quote(amountIn: UFix64): UFix64
_13
_13
// Get swap route information
_13
pub fun getRoute(): SwapRoute
_13
}

Implementations:

  • UniswapV3SwapConnectors: Swap via Uniswap V3 pools on Flow EVM
  • TeleportCustodySwapConnectors: Swap via Teleport custody protocol
  • IncrementSwapConnectors: Swap via Increment DEX

Example usage:


_11
// Swap MOET → FLOW via Uniswap V3
_11
let swapConnector <- UniswapV3SwapConnectors.createConnector(
_11
tokenIn: Type<@MOET.Vault>(),
_11
tokenOut: Type<@FlowToken.Vault>(),
_11
poolFee: 3000 // 0.3% fee tier
_11
)
_11
_11
let flowVault <- swapConnector.swap(
_11
vaultIn: <-moetVault,
_11
amountOutMin: 95.0 // 5% slippage tolerance
_11
)

2. Sink Actions (SinkConnectors)

Deposit tokens into yield-generating protocols.

Interface:


_10
pub resource interface SinkConnector {
_10
// Deposit tokens to yield protocol
_10
pub fun deposit(vault: @FungibleToken.Vault)
_10
_10
// Get current deposited balance
_10
pub fun getBalance(): UFix64
_10
_10
// Get vault metadata
_10
pub fun getVaultInfo(): VaultInfo
_10
}

Implementations:

  • ERC4626SinkConnectors: Deposit to ERC4626-compliant vaults
  • DrawDownSink: Bridge to ALP borrowing positions
  • StakingSinkConnectors: Stake tokens in staking protocols

Example usage:


_10
// Deposit to ERC4626 vault
_10
let sinkConnector <- ERC4626SinkConnectors.createConnector(
_10
vaultAddress: 0x123..., // ERC4626 vault address
_10
tokenType: Type<@YieldToken.Vault>()
_10
)
_10
_10
sinkConnector.deposit(vault: <-yieldTokens)
_10
// Tokens now earning yield in ERC4626 vault

3. Source Actions (SourceConnectors)

Withdraw tokens from yield-generating protocols.

Interface:


_10
pub resource interface SourceConnector {
_10
// Withdraw specified amount
_10
pub fun withdraw(amount: UFix64): @FungibleToken.Vault
_10
_10
// Withdraw all available balance
_10
pub fun withdrawAll(): @FungibleToken.Vault
_10
_10
// Get available withdrawal amount
_10
pub fun getAvailableBalance(): UFix64
_10
}

Implementations:

  • ERC4626SourceConnectors: Withdraw from ERC4626 vaults
  • TopUpSource: Provide liquidity from ALP positions
  • UnstakingSourceConnectors: Unstake from staking protocols

Example usage:


_10
// Withdraw from ERC4626 vault
_10
let sourceConnector <- ERC4626SourceConnectors.createConnector(
_10
vaultAddress: 0x123...,
_10
tokenType: Type<@YieldToken.Vault>()
_10
)
_10
_10
let withdrawn <- sourceConnector.withdraw(amount: 100.0)
_10
// Yield tokens withdrawn from vault

Action Composition

The power of DeFi Actions comes from composition—chaining multiple Actions to create complex flows.

Example: TracerStrategy Composition

TracerStrategy composes five Actions to implement leveraged yield farming:

1. Borrow Action (DrawDownSink):


_10
// Borrow MOET from ALP position
_10
let borrowAction <- DrawDownSink.create(positionCap: positionCapability)
_10
borrowAction.deposit(vault: <-initialCollateral)
_10
// Position auto-borrows MOET

2. Swap Action #1 (MOET → YieldToken):


_11
// Convert borrowed MOET to yield tokens
_11
let swapAction1 <- UniswapV3SwapConnectors.createConnector(
_11
tokenIn: Type<@MOET.Vault>(),
_11
tokenOut: Type<@YieldToken.Vault>(),
_11
poolFee: 3000
_11
)
_11
_11
let yieldTokens <- swapAction1.swap(
_11
vaultIn: <-moetVault,
_11
amountOutMin: 95.0
_11
)

3. Sink Action (YieldToken → ERC4626):


_10
// Deposit yield tokens to earn
_10
let sinkAction <- ERC4626SinkConnectors.createConnector(
_10
vaultAddress: 0x789...,
_10
tokenType: Type<@YieldToken.Vault>()
_10
)
_10
_10
sinkAction.deposit(vault: <-yieldTokens)
_10
// Now earning yield

4. Source Action (ERC4626 → YieldToken):


_10
// Withdraw when rebalancing needed
_10
let sourceAction <- ERC4626SourceConnectors.createConnector(
_10
vaultAddress: 0x789...,
_10
tokenType: Type<@YieldToken.Vault>()
_10
)
_10
_10
let withdrawn <- sourceAction.withdraw(amount: excessAmount)

5. Swap Action #2 (YieldToken → FLOW):


_12
// Convert back to collateral
_12
let swapAction2 <- UniswapV3SwapConnectors.createConnector(
_12
tokenIn: Type<@YieldToken.Vault>(),
_12
tokenOut: Type<@FlowToken.Vault>(),
_12
poolFee: 3000
_12
)
_12
_12
let flowCollateral <- swapAction2.swap(
_12
vaultIn: <-withdrawn,
_12
amountOutMin: 95.0
_12
)
_12
// Deposit back to position as additional collateral

Composition Diagram


_17
graph LR
_17
Collateral[FLOW Collateral] -->|1. Deposit| Borrow[DrawDownSink]
_17
Borrow -->|2. Borrow| MOET[MOET Tokens]
_17
MOET -->|3. Swap| Swap1[UniswapV3Swap]
_17
Swap1 -->|4. Convert| Yield[YieldTokens]
_17
Yield -->|5. Deposit| Sink[ERC4626Sink]
_17
Sink -->|6. Earn| Vault[ERC4626 Vault]
_17
_17
Vault -->|7. Withdraw| Source[ERC4626Source]
_17
Source -->|8. Convert| Swap2[UniswapV3Swap]
_17
Swap2 -->|9. Return| Collateral
_17
_17
style Borrow fill:#f9f
_17
style Swap1 fill:#bfb
_17
style Sink fill:#bbf
_17
style Source fill:#bbf
_17
style Swap2 fill:#bfb

Strategy Composer Pattern

The StrategyComposer pattern assembles Actions into complete strategies:


_21
pub resource StrategyComposer {
_21
// Action components
_21
access(self) let borrowAction: @DrawDownSink
_21
access(self) let swapToYieldAction: @SwapConnector
_21
access(self) let sinkAction: @SinkConnector
_21
access(self) let sourceAction: @SourceConnector
_21
access(self) let swapToCollateralAction: @SwapConnector
_21
_21
// Compose into strategy
_21
pub fun composeStrategy(): @Strategy {
_21
let strategy <- create TracerStrategy(
_21
borrowAction: <-self.borrowAction,
_21
swapToYield: <-self.swapToYieldAction,
_21
sink: <-self.sinkAction,
_21
source: <-self.sourceAction,
_21
swapToCollateral: <-self.swapToCollateralAction
_21
)
_21
_21
return <-strategy
_21
}
_21
}

Benefits of this pattern:

  • Flexibility: Swap any Action implementation without changing strategy logic
  • Testability: Mock Actions for testing strategies in isolation
  • Reusability: Same Actions used across multiple strategies
  • Upgradability: Replace Actions with improved versions

Creating Custom Strategies

Developers can create custom strategies by composing different Actions:

Example: Conservative Stablecoin Strategy


_19
pub resource ConservativeStrategy {
_19
// Simplified strategy: just deposit to yield vault
_19
access(self) let sinkAction: @ERC4626SinkConnector
_19
access(self) let sourceAction: @ERC4626SourceConnector
_19
_19
pub fun deposit(vault: @FungibleToken.Vault) {
_19
// Direct deposit, no borrowing or swapping
_19
self.sinkAction.deposit(vault: <-vault)
_19
}
_19
_19
pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
_19
// Direct withdrawal
_19
return <-self.sourceAction.withdraw(amount: amount)
_19
}
_19
_19
pub fun getBalance(): UFix64 {
_19
return self.sinkAction.getBalance()
_19
}
_19
}

Example: Multi-Vault Strategy


_21
pub resource MultiVaultStrategy {
_21
// Diversify across multiple vaults
_21
access(self) let vaults: @{String: SinkConnector}
_21
_21
pub fun deposit(vault: @FungibleToken.Vault) {
_21
let amount = vault.balance
_21
_21
// Split across 3 vaults
_21
let vault1Amount = amount * 0.4
_21
let vault2Amount = amount * 0.3
_21
let vault3Amount = amount * 0.3
_21
_21
let vault1 <- vault.withdraw(amount: vault1Amount)
_21
let vault2 <- vault.withdraw(amount: vault2Amount)
_21
let vault3 <- vault
_21
_21
self.vaults["vault1"]?.deposit(vault: <-vault1)
_21
self.vaults["vault2"]?.deposit(vault: <-vault2)
_21
self.vaults["vault3"]?.deposit(vault: <-vault3)
_21
}
_21
}

Action Registry

The ActionRegistry maintains available Action implementations:


_24
pub contract ActionRegistry {
_24
// Registry of available Actions
_24
access(contract) var swapConnectors: {String: Type}
_24
access(contract) var sinkConnectors: {String: Type}
_24
access(contract) var sourceConnectors: {String: Type}
_24
_24
// Register new Action
_24
pub fun registerSwapConnector(name: String, type: Type) {
_24
self.swapConnectors[name] = type
_24
}
_24
_24
// Get available Actions
_24
pub fun getAvailableSwapConnectors(): [String] {
_24
return self.swapConnectors.keys
_24
}
_24
_24
// Create Action instance
_24
pub fun createSwapConnector(name: String, config: {String: AnyStruct}): @SwapConnector {
_24
let connectorType = self.swapConnectors[name]
_24
?? panic("Connector not found")
_24
_24
return <-create connectorType(config: config)
_24
}
_24
}

Benefits:

  • Discovery: Users can enumerate available Actions
  • Versioning: Multiple versions of same Action can coexist
  • Governance: Community can vote on adding new Actions

Advanced Composition Patterns

1. Sequential Composition

Chain Actions in sequence:


_10
// FLOW → MOET → YieldToken → ERC4626
_10
let result <- action1.execute(input: <-flowVault)
_10
|> action2.execute(input: <-result)
_10
|> action3.execute(input: <-result)
_10
|> action4.execute(input: <-result)

2. Parallel Composition

Execute multiple Actions concurrently:


_10
// Deposit to 3 vaults simultaneously
_10
async {
_10
vault1.deposit(vault: <-split1)
_10
vault2.deposit(vault: <-split2)
_10
vault3.deposit(vault: <-split3)
_10
}

3. Conditional Composition

Choose Actions based on conditions:


_10
if ratio > 1.05 {
_10
// Withdraw and swap
_10
let withdrawn <- sourceAction.withdraw(amount: excess)
_10
let collateral <- swapAction.swap(vaultIn: <-withdrawn)
_10
} else if ratio < 0.95 {
_10
// Borrow and swap
_10
let borrowed <- borrowAction.borrow(amount: deficit)
_10
let yieldTokens <- swapAction.swap(vaultIn: <-borrowed)
_10
}

4. Recursive Composition

Actions that contain other Actions:


_10
pub resource CompositeAction: SwapConnector {
_10
// Multi-hop swap composed of single-hop swaps
_10
access(self) let hop1: @SwapConnector
_10
access(self) let hop2: @SwapConnector
_10
_10
pub fun swap(vaultIn: @FungibleToken.Vault): @FungibleToken.Vault {
_10
let intermediate <- self.hop1.swap(vaultIn: <-vaultIn)
_10
return <-self.hop2.swap(vaultIn: <-intermediate)
_10
}
_10
}

Best Practices

Keep Actions Small: Each Action should have single, clear responsibility.

Use Interfaces: Depend on Action interfaces, not concrete implementations.

Handle Failures: Implement proper error handling and revert logic.

Document Dependencies: Clearly specify required Action sequences.

Version Actions: Track Action versions for compatibility.

Test Composition: Unit test Actions individually, integration test compositions.

Summary

DeFi Actions provide the composability framework that powers FYV's flexibility through modular Actions for swaps, deposits, and withdrawals, standardized interfaces enabling interchangeability, composition patterns supporting complex strategies, and the registry system allowing Action discovery and versioning.

Key components:

  • SwapConnectors: Token conversion via DEXes
  • SinkConnectors: Deposits to yield protocols
  • SourceConnectors: Withdrawals from yield protocols
  • StrategyComposer: Assembles Actions into strategies
  • ActionRegistry: Discovers and versions Actions

Key Takeaway

DeFi Actions are like LEGO blocks for DeFi strategies. By composing simple, reusable Actions, FYV enables sophisticated yield farming flows while maintaining clean separation of concerns and allowing easy customization.