oracle

模块说明

本文将介绍和使用Sui官方提供的 天气预言机weather-oracle 进行随机数的创建。

注:因目前官方提供的部署在主网上的天气预言机版本比较老,已经无法正常使用了,故本示例中将在测试网上部署一个简化版本来演示。期待官方后续进行下更新。

源码路径

方法图解

代码示例

以下同样是投骰子的示例,每次调用将通过天气预言机得到一个 1~6 的随机数 NFT。

module cookbook::weather_oracle {
    use sui::event;
    use simple_weather_oracle::simple_weather::{WeatherOracle};

    public struct Dice has key, store {
        id: UID,
        value: u32,
    }

    public struct DiceEvent has copy, drop {
        value: u32,
    }

    entry fun roll_dice(weather_oracle: &WeatherOracle, geoname_id: u32): u32 {
        let random_pressure_sz = 
            simple_weather_oracle::simple_weather::city_weather_oracle_pressure(weather_oracle, geoname_id);

        let result = random_pressure_sz % 6 + 1;
        result
    }

    entry fun roll_dice_nft(weather_oracle: &WeatherOracle, geoname_id: u32, ctx: &mut TxContext) {
        let value = roll_dice(weather_oracle, geoname_id);
        let dice = Dice {
            id: object::new(ctx),
            value,
        };

        event::emit(DiceEvent { value });

        transfer::transfer(dice, tx_context::sender(ctx));
    }
}

合约操作

前置准备

因官方的天气预言机已经无法正常使用,故个人在测试网部署一个简化版本,进行测试。待官方升级后,使用方式类似。

具体步骤如下:

(1)编写简化版weather-oracle合约

module oracle::simple_weather {
    use std::string::{Self, String};
    use sui::dynamic_object_field as dof;
    use sui::package;

    /// Define a capability for the admin of the oracle.
    public struct AdminCap has key, store { id: UID }

    /// // Define a one-time witness to create the `Publisher` of the oracle.
    public struct SIMPLE_WEATHER has drop {}

    // Define a struct for the weather oracle
    public struct WeatherOracle has key {
        id: UID,
        /// The address of the oracle.
        address: address,
        /// The name of the oracle.
        name: String,
        /// The description of the oracle.
        description: String,
    }

    // Define a struct for each city that the oracle covers
    public struct CityWeatherOracle has key, store {
        id: UID,
        geoname_id: u32, // The unique identifier of the city
        name: String, // The name of the city
        country: String, // The country of the city
        pressure: u32, // The atmospheric pressure in hPa
        // ... 
    }

    /// Module initializer. Uses One Time Witness to create Publisher and transfer it to sender.
    fun init(otw: SIMPLE_WEATHER, ctx: &mut TxContext) {
        package::claim_and_keep(otw, ctx); // Claim ownership of the one-time witness and keep it

        let cap = AdminCap { id: object::new(ctx) }; // Create a new admin capability object
        transfer::share_object(WeatherOracle {
            id: object::new(ctx),
            address: tx_context::sender(ctx),
            name: string::utf8(b"SuiMeteo"),
            description: string::utf8(b"A weather oracle for posting weather updates (temperature, pressure, humidity, visibility, wind metrics and cloud state) for major cities around the world. Currently the data is fetched from https://openweathermap.org. SuiMeteo provides the best available information, but it does not guarantee its accuracy, completeness, reliability, suitability, or availability. Use it at your own risk and discretion."),
        });
        transfer::public_transfer(cap, tx_context::sender(ctx)); // Transfer the admin capability to the sender.
    }

    // Public function for adding a new city to the oracle
    public fun add_city(
        _: &AdminCap, // The admin capability
        oracle: &mut WeatherOracle, // A mutable reference to the oracle object
        geoname_id: u32, // The unique identifier of the city
        name: String, // The name of the city
        country: String, // The country of the city
        ctx: &mut TxContext // A mutable reference to the transaction context
    ) {
        dof::add(&mut oracle.id, geoname_id, // Add a new dynamic object field to the oracle object with the geoname ID as the key and a new city weather oracle object as the value.
            CityWeatherOracle {
                id: object::new(ctx), // Assign a unique ID to the city weather oracle object 
                geoname_id, // Set the geoname ID of the city weather oracle object
                name,  // Set the name of the city weather oracle object
                country,  // Set the country of the city weather oracle object
                pressure: 0, // Initialize the pressure to be zero 
            }
        );
    }

    // Public function for removing an existing city from the oracle
    public fun remove_city(_: &AdminCap, oracle: &mut WeatherOracle, geoname_id: u32) {
        let CityWeatherOracle { id, geoname_id: _, name: _, country: _, pressure: _} = dof::remove(&mut oracle.id, geoname_id);
        object::delete(id);
    }

    // Public function for updating the weather conditions of a city
    public fun update(
        _: &AdminCap,
        oracle: &mut WeatherOracle,
        geoname_id: u32,
        pressure: u32,
    ) {
        let city_weather_oracle_mut = dof::borrow_mut<u32, CityWeatherOracle>(&mut oracle.id, geoname_id); // Borrow a mutable reference to the city weather oracle object with the geoname ID as the key
        city_weather_oracle_mut.pressure = pressure;
    }

    /// Returns the `pressure` of the `CityWeatherOracle` with the given `geoname_id`.
    public fun city_weather_oracle_pressure(
        weather_oracle: &WeatherOracle, 
        geoname_id: u32
    ): u32 {
        let city_weather_oracle = dof::borrow<u32, CityWeatherOracle>(&weather_oracle.id, geoname_id);
        city_weather_oracle.pressure
    }

