| name | ritual-dapp-deploy |
| description | Deployment and chain configuration for Ritual dApps. Use when deploying contracts, configuring chain connection, or setting up development environment. |
Ritual Chain — Deployment & Configuration Guide
Chain Configuration
Core Parameters
| Parameter | Value |
|---|
| Chain ID | 1979 |
| Chain Name | Ritual |
| Native Currency | RITUAL (18 decimals, testnet, no real value) |
| RPC (HTTP) | https://rpc.ritualfoundation.org |
| RPC (WebSocket) | wss://rpc.ritualfoundation.org/ws |
| Block Explorer | https://explorer.ritualfoundation.org |
EIP-1559 only. Ritual Chain requires EIP-1559 (type-2) transactions. Legacy (type-0) transactions are rejected with transaction type not supported. Do NOT use --legacy flag with forge/cast, and ensure your web3 library sends EIP-1559 transactions (viem does this by default; web3.py and ethers.js may need explicit configuration).
viem Chain & Client Setup
Create viem clients configured for Ritual Chain:
import { createPublicClient, createWalletClient, http, defineChain } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
const ritualChain = defineChain({
id: 1979,
name: 'Ritual',
nativeCurrency: { name: 'RITUAL', symbol: 'RITUAL', decimals: 18 },
rpcUrls: {
default: {
http: [process.env.RITUAL_RPC_URL || 'https://rpc.ritualfoundation.org'],
webSocket: [process.env.RITUAL_WS_URL || 'wss://rpc.ritualfoundation.org/ws'],
},
},
blockExplorers: {
default: { name: 'Ritual Explorer', url: 'https://explorer.ritualfoundation.org' },
},
contracts: {
multicall3: { address: '0x5577Ea679673Ec7508E9524100a188E7600202a3' },
},
});
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({ chain: ritualChain, transport: http() });
const walletClient = createWalletClient({ account, chain: ritualChain, transport: http() });
viem Chain Definition (Standalone)
A standalone chain definition for use in wagmi config or other contexts:
import { defineChain } from 'viem';
export const ritualChain = defineChain({
id: 1979,
name: 'Ritual',
nativeCurrency: {
decimals: 18,
name: 'Ritual',
symbol: 'RITUAL',
},
rpcUrls: {
default: {
http: ['https://rpc.ritualfoundation.org'],
webSocket: ['wss://rpc.ritualfoundation.org/ws'],
},
},
blockExplorers: {
default: {
name: 'Ritual Explorer',
url: 'https://explorer.ritualfoundation.org',
},
},
contracts: {
multicall3: {
address: '0x5577Ea679673Ec7508E9524100a188E7600202a3',
},
},
});
wagmi Configuration
Basic wagmi Setup
import { http, createConfig } from 'wagmi';
import { injected, walletConnect } from 'wagmi/connectors';
import { defineChain } from 'viem';
const ritualChain = defineChain({
id: 1979,
name: 'Ritual',
nativeCurrency: {
decimals: 18,
name: 'Ritual',
symbol: 'RITUAL',
},
rpcUrls: {
default: {
http: ['https://rpc.ritualfoundation.org'],
webSocket: ['wss://rpc.ritualfoundation.org/ws'],
},
},
blockExplorers: {
default: {
name: 'Ritual Explorer',
url: 'https://explorer.ritualfoundation.org',
},
},
contracts: {
multicall3: {
address: '0x5577Ea679673Ec7508E9524100a188E7600202a3',
},
},
});
export const config = createConfig({
chains: [ritualChain],
connectors: [
injected(),
walletConnect({
projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
}),
],
transports: {
[ritualChain.id]: http('https://rpc.ritualfoundation.org'),
},
});
wagmi with RainbowKit
import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { defineChain } from 'viem';
const ritualChain = defineChain({
id: 1979,
name: 'Ritual',
nativeCurrency: { decimals: 18, name: 'Ritual', symbol: 'RITUAL' },
rpcUrls: {
default: {
http: ['https://rpc.ritualfoundation.org'],
},
},
blockExplorers: {
default: {
name: 'Ritual Explorer',
url: 'https://explorer.ritualfoundation.org',
},
},
});
export const config = getDefaultConfig({
appName: 'My Ritual dApp',
projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
chains: [ritualChain],
});
Next.js App Layout with wagmi
'use client';
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from './wagmi';
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}
Contract Deployment with Foundry
foundry.toml Configuration
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.20"
optimizer = true
optimizer_runs = 200
[rpc_endpoints]
ritual = "${RITUAL_RPC_URL}"
[etherscan]
ritual = { key = "unused", url = "${RITUAL_VERIFIER_URL}" }
Deploy Script
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {MyRitualConsumer} from "../src/MyRitualConsumer.sol";
contract DeployScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
MyRitualConsumer consumer = new MyRitualConsumer();
console.log("MyRitualConsumer deployed to:", address(consumer));
vm.stopBroadcast();
}
}
Deploy Commands
source .env
forge script script/Deploy.s.sol:DeployScript \
--rpc-url $RITUAL_RPC_URL \
--broadcast \
-vvvv
forge verify-contract \
--chain 1979 \
--watch \
--verifier custom \
--verifier-url "$RITUAL_VERIFIER_URL" \
--verifier-api-key unused \
<CONTRACT_ADDRESS> \
src/MyRitualConsumer.sol:MyRitualConsumer
Deploy with forge create (Single Contract)
forge create deploys a single contract without needing a deploy script. This is useful for quick deployments and testing.
forge create src/MyRitualConsumer.sol:MyRitualConsumer \
--rpc-url $RITUAL_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast
forge create src/MyRitualConsumer.sol:MyRitualConsumer \
--rpc-url $RITUAL_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--constructor-args 0x532F0dF0896F353d8C3DD8cc134e8129DA2a3948 100
forge create src/MyRitualConsumer.sol:MyRitualConsumer \
--rpc-url $RITUAL_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--verify \
--verifier custom \
--verifier-url "$RITUAL_VERIFIER_URL" \
--verifier-api-key unused
Warning: Without --broadcast, forge create only simulates the deployment — the contract will NOT actually be deployed on-chain. Always include --broadcast for real deployments.
Verification note: Use --verifier custom with the chain's verification service URL. Do NOT use Sourcify (chain 1979 is not registered). Do NOT point at the scanner UI hostname — use the RPC domain's /api/verify/ path.
Contract Verification
Ritual Chain (1979) uses a custom verification service accessible via the RPC domain. Sourcify does not support chain 1979. The scanner UI does not expose Etherscan/Blockscout-compatible API endpoints.
Verify an Already-Deployed Contract
forge verify-contract \
--chain 1979 \
--watch \
--verifier custom \
--verifier-url "$RITUAL_VERIFIER_URL" \
--verifier-api-key unused \
<CONTRACT_ADDRESS> \
src/MyContract.sol:MyContract
Expected output sequence:
Submitting verification for [src/MyContract.sol:MyContract] 0x...
Response: OK / GUID: <uuid>
- Status polling:
Pending in queue (may take ~15s)
Details: Pass - Verified
Contract successfully verified
Deploy + Verify One-Liner
For contracts without constructor args:
forge create \
--broadcast \
--verify \
--verifier custom \
--verifier-url "$RITUAL_VERIFIER_URL" \
--verifier-api-key unused \
--rpc-url $RITUAL_RPC_URL \
--private-key $PRIVATE_KEY \
src/MyContract.sol:MyContract
For contracts with constructor args, prefer two-step: deploy first with forge create --broadcast, then verify separately with forge verify-contract. The combined one-liner can be brittle with complex constructor arg types.
Verification Troubleshooting
| Symptom | Cause | Fix |
|---|
unsupported_chain | Used Sourcify (default verifier) | Add --verifier custom --verifier-url "$RITUAL_VERIFIER_URL" |
Bytecode does not match | Compiler/optimizer/EVM version mismatch | Check foundry.toml matches the settings used at deploy time |
Pending in queue for >2 min | Verifier service overloaded or stalled | Retry; check service health |
| HTML 404 response | Pointed verifier at scanner UI URL instead of RPC domain | Use https://rpc.ritualfoundation.org/api/verify not the scanner hostname |
Foundry Project Initialization
forge init my-ritual-contracts
cd my-ritual-contracts
forge install OpenZeppelin/openzeppelin-contracts
echo '@openzeppelin/=lib/openzeppelin-contracts/' >> remappings.txt
Contract Deployment with Hardhat
hardhat.config.ts
import { HardhatUserConfig } from 'hardhat/config';
import '@nomicfoundation/hardhat-toolbox';
import 'dotenv/config';
const config: HardhatUserConfig = {
solidity: {
version: '0.8.20',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
ritual: {
url: process.env.RITUAL_RPC_URL || 'https://rpc.ritualfoundation.org',
chainId: 1979,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
};
export default config;
Hardhat Deploy Script
import { ethers } from 'hardhat';
async function main() {
const [deployer] = await ethers.getSigners();
console.log('Deploying with:', deployer.address);
const balance = await ethers.provider.getBalance(deployer.address);
console.log('Balance:', ethers.formatEther(balance), 'RITUAL');
const Consumer = await ethers.getContractFactory('MyRitualConsumer');
const consumer = await Consumer.deploy();
await consumer.waitForDeployment();
console.log('MyRitualConsumer deployed to:', await consumer.getAddress());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Deploy Command
npx hardhat run scripts/deploy.ts --network ritual
System Contract Addresses
Core System Contracts
| Contract | Address |
|---|
| RitualWallet | 0x532F0dF0896F353d8C3DD8cc134e8129DA2a3948 |
| AsyncJobTracker | 0xC069FFCa0389f44eCA2C626e55491b0ab045AEF5 |
| TEEServiceRegistry | 0x9644e8562cE0Fe12b4deeC4163c064A8862Bf47F |
| Scheduler | 0x56e776BAE2DD60664b69Bd5F865F1180ffB7D58B |
| SecretsAccessControl | 0xf9BF1BC8A3e79B9EBeD0fa2Db70D0513fecE32FD |
Agent Factory Contracts
| Contract | Address |
|---|
| SovereignAgentFactory | 0x9dC4C054e53bCc4Ce0A0Ff09E890A7a8e817f304 |
| PersistentAgentFactory | 0xD4AA9D55215dc8149Af57605e70921Ea16b73591 |
Verify before launch:
cast code "0x9dC4C054e53bCc4Ce0A0Ff09E890A7a8e817f304" --rpc-url "$RITUAL_RPC_URL"
cast code "0xD4AA9D55215dc8149Af57605e70921Ea16b73591" --rpc-url "$RITUAL_RPC_URL"
If either returns 0x, your deployment config is wrong for that RPC.
Precompile Addresses (Async)
| Precompile | Short | Full Address |
|---|
| HTTP | 0x0801 | 0x0000000000000000000000000000000000000801 |
| LLM | 0x0802 | 0x0000000000000000000000000000000000000802 |
| Long HTTP | 0x0805 | 0x0000000000000000000000000000000000000805 |
| ZK | 0x0806 | 0x0000000000000000000000000000000000000806 |
| Image | 0x0818 | 0x0000000000000000000000000000000000000818 |
| Audio | 0x0819 | 0x0000000000000000000000000000000000000819 |
| Video | 0x081A | 0x000000000000000000000000000000000000081A |
Precompile Addresses (Native/Sync)
| Precompile | Short | Full Address |
|---|
| ONNX | 0x0800 | 0x0000000000000000000000000000000000000800 |
| JQ | 0x0803 | 0x0000000000000000000000000000000000000803 |
| Ed25519 | 0x0009 | 0x0000000000000000000000000000000000000009 |
| SECP256R1 | 0x0100 | 0x0000000000000000000000000000000000000100 |
| Nitro | 0x0104 | 0x0000000000000000000000000000000000000104 |
External Contracts
| Contract | Address |
|---|
| Multicall3 | 0x5577Ea679673Ec7508E9524100a188E7600202a3 |
| WETH | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
| USDC | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| Uniswap V3 Router | 0xE592427A0AEce92De3Edee1F18E0157C05861564 |
| Uniswap V3 Factory | 0x1F98431c8aD98523631AE4a59f267346ea31F984 |
Addresses as TypeScript Constants
Use these inline constants in your TypeScript code:
const PRECOMPILES = {
HTTP_CALL: '0x0000000000000000000000000000000000000801',
LLM: '0x0000000000000000000000000000000000000802',
LONG_RUNNING_HTTP: '0x0000000000000000000000000000000000000805',
ZK_TWO_PHASE: '0x0000000000000000000000000000000000000806',
IMAGE_CALL: '0x0000000000000000000000000000000000000818',
AUDIO_CALL: '0x0000000000000000000000000000000000000819',
VIDEO_CALL: '0x000000000000000000000000000000000000081A',
ONNX: '0x0000000000000000000000000000000000000800',
JQ: '0x0000000000000000000000000000000000000803',
} as const;
const SYSTEM_CONTRACTS = {
RITUAL_WALLET: '0x532F0dF0896F353d8C3DD8cc134e8129DA2a3948',
ASYNC_JOB_TRACKER: '0xC069FFCa0389f44eCA2C626e55491b0ab045AEF5',
TEE_SERVICE_REGISTRY: '0x9644e8562cE0Fe12b4deeC4163c064A8862Bf47F',
SCHEDULER: '0x56e776BAE2DD60664b69Bd5F865F1180ffB7D58B',
SECRETS_ACCESS_CONTROL: '0xf9BF1BC8A3e79B9EBeD0fa2Db70D0513fecE32FD',
} as const;
const AGENT_FACTORIES = {
SOVEREIGN_FACTORY: '0x9dC4C054e53bCc4Ce0A0Ff09E890A7a8e817f304',
PERSISTENT_FACTORY: '0xD4AA9D55215dc8149Af57605e70921Ea16b73591',
} as const;
Solidity Address Constants
For use in smart contracts:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
library RitualAddresses {
// Precompiles (async)
address constant HTTP_PRECOMPILE = address(0x0801);
address constant LLM_PRECOMPILE = address(0x0802);
address constant LONG_HTTP_PRECOMPILE = address(0x0805);
address constant ZK_PRECOMPILE = address(0x0806);
address constant IMAGE_PRECOMPILE = address(0x0818);
address constant AUDIO_PRECOMPILE = address(0x0819);
address constant VIDEO_PRECOMPILE = address(0x081A);
// Precompiles (native)
address constant ONNX_PRECOMPILE = address(0x0800);
address constant JQ_PRECOMPILE = address(0x0803);
address constant ED25519_PRECOMPILE = address(0x0009);
address constant SECP256R1_PRECOMPILE = address(0x0100);
address constant NITRO_PRECOMPILE = address(0x0104);
// System contracts
address constant RITUAL_WALLET = 0x532F0dF0896F353d8C3DD8cc134e8129DA2a3948;
address constant ASYNC_JOB_TRACKER = 0xC069FFCa0389f44eCA2C626e55491b0ab045AEF5;
address constant TEE_SERVICE_REGISTRY = 0x9644e8562cE0Fe12b4deeC4163c064A8862Bf47F;
address constant SCHEDULER = 0x56e776BAE2DD60664b69Bd5F865F1180ffB7D58B;
address constant SECRETS_ACCESS_CONTROL = 0xf9BF1BC8A3e79B9EBeD0fa2Db70D0513fecE32FD;
}
Environment Configuration
.env Template
RITUAL_RPC_URL=https://rpc.ritualfoundation.org
RITUAL_WS_URL=wss://rpc.ritualfoundation.org/ws
PRIVATE_KEY=0x...your_deployer_private_key...
RITUAL_VERIFIER_URL=https://rpc.ritualfoundation.org/api/verify
NEXT_PUBLIC_RITUAL_RPC_URL=https://rpc.ritualfoundation.org
NEXT_PUBLIC_WC_PROJECT_ID=your_walletconnect_project_id
NEXT_PUBLIC_CONSUMER_CONTRACT=0x...your_deployed_contract...
SOVEREIGN_FACTORY_ADDRESS=0x9dC4C054e53bCc4Ce0A0Ff09E890A7a8e817f304
PERSISTENT_FACTORY_ADDRESS=0xD4AA9D55215dc8149Af57605e70921Ea16b73591
Agent Factory Preflight
cast code "$SOVEREIGN_FACTORY_ADDRESS" --rpc-url "$RITUAL_RPC_URL"
cast code "$PERSISTENT_FACTORY_ADDRESS" --rpc-url "$RITUAL_RPC_URL"
cast call "$SOVEREIGN_FACTORY_ADDRESS" "scheduler()(address)" --rpc-url "$RITUAL_RPC_URL"
cast call "$SOVEREIGN_FACTORY_ADDRESS" "ritualWallet()(address)" --rpc-url "$RITUAL_RPC_URL"
cast call "$PERSISTENT_FACTORY_ADDRESS" "scheduler()(address)" --rpc-url "$RITUAL_RPC_URL"
cast call "$PERSISTENT_FACTORY_ADDRESS" "ritualWallet()(address)" --rpc-url "$RITUAL_RPC_URL"
Encrypted Secrets (dKMS) Configuration
Encrypted secrets require the dKMS service to be enabled and registered:
DKMS_ENABLED=true
TEE_SERVICE_REGISTRY_CONTRACT_ADDRESS=0x...
The executor discovers dKMS endpoints dynamically from TEEServiceRegistry. If DKMS_ENABLED is false (the default), encrypted secret operations will time out silently.
.gitignore Entries
# Environment
.env
.env.local
.env.*.local
# Foundry
out/
cache/
broadcast/
# Hardhat
artifacts/
cache/
typechain-types/
# Dependencies
node_modules/
TypeScript Environment Typing
declare namespace NodeJS {
interface ProcessEnv {
RITUAL_RPC_URL: string;
RITUAL_WS_URL: string;
PRIVATE_KEY: `0x${string}`;
RITUAL_VERIFIER_URL?: string;
NEXT_PUBLIC_RITUAL_RPC_URL: string;
NEXT_PUBLIC_WC_PROJECT_ID: string;
NEXT_PUBLIC_CONSUMER_CONTRACT?: `0x${string}`;
SOVEREIGN_FACTORY_ADDRESS?: `0x${string}`;
PERSISTENT_FACTORY_ADDRESS?: `0x${string}`;
}
}
Testnet Faucet
Web UI
Navigate to https://faucet.ritualfoundation.org, connect your wallet or paste your address, and request testnet RITUAL.
Programmatic (REST API)
For headless/SSH environments, use the faucet API directly:
curl https://faucet.ritualfoundation.org/api/info
curl -X POST https://faucet.ritualfoundation.org/api/claim \
-H "Content-Type: application/json" \
-d '{"address": "0xYourAddressHere"}'
Rate-limited per address. For larger amounts needed for load testing, reach out via Ritual Discord.
For private/internal testnet deployments, the faucet runs on a deployment-specific endpoint. Consult your chain-deployment-infra configuration for the correct faucet service URL.
Development Workflow
Recommended Project Structure
my-ritual-dapp/
├── contracts/ # Foundry project
│ ├── src/
│ │ └── MyConsumer.sol
│ ├── script/
│ │ └── Deploy.s.sol
│ ├── test/
│ │ └── MyConsumer.t.sol
│ ├── foundry.toml
│ └── .env
├── frontend/ # Next.js app
│ ├── app/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── providers.tsx
│ ├── lib/
│ │ ├── wagmi.ts
│ │ ├── ritual.ts # viem client setup
│ │ └── addresses.ts # Deployed contract addresses
│ ├── package.json
│ └── .env.local
├── backend/ # Optional API/indexer
│ ├── src/
│ │ └── index.ts
│ └── package.json
└── README.md
viem Client Setup for Frontend
import { createPublicClient, createWalletClient, http, defineChain } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
const ritualChain = defineChain({
id: 1979,
name: 'Ritual',
nativeCurrency: { name: 'RITUAL', symbol: 'RITUAL', decimals: 18 },
rpcUrls: {
default: {
http: [process.env.NEXT_PUBLIC_RITUAL_RPC_URL || 'https://rpc.ritualfoundation.org'],
},
},
blockExplorers: {
default: { name: 'Ritual Explorer', url: 'https://explorer.ritualfoundation.org' },
},
});
let publicClientInstance: ReturnType<typeof createPublicClient> | null = null;
export function getPublicClient() {
if (!publicClientInstance) {
publicClientInstance = createPublicClient({ chain: ritualChain, transport: http() });
}
return publicClientInstance;
}
export function getSignerClients(privateKey: `0x${string}`) {
const account = privateKeyToAccount(privateKey);
return {
publicClient: createPublicClient({ chain: ritualChain, transport: http() }),
walletClient: createWalletClient({ account, chain: ritualChain, transport: http() }),
};
}
Deploying and Wiring Contracts
Step-by-step deployment workflow:
cd contracts
forge build
forge test -vvv
forge script script/Deploy.s.sol:DeployScript \
--rpc-url $RITUAL_RPC_URL \
--broadcast \
-vvvv
forge verify-contract \
--chain 1979 \
--watch \
--verifier custom \
--verifier-url "$RITUAL_VERIFIER_URL" \
--verifier-api-key unused \
0x1234... \
src/MyRitualConsumer.sol:MyRitualConsumer
echo 'NEXT_PUBLIC_CONSUMER_CONTRACT=0x1234...' >> ../frontend/.env.local
RPC Proxy (for Restricted Environments)
When the RPC endpoint is not directly reachable from the browser (internal testnet, IP-restricted, CORS issues), proxy RPC calls through your Next.js API route:
import { NextResponse } from 'next/server';
const RPC_URL = process.env.RITUAL_RPC_URL || 'https://rpc.ritualfoundation.org';
export async function POST(req: Request) {
const body = await req.text();
const resp = await fetch(RPC_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
});
return new NextResponse(await resp.text(), {
headers: { 'Content-Type': 'application/json' },
});
}
Then configure your wagmi/viem transport to use /api/rpc instead of the direct RPC URL:
const ritualChain = defineChain({
id: 1979,
name: 'Ritual',
nativeCurrency: { name: 'RITUAL', symbol: 'RITUAL', decimals: 18 },
rpcUrls: {
default: { http: ['/api/rpc'] },
},
});
Post-Deployment Checklist
After deploying your dApp contracts, verify these items:
Contract Source Verification
RitualWallet Setup
import { formatEther } from 'viem';
const RITUAL_WALLET = '0x532F0dF0896F353d8C3DD8cc134e8129DA2a3948' as const;
const RITUAL_WALLET_ABI = [
{ inputs: [{ name: 'user', type: 'address' }], name: 'balanceOf', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function' },
] as const;
const balance = await publicClient.readContract({
address: RITUAL_WALLET,
abi: RITUAL_WALLET_ABI,
functionName: 'balanceOf',
args: [deployerAddress],
});
console.log('RitualWallet balance:', formatEther(balance), 'RITUAL');
Executor Availability
const TEE_SERVICE_REGISTRY = '0x9644e8562cE0Fe12b4deeC4163c064A8862Bf47F' as const;
const TEE_SERVICE_REGISTRY_ABI = [
{
inputs: [{ name: 'capability', type: 'uint8' }, { name: 'checkValidity', type: 'bool' }],
name: 'getServicesByCapability',
outputs: [{
type: 'tuple[]',
components: [
{ name: 'node', type: 'tuple', components: [
{ name: 'paymentAddress', type: 'address' },
{ name: 'teeAddress', type: 'address' },
{ name: 'teeType', type: 'uint8' },
{ name: 'publicKey', type: 'bytes' },
{ name: 'endpoint', type: 'string' },
{ name: 'certPubKeyHash', type: 'bytes32' },
{ name: 'capability', type: 'uint8' },
]},
{ name: 'isValid', type: 'bool' },
{ name: 'workloadId', type: 'bytes32' },
],
}],
stateMutability: 'view',
type: 'function',
},
] as const;
const HTTP_CALL_CAPABILITY = 0;
const services = await publicClient.readContract({
address: TEE_SERVICE_REGISTRY,
abi: TEE_SERVICE_REGISTRY_ABI,
functionName: 'getServicesByCapability',
args: [HTTP_CALL_CAPABILITY, true],
});
const validServices = services.filter((svc) => svc.isValid);
console.log(`Found ${services.length} HTTP services (${validServices.length} valid)`);
Contract Configuration
Frontend Integration
Monitoring
Common Issues and Solutions
Hardcoded Contract Addresses Cause Silent Failures After Redeployment
Symptom: After redeploying contracts to a different chain or after a fresh deployment, the frontend shows empty state — no markets, no data, no errors. Contract calls silently revert because they target the old address which either does not exist or has different bytecode on the new chain.
Cause: Contract addresses were hardcoded directly in source files (e.g., const FACTORY = '0xabc...'). After redeployment, the address changes but the source code still points to the old one.
Solution: Always read deployed contract addresses from environment variables, with an optional fallback for local development:
import { type Address } from 'viem';
export const FACTORY_ADDRESS = (
process.env.NEXT_PUBLIC_FACTORY_ADDRESS ?? '0x0000000000000000000000000000000000000000'
) as Address;
export const MARKET_ADDRESS = (
process.env.NEXT_PUBLIC_MARKET_ADDRESS ?? '0x0000000000000000000000000000000000000000'
) as Address;
export const CONSUMER_ADDRESS = (
process.env.NEXT_PUBLIC_CONSUMER_CONTRACT ?? '0x0000000000000000000000000000000000000000'
) as Address;
Then in .env.local (frontend) or .env (backend):
NEXT_PUBLIC_FACTORY_ADDRESS=0x1234...actual_deployed_address
NEXT_PUBLIC_MARKET_ADDRESS=0x5678...actual_deployed_address
NEXT_PUBLIC_CONSUMER_CONTRACT=0xabcd...actual_deployed_address
Rules:
- System contracts (RitualWallet, AsyncJobTracker, precompiles) have fixed addresses across all Ritual Chain deployments — these CAN be hardcoded as constants
- Your deployed contracts (factories, markets, consumers) get new addresses on each deployment — these MUST come from environment variables
- After redeployment, update
.env.local with the new addresses from the deploy script output and restart the frontend
- Add a startup check to verify critical addresses are set:
if (!process.env.NEXT_PUBLIC_FACTORY_ADDRESS) {
console.warn(
'NEXT_PUBLIC_FACTORY_ADDRESS not set — contract reads will fail. ' +
'Run the deploy script and update .env.local with the new address.'
);
}
"No executors found" Error
The TEEServiceRegistry has no services registered for the requested capability.
const HTTP_CALL_CAPABILITY = 0;
const services = await publicClient.readContract({
address: '0x9644e8562cE0Fe12b4deeC4163c064A8862Bf47F',
abi: TEE_SERVICE_REGISTRY_ABI,
functionName: 'getServicesByCapability',
args: [HTTP_CALL_CAPABILITY, true],
});
if (services.length === 0) {
console.error('No HTTP services available. Check:');
console.error('1. Are services registered on this network?');
console.error('2. Is the capability enum correct?');
}
Insufficient RitualWallet Balance
Async calls fail if your RitualWallet deposit is too low or not locked.
import { parseEther } from 'viem';
const RITUAL_WALLET = '0x532F0dF0896F353d8C3DD8cc134e8129DA2a3948' as const;
const DEPOSIT_ABI = [
{ inputs: [{ name: 'lockDuration', type: 'uint256' }], name: 'deposit', outputs: [], stateMutability: 'payable', type: 'function' },
] as const;
const hash = await walletClient.writeContract({
address: RITUAL_WALLET,
abi: DEPOSIT_ABI,
functionName: 'deposit',
args: [5000n],
value: parseEther('5'),
});
await publicClient.waitForTransactionReceipt({ hash });
Wrong Chain in Wallet
Users connecting from MetaMask may be on the wrong network.
import { useChainId, useSwitchChain } from 'wagmi';
function ChainGuard({ children }: { children: React.ReactNode }) {
const chainId = useChainId();
const { switchChain } = useSwitchChain();
if (chainId !== 1979) {
return (
<button onClick={() => switchChain({ chainId: 1979 })}>
Switch to Ritual Chain
</button>
);
}
return <>{children}</>;
}
Gas Estimation Failures
Async precompile calls cannot be gas-estimated with standard estimateGas. Use explicit gas limits:
const HTTP_PRECOMPILE = '0x0000000000000000000000000000000000000801' as const;
const hash = await walletClient.sendTransaction({
to: HTTP_PRECOMPILE,
data: encoded,
gas: 2_000_000n,
maxFeePerGas: 20_000_000_000n,
maxPriorityFeePerGas: 2_000_000_000n,
});
Package.json Dependencies
Frontend (Next.js + viem)
{
"dependencies": {
"@rainbow-me/rainbowkit": "^2.0.0",
"@tanstack/react-query": "^5.0.0",
"eciesjs": "^0.4.0",
"next": "^14.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"viem": "^2.0.0",
"wagmi": "^2.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.0.0",
"typescript": "^5.0.0"
}
}
Backend (Node.js + viem)
{
"dependencies": {
"viem": "^2.0.0",
"eciesjs": "^0.4.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"tsx": "^4.0.0"
}
}
Related Skills
ritual-dapp-overview — Architecture overview, precompile categories, async lifecycle
ritual-dapp-contracts — Consumer contract patterns and Solidity templates
ritual-dapp-frontend — React/Next.js frontend with async state machine
ritual-dapp-wallet — RitualWallet deposit, locking, and fee management
ritual-dapp-testing — Testing and debugging Ritual dApps