如何使用SEDA构建地震保险预言机

本文档提供了一个在SEDA网络上创建完整Oracle程序的详细指南,该程序从USGS API获取实时地震数据。内容涵盖智能合约开发和交互式前端界面的构建,包括环境配置、项目结构、Rust Oracle程序开发、智能合约创建、前端构建、测试和部署等步骤,最终实现一个完整的地震数据预言机。

本指南将引导你创建一个完整的 SEDA 网络上的 Oracle Program,该程序从 USGS API 获取实时地震数据,包括智能合约开发和交互式前端界面。

目录

  1. 准备工作和设置
  2. 项目结构
  3. 开发你的 Oracle 程序
  4. 构建和部署到 SEDA
  5. 创建智能合约
  6. 构建前端
  7. 测试和部署
  8. 故障排除

准备工作和设置

开始之前,请确保已安装以下工具:

  • Rust(最新稳定版本)
  • Node.js(18 或更高版本)
  • npmyarn 包管理器
  • Git 版本控制
  • wasm-opt (用于 WebAssembly 优化)

项目结构

步骤 1:安装开发环境

安装 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

步骤 2:设置所需的帐户

你需要在以下平台上拥有帐户:

  • 用于 Oracle Program 部署的 SEDA 网络帐户
  • 用于智能合约部署的 Base Sepolia 测试网钱包
  • 用于前端部署的 Vercel 帐户(可选)

步骤 3:克隆 SEDA Starter Kit

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 前端 (你将创建它)

开发你的 Oracle 程序

步骤 4:创建执行阶段逻辑

替换 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(())
}

步骤 5:创建统计阶段逻辑

替换 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(())
}

步骤 6:更新 Cargo 配置

确保你的 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"

构建和部署到 SEDA

步骤 7:编译 WASM 二进制文件

将你的 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 名称。

步骤 8:配置环境变量

在你的项目根目录中创建一个 .env 文件:

SEDA_RPC_ENDPOINT=https://rpc.testnet.seda.xyz
SEDA_MNEMONIC=your_mnemonic_here

步骤 9:部署到 SEDA 网络

使用部署脚本部署你的 Oracle 程序:

bun run build
bun run deploy

重要提示: 保存部署后返回的 Oracle Program ID。将其添加到你的 .env 文件中:

ORACLE_PROGRAM_ID=your_oracle_program_id_here

步骤 10:验证部署

使用示例数据请求测试你的 Oracle 程序:

bun run post-dr

创建智能合约

步骤 11:设置 Hardhat 环境

导航到 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

步骤 12:创建 Earthquake Feed 合约

创建 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 "";
    }
}

步骤 13:配置 Hardhat

更新 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;

步骤 14:创建部署脚本

创建 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);
  });

步骤 15:部署你的合约

安装依赖项并部署:

npm install
npx hardhat deploy --network baseSepolia

重要提示: 保存已部署的合约地址,以便在前端中使用。

步骤 16:设置前端项目

创建并导航到前端目录:

mkdir frontend
cd frontend
npm init -y
npm install next react react-dom ethers
npm install -D @types/node @types/react typescript

步骤 17:创建合约集成

创建 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;
  }
}

步骤 18:配置环境变量

创建 .env.local

NEXT_PUBLIC_CONTRACT_ADDRESS=your_deployed_contract_address
NEXT_PUBLIC_CHAIN_ID=84532

步骤 19:创建仪表板组件

使用你的仪表板界面创建 src/components/EarthquakeDashboard.tsx,该界面处理:

  • 钱包连接和状态
  • 从你的智能合约中提取地震数据
  • 从 SEDA oracle 请求新数据
  • 显示带有严重程度指示器的地震信息
  • 错误处理和加载状态

步骤 20:更新主页

更新 src/app/page.tsx

import EarthquakeDashboard from '@/components/EarthquakeDashboard';
export default function Home() {
  return <EarthquakeDashboard />;
}

测试和部署

步骤 21:本地测试

在本地运行你的前端应用程序:

cd frontend
npm run dev

在浏览器中打开 http://localhost:3000 以查看你的地震仪表板。

步骤 22:部署到生产环境

部署到你喜欢的托管服务(Vercel、Netlify 等),确保你配置了正确的环境变量。

常见问题及解决方案

Ethers 导入错误:

  • 使用 ethers v6 语法:new ethers.BrowserProvider(window.ethereum)
  • 避免使用 v5 语法:new ethers.providers.JsonRpcProvider()

合约部署问题:

  • 确保你的钱包中有足够的测试网 ETH
  • 验证 RPC URL 对于 Base Sepolia 来说是否正确
  • 检查网络配置是否与部署目标匹配
  • 确认私钥已在环境变量中正确设置

前端构建错误:

  • 删除未使用的变量声明
  • 修复 import 语句语法
  • 确保所有环境变量都已配置
  • 检查组件导出是否与导入匹配

Oracle 程序上传失败:

  • 验证是否应用了 wasm-opt 优化
  • 检查二进制文件是否针对正确的目标编译
  • 确保 Oracle Program ID 已正确保存

环境变量参考

对于 Hardhat ( integrations/evm-hardhat/.env):

  • PRIVATE_KEY: 你的钱包私钥
  • ORACLE_PROGRAM_ID: 从 SEDA oracle 上传返回的 ID
  • BASE_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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockmagnates
blockmagnates
The New Crypto Publication on The Block