    // This function updates the name of a weather oracle contract.
    // It takes an admin capability, a mutable reference to the weather oracle, and a new name as arguments.
    // It assigns the new name to the weather oracle's name field.
    public fun update_name(_: &AdminCap, weather_oracle: &mut WeatherOracle, name: String) {
        weather_oracle.name = name;
    }

    // This function updates the description of a weather oracle contract.
    // It takes an admin capability, a mutable reference to the weather oracle, and a new description as arguments.
    // It assigns the new description to the weather oracle's description field.
    public fun update_description(_: &AdminCap, weather_oracle: &mut WeatherOracle, description: String) {
        weather_oracle.description = description;
    }
}

(2)部署简化版weather-oracle合约

$ sui client publish
  • 记录关键对象
export PACKAGE_ID=0xd12ea4bb23747bbf18b54e3ce4857aa84a89f053e677d79f84642e99a1753f06

export WEATHER_ORACLE=0x50fe253f3913d4c120f1cc96a69db10b9bebed01a4fcb4e3957336f33d9fa4c3

export ADMIN_CAP=0x02714caa89b4d990321035c0d946c650bb5e20f3a73679cfc3ce2de15bb00103

(3)修改Move.toml

  • 将PACKAGE_ID更新到Move.toml
[package]
name = "simple_weather_oracle"
edition = "2024.beta"  
version = "1.0.0"
published-at = "0xd12ea4bb23747bbf18b54e3ce4857aa84a89f053e677d79f84642e99a1753f06"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }

[addresses]
oracle = "0xd12ea4bb23747bbf18b54e3ce4857aa84a89f053e677d79f84642e99a1753f06"

(4)提交简化版weather-oracle到github

(5)添加城市

$ export GEO_NAME_ID_SZ=1795566

$ sui client call --package $PACKAGE_ID --module simple_weather --function add_city --args $ADMIN_CAP $WEATHER_ORACLE $GEO_NAME_ID_SZ Shenzhen CN 

(6)更新气压值

$ sui client call --package $PACKAGE_ID --module simple_weather --function update --args $ADMIN_CAP $WEATHER_ORACLE $GEO_NAME_ID_SZ 99700

(7)获取气压值

$ sui client call --package $PACKAGE_ID --module simple_weather --function city_weather_oracle_pressure --args $WEATHER_ORACLE $GEO_NAME_ID_SZ

业务合约部署

$ sui client publish
  • 记录关键信息
export PACKAGE_ID=0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e

浏览器调用

https://suiscan.xyz/testnet/object/0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e/contracts

image-20240905121754297

可见得到的骰子点数为:5:

https://suiscan.xyz/testnet/tx/F1XkZ4VmyajJrwuuj6ooQFAnR2sGPna8LMwyf3a3yGuQ

image-20240905121836250

命令行调用

$ sui client call --package $PACKAGE_ID --module weather_oracle --function roll_dice_nft --args $WEATHER_ORACLE $GEO_NAME_ID_SZ

可见得到的骰子点数为:5。

image-20240905121531766

代码调用

import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { SuiClient } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";

import dotenv from "dotenv";
dotenv.config();
const MNEMONIC = process.env.MNEMONIC!;
const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC);

const FULLNODE_URL = "https://fullnode.testnet.sui.io:443";
const PACKAGE_ID =
  "0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e";
const WEATHER_ORACLE =
  "0x50fe253f3913d4c120f1cc96a69db10b9bebed01a4fcb4e3957336f33d9fa4c3";
const MODULE_NAME = "weather_oracle";
const FUNCTION_NAME = "roll_dice_nft";
const GEO_NAME_ID_SZ = 1795566;

const SUI_CLIENT = new SuiClient({ url: FULLNODE_URL });

(async () => {
  let tx = new Transaction();
  tx.moveCall({
    target: `${PACKAGE_ID}::${MODULE_NAME}::${FUNCTION_NAME}`,
    arguments: [tx.object(WEATHER_ORACLE), tx.pure.u32(Number(GEO_NAME_ID_SZ))],
  });

  try {
    const result = await SUI_CLIENT.signAndExecuteTransaction({
      transaction: tx,
      signer: keypair,
      options: {
        showEvents: true,
      },
    });

    console.log(
      `signAndExecuteTransactionBlock result: ${JSON.stringify(
        result,
        null,
        2
      )}`
    );
  } catch (e) {
    console.error(e);
  }
})();
  • 执行输出

可见得到的骰子点数为:5。

$ ts-node client_weather_oracle.ts 
signAndExecuteTransactionBlock result: {
  "digest": "E9MYantGeDx4XaWDhpBzCMELsLm3WeB5uq2SqGHfiaWb",
  "events": [
    {
      "id": {
        "txDigest": "E9MYantGeDx4XaWDhpBzCMELsLm3WeB5uq2SqGHfiaWb",
        "eventSeq": "0"
      },
      "packageId": "0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e",
      "transactionModule": "weather_oracle",
      "sender": "0xa244617bc05e4122fb825d3b9c63dbad96dd06fae8183c2f03027b1feff12028",
      "type": "0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e::weather_oracle::DiceEvent",
      "parsedJson": {
        "value": 5
      },
      "bcs": "8QwQj"
    }
  ],
  "confirmedLocalExecution": false
}

更多资料

https://docs.sui.io/guides/developer/app-examples/weather-oracle

https://blog.sui.io/sui-weather-oracle/