本文档提供了一个在SEDA网络上创建完整Oracle程序的详细指南,该程序从USGS API获取实时地震数据。内容涵盖智能合约开发和交互式前端界面的构建,包括环境配置、项目结构、Rust Oracle程序开发、智能合约创建、前端构建、测试和部署等步骤,最终实现一个完整的地震数据预言机。
本指南将引导你创建一个完整的 SEDA 网络上的 Oracle Program,该程序从 USGS API 获取实时地震数据,包括智能合约开发和交互式前端界面。
开始之前,请确保已安装以下工具:
安装 Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
安装 WebAssembly 优化器:
brew install binaryen
安装用于智能合约开发的 Hardhat:
npm install --save-dev hardhat
你需要在以下平台上拥有帐户:
git clone https://github.com/sedaprotocol/seda-starter-kit.git
cd seda-starter-kit
seda-starter-kit/
├── src/ # Rust Oracle Program 代码
├── integrations/evm-hardhat/ # 智能合约和部署脚本
└── frontend/ # Next.js 前端 (你将创建它)
替换 src/execution_phase.rs
的内容:
use anyhow::Result;
use seda_sdk_rs::{elog, http_fetch, log, Process};
use serde::{Deserialize, Serialize};
##[derive(Serialize, Deserialize, Debug)]
pub struct EarthquakeInfo {
pub magnitude: f32,
pub location: String,
pub time: i64, // milliseconds since epoch
}#[derive(Deserialize, Debug)]
struct GeoJsonResponse {
features: Vec<Feature>,
}#[derive(Deserialize, Debug)]
struct Feature {
properties: Properties,
}#[derive(Deserialize, Debug)]
struct Properties {
mag: Option<f32>,
place: Option<String>,
time: Option<i64>,
}pub fn execution_phase() -> Result<()> {
// 从 USGS 获取最近的地震事件
let url = "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&limit=1";
log!("Fetching most recent earthquake from: {}", url);
let response = http_fetch(url, None); if !response.is_ok() {
elog!(
"HTTP Response was rejected: {} - {}",
response.status,
String::from_utf8(response.bytes)?
);
Process::error("Error while fetching earthquake data".as_bytes());
return Ok(());
} let geojson: GeoJsonResponse = serde_json::from_slice(&response.bytes)?;
let feature = geojson
.features
.first()
.ok_or_else(|| anyhow::anyhow!("No earthquake data found"))?;
let props = &feature.properties; let magnitude = props
.mag
.ok_or_else(|| anyhow::anyhow!("No magnitude in data"))?;
let location = props
.place
.clone()
.unwrap_or_else(|| "Unknown location".to_string());
let time = props
.time
.ok_or_else(|| anyhow::anyhow!("No time in data"))?; log!(
"Fetched earthquake: magnitude={}, location={}, time={}",
magnitude,
location,
time
); let eq_info = EarthquakeInfo {
magnitude,
location,
time,
}; // 将 struct 序列化为 JSON 字节
let result_bytes = serde_json::to_vec(&eq_info)?;
log!(
"Reporting earthquake info as bytes: {} bytes",
result_bytes.len()
);
Process::success(&result_bytes);
Ok(())
}
替换 src/tally_phase.rs
的内容:
use crate::execution_phase::EarthquakeInfo;
use anyhow::Result;
use seda_sdk_rs::{elog, get_reveals, log, Process};
pub fn tally_phase() -> Result<()> {
// 从统计阶段检索共识揭示
let reveals = get_reveals()?;
let mut eqs: Vec<EarthquakeInfo> = Vec::new(); // 将每个揭示解析为 EarthquakeInfo 并存储在数组中
for reveal in reveals {
match serde_json::from_slice::<EarthquakeInfo>(&reveal.body.reveal) {
Ok(eq) => {
log!(
"Received earthquake: magnitude={}, location={}, time={}",
eq.magnitude,
eq.location,
eq.time
);
eqs.push(eq);
}
Err(_err) => {
elog!("Reveal body could not be parsed as EarthquakeInfo");
continue;
}
}
} if eqs.is_empty() {
Process::error("No consensus among revealed results".as_bytes());
return Ok(());
} // 按震级排序并选择中间条目
eqs.sort_by(|a, b| {
a.magnitude
.partial_cmp(&b.magnitude)
.unwrap_or(std::cmp::Ordering::Equal)
});
let middle = eqs.len() / 2;
let median_eq = if eqs.len() % 2 == 0 {
&eqs[middle - 1] // 对于偶数,选择较低的中位数
} else {
&eqs[middle]
}; // 报告成功结果,编码为 JSON 字节
let result_bytes = serde_json::to_vec(median_eq)?;
log!(
"Reporting median earthquake: magnitude={}, location={}, time={}",
median_eq.magnitude,
median_eq.location,
median_eq.time
);
Process::success(&result_bytes);
Ok(())
}
确保你的 Cargo.toml
文件包含所有必要的依赖项:
[package]
name = "seda-oracle"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"][dependencies]
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
seda-sdk-rs = "0.1.0"
将你的 Oracle 程序构建为 WebAssembly 二进制文件:
cargo build --target wasm32-wasip1 --release
优化 WASM 二进制文件 (SEDA 需要):
wasm-opt -O4 -o target/wasm32-wasip1/release/seda_starter_kit_opt.wasm target/wasm32-wasip1/release/seda_starter_kit.wasm
注意: 将
seda_starter_kit
替换为你实际的 crate 名称。
在你的项目根目录中创建一个 .env
文件:
SEDA_RPC_ENDPOINT=https://rpc.testnet.seda.xyz
SEDA_MNEMONIC=your_mnemonic_here
使用部署脚本部署你的 Oracle 程序:
bun run build
bun run deploy
重要提示: 保存部署后返回的 Oracle Program ID。将其添加到你的 .env
文件中:
ORACLE_PROGRAM_ID=your_oracle_program_id_here
使用示例数据请求测试你的 Oracle 程序:
bun run post-dr
导航到 hardhat 目录并设置环境变量:
cd integrations/evm-hardhat
在此目录中创建 .env
文件:
PRIVATE_KEY=your_private_key_here
ORACLE_PROGRAM_ID=your_oracle_program_id_here
BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
ETHERSCAN_API_KEY=your_etherscan_api_key
创建 contracts/EarthquakeFeed.sol
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {ISedaCore} from "@seda-protocol/evm/contracts/interfaces/ISedaCore.sol";
import {SedaDataTypes} from "@seda-protocol/evm/contracts/libraries/SedaDataTypes.sol";contract EarthquakeFeed {
/// @notice SedaCore 合约的实例
ISedaCore public immutable SEDA_CORE; /// @notice SEDA 网络上请求 WASM 二进制文件的 ID
bytes32 public immutable ORACLE_PROGRAM_ID; /// @notice 最近请求的 ID
bytes32 public requestId; /// @notice 尝试在任何请求传输之前获取结果时抛出
error RequestNotTransmitted(); /**
* @notice 使用 SEDA 网络参数设置合约
* @param _sedaCoreAddress SedaCore 合约的地址
* @param _oracleProgramId 用于处理请求的 WASM 二进制文件的 ID
*/
constructor(address _sedaCoreAddress, bytes32 _oracleProgramId) {
SEDA_CORE = ISedaCore(_sedaCoreAddress);
ORACLE_PROGRAM_ID = _oracleProgramId;
} /**
* @notice 在 SEDA 网络上创建新的地震数据请求
* @param requestFee 请求的费用
* @param resultFee 结果的费用
* @param batchFee 批处理的费用
* @return 创建的请求的 ID
*/
function transmit(uint256 requestFee, uint256 resultFee, uint256 batchFee) external payable returns (bytes32) {
SedaDataTypes.RequestInputs memory inputs = SedaDataTypes.RequestInputs(
ORACLE_PROGRAM_ID, // execProgramId (执行 WASM 二进制文件 ID)
bytes("earthquake-data"), // execInputs (执行 WASM 的输入)
20000000000000, // execGasLimit
ORACLE_PROGRAM_ID, // tallyProgramId (在本例中与 execProgramId 相同)
hex"00", // tallyInputs
20000000000000, // tallyGasLimit
1, // replicationFactor (所需 DR 执行器的数量)
hex"00", // consensusFilter (设置为 `None`)
2000, // gasPrice (每个 gas 单位的 SEDA 代币)
abi.encodePacked(block.number) // memo (其他公共信息)
); // 将 msg.value 作为费用传递给 SEDA Core
requestId = SEDA_CORE.postRequest{value: msg.value}(inputs, requestFee, resultFee, batchFee);
return requestId;
} /**
* @notice 检索最新的地震数据请求的结果
* @return 地震数据作为字节,如果没有达成共识,则返回空字节
*/
function latestAnswer() public view returns (bytes memory) {
if (requestId == bytes32(0)) revert RequestNotTransmitted(); SedaDataTypes.Result memory result = SEDA_CORE.getResult(requestId); if (result.consensus) {
return result.result;
} return "";
}
}
更新 hardhat.config.ts
:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";
dotenv.config();const config: HardhatUserConfig = {
solidity: {
version: "0.8.19",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
baseSepolia: {
url: process.env.BASE_SEPOLIA_RPC_URL || "https://sepolia.base.org",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
chainId: 84532,
},
},
etherscan: {
apiKey: {
baseSepolia: process.env.ETHERSCAN_API_KEY || "",
},
customChains: [\
{\
network: "baseSepolia",\
chainId: 84532,\
urls: {\
apiURL: "https://api-sepolia.basescan.org/api",\
browserURL: "https://sepolia.basescan.org",\
},\
},\
],
},
};export default config;
创建 tasks/deploy.ts
:
import { task } from "hardhat/config";
import { getSedaConfig } from "./utils";
task("deploy", "Deploy EarthquakeFeed contract")
.setAction(async (taskArgs, hre) => {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying contracts with account:", deployer.address); // 获取网络的 SEDA 配置
const sedaConfig = getSedaConfig(hre.network.name);
const EarthquakeFeed = await hre.ethers.getContractFactory("EarthquakeFeed");
const earthquakeFeed = await EarthquakeFeed.deploy(
sedaConfig.coreAddress,
sedaConfig.oracleProgramId
); await earthquakeFeed.waitForDeployment();
const address = await earthquakeFeed.getAddress(); console.log("EarthquakeFeed deployed to:", address);
console.log("SEDA Core Address:", sedaConfig.coreAddress);
console.log("Oracle Program ID:", sedaConfig.oracleProgramId);
});
安装依赖项并部署:
npm install
npx hardhat deploy --network baseSepolia
重要提示: 保存已部署的合约地址,以便在前端中使用。
创建并导航到前端目录:
mkdir frontend
cd frontend
npm init -y
npm install next react react-dom ethers
npm install -D @types/node @types/react typescript
创建 src/lib/contract.ts
:
import { ethers } from 'ethers';
// 合约 ABI - 仅包含你所需的方法
const CONTRACT_ABI = [\
"function latestAnswer() external view returns (bytes memory)",\
"function transmit(uint256 requestFee, uint256 resultFee, uint256 batchFee) external payable returns (bytes32)",\
"function requestId() external view returns (bytes32)",\
"function SEDA_CORE() external view returns (address)",\
"function ORACLE_PROGRAM_ID() external view returns (bytes32)"\
];export interface EarthquakeData {
magnitude: number;
location: string;
time: number;
}export function getContractInstance(provider: ethers.Provider) {
const contractAddress = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS;
if (!contractAddress) {
throw new Error('Contract address not configured');
}
return new ethers.Contract(contractAddress, CONTRACT_ABI, provider);
}export function parseEarthquakeData(data: string): EarthquakeData | null {
try {
// 删除 0x 前缀(如果存在)
const cleanData = data.startsWith('0x') ? data.slice(2) : data;
const jsonString = Buffer.from(cleanData, 'hex').toString('utf-8');
return JSON.parse(jsonString);
} catch (error) {
console.error('Failed to parse earthquake data:', error);
return null;
}
}
创建 .env.local
:
NEXT_PUBLIC_CONTRACT_ADDRESS=your_deployed_contract_address
NEXT_PUBLIC_CHAIN_ID=84532
使用你的仪表板界面创建 src/components/EarthquakeDashboard.tsx
,该界面处理:
更新 src/app/page.tsx
:
import EarthquakeDashboard from '@/components/EarthquakeDashboard';
export default function Home() {
return <EarthquakeDashboard />;
}
在本地运行你的前端应用程序:
cd frontend
npm run dev
在浏览器中打开 http://localhost:3000
以查看你的地震仪表板。
部署到你喜欢的托管服务(Vercel、Netlify 等),确保你配置了正确的环境变量。
Ethers 导入错误:
new ethers.BrowserProvider(window.ethereum)
new ethers.providers.JsonRpcProvider()
合约部署问题:
前端构建错误:
Oracle 程序上传失败:
对于 Hardhat ( integrations/evm-hardhat/.env
):
PRIVATE_KEY
: 你的钱包私钥ORACLE_PROGRAM_ID
: 从 SEDA oracle 上传返回的 IDBASE_SEPOLIA_RPC_URL
: Base Sepolia 测试网的 RPC 端点ETHERSCAN_API_KEY
: 你的 Etherscan API 密钥对于前端 ( frontend/.env.local
):
NEXT_PUBLIC_CONTRACT_ADDRESS
: 你部署的合约地址NEXT_PUBLIC_CHAIN_ID
: Base Sepolia 的链 ID (84532)该系统通过集成多个组件,成功提供了一个实时地震监测解决方案。 它从 USGS API 中提取实时数据,通过去中心化的 SEDA 网络对其进行处理,通过智能合约将验证后的结果存储在链上,并通过交互式 Web 界面呈现数据。 这些部分共同构成了一个完整且透明的地震保险 oracle。
- 原文链接: blog.blockmagnates.com/h...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!