Get the price of weed on Solana
Cluutch is now running a Switchboard oracle for the daily average price of weed on Solana Mainnet.
Want to know the average price for an ounce of weed in the U.S.A. today? Just run:
const url = clusterApiUrl('mainnet-beta');
let connection = new Connection(url, 'processed');
let dataFeedPubkey = new PublicKey("GvNzEWX3hV9aowJbRvjw3Avnp6ynP9XKVC2SFTRCJ3fv");
await parseAggregatorAccountData(connection, dataFeedPubkey);
When you execute this command, you are interacting with a Solana on-chain oracle. That oracle is running Switchboard code and populated by a fulfillment manager maintained by Cluutch. The Internet endpoint being queried, is a Google Cloud API Gateway instance also maintained by Cluutch. Without using Solana, you can access the same prices using the command below (full docs):
➜ curl https://cluutch-api-gateway-bh8jku5i.uc.gateway.dev/v3/dailies
[…
{
"date":{
"value":"2021-11-04"
},
"avg_price_per_ounce":631.1434070669786,
"jurisdiction":"All U.S."
},
…]
Weed and Solana?
This work continues to be just a fun weekend project so I am not sure what actual use cases exist for having weed prices on Solana. For now, I’m learning a lot by going through the motions of setting all this up. And it has been interesting enough just to observe that weed prices have been slowly decreasing over the past few weeks.
In a future week, I will provide an update about exactly how the price is being computed. The short version is that I’m paying for a service to scrape 4,442 weed dispensary websites every day and calculating the mean price for an ounce of weed.
Setting up a Switchboard Oracle
The primary technical challenge that this post covers is setting up a Switchboard oracle. I actually already covered this in an earlier post. At that point, I wrote custom code to use the Switchboard API.
Another option would have been using the official example code to do the same thing. This post will attempt to follow the newer switchbaord-feed-factory instructions.
It is worth noting that Switchboard is actively developing a v2 release of their API. Some of these instructions may quickly become outdated once that release is official.
Setup a Fulfillment Manager
These instructions mostly follow the README for switchboard-feed-factory. Some small gotchas are also included. The instructions also include steps to setup the fulfillment manager on Google Cloud.
➜ npm install -D ts-node
➜ git clone git@github.com:switchboard-xyz/switchboard-feed-factory.git
➜ cd switchboard-feed-factory
➜ npm i
➜ solana-keygen new --outfile example-keypair.json
➜ solana airdrop 5 example-keypair.json
➜ ts-node src/utils/createFulfillmentManager.ts --payerKeypairFile=example-keypair.json
Take note of the output for FULFILLMENT_MANAGER_KEY
and AUTH_KEY
, you’ll need those later.
Create Google Compute Engine instance for Fulfillment Manager
Create container optimized compute instance
SSH into compute instance then clone switchboard-feed-factory repo.
$ git clone https://github.com/switchboard-xyz/switchboard-feed-factory cd switchboard-feed-factory
Export
FULFILLMENT_MANAGER_KEY
andAUTH_KEY
from earlier step then copy overexample-keypair.json
.$ export FULFILLMENT_MANAGER_KEY=COPY_HERE $ export AUTH_KEY=COPY_HERE $ vi example-keypair.json
Start docker-compose.
$ docker run --rm \ -e FULFILLMENT_MANAGER_KEY="$FULFILLMENT_MANAGER_KEY" \ -e AUTH_KEY="$AUTH_KEY" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$PWD:$PWD" \ -w="$PWD" \ docker/compose:1.24.0 up
Create a Data Feed
Neither switchboard-examples or switchboard-feed-factory can be used to create a custom data feed after running step 3 of switchboard-feed-factory. The remaining instructions will only allow you to setup a feed for NBA and EPL games. Meanwhile, part 2a of switchboard-examples combines fulfillment manager account creation with data feed creation.
This PR copies key bits of switchboard-examples #2a into a new #2c which creates a data feed from a pre-existing fulfillment manager. We can then create a custom feed using a command like this:
$ ts-node example_2c.ts \
--payerFile=example-keypair.json \
--fulfillmentFile=fulfillment-keypair.json \
--apiEndpoint="https://cluutch-api-gateway-bh8jku5i.uc.gateway.dev/v3/dailies" \
--apiJsonPath="$[0].avg_price_per_ounce"
Load data on-chain
By this point, all of the pieces of the pipeline are in place. The last step is to run example #2b in order to trigger the oracle to read data.
$ ts-node example_2b.ts \
--payerFile=example-keypair.json \
--dataFeedPubkey=INSERT_HERE \
--updateAuthPubkey=INSERT_HERE
Awaiting update transaction finalization...
(BKgkSRUkgAbHBcYNc8QEekEGHqeHcnPoBHt2i9qkfbR2) state.
{
"version": 1,
"configs": {
"locked": false,
"minConfirmations": 1,
"minUpdateDelaySeconds": "10"
},
"fulfillmentManagerPubkey": "58J7GIvn31OYH1GACBRgbORc6rk65kmV7FKDNHEyV/I=",
"jobDefinitionPubkeys": [
"orrrd2PtpZZIB0crNBvKgfxmTy8GUnxlihdvHaQXM08="
],
"agreement": {
"nodePubkeys": [
"cUL1dfu+acjcwq+lrgP67qdf/WhU8jBN2lhXZZ9DA58="
],
"requested": true
},
"currentRoundResult": {
"numSuccess": 1,
"numError": 0,
"result": 627.9081534474341,
"roundOpenSlot": "92721387",
"roundOpenTimestamp": "1636323128",
"minResponse": 627.9081534474341,
"maxResponse": 627.9081534474341,
"medians": [
627.9081534474341
]
},
"lastRoundResult": {
"numSuccess": 1,
"numError": 0,
"result": 627.9081534474341,
"roundOpenSlot": "92721334",
"roundOpenTimestamp": "1636323107",
"minResponse": 627.9081534474341,
"maxResponse": 627.9081534474341,
"medians": [
627.9081534474341
]
},
"parseOptimizedResultAddress": "xPydn6/2CdvhijLXmysqcHEhkk9zjVA/t0keYmA4zb8="
}
From the response, you can see the average price for weed today is $627.9.
End-to-end instructions
Repeating everything above, but running on Solana Mainnet. Forks of switchboard-examples and switchboard-feed-factory were created to work with mainnet and other changes mentioned in this blog.
# From switchboard-feed-factory
➜ cd switchboard-feed-factory
➜ ts-node src/utils/createFulfillmentManager.ts \
--payerKeypairFile=example-keypair.json \
--cluster=mainnet-beta
✔ Enter a name for the output file … fulfillment-keypair.json
export FULFILLMENT_MANAGER_KEY=XXX
export AUTH_KEY=XXX
# From switchboard-examples
➜ cd switchboard-examples/ts-example
➜ cp /PATH/TO/fulfillment-keypair.json .
## Manually update example-2c.ts PID to SWITCHBOARD_MAINNET_PID
➜ ts-node example_2c.ts \
--payerFile=example-keypair.json \
--fulfillmentFile=fulfillment-keypair.json \
--apiEndpoint="https://cluutch-api-gateway-bh8jku5i.uc.gateway.dev/v3/dailies" \
--apiJsonPath="$[0].avg_price_per_ounce" \
--cluster=mainnet-beta
# From Google Cloud Compute Instance
## Manually update docker-compose.yml to use mainnet-beta
## Update example-keypair.json with the real payer
## Update timeout with a larger interval (12h) so you don't deplete all your funds
$ docker run --rm \
-e FULFILLMENT_MANAGER_KEY=ENTER_HERE \
-e AUTH_KEY=ENTER_HERE \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD:$PWD" \
-w="$PWD" \
docker/compose:1.24.0 up
# From switchboard-examples
✗ ts-node example_2b.ts \
--payerFile=example-wallet.json \
--dataFeedPubkey=ENTER_HERE \
--updateAuthPubkey=ENTER_HERE \
--cluster=mainnet-beta
Awaiting update transaction finalization...
(GvNzEWX3hV9aowJbRvjw3Avnp6ynP9XKVC2SFTRCJ3fv) state.
{
"version": 1,
"configs": {
"locked": false,
"minConfirmations": 1,
"minUpdateDelaySeconds": "10"
},
"fulfillmentManagerPubkey": "B4iA9qxJC3NQHgSeYHF6NqMa1HCtkQGl4CBkG6I5uVE=",
"jobDefinitionPubkeys": [
"fjTUSSDKS8RxWeEKA5PPg0P6k40ckoVI9XFgOzKG4FE="
],
"agreement": {
"nodePubkeys": [
"cUL1dfu+acjcwq+lrgP67qdf/WhU8jBN2lhXZZ9DA58="
],
"requested": true
},
"currentRoundResult": {
"numSuccess": 1,
"numError": 0,
"result": 627.9081534474341,
"roundOpenSlot": "105757955",
"roundOpenTimestamp": "1636327172",
"minResponse": 627.9081534474341,
"maxResponse": 627.9081534474341,
"medians": [
627.9081534474341
]
},
"lastRoundResult": {
"numSuccess": 1,
"numError": 0,
"result": 627.9081534474341,
"roundOpenSlot": "105756874",
"roundOpenTimestamp": "1636326612",
"minResponse": 627.9081534474341,
"maxResponse": 627.9081534474341,
"medians": [
627.9081534474341
]
},
"parseOptimizedResultAddress": "2S569MI0vCaroPqinOTHBudNO3yRjqbFSvcpwMMe0LI="
}
To create a fulfillment manager and feed cost 0.07304 SOL, or $18 (at the current market price of $249 per SOL). Each update of the feed costs 0.00001 SOL, less than a penny.
Things to do next:
Configure feed and fulfillment manager: There are a few parameters like heartbeat and authorizeUsage that I should better understand and leverage.
Migrate from Dataprep to pure Cloud Functions: Pure functions will be cheaper and more efficient than Dataprep.
Setup API DNS: The Cluutch API endpoint should not be so ugly, https://cluutch-api-gateway-bh8jku5i.uc.gateway.dev/v3
Setup a Hedgehog market that uses this feed to resolve.