Use this template to write, test and deploy Web3 Functions.
Web3 Functions are decentralized cloud functions that work similarly to AWS Lambda or Google Cloud, just for web3. They enable developers to execute on-chain transactions based on arbitrary off-chain data (APIs / subgraphs, etc) & computation. These functions are written in Typescript, stored on IPFS and run by Gelato.
You can find the official Web3 Functions documentation here.
Web3 Functions are currently in private Beta and can only be used by whitelisted users. If you would like to be added to the waitlist, please reach out to the team on Discord or apply using this form.
- What are Web3 Functions?
- Documentation
- Private Beta Restriction
- Table of Content
- Project Setup
- Write a Web3 Function
- Test your web3 function
- Use User arguments
- Use State / Storage
- Use user secrets
- Deploy your Web3Function on IPFS
- Create your Web3Function task
- More examples
- Install project dependencies
yarn install
- Configure your local environment:
- Copy
.env.example
to init your own.env
file
cp .env.example .env
- Complete your
.env
file with your private settings
PROVIDER_URLS="" # your provider URLS seperated by comma (e.g. https://eth-mainnet.alchemyapi.io/v2/YOUR_ALCHEMY_ID,https://eth-goerli.alchemyapi.io/v2/YOUR_ALCHEMY_ID)
PRIVATE_KEY="" # optional: only needed if you wish to create a task from the CLI instead of the UI
- Go to
src/web3-functions/my-web3-function
- Write your Web3 Function logic within the
Web3Function.onRun
function. - Example:
import { Web3Function, Web3FunctionContext } from "@gelatonetwork/web3-functions-sdk";
import { Contract, ethers } from "ethers";
import ky from "ky"; // we recommend using ky as axios doesn't support fetch by default
const ORACLE_ABI = [
"function lastUpdated() external view returns(uint256)",
"function updatePrice(uint256)",
];
Web3Function.onRun(async (context: Web3FunctionContext) => {
const { userArgs, gelatoArgs, multiChainProvider } = context;
const provider = multiChainProvider.default();
// Retrieve Last oracle update time
const oracleAddress = "0x71B9B0F6C999CBbB0FeF9c92B80D54e4973214da";
const oracle = new Contract(oracleAddress, ORACLE_ABI, provider);
const lastUpdated = parseInt(await oracle.lastUpdated());
console.log(`Last oracle update: ${lastUpdated}`);
// Check if it's ready for a new update
const nextUpdateTime = lastUpdated + 300; // 5 min
const timestamp = (await provider.getBlock("latest")).timestamp;
console.log(`Next oracle update: ${nextUpdateTime}`);
if (timestamp < nextUpdateTime) {
return { canExec: false, message: `Time not elapsed` };
}
// Get current price on coingecko
const currency = "ethereum";
const priceData: any = await ky
.get(
`https://api.coingecko.com/api/v3/simple/price?ids=${currency}&vs_currencies=usd`,
{ timeout: 5_000, retry: 0 }
)
.json();
price = Math.floor(priceData[currency].usd);
console.log(`Updating price: ${price}`);
// Return execution call data
return {
canExec: true,
callData: [{to: oracleAddress, data: oracle.interface.encodeFunctionData("updatePrice", [price]}]),
};
});
- Each Web3 Function has a
schema.json
file to specify the runtime configuration. In later versions you will have more optionality to define what resources your Web3 Function requires.
{
"web3FunctionVersion": "2.0.0",
"runtime": "js-1.0",
"memory": 128,
"timeout": 30,
"userArgs": {}
}
-
Use
npx w3f test FILEPATH
command to test your function -
Options:
--logs
Show internal Web3 Function logs--debug
Show Runtime debug messages--chain-id=[number]
Specify the chainId to be used for your Web3 Function (default:5
for Goerli)
-
Example:
npx w3f test src/web3-functions/oracle/index.ts --show-logs
-
Output:
Web3Function Build result: ✓ Schema: src/web3-functions/oracle/schema.json ✓ Built file: /Users/chuahsonglin/Documents/GitHub/Gelato/backend/js-resolver-template/.tmp/index.js ✓ File size: 1.63mb ✓ Build time: 91.34ms
Web3Function user args validation: ✓ currency: ethereum ✓ oracle: 0x71B9B0F6C999CBbB0FeF9c92B80D54e4973214da
Web3Function running...
Web3Function Result: ✓ Return value: { canExec: true, callData: [ { to: '0x71B9B0F6C999CBbB0FeF9c92B80D54e4973214da', data: '0x8d6cc56d0000000000000000000000000000000000000000000000000000000000000769' } ] }
Web3Function Runtime stats: ✓ Duration: 3.29s ✓ Memory: 74.78mb ✓ Storage: 0.03kb ✓ Rpc calls: 3
### Writing unit test for your web3 function
- Define your tests in `src/test/hellow-world.test.ts`
- Use `yarn test` command to run unit test suite.
You can fork a network in your unit test.
RPC methods of provider can be found in [Foundry's Anvil docs](https://book.getfoundry.sh/reference/anvil/)
Example: [`src/test/advertising-board.test.ts`](./src/test/advertising-board.test.ts)
```ts
import { AnvilServer } from "./utils/anvil-server";
goerliFork = await AnvilServer.fork({
forkBlockNumber: 8483100,
forkUrl: "https://rpc.ankr.com/eth_goerli",
});
const forkedProvider = goerliFork.provider;
- Declare your expected
userArgs
in your schema, accepted types are 'string', 'string[]', 'number', 'number[]', 'boolean', 'boolean[]':
{
"web3FunctionVersion": "2.0.0",
"runtime": "js-1.0",
"memory": 128,
"timeout": 30,
"userArgs": {
"currency": "string",
"oracle": "string"
}
}
- Access your
userArgs
from the Web3Function context:
Web3Function.onRun(async (context: Web3FunctionContext) => {
const { userArgs, gelatoArgs, secrets } = context;
// User args:
console.log("Currency:", userArgs.currency);
console.log("Oracle:", userArgs.oracle);
});
- Populate
userArgs
inuserArgs.json
and test your web3 function:
{
"currency": "ethereum",
"oracle": "0x71B9B0F6C999CBbB0FeF9c92B80D54e4973214da"
}
npx w3f test src/web3-functions/oracle/index.ts --logs
Web3Functions are stateless scripts, that will run in a new & empty memory context on every execution.
If you need to manage some state variable, we provide a simple key/value store that you can access from your web3 function context
.
See the below example to read & update values from your storage:
import {
Web3Function,
Web3FunctionContext,
} from "@gelatonetwork/web3-functions-sdk";
Web3Function.onRun(async (context: Web3FunctionContext) => {
const { storage, multiChainProvider } = context;
const provider = multiChainProvider.default();
// Use storage to retrieve previous state (stored values are always string)
const lastBlockStr = (await storage.get("lastBlockNumber")) ?? "0";
const lastBlock = parseInt(lastBlockStr);
console.log(`Last block: ${lastBlock}`);
const newBlock = await provider.getBlockNumber();
console.log(`New block: ${newBlock}`);
if (newBlock > lastBlock) {
// Update storage to persist your current state (values must be cast to string)
await storage.set("lastBlockNumber", newBlock.toString());
}
return {
canExec: false,
message: `Updated block number: ${newBlock.toString()}`
};
});
Test storage execution:
npx w3f test src/web3-functions/storage/index.ts --logs
You will see your updated key/values:
Simulated Web3Function Storage update:
✓ lastBlockNumber: '8944652'
- Input your secrets in
.env
file in the same directory as your web3 function.
COINGECKO_API=https://api.coingecko.com/api/v3
- Access your secrets from the Web3Function context:
// Get api from secrets
const coingeckoApi = await context.secrets.get("COINGECKO_API");
if (!coingeckoApi)
return { canExec: false, message: `COINGECKO_API not set in secrets` };
- Test your Web3 Function using secrets:
npx w3f test src/web3-functions/secrets/index.ts --logs
Use npx w3f deploy FILEPATH
command to deploy your web3 function.
Example:
npx w3f deploy src/web3-functions/oracle/index.ts
The deployer will output your Web3Function IPFS CID, that you can use to create your task:
✓ Web3Function deployed to ipfs.
✓ CID: QmVfDbGGN6qfPs5ocu2ZuzLdBsXpu7zdfPwh14LwFUHLnc
To create a task that runs your Web3 Function every minute, visit:
> https://beta.app.gelato.network/new-task?cid=QmVfDbGGN6qfPs5ocu2ZuzLdBsXpu7zdfPwh14LwFUHLnc
Use the automate-sdk
to easily create a new task (make sure you have your private_key in .env):
const { taskId, tx } = await automate.createBatchExecTask({
name: "Web3Function - Eth Oracle",
web3FunctionHash: cid,
web3FunctionArgs: {
oracle: oracle.address,
currency: "ethereum",
},
});
await tx.wait();
If your task utilizes secrets, you can set them after the task has been created.
// Set task specific secrets
const secrets = oracleW3f.getSecrets();
if (Object.keys(secrets).length > 0) {
await web3Function.secrets.set(secrets, taskId);
console.log(`Secrets set`);
}
Test it with our sample task creation script:
yarn create-task:oracle
Deploying Web3Function on IPFS...
Web3Function IPFS CID: QmVfDbGGN6qfPs5ocu2ZuzLdBsXpu7zdfPwh14LwFUHLnc
Creating automate task...
Task created, taskId: 0x8438933eb9c6e4632d984b4db1e7672082d367b900e536f86295b2e23dbcaff3
> https://beta.app.gelato.network/task/0x8438933eb9c6e4632d984b4db1e7672082d367b900e536f86295b2e23dbcaff3?chainId=5
Fetch price data from Coingecko API to update your on-chain Oracle
Source: src/web3-functions/oracle/index.ts
Run:
npx w3f test src/web3-functions/oracle/index.ts --logs
Create task:
yarn create-task:oracle
Listen to smart contract events and use storage context to maintain your execution state.
Source: src/web3-functions/event-listener/index.ts
Run:
npx w3f test src/web3-functions/event-listener/index.ts --logs
Create task:
yarn create-task:event
Fetch data from a private API to update your on-chain Oracle
Source: src/web3-functions/secrets/index.ts
Run:
npx w3f test src/web3-functions/secrets/index.ts --logs
Create task:
yarn create-task:secrets
Fetch a random quote from an API and post it on chain.
Source: src/web3-functions/advertising-board/index.ts
Run:
npx w3f test src/web3-functions/advertising-board/index.ts
Create task:
yarn create-task:ad-board