Flare for Python Devs
This guide is for developers who want to interact with Flare using Python. In this guide, using Python, you will learn how to:
- Query a contract on Flare using web3.py, an async/sync library for interacting with Ethereum-like chains.
- Compile a Solidity contract using py-solc-x, a wrapper around the Solidity compiler.
- Deploy your compiled contract on Flare.
All examples in this guide are available at developer-hub/examples.
Getting started
- uv
- pip
uv add web3 py-solc-x
pip install web3 py-solc-x
Usage
You need to connect to testnet or mainnet via an RPC, any RPC listed on the Network Configuration page will work. For this guide, you can use the Public RPC.
- Flare Testnet Coston2
- Flare Mainnet
import asyncio
from web3 import AsyncHTTPProvider, AsyncWeb3
from web3.middleware import ExtraDataToPOAMiddleware
async def main() -> int:
# Inject middleware to handle testnet PoA consensus
w3 = AsyncWeb3(
AsyncHTTPProvider("https://coston2-api.flare.network/ext/C/rpc"),
middleware=[ExtraDataToPOAMiddleware],
)
chain_id = await w3.eth.chain_id
print(f"Chain ID: {chain_id}")
# Chain ID: 114
return chain_id
if __name__ == "__main__":
asyncio.run(main())
import asyncio
from web3 import AsyncHTTPProvider, AsyncWeb3
async def main() -> int:
w3 = AsyncWeb3(
AsyncHTTPProvider("https://flare-api.flare.network/ext/C/rpc"),
)
chain_id = await w3.eth.chain_id
print(f"Chain ID: {chain_id}")
# Chain ID: 14
return chain_id
if __name__ == "__main__":
asyncio.run(main())
Querying a contract
To query a contract, two pieces of information are required:
- Contract address
- Contract ABI (Application Binary Interface)
For this example, you can query the FlareContractRegistry
contract which has the same address 0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019
across both testnet and mainnet.
Fetch ABI
To fetch a contract's ABI programmatically, you can query the Flare Blockchain Explorer API:
- Flare Testnet Coston2
- Flare Mainnet
import asyncio
import json
import aiohttp
async def main() -> dict:
params = {
"module": "contract",
"action": "getabi",
"address": "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019",
}
async with (
aiohttp.ClientSession() as session,
session.get(
"https://coston2-explorer.flare.network/api", params=params
) as response,
):
abi = json.loads((await response.json())["result"])
print(abi[4]["name"])
# getContractAddressByName
return abi
if __name__ == "__main__":
asyncio.run(main())
import asyncio
import json
import aiohttp
async def main() -> dict:
params = {
"module": "contract",
"action": "getabi",
"address": "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019",
}
async with (
aiohttp.ClientSession() as session,
session.get(
"https://flare-explorer.flare.network/api", params=params
) as response,
):
res = await response.json()
abi = json.loads(res["result"])
print(abi[4]["name"])
# getContractAddressByName
return abi
if __name__ == "__main__":
asyncio.run(main())
Make query
You can now query the FlareContractRegistry
contract to get the addresses of other Flare contracts.
For example, querying it for the address of the WNat
contract:
- Flare Testnet Coston2
- Flare Mainnet
import asyncio
import json
import aiohttp
from web3 import AsyncHTTPProvider, AsyncWeb3
from web3.middleware import ExtraDataToPOAMiddleware
async def main() -> str:
registry_addr = "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019"
w3 = AsyncWeb3(
AsyncHTTPProvider("https://coston2-api.flare.network/ext/C/rpc"),
middleware=[ExtraDataToPOAMiddleware],
)
params = {
"module": "contract",
"action": "getabi",
"address": registry_addr,
}
async with (
aiohttp.ClientSession() as session,
session.get(
"https://coston2-explorer.flare.network/api", params=params
) as response,
):
res = await response.json()
abi = json.loads(res["result"])
registry = w3.eth.contract(address=w3.to_checksum_address(registry_addr), abi=abi)
res = await registry.functions.getContractAddressByName("WNat").call()
print(f"WNat address: {res}")
# WNat address: 0xC67DCE33D7A8efA5FfEB961899C73fe01bCe9273
return res
if __name__ == "__main__":
asyncio.run(main())
import asyncio
import json
import aiohttp
from web3 import AsyncHTTPProvider, AsyncWeb3
from web3.middleware import ExtraDataToPOAMiddleware
async def main() -> str:
registry_addr = "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019"
w3 = AsyncWeb3(
AsyncHTTPProvider("https://flare-api.flare.network/ext/C/rpc"),
middleware=[ExtraDataToPOAMiddleware],
)
params = {
"module": "contract",
"action": "getabi",
"address": registry_addr,
}
async with (
aiohttp.ClientSession() as session,
session.get(
"https://flare-explorer.flare.network/api", params=params
) as response,
):
res = await response.json()
abi = json.loads(res["result"])
registry = w3.eth.contract(address=w3.to_checksum_address(registry_addr), abi=abi)
res = await registry.functions.getContractAddressByName("WNat").call()
print(f"WNat address: {res}")
# WNat address: 0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d
return res
if __name__ == "__main__":
asyncio.run(main())
Compiling a contract
For this example, you can use the FtsoV2FeedConsumer
contract to query the FTSOv2 feeds. Copy the FtsoV2FeedConsumer
sample contract code given below, and save the .sol
file in the same folder as your Python script.
FtsoV2FeedConsumer
sample contract
Note that the contract interface dependencies have been inlined to avoid any import issues.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
interface IFlareContractRegistry {
function getContractAddressByName(
string calldata _name
) external view returns (address);
}
/**
* THIS IS A TEST INTERFACE.
* Functions are payable in the production interface.
*/
interface TestFtsoV2Interface {
function getFeedsById(
bytes21[] calldata _feedIds
)
external
view
returns (
uint256[] memory _values,
int8[] memory _decimals,
uint64 _timestamp
);
}
/**
* THIS IS AN EXAMPLE CONTRACT.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract FtsoV2FeedConsumer {
IFlareContractRegistry internal contractRegistry;
TestFtsoV2Interface internal ftsoV2;
// Feed IDs, see https://dev.flare.network/ftso/feeds for full list
bytes21[] public feedIds = [
bytes21(0x01464c522f55534400000000000000000000000000), // FLR/USD
bytes21(0x014254432f55534400000000000000000000000000), // BTC/USD
bytes21(0x014554482f55534400000000000000000000000000) // ETH/USD
];
/**
* Constructor initializes the FTSOv2 contract.
* The contract registry is used to fetch the FtsoV2 contract address.
*/
constructor() {
contractRegistry = IFlareContractRegistry(
0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019
);
ftsoV2 = TestFtsoV2Interface(
contractRegistry.getContractAddressByName("FtsoV2")
);
}
/**
* Get the current value of the feeds.
*/
function getFtsoV2CurrentFeedValues()
external
view
returns (
uint256[] memory _feedValues,
int8[] memory _decimals,
uint64 _timestamp
)
{
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedsById(feedIds);
}
}
Set up utils
To compile the contracts, you need to first set up some basic helper functions to read the .sol
file and write the compiled output to a .json
file. In a utils.py
file, you can define the following functions:
import json
from pathlib import Path
CURRENT_DIR = Path(__file__).resolve().parent
def load_contract(name: str) -> str:
"""Load contract from Solidity file."""
with (CURRENT_DIR / f"{name}.sol").open() as file:
return file.read()
def save_compiled_contract(name: str, compiled_contract: dict) -> None:
"""Save compiled contract to JSON file."""
with (CURRENT_DIR / f"{name}.json").open("w") as file:
json.dump(compiled_contract, file)
def load_contract_interface(name: str) -> dict:
"""Load contract interface from JSON."""
with (CURRENT_DIR / f"{name}.json").open() as file:
compiled_contract = json.load(file)
return compiled_contract["contracts"][f"{name}.sol"][f"{name}"]
Compile with py-solc-x
With these functions ready, you can now compile the contract. In a compile_contract.py
file, you can define the following code to install the correct version of the Solidity compiler and compile the contract:
from solcx import compile_standard, install_solc
from utils import load_contract, save_compiled_contract
if __name__ == "__main__":
contract_name = "FtsoV2FeedConsumer"
contract_code = load_contract(contract_name)
# Install the solc compiler
# Versions >0.8.25 may work, but have not been tested
solc_version = "0.8.25"
install_solc(solc_version)
compiled_sol = compile_standard(
{
"language": "Solidity",
"sources": {f"{contract_name}.sol": {"content": contract_code}},
"settings": {
"outputSelection": {
"*": {
"*": [
"abi",
"metadata",
"evm.bytecode",
"evm.bytecode.sourceMap",
],
}
},
"optimizer": {"enabled": True, "runs": 200},
"evmVersion": "london",
},
},
solc_version=solc_version,
)
save_compiled_contract(contract_name, compiled_sol)
You can now run the compile_contract.py
script to compile the contract. The compiled output will be saved to FtsoV2FeedConsumer.json
.
- uv
- pip
uv run compile_contract.py
python compile_contract.py
Create account
Before deploying a contract, you need to have an account with some testnet or mainnet gas tokens. You can create a new Flare account from the CLI:
from web3 import Web3
w3 = Web3()
acc = w3.eth.account.create()
print(f"Account: {acc.address}, Private Key: {w3.to_hex(acc.key)}")
This will output a new private key and an account pair.
- Never share your private keys.
- Never put your private keys in source code.
- Never commit private keys to a Git repository.
You can save the account and private key into environment variables ACCOUNT
and ACCOUNT_PRIVATE_KEY
respectively.
export ACCOUNT=<address above>
export ACCOUNT_PRIVATE_KEY=<private key above>
You can also import the raw hex private key to MetaMask and any other wallet - the private key can be shared between your Python code and any number of wallets.
- For testnet, you can get free testnet C2FLR on the Coston2 Faucet.
- For mainnet you will need to fund the account with FLR. You can buy FLR with regular currency in places like centralized exchanges, crypto on-ramps, or swap other tokens for FLR on decentralized exchanges.
Deploying with web3.py
With the functions and account ready, you can now deploy the contract. In a deploy_contract.py
file, you can define the following code to deploy the contract:
- Flare Testnet Coston2
- Flare Mainnet
# THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
import asyncio
import os
from web3 import AsyncHTTPProvider, AsyncWeb3
from web3.middleware import ExtraDataToPOAMiddleware
from utils import load_contract_interface
async def main() -> None:
account, private_key = os.getenv("ACCOUNT"), os.getenv("ACCOUNT_PRIVATE_KEY")
contract_interface = load_contract_interface("FtsoV2FeedConsumer")
w3 = AsyncWeb3(
AsyncHTTPProvider("https://coston2-api.flare.network/ext/C/rpc"),
middleware=[ExtraDataToPOAMiddleware],
)
account = w3.to_checksum_address(account)
ftso_v2_feed_consumer = w3.eth.contract(
abi=contract_interface["abi"],
bytecode=contract_interface["evm"]["bytecode"]["object"],
)
tx = await ftso_v2_feed_consumer.constructor().build_transaction(
{
"from": account,
"nonce": await w3.eth.get_transaction_count(account),
"gasPrice": await w3.eth.gas_price,
}
)
signed_tx = w3.eth.account.sign_transaction(tx, private_key=private_key)
print("Deploying Contract...")
tx_hash = await w3.eth.send_raw_transaction(signed_tx.raw_transaction)
tx_receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Contract deployed at {tx_receipt['contractAddress']}")
if __name__ == "__main__":
asyncio.run(main())
# THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
import asyncio
import os
from web3 import AsyncHTTPProvider, AsyncWeb3
from utils import load_contract_interface
async def main() -> None:
account, private_key = os.getenv("ACCOUNT"), os.getenv("ACCOUNT_PRIVATE_KEY")
contract_interface = load_contract_interface("FtsoV2FeedConsumer")
w3 = AsyncWeb3(AsyncHTTPProvider("https://flare-api.flare.network/ext/C/rpc"))
ftso_v2_feed_consumer = w3.eth.contract(
abi=contract_interface["abi"],
bytecode=contract_interface["evm"]["bytecode"]["object"],
)
account = w3.to_checksum_address(account)
tx = await ftso_v2_feed_consumer.constructor().build_transaction(
{
"from": account,
"nonce": await w3.eth.get_transaction_count(account),
"gasPrice": await w3.eth.gas_price,
}
)
signed_tx = w3.eth.account.sign_transaction(tx, private_key=private_key)
print("Deploying Contract...")
tx_hash = await w3.eth.send_raw_transaction(signed_tx.raw_transaction)
tx_receipt = await w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Contract deployed at {tx_receipt['contractAddress']}")
if __name__ == "__main__":
asyncio.run(main())
You can now run the deploy_contract.py
script to deploy the contract. The contract address will be printed once the deployment is successful. You can check the contract address on a Flare Blockchain Explorer, linked on the Network Configuration page.
- uv
- pip
uv run deploy_contract.py
python deploy_contract.py
Congratulations! You have now successfully deployed a contract on Flare using Python 🐍.
Learn how to interact with Flare's enshrined oracle FTSOv2 using Python.