Advanced Topics
This section covers advanced FlowNodes usage: adding custom Solidity logic, building upgradeable contracts with UUPS proxies, integrating Chainlink data feeds, and managing project versions.
Custom Solidity functions
The Custom Function block lets you write arbitrary Solidity logic inside a managed node. This is useful when no existing block covers your specific requirement.
Adding a Custom Function block
Drag the custom_function block from the Advanced category onto the canvas. Select it to open the Properties Panel, then configure:
| Property | Description | Example |
|---|---|---|
| functionName | The Solidity function name (camelCase) | withdrawEth |
| visibility | public / external / internal / private | external |
| stateMutability | view / pure / payable / (empty for state-modifying) | |
| params | Array of {name, type} parameter objects | [{name:"amount",type:"uint256"}] |
| returns | Array of {type} return type objects | [{type:"bool"}] |
| body | Raw Solidity for the function body | payable(owner()).transfer(amount); |
Example: custom withdrawal function
// Generated from Custom Function block config:
// functionName: withdrawEth
// visibility: external
// stateMutability: (none — state-modifying)
// params: [{name: "to", type: "address"}, {name: "amount", type: "uint256"}]
function withdrawEth(address to, uint256 amount) external onlyOwner {
require(address(this).balance >= amount, "Insufficient balance");
(bool success, ) = to.call{value: amount}("");
require(success, "Transfer failed");
}onlyOwner modifier is only available if an Ownable block is connected. If you connect AccessControl, use theonlyRole(ROLE_NAME) modifier instead.Custom Modifier block
Similarly, the custom_modifier block defines a reusable function guard. Reference it from a Custom Function block by adding its name to the function's modifiers list.
Custom Event block
The custom_event_def block defines a Solidity event. Addemit EventName(arg1, arg2) inside a Custom Function block body to emit it. Events are automatically added to the ABI and indexed by the Alchemy notification pipeline.
State Variable block
Use custom_state_variable to add contract-level storage variables. Configure the type, name, visibility, and initial value in the Properties Panel.
Upgradeable contracts (UUPS)
UUPS (Universal Upgradeable Proxy Standard, EIP-1822) stores the upgrade logic in the implementation contract rather than the proxy. This is more gas-efficient than Transparent Proxy and the recommended upgrade pattern for new contracts.
Required block configuration
To build a UUPS upgradeable contract, connect these blocks:
- 1
oz_uups_proxy— Adds the UUPS proxy storage slot and upgrade authorization hook. - 2
oz_initializer— Replaces the constructor. The initialize() function is called once at proxy deployment. All other blocks' constructors become initializer calls. - 3
oz_ownable2step— Strongly recommended — the _authorizeUpgrade() function should be restricted to the owner (or multi-sig). Ownable2Step prevents accidental ownership renouncement.
Generated UUPS pattern
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyTokenV1 is
Initializable,
ERC20Upgradeable,
Ownable2StepUpgradeable,
UUPSUpgradeable
{
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() { _disableInitializers(); }
function initialize(address initialOwner) public initializer {
__ERC20_init("MyToken", "MTK");
__Ownable2Step_init();
__UUPSUpgradeable_init();
_transferOwnership(initialOwner);
}
function _authorizeUpgrade(address newImpl)
internal override onlyOwner {}
}uint256[50] private __gap).Deploying an upgradeable contract
The Deploy Modal detects UUPS blocks and automatically deploys both the implementation contract and a ERC1967Proxy pointing to it. The proxy address is what users interact with — it is the address shown in your Deployed Contracts list.
Chainlink price feeds
Connect the chainlink_price_feed block to read live asset prices directly in your contract. This is used by the FlowNodesPaymentVault to convert ETH deposits to USD-denominated credits.
Available Mainnet feeds
| Feed | Address | Decimals |
|---|---|---|
| ETH / USD | 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 | 8 |
| BTC / USD | 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88b | 8 |
| USDC / USD | 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6 | 8 |
| LINK / USD | 0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c | 8 |
| MATIC / USD | 0x7bAC85A8a13A4BcD8abb3eB7d6b4d632c895a359 | 8 |
The full list of Mainnet data feeds is at docs.chain.link.
Staleness check
The generated code always includes a staleness check. If the price is older thanmaxStalenessSeconds (default: 3600s), the call reverts withStalePrice(). This prevents your contract from using stale data during Chainlink network disruptions.
function _getLatestPrice() internal view returns (int256) {
(
uint80 roundId,
int256 answer,
,
uint256 updatedAt,
uint80 answeredInRound
) = priceFeed.latestRoundData();
require(answeredInRound >= roundId, "Stale price");
require(updatedAt >= block.timestamp - maxStalenessSeconds, "Price too old");
require(answer > 0, "Invalid price");
return answer;
}Project versioning
FlowNodes auto-saves your canvas to the database every 30 seconds when there are unsaved changes. The save indicator in the top-left of the editor shows the current status (Saved / Unsaved / Saving...).
Manual save
Press ⌘S (Ctrl+S on Windows) to save immediately. This is useful before running a security scan or deployment.
Graph JSON format
The canvas state is stored as a FlowNodesGraph JSON object in the projects table. You can inspect it (or use it to reproduce a project) by reading thegraphJson field via the API.
Compiled artifact caching
Each time you compile, the ABI, bytecode, and Solidity source are cached in the project record. This allows the Deploy Modal to work immediately on page load without recompiling.
