Skip to content

Commit c5a654c

Browse files
authored
Add DeFi Actions Project Starter (#2143)
1 parent caae02c commit c5a654c

6 files changed

Lines changed: 428 additions & 2 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# DeFi Actions Starter
2+
3+
This project demonstrates how to build with Flow Actions - a standard for composable DeFi connectors on the Flow blockchain.
4+
5+
## Overview
6+
7+
This starter project includes a minimal example connector (`TokenSink`) that implements the `DeFiActions.Sink` interface. It shows how to:
8+
9+
- Create a connector that accepts fungible tokens via a receiver capability
10+
- Compose transactions using Flow Actions patterns
11+
- Test connectors using the Flow Testing Framework
12+
13+
## Quick Start
14+
15+
Get started in seconds:
16+
17+
```bash
18+
flow test # Run tests to verify everything works
19+
```
20+
21+
That's it! The test deploys all contracts and executes an example transaction.
22+
23+
## Prerequisites
24+
25+
Before running this project, you need to install the Flow CLI:
26+
27+
- **Installation Guide**: https://developers.flow.com/tools/flow-cli/install
28+
29+
**Note**: All dependencies (FungibleToken, FlowToken, DeFiActions, etc.) are already installed in your project during initialization.
30+
31+
## Getting Started
32+
33+
### 1. Run Tests (Recommended First Step)
34+
35+
```bash
36+
flow test
37+
```
38+
39+
This runs the test suite to verify everything works. The tests automatically deploy all contracts and execute the example transaction in an isolated test environment.
40+
41+
### 2. Start the Flow Emulator
42+
43+
```bash
44+
flow emulator
45+
```
46+
47+
This starts a local Flow blockchain for development.
48+
49+
### 3. Deploy the Contracts
50+
51+
In a new terminal:
52+
53+
```bash
54+
flow project deploy --network emulator
55+
```
56+
57+
This deploys all required contracts to the emulator:
58+
- `DeFiActionsMathUtils` - Math utilities for DeFi Actions
59+
- `DeFiActionsUtils` - Helper utilities for DeFi Actions
60+
- `DeFiActions` - Core DeFi Actions framework
61+
- `ExampleConnectors` - Your TokenSink connector
62+
63+
### 4. Run the Example Transaction
64+
65+
Send tokens to yourself using the TokenSink:
66+
67+
```bash
68+
flow transactions send cadence/transactions/DepositViaSink.cdc \
69+
--signer emulator-account \
70+
--network emulator \
71+
--args-json '[{"type":"Address","value":"0xf8d6e0586b0a20c7"},{"type":"UFix64","value":"1.0"}]'
72+
```
73+
74+
This sends `1.0` FLOW from the emulator account to itself (`0xf8d6e0586b0a20c7`) using the `TokenSink` connector.
75+
76+
## Testing
77+
78+
Run the test suite to verify the connector works correctly:
79+
80+
```bash
81+
flow test
82+
```
83+
84+
The tests run in an isolated environment and automatically:
85+
1. Deploy all DeFi Actions dependencies (`DeFiActionsMathUtils`, `DeFiActionsUtils`, `DeFiActions`)
86+
2. Deploy the `ExampleConnectors` contract
87+
3. Execute the `DepositViaSink` transaction
88+
4. Verify tokens are deposited successfully
89+
90+
**Note**: Tests don't require the emulator to be running - they use their own test environment.
91+
92+
## Project Structure
93+
94+
- `cadence/contracts/` - Smart contracts
95+
- `ExampleConnectors.cdc` - TokenSink connector implementation
96+
- `cadence/transactions/` - Transaction files
97+
- `DepositViaSink.cdc` - Example transaction using TokenSink
98+
- `cadence/tests/` - Test files
99+
- `ExampleConnectors_test.cdc` - Integration test for TokenSink
100+
- `flow.json` - Flow project configuration with DeFiActions dependencies
101+
102+
## Dependencies
103+
104+
This project includes the following dependencies (already installed):
105+
106+
**Core Dependencies:**
107+
- `FungibleToken` - Standard fungible token interface
108+
- `FlowToken` - Native FLOW token implementation
109+
110+
**DeFi Actions Framework:**
111+
- `DeFiActions` - Core framework for composable DeFi connectors
112+
- `DeFiActionsUtils` - Helper utilities
113+
- `DeFiActionsMathUtils` - Math utilities for DeFi operations
114+
115+
**Network Configuration:**
116+
- **Testnet**: All DeFi Actions contracts available at `0x4c2ff9dd03ab442f`
117+
- **Mainnet**: All DeFi Actions contracts available at `0x92195d814edf9cb0`
118+
- **Emulator**: Contracts are deployed from source to your emulator account
119+
120+
## Understanding the TokenSink Connector
121+
122+
The `TokenSink` connector demonstrates a minimal implementation of the `DeFiActions.Sink` interface:
123+
124+
- Accepts a `FungibleToken.Receiver` capability (publicly available)
125+
- Deposits tokens into the recipient's vault via `depositCapacity()`
126+
- Includes type checks to ensure safe deposits
127+
128+
## Next Steps
129+
130+
- Explore the [Flow Actions FLIP](https://github.com/onflow/flips/pull/339) for more details on the standard
131+
- Build your own connectors implementing `Source`, `Sink`, or `Swapper` interfaces
132+
- Compose complex DeFi operations by chaining multiple connectors
133+
134+
## Resources
135+
136+
- **Flow Actions Repository**: https://github.com/onflow/FlowActions
137+
- **Flow Documentation**: https://developers.flow.com/
138+
- **Cadence Language**: https://cadence-lang.org/docs/language
139+
140+
## Community
141+
142+
- [Flow Community Forum](https://forum.flow.com/)
143+
- [Flow Discord](https://discord.gg/flow)
144+
- [Flow Twitter](https://x.com/flow_blockchain)
145+
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Contract: ExampleConnectors
2+
// Purpose: Minimal example of a DeFiActions Sink implementation that accepts any
3+
// fungible token vault type and deposits it into a provided vault capability.
4+
//
5+
// Concepts demonstrated:
6+
// - Implementing DeFiActions.Sink with type-safe deposits
7+
// - Using a capability to a fungible vault for deposits
8+
// - Exposing component metadata and optional UniqueIdentifier wiring
9+
//
10+
// Safety:
11+
// - depositCapacity enforces type equality with a precondition
12+
// - Withdrawals are sized by callers via minimumCapacity() or DeFiActions patterns
13+
import "FungibleToken"
14+
import "DeFiActions"
15+
16+
access(all) contract ExampleConnectors {
17+
// TokenSink: A simple Sink that deposits everything it receives
18+
// NOTE: In practice, this should not be used as it is essentially a duplicate
19+
// of the standard FungibleTokenConnectors.VaultSink connector.
20+
access(all) struct TokenSink: DeFiActions.Sink {
21+
// Capability to a receiver that can accept deposits of the matching vault type
22+
access(contract) let vault: Capability<&{FungibleToken.Receiver}>
23+
// Optional tracing ID used by DeFiActions to correlate flows
24+
access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
25+
26+
init(
27+
vault: Capability<&{FungibleToken.Receiver}>,
28+
uniqueID: DeFiActions.UniqueIdentifier?
29+
) {
30+
self.vault = vault
31+
self.uniqueID = uniqueID
32+
}
33+
34+
// Required by Sink: advertise the exact deposit type supported
35+
access(all) view fun getSinkType(): Type {
36+
return self.vault.borrow()!.getType()
37+
}
38+
39+
// This sink places no limit on deposits; callers may size with their own rules
40+
access(all) fun minimumCapacity(): UFix64 {
41+
return UFix64.max
42+
}
43+
44+
// Deposit the full balance from the provided vault into the target vault
45+
access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
46+
pre {
47+
// Enforce exact type match between provided vault and sink type
48+
from.getType() == self.getSinkType():
49+
"Invalid vault provided for deposit - \(from.getType().identifier) is not \(self.getSinkType().identifier)"
50+
}
51+
// No-op for empty transfers
52+
let amount: UFix64 = from.balance
53+
if amount == 0.0 { return }
54+
// Move all funds and deposit
55+
let payment <- from.withdraw(amount: amount)
56+
self.vault.borrow()!.deposit(from: <-payment)
57+
}
58+
59+
// Report metadata about this component for DeFiActions graph inspection
60+
access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
61+
return DeFiActions.ComponentInfo(
62+
type: self.getType(),
63+
id: self.id(),
64+
innerComponents: []
65+
)
66+
}
67+
68+
// Implementation detail for UniqueIdentifier passthrough
69+
access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
70+
return self.uniqueID
71+
}
72+
73+
// Allow the framework to set/propagate a UniqueIdentifier for tracing
74+
access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
75+
self.uniqueID = id
76+
}
77+
}
78+
}
79+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Test
2+
import "FungibleToken"
3+
import "FlowToken"
4+
import "ExampleConnectors"
5+
import "DeFiActions"
6+
7+
access(all) let serviceAccount = Test.serviceAccount()
8+
access(all) let recipient = Test.createAccount()
9+
10+
access(all) fun setup() {
11+
// Deploy DeFi Actions dependencies first
12+
var err = Test.deployContract(
13+
name: "DeFiActionsMathUtils",
14+
path: "../../imports/92195d814edf9cb0/DeFiActionsMathUtils.cdc",
15+
arguments: []
16+
)
17+
Test.expect(err, Test.beNil())
18+
19+
err = Test.deployContract(
20+
name: "DeFiActionsUtils",
21+
path: "../../imports/92195d814edf9cb0/DeFiActionsUtils.cdc",
22+
arguments: []
23+
)
24+
Test.expect(err, Test.beNil())
25+
26+
err = Test.deployContract(
27+
name: "DeFiActions",
28+
path: "../../imports/92195d814edf9cb0/DeFiActions.cdc",
29+
arguments: []
30+
)
31+
Test.expect(err, Test.beNil())
32+
33+
// Deploy ExampleConnectors
34+
err = Test.deployContract(
35+
name: "ExampleConnectors",
36+
path: "../contracts/ExampleConnectors.cdc",
37+
arguments: []
38+
)
39+
Test.expect(err, Test.beNil())
40+
}
41+
42+
access(all) fun testTokenSinkDeposit() {
43+
// Execute transaction to test TokenSink
44+
// Service account already has FLOW tokens
45+
let code = Test.readFile("../transactions/DepositViaSink.cdc")
46+
let tx = Test.Transaction(
47+
code: code,
48+
authorizers: [serviceAccount.address],
49+
signers: [serviceAccount],
50+
arguments: [recipient.address, 10.0]
51+
)
52+
53+
let result = Test.executeTransaction(tx)
54+
Test.expect(result, Test.beSucceeded())
55+
}
56+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import "FungibleToken"
2+
import "FlowToken"
3+
import "DeFiActions"
4+
import "ExampleConnectors"
5+
6+
/// Deposit FlowToken into a recipient's vault using ExampleConnectors.TokenSink
7+
///
8+
/// This transaction demonstrates:
9+
/// - Creating a TokenSink pointing to a recipient's vault capability
10+
/// - Withdrawing tokens from the signer's vault
11+
/// - Depositing via the sink's depositCapacity method
12+
///
13+
/// @param recipient: Address of the account to receive tokens
14+
/// @param amount: Amount of FlowToken to send
15+
transaction(recipient: Address, amount: UFix64) {
16+
let senderVault: auth(FungibleToken.Withdraw) &FlowToken.Vault
17+
let sink: ExampleConnectors.TokenSink
18+
19+
prepare(signer: auth(BorrowValue) &Account) {
20+
// Borrow the signer's FlowToken vault
21+
self.senderVault = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
22+
from: /storage/flowTokenVault
23+
) ?? panic("Could not borrow FlowToken vault from signer")
24+
25+
// Get a capability to the recipient's FlowToken receiver
26+
let recipientCap = getAccount(recipient)
27+
.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
28+
29+
// Create a TokenSink that will deposit into the recipient's vault
30+
self.sink = ExampleConnectors.TokenSink(
31+
vault: recipientCap,
32+
uniqueID: nil
33+
)
34+
}
35+
36+
execute {
37+
// Withdraw tokens from signer
38+
let tokens <- self.senderVault.withdraw(amount: amount)
39+
40+
// Deposit via the sink
41+
self.sink.depositCapacity(from: &tokens as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
42+
43+
// Ensure everything was deposited
44+
assert(tokens.balance == 0.0, message: "Tokens remaining after deposit")
45+
destroy tokens
46+
}
47+
}
48+

internal/super/init.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ func startInteractiveInit(
263263
ProjectTypeDefault,
264264
ProjectTypeScheduledTransactions,
265265
ProjectTypeStablecoin,
266+
ProjectTypeDeFiActions,
266267
ProjectTypeCustom,
267268
}
268269
projectOptions := make([]string, len(projectTypes))
@@ -295,7 +296,7 @@ func startInteractiveInit(
295296
return "", err
296297
}
297298
projectType = ProjectTypeDefault
298-
case ProjectTypeScheduledTransactions, ProjectTypeStablecoin:
299+
case ProjectTypeScheduledTransactions, ProjectTypeStablecoin, ProjectTypeDeFiActions:
299300
err := installProjectDependencies(logger, state, tempDir, projectType)
300301
if err != nil {
301302
return "", err
@@ -311,7 +312,7 @@ func startInteractiveInit(
311312
}
312313

313314
// Add project-specific contract deployments
314-
if projectType == ProjectTypeScheduledTransactions || projectType == ProjectTypeStablecoin {
315+
if projectType == ProjectTypeScheduledTransactions || projectType == ProjectTypeStablecoin || projectType == ProjectTypeDeFiActions {
315316
err = addContractDeployments(state, projectType)
316317
if err != nil {
317318
return "", err

0 commit comments

Comments
 (0)