Become a Data Provider
Running your own bundler involves setting up a modified version of the Voltaire Bundler that supports data-dependent operations. This allows you to manage your own data provider and serve oracle data through the bundler. By being a data provider you will get paid twice: you get the bundler fee from user operations and the all the data fees whenever you bundle a data-dependent user operation successfully.
Step 1: Get the Code
Download our fork of the Voltaire Bundler.
git clone git@github.com:Morpher-io/dd-voltaire.git
Step 2: Install Dependencies
Just use poetry:
curl -sSL https://install.python-poetry.org | python3 -
poetry install
Step 3: Obtain an RPC Endpoint
To run the bundler, you need access to a reliable RPC endpoint for your chosen EVM based blockchain. This node must support debug_traceCall with custom tracers and eth_call with state overrides. You can either get a good RPC service or run your own node.
Step 4: Create your provider Accounts
Create an ethereum keypair and fund it. The address will be the provider address for your data. The funds will be needed to perform bundling. This is also the address where you will receive the data fees.
Then, create a Safe smart contract account as explained here , owned by your previously created account. Fund this account. You will pay your user operations containing the data with this account, so be sure to keep it funded and to set your data prices (see step 6) high enough so you can cover these expenses.
Step 5: Run the Data Provider Server
The bundler will need to fetch data from your own data server. You can set up this server by following the example provided in the data provider example . You will need to have the /fetch endpoint ready to respond with your own data. The /keys endpoint is needed to provide DApps developers with information regarding the data you provide. You also need to publish somewhere how you encode your data in the bytes32 format.
Step 6: Set your Prices
Now you can set the price for each feed you provide. You can do this calling the Oracle Entrypoint’s setPrice function. If you don’t want to pay for a transaction for each feed, why not using your new SCA already? ;)
import {
MetaTransaction,
getFunctionSelector,
createCallData
} from "abstractionkit";
import Web3 from 'web3';
import { secp256k1 } from "ethereum-cryptography/secp256k1";
// key is bytes32, price is uint in wei
const YOUR_DATA_PRICES: { key: `0x${string}`, price: number }[] = [];
const YOUR_PROVIDER_ADDRESS: string = "";
const YOUR_PROVIDER_PK: string = "";
const ORACLE_ADDRESS = "0x9F82E17fb4d5815cf261a9AafFE53A9834F55b9F"; // address on sepolia testnet
const createSetPriceMetaTxs = async () => {
const metaTransactions: MetaTransaction[] = [];
const priceFunctionSignature = 'setPrice(address,uint256,bytes32,uint256,bytes32,bytes32,uint8)';
const priceFunctionSelector = getFunctionSelector(priceFunctionSignature);
let nonce = 0; // it's 0 if you never called the Oracle Entrypoint, otherwise get it with OracleEntrypoint.nonces
for (const { key, price } of YOUR_DATA_PRICES) {
const priceChangeUnsignedData = [
YOUR_PROVIDER_ADDRESS,
nonce,
key,
price
];
const priceChangePackedHexString = Web3.utils.encodePacked(
{ value: YOUR_PROVIDER_ADDRESS, type: 'address' },
{ value: nonce, type: 'uint256' },
{ value: key, type: 'bytes32' },
{ value: price, type: 'uint256' }
);
const preamble = "\\x19Oracle Signed Price Change:\\n116";
const signature = sign(Buffer.from(priceChangePackedHexString.slice(2), 'hex'), YOUR_PROVIDER_PK, preamble);
const priceChangeTransactionCallData = createCallData(
priceFunctionSelector,
["address", "uint256", "bytes32", "uint256", "bytes32", "bytes32", "uint8"],
[...priceChangeUnsignedData, signature.r, signature.s, signature.v]
);
const priceChangeTransaction: MetaTransaction = {
to: ORACLE_ADDRESS,
value: BigInt(0),
data: priceChangeTransactionCallData,
}
metaTransactions.push(priceChangeTransaction);
}
return metaTransactions;
}
function sign(messageBuffer: Buffer, privateKey: string, preamble: string) {
const preambleBuffer = Buffer.from(preamble);
const message = Buffer.concat([preambleBuffer, messageBuffer]);
const hash = Web3.utils.keccak256(message);
const signaturePayload = secp256k1.sign(
Buffer.from(hash.substring(2), 'hex'),
Buffer.from(privateKey.substring(2), 'hex')
);
const r = '0x' + signaturePayload.r.toString(16).padStart(64, "0");
const s = '0x' + signaturePayload.s.toString(16).padStart(64, "0");
const v = 27 + signaturePayload.recovery;
return { r, s, v };
}
Step 7: Run the Bundler
Finally, you can run the bundler!
poetry run python3 -m voltaire_bundler --entrypoints $ENTRYPOINT \
--bundler_secret $PROVIDER_EOA_PK --bundler_smart_wallet $PROVIDER_SCA_ADDRESS \
--chain_id $CHAIN_ID --ethereum_node_url $ETHEREUM_RPC --oracle $ORACLE_ADDRESS --verbose
Remember to add the following to your environment:
- $ENTRYPOINT: The ERC-4337 entry point contract address.
- $PROVIDER_EOA_PK: Your provider’s private key.
- $PROVIDER_SCA_ADDRESS: Your provider’s smart contract account address.
- $CHAIN_ID: The chain ID for the blockchain network you are using.
- $ETHEREUM_RPC: The URL for your blockchain node RPC endpoint.
- $ORACLE_ADDRESS: The address of the OracleEntrypoint.
Note: The Oracle Entrypoint contract is already deployed on Polygon at the address 0xd4f4baD1Fba15F8B136DBb9A44CE44caa3E92A5A and on Sepolia at the address 0x9F82E17fb4d5815cf261a9AafFE53A9834F55b9F.