Before you can build and run the project, you'll need to have Docker installed on your system. Docker allows you to package and distribute applications as lightweight containers, making it easy to manage dependencies and ensure consistent behavior across different environments. Switchboard Functions are built and run within containers, so you'll need a docker daemon running to publish a new function.
If you don't have Docker installed, you can follow these steps to get it up and running:
-
Linux: Depending on your Linux distribution, you might need to use different package managers. For Ubuntu, you can use
apt
:sudo apt update sudo apt install docker.io
For other distributions, consult your package manager's documentation.
-
macOS: You can install Docker Desktop for macOS by downloading the installer from the Docker website and following the installation instructions.
-
Windows: Similarly, you can install Docker Desktop for Windows from the Docker website and follow the provided instructions.
After installing Docker, make sure it's running by opening a terminal/command prompt and running:
docker --version
This should display the installed Docker version, confirming that Docker is installed and running properly.
You'll need to login to docker. If you don't yet have an account, you'll need one to publish images to dockerhub. You can sign up at https://hub.docker.com.
docker login --username <your-username> --password <your-password>
pnpm i
pnpm install -g @switchboard-xyz/cli
sb evm function --help
This example contract acts as the ingester of the switchboard-function in this directory to fetch implied volatility parameters via deribit. The example contract is an example of the ERC2535 diamond contract pattern so it can be extended and upgraded for your needs.
When you deploy this contract, it will await to be bound to a switchboard function calling into it.
- setup your keypair by modifying
.env.example
and renaming it to.env
, including your deployment key in the first line - set the
SWITCHBOARD_ADDRESS
env variable to target whichever address is appropriate for the network you're targeting - for arbitrum testnet, this is:
0xA3c9F9F6E40282e1366bdC01C1D30F7F7F58888e
To first deploy the contract, run:
# ex:
# pnpm deploy:coredaotestnet
# pnpm deploy:coredaomain
# pnpm deploy:arbitrumtestnet
export NETWORK_NAME=arbitrumtestnet
pnpm deploy:${NETWORK_NAME}
More deploy commands are available in package.json scripts.
You will see the last line of this script output
export CALLBACK_ADDRESS=<CALLBACK_ADDRESS>
Export the address to your environment and navigate to ./switchboard-function/
The bulk of the function logic can be found in ./switchboard-function/src/main.rs.
You'll also need to pick a container name that your switchboard function will use on dockerhub.
Here, set the name of your container and deploy it using:
cd switchboard-function
export CONTAINER_NAME=your_docker_username/switchboard-function
export CALLBACK_ADDRESS=<RECEIVER_ADDRESS>
make build
make publish
Common gotcha: make sure this published container is publically visible
After this is published, you are free to make your function account to set the rate of run for the function.
You'll also need to create a file with your private key in it. This is used to sign the transactions that will be sent to the switchboard contract.
In order to write a successfully running switchboard function, you'll need to import switchboard-evm
to use the libraries which communicate the function results (which includes transactions to run) to the Switchboard Verifiers that execute these metatransactions.
We can't guarantee that the function will run on the blockchain, but we can test that it compiles and runs locally.
Run the following to test your function:
export CALLBACK_ADDRESS=${SWITCHBOARD_ADDRESS?} # can be any valid address
# test run the function - modify these params to match your desired output
sb evm function test --chain arbitrum --parameters "string:ETH,uint256:1698781846,uint256:2000,uint8:0" --network testnet
The above corresponds to the following parameters in the function:
#[derive(EthAbiType, EthAbiCodec, Default, Debug, Clone)]
pub struct Order {
market_id: String, // asset name
exp_date: U256, // expiration date
strike_price: U256, // strike price in integers (this ultimately should depend on the asset / exchange)
option_type: U256, // Assuming OptionType is an enum, which in Solidity is represented as uint
}
Successful output:
WARNING: Error generating quote. This is likely due to the enclave not being initialized.
FN_OUT: ...
3030303030303030303030303030303030303032366635383661...
...
The error
above simply means your function could not produce a secure signature for the test since it was run outside the enclave. Error: Failed to parse function result
can also be safely ignored.
You can use the Switchboard cli to bind this docker container to an on-chain representation:
export SWITCHBOARD_ADDRESS_ARBITRUM_TESTNET=0xA3c9F9F6E40282e1366bdC01C1D30F7F7F58888e
export QUEUE_ID=0x54f8A91bE5baAD3E2368b00A11bF4012EeA6b031F # default testnet queue
export MEASUREMENT=<YOUR CONTAINER MEASUREMENT>
export CLUSTER=arbitrumTestnet # or arbitrumMainnet etc
sb evm function create $QUEUE_ID --container ${CONTAINER_NAME} --containerRegistry dockerhub \
--mrEnclave ${MEASUREMENT?} --name "options_example" --chain arbitrum --account /path/to/signer \
--network testnet --programId $SWITCHBOARD_ADDRESS_ARBITRUM_TESTNET
# ...
export FUNCTION_ID=<YOUR FUNCTION ID>
# will fund the request with 0.01 ETH and send the request to the switchboard contract - this can be reduced significantly
pnpm exec hardhat run scripts/request.ts --network arbitrumTestnet
This repo contains an example script to view the current verified deribit implied volatility info currently in contract:
npx hardhat run --network arbitrumTestnet scripts/get_state.ts
When it comes time to update the function image, you can use the following command to update function image:
export CONTAINER_NAME=<NEW_IMAGE>
export MEASUREMENT=<NEW_MEASUREMENT>
export FUNCTION_ID=<YOUR FUNCTION ID>
export SWITCHBOARD_ADDRESS=$SWITCHBOARD_ADDRESS_ARBITRUM_TESTNET
# update the script to set the function name - will default to container name if not set
# vi scripts/update_function_config.ts
# ...
# update the function image
pnpm exec hardhat run scripts/update_function_config.ts --network arbitrumTestnet
And the following to update the function measurement:
sb evm function add-enclave $FUNCTION_ID --chain arbitrum --network testnet --mrEnclave $MEASUREMENT \
--account /path/to/signer --programId $SWITCHBOARD_ADDRESS_ARBITRUM_TESTNET
The maximum gas for switchboard functions is 5.5 Million. This is a hard limit set by the switchboard contract. If you need to increase this limit, please reach out to us. Each run of a switchboard function results in a transaction that is baked into a larger transaction that is sent to the switchboard contract. So keep the downstream function costs in mind when setting the gas limit for your function.
You can use the Switchboard cli to create a request to your function:
# for creating a one-off run of the function, you can use the following
sb evm request send $FUNCTION_ID --chain arbitrum --parameters "string:ETH,uint256:1698781846,uint256:2000,uint8:0" \
--account /path/to/keypair --network testnet --programId $SWITCHBOARD_ADDRESS_ARBITRUM_TESTNET
NOTE: the above will not work with the example contract as it is not set up to handle uninitialized calls
For creating a Routine, which is a recurring function call, you can modify the parameters and schedule in the following commands:
# for creating a recurring run of the function, you can use the following
sb evm routine create $FUNCTION_ID --chain arbitrum --schedule "*/10 * * * * *" --account /path/to/keypair --network testnet \
--programId $SWITCHBOARD_ADDRESS_ARBITRUM_TESTNET \
--parameters="string:ETH,uint256:1698781846,uint256:2000,uint8:0"
export ROUTINE_ID=<YOUR ROUTINE ID>
## For funding the function, you can use the following:
sb evm routine fund ${ROUTINE_ID?} --chain arbitrum --fundAmount 0.01 --account /path/to/keypair \
--network testnet --programId $SWITCHBOARD_ADDRESS_ARBITRUM_TESTNET
NOTE: the above will not work with the example contract as it is not set up to handle uninitialized calls
After you publish the function and create it on the blockchain, you must keep the function escrow account funded to cover gas fees. Revisions to the function can be made by deploying a new version and updating the function config on-chain.
While Switchboard Functions can call back into any number of on-chain functions, it's useful to limit access to some privileged functions to just your Switchboard Function.
In order to do this you'll need to know the switchboard address you're using, and which functionId will be calling into the function in question.
Last gotcha: when encoding parameters with variable length fields Solidity-side, make sure you spread the params in the encode call. For example, if you have a struct with a string and a uint, you'll need to do something like:
// Get order data from the market
ReceiverLib.Order memory order = ReceiverLib.Order({
marketId: "ETH",
expDate: expirationDate, // uint256 unix timestamp
strikePrice: strikePrice, // uint256
optionType: optionType // enum -> u8
});
// Encode the order data to pass to the switchboard function request
// NOTE: We spread the fields here because solidity encodes structs with variable length fields differently
bytes memory orderData = abi.encode(
order.marketId, // <-- strings are variable length
order.expDate,
order.strikePrice,
order.optionType
);
// Create the Switchboard Function Request & store it
ISwitchboard switchboard = ISwitchboard(AdminLib.switchboard());
address callId = switchboard.sendRequest{value: msg.value}(
AdminLib.functionId(),
orderData
);