Testnet Guide

Current limitation: this is a V5 deployment and smoke-test runbook, not a currently executable public testnet flow. Use it only after a verified V5 Sepolia address and matching deployment record are published and configured in oak-chain-infra. Local mock mode remains the executable evaluation path.

Get Sepolia ETH first, configure the environment, then run one settlement plus write proposal cycle.

Sepolia Testnet

Data Table
Property
Value
Network
Sepolia
Chain ID
11155111
Currency
SepoliaETH (test)
Block Time
~12 seconds
Epoch Duration
~6.4 minutes

Smart Contract

ValidatorPaymentV5

The intended V5 deployment record must provide:

Data Table
Network
Address
Source of Truth
Sepolia
<SEPOLIA_V5_ADDRESS>
Not yet recorded or verified
Mainnet
TBD
Configuration stub only

The canonical address is set in OAK_BLOCKCHAIN_CONTRACT_ADDRESS within sepolia.env. Validator processes load it at startup.

Contract Surface

// Settlement
function settleWrite(bytes32 proposalId, uint32 capabilityFlags) external payable;
function settleDelete(bytes32 proposalId, uint32 capabilityFlags) external payable;

// Quote (view)
function quotePriceETH(ProposalKind proposalKind, uint32 capabilityFlags) external view returns (uint256);

// Capability flags (bitfield)
uint32 constant CAPABILITY_NONE = 0;
uint32 constant CAPABILITY_VALIDATOR_HOSTED_BINARY = 1 << 0;

// Load-bearing event watched by validators
event ProposalSettledV5(
    bytes32 indexed proposalId,
    address indexed payer,
    ProposalKind proposalKind,
    uint256 amount,
    uint32 capabilityFlags
);

For the full surface (treasury management, owner controls, gas baseline), see Smart Contract.

Get Test ETH

Faucets

Data Table
Faucet
URL
Amount
Alchemy
sepoliafaucet.com
0.5 ETH/day
Infura
infura.io/faucet/sepolia
0.5 ETH/day
QuickNode
faucet.quicknode.com/ethereum/sepolia
0.1 ETH
Google Cloud
cloud.google.com/web3/faucet
0.05 ETH

Steps

  1. Install MetaMask
  2. Add Sepolia network (or select from network list)
  3. Copy your wallet address
  4. Visit faucet and request test ETH
  5. Wait for transaction confirmation

Each settlement costs the write price (see Smart Contract) plus gas, so one faucet drip covers many test writes.

Configure Validator

Environment Variables

# The validator reads its Sepolia RPC endpoint from OAK_BLOCKCHAIN_RPC_URL
export OAK_BLOCKCHAIN_RPC_URL=https://sepolia.infura.io/v3/your-infura-project-id

# Or set only the Infura key; sepolia.env builds OAK_BLOCKCHAIN_RPC_URL from it
export INFURA_API_KEY=your-infura-project-id

modes/sepolia/validators/lifecycle/start-cluster.sh loads modes/sepolia/validators/config/sepolia.env, which sets OAK_BLOCKCHAIN_MODE=sepolia and OAK_BLOCKCHAIN_CONTRACT_ADDRESS. Check that this address is the verified V5 deployment before you run. INFURA_API_KEY is read only by the infra scripts, not by the validator.

Get Infura API Key

  1. Go to infura.io
  2. Create free account
  3. Create new project
  4. Copy Project ID (this is your API key)

Run the Sepolia Workflow

mkdir oak-chain-workspace
cd oak-chain-workspace

git clone https://github.com/somarc/oak-chain-infra.git
git clone https://github.com/somarc/jackrabbit-oak.git

cd oak-chain-infra
./modes/sepolia/validators/lifecycle/start-cluster.sh --build --fresh

Make a Test Settlement

Using ethers.js

import { ethers } from 'ethers';

const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

// From modes/sepolia/validators/config/sepolia.env
const contractAddress = '<SEPOLIA_V5_ADDRESS>';
const abi = [
  'function settleWrite(bytes32 proposalId, uint32 capabilityFlags) payable',
  'event ProposalSettledV5(bytes32 indexed proposalId, address indexed payer, uint8 proposalKind, uint256 amount, uint32 capabilityFlags)',
];

const contract = new ethers.Contract(contractAddress, abi, signer);

const proposalId = ethers.keccak256(
  ethers.toUtf8Bytes(`proposal:${Date.now()}`)
);

// Standard write, no capability flags
const CAPABILITY_NONE = 0;
const PRICE = ethers.parseEther('0.001');

const tx = await contract.settleWrite(proposalId, CAPABILITY_NONE, {
  value: PRICE,
});

console.log('Settlement tx:', tx.hash);
const receipt = await tx.wait();
console.log('Settlement confirmed!');

Oak validators expect the transaction hash from the settleWrite call. Reuse the same proposalId when you call POST /v1/propose-write. Excess ETH is refunded automatically, so over-funding is safe.

Verify Settlement

Check Transaction

# Using curl + Infura
curl https://sepolia.infura.io/v3/${INFURA_API_KEY} \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_getTransactionReceipt",
    "params": ["0xYourTxHash"],
    "id": 1
  }'

Look for the ProposalSettledV5 event log in the receipt. In sepolia mode, the validator queues the write proposal and releases it to consensus only after that event is confirmed for the corresponding proposal id. Chain-backed payments are not yet validated.

Etherscan

After deployment, record the transaction and use an address-specific Sepolia Etherscan verification link. A placeholder is not a deployed contract.

Pricing (Sepolia)

Prices and gas costs are on Smart Contract. Use quotePriceETH(proposalKind, capabilityFlags) off-chain to compute the exact value before the call. A deployed testnet contract must be verified before you treat its prices as operational.

Complete Test Flow

async function testSepoliaWrite() {
  // 1. Connect wallet
  const accounts = await window.ethereum.request({
    method: 'eth_requestAccounts'
  });
  const wallet = accounts[0];

  // 2. Settle on-chain
  const proposalId = ethers.keccak256(
    ethers.toUtf8Bytes(`proposal:${Date.now()}`)
  );
  const settleTx = await contract.settleWrite(
    proposalId,
    0, // CAPABILITY_NONE
    { value: ethers.parseEther('0.001') }
  );

  // 3. Wait for confirmation
  console.log('Settlement tx:', settleTx.hash);
  await settleTx.wait();

  // 4. Sign content
  const content = { title: 'Sepolia Test!' };
  const message = JSON.stringify({ content, timestamp: Date.now() });

  const signature = await window.ethereum.request({
    method: 'personal_sign',
    params: [message, wallet],
  });

  // 5. Submit to validator
  const response = await fetch('http://localhost:8090/v1/propose-write', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      proposalId,
      walletAddress: wallet,
      message,
      ethereumTxHash: settleTx.hash,
      signature,
    }),
  });

  // 202 Accepted, state PENDING. Poll GET /v1/ops/operations/{proposalId} for COMMITTED.
  console.log('Write result:', await response.json());
}

Troubleshooting

"Settlement not found"

"Already paid"

The contract enforces replay protection. A given proposalId can be settled exactly once. Generate a fresh proposal id and retry.

"Insufficient funds"

"Invalid API key"

Next Steps