> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ifalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Read Latest Price (Sui)

> Fetch live stablecoin prices from the IFÁ Labs oracle on Sui — using Move contracts, the Sui TypeScript SDK, and the Sui CLI.

This page covers how to read price feeds from the IFÁ Labs oracle on Sui Testnet. The integration model is fundamentally different from EVM — instead of calling a contract address, you pass a shared object by reference in a transaction or inspect call.

If you are building on Base Mainnet or another EVM chain, see [Read Latest Price](/read-latest-price) for the Solidity integration guide.

<Warning>
  The oracle package was recently redeployed — all object IDs on this page were updated. If you have older IDs saved anywhere in your code, replace them now.
</Warning>

***

## Key Differences from EVM

Before writing any code, understand how Sui differs from EVM for oracle consumption:

| Concept                 | EVM                                           | Sui                                            |
| ----------------------- | --------------------------------------------- | ---------------------------------------------- |
| How you read prices     | Call `getAssetInfo` on a contract address     | Pass `IfaPriceFeed` shared object by reference |
| Price type              | `int256` (signed)                             | `u256` (unsigned)                              |
| Decimal convention      | Negative — e.g. `-18` means divide by `10^18` | Positive — e.g. `18` means divide by `10^18`   |
| Derived pair decimal    | Per-asset                                     | Always `30` — divide by `10^30`                |
| Missing asset behaviour | Returns `exists = false`                      | Aborts the transaction                         |
| Freshness check         | Implement yourself                            | Built-in `is_fresh` helper available           |
| Asset ID format         | `bytes32` hex string                          | `vector<u8>` — same 32 bytes, no `0x` prefix   |

***

## Sui Testnet Object IDs

You need these three values before integrating:

| Object                 | ID                                                                   |
| ---------------------- | -------------------------------------------------------------------- |
| **Package ID**         | `0x99847c953ee11ca57b6d602494c914d7d82a2d42c4c8a80307331fead5b22c4a` |
| **Feed Object ID**     | `0x5d2728b862de08de767e4f8841a38c9452d951c0602e77fc47bfbb3ab03f33f0` |
| **Verifier Object ID** | `0x028e5df554ac7d253a132c60bcd13cf32bea9eb8c81d04d685c2a31299fbe43b` |

<Warning>
  Always pass the **Feed Object ID** when reading prices — not the Package ID. The Package ID is only used when importing `ifa_oracle` as a dependency in your own Move package.
</Warning>

***

## Asset IDs on Sui

Asset IDs are the same 32-byte values as EVM — but formatted differently in Move and TypeScript.

```move theme={null}
// Move — use hex literal syntax, no 0x prefix
use ifa_oracle::bytes32;

let usdt_id = bytes32::new(x"6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d");
let usdc_id = bytes32::new(x"f989296bde68043d307a2bc0e59de3445defc5f292eb390b80d78162c8a6b13d");
let cngn_id = bytes32::new(x"83a18c73cf75a028a24b79cbedb3b8d8ba363b748a3210ddbcaa95eec3b87b3a");
let zarp_id = bytes32::new(x"12373a3b1c4827c84bf6d7b11df100442695d0abfdb7a20d30a41d67d58e75a8");
let brz_id  = bytes32::new(x"bc60b55b031dce1ee5679098bf2f35d66a94a566124e2b233324d2bafcc6d5b5");
let eth_id  = bytes32::new(x"8c3fb07cab369fe230ca4e45d095f796c4c1a30131f1799766d4fec5ee1325c0");
```

```typescript theme={null}
// TypeScript — convert hex to byte array, no 0x prefix
const ASSET_IDS = {
  "USDT/USD": Array.from(Buffer.from("6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d", "hex")),
  "USDC/USD": Array.from(Buffer.from("f989296bde68043d307a2bc0e59de3445defc5f292eb390b80d78162c8a6b13d", "hex")),
  "CNGN/USD": Array.from(Buffer.from("83a18c73cf75a028a24b79cbedb3b8d8ba363b748a3210ddbcaa95eec3b87b3a", "hex")),
  "ZARP/USD": Array.from(Buffer.from("12373a3b1c4827c84bf6d7b11df100442695d0abfdb7a20d30a41d67d58e75a8", "hex")),
  "BRZ/USD":  Array.from(Buffer.from("bc60b55b031dce1ee5679098bf2f35d66a94a566124e2b233324d2bafcc6d5b5", "hex")),
  "ETH/USD":  Array.from(Buffer.from("8c3fb07cab369fe230ca4e45d095f796c4c1a30131f1799766d4fec5ee1325c0", "hex")),
};
```

***

## Reading a Price in Move

### Single Asset

```move theme={null}
module my_protocol::price_reader;

use ifa_oracle::bytes32;
use ifa_oracle::price_feed::{Self, IfaPriceFeed};

/// Read the USDT/USD price from IFÁ Labs
/// feed: pass the IfaPriceFeed shared object by reference
public fun get_usdt_price(feed: &IfaPriceFeed): u256 {
    let asset_id = bytes32::new(
        x"6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d"
    );

    let (price_feed, exists) = price_feed::get_asset_info(feed, asset_id);

    assert!(exists, 0); // asset must exist — no false flag on Sui

    // Sui uses positive decimals
    // decimal = 18 means divide by 10^18
    // price_feed::price_decimal(price_feed) returns 18
    // price_feed::price(price_feed) returns e.g. 1_000_124_000_000_000_000
    // human-readable: 1_000_124_000_000_000_000 / 10^18 = 1.000124

    price_feed::price(price_feed)
}
```

### With Freshness Check

```move theme={null}
module my_protocol::price_reader;

use ifa_oracle::bytes32;
use ifa_oracle::price_feed::{Self, IfaPriceFeed};
use sui::clock::Clock;

const E_PRICE_STALE: u64 = 1;
const MAX_AGE_MS: u64 = 3_600_000; // 1 hour in milliseconds

public fun get_fresh_usdt_price(feed: &IfaPriceFeed, clock: &Clock): u256 {
    let asset_id = bytes32::new(
        x"6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d"
    );

    let (price_feed, exists) = price_feed::get_asset_info(feed, asset_id);
    assert!(exists, 0);

    // Use the built-in is_fresh helper
    // current_time comes from the Sui Clock object
    let current_time = sui::clock::timestamp_ms(clock);
    assert!(
        price_feed::is_fresh(price_feed, current_time, MAX_AGE_MS),
        E_PRICE_STALE
    );

    price_feed::price(price_feed)
}
```

<Note>
  Sui timestamps are in **milliseconds**. Set `MAX_AGE_MS` accordingly — 1 hour is `3_600_000`, not `3_600`. This is different from EVM where `block.timestamp` is in seconds.
</Note>

### Batch Read (Multiple Assets)

```move theme={null}
module my_protocol::price_reader;

use ifa_oracle::bytes32::{Self, Bytes32};
use ifa_oracle::price_feed::{Self, IfaPriceFeed, PriceFeed};

public fun get_multiple_prices(feed: &IfaPriceFeed): (vector<PriceFeed>, vector<bool>) {
    let mut asset_ids = vector<Bytes32>[];

    asset_ids.push_back(bytes32::new(
        x"6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d" // USDT
    ));
    asset_ids.push_back(bytes32::new(
        x"83a18c73cf75a028a24b79cbedb3b8d8ba363b748a3210ddbcaa95eec3b87b3a" // CNGN
    ));
    asset_ids.push_back(bytes32::new(
        x"12373a3b1c4827c84bf6d7b11df100442695d0abfdb7a20d30a41d67d58e75a8" // ZARP
    ));

    price_feed::get_assets_info(feed, asset_ids)
}
```

### Derived Pair (Cross-Asset Price)

```move theme={null}
module my_protocol::price_reader;

use ifa_oracle::bytes32;
use ifa_oracle::price_feed::{Self, IfaPriceFeed, DerivedPair};

// Direction: 0 = Forward (asset0 / asset1), 1 = Backward (asset1 / asset0)
const FORWARD: u8 = 0;

/// Get CNGN priced in USDT terms
public fun get_cngn_usdt_price(feed: &IfaPriceFeed): DerivedPair {
    let cngn_id = bytes32::new(
        x"83a18c73cf75a028a24b79cbedb3b8d8ba363b748a3210ddbcaa95eec3b87b3a"
    );
    let usdt_id = bytes32::new(
        x"6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d"
    );

    // Returns DerivedPair with decimal = 30 always
    // derived_price is scaled by 10^30
    price_feed::get_pair_by_id(feed, cngn_id, usdt_id, FORWARD)
}
```

<Warning>
  Derived pair functions **abort** if either underlying asset does not exist in the feed. Unlike EVM which returns `exists = false`, Sui will revert your entire transaction. Always verify both assets exist with `get_asset_info` before calling pair functions in production.
</Warning>

***

## Reading a Price in TypeScript

Use the Sui TypeScript SDK with `devInspectTransactionBlock` — no wallet or gas required for read-only queries.

### Setup

```bash theme={null}
npm install @mysten/sui
```

```typescript theme={null}
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";
import { Transaction }               from "@mysten/sui/transactions";

const client = new SuiClient({ url: getFullnodeUrl("testnet") });

const IFA = {
  packageId: "0x99847c953ee11ca57b6d602494c914d7d82a2d42c4c8a80307331fead5b22c4a",
  feedId:    "0x5d2728b862de08de767e4f8841a38c9452d951c0602e77fc47bfbb3ab03f33f0",
} as const;
```

***

### Single Asset Price

```typescript theme={null}
async function getAssetPrice(symbol: string): Promise<{
  price:          bigint;
  decimal:        number;
  lastUpdateTime: bigint;
  exists:         boolean;
}> {
  const assetIds: Record<string, string> = {
    "USDT/USD": "6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d",
    "USDC/USD": "f989296bde68043d307a2bc0e59de3445defc5f292eb390b80d78162c8a6b13d",
    "CNGN/USD": "83a18c73cf75a028a24b79cbedb3b8d8ba363b748a3210ddbcaa95eec3b87b3a",
    "ZARP/USD": "12373a3b1c4827c84bf6d7b11df100442695d0abfdb7a20d30a41d67d58e75a8",
    "BRZ/USD":  "bc60b55b031dce1ee5679098bf2f35d66a94a566124e2b233324d2bafcc6d5b5",
    "ETH/USD":  "8c3fb07cab369fe230ca4e45d095f796c4c1a30131f1799766d4fec5ee1325c0",
  };

  const hex = assetIds[symbol];
  if (!hex) throw new Error(`Unsupported asset: ${symbol}`);

  const assetIdBytes = Array.from(Buffer.from(hex, "hex"));

  const tx = new Transaction();
  tx.moveCall({
    target:    `${IFA.packageId}::price_feed::get_asset_info`,
    arguments: [
      tx.object(IFA.feedId),
      tx.pure.vector("u8", assetIdBytes),
    ],
  });

  const result = await client.devInspectTransactionBlock({
    transactionBlock: tx,
    sender: "0x0000000000000000000000000000000000000000000000000000000000000000",
  });

  if (result.effects.status.status !== "success") {
    throw new Error(`Query failed: ${result.effects.status.error}`);
  }

  // Parse return values from the devInspect result
  const returnValues = result.results?.[0]?.returnValues;
  if (!returnValues) throw new Error("No return values");

  // returnValues[0] = PriceFeed struct, returnValues[1] = bool exists
  // Deserialize based on BCS encoding
  const exists = returnValues[1][0][0] === 1;

  return { price: 0n, decimal: 0, lastUpdateTime: 0n, exists };
  // Full BCS deserialization shown below
}
```

### Full Example with BCS Deserialization

```typescript theme={null}
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";
import { Transaction }               from "@mysten/sui/transactions";
import { bcs }                       from "@mysten/sui/bcs";

const client = new SuiClient({ url: getFullnodeUrl("testnet") });

const IFA = {
  packageId: "0x99847c953ee11ca57b6d602494c914d7d82a2d42c4c8a80307331fead5b22c4a",
  feedId:    "0x5d2728b862de08de767e4f8841a38c9452d951c0602e77fc47bfbb3ab03f33f0",
} as const;

// BCS schema matching the Move PriceFeed struct
const PriceFeedSchema = bcs.struct("PriceFeed", {
  price:           bcs.u256(),
  decimal:         bcs.u8(),
  lastUpdateTime:  bcs.u64(),
});

async function getPrice(symbolHex: string) {
  const assetIdBytes = Array.from(Buffer.from(symbolHex, "hex"));

  const tx = new Transaction();
  tx.moveCall({
    target:    `${IFA.packageId}::price_feed::get_asset_info`,
    arguments: [
      tx.object(IFA.feedId),
      tx.pure.vector("u8", assetIdBytes),
    ],
  });

  const result = await client.devInspectTransactionBlock({
    transactionBlock: tx,
    sender: "0x0000000000000000000000000000000000000000000000000000000000000000",
  });

  if (result.effects.status.status !== "success") {
    throw new Error(`Query failed: ${result.effects.status.error}`);
  }

  const returnValues = result.results?.[0]?.returnValues;
  if (!returnValues || returnValues.length < 2) {
    throw new Error("Unexpected return value structure");
  }

  // Deserialize PriceFeed struct
  const priceFeedBytes = new Uint8Array(returnValues[0][0]);
  const feed           = PriceFeedSchema.parse(priceFeedBytes);

  // Deserialize exists bool
  const exists = returnValues[1][0][0] === 1;

  if (!exists) throw new Error("Asset not supported on this feed");

  // Convert to human-readable
  // Sui: decimal is positive — divide by 10^decimal
  const divisor      = 10n ** BigInt(feed.decimal);
  const humanPrice   = Number(feed.price) / Number(divisor);
  const ageMs        = Date.now() - Number(feed.lastUpdateTime);
  const ageSeconds   = Math.floor(ageMs / 1000);

  return {
    price:          humanPrice.toFixed(6),
    rawPrice:       feed.price.toString(),
    decimal:        feed.decimal,
    lastUpdateTime: feed.lastUpdateTime.toString(),
    ageSeconds,
  };
}

// Example usage
const USDT_HEX = "6ca0cef6107263f3b09a51448617b659278cff744f0e702c24a2f88c91e65a0d";
const result   = await getPrice(USDT_HEX);
console.log(`USDT/USD: $${result.price} (${result.ageSeconds}s ago)`);
// USDT/USD: $1.000124 (412s ago)
```

***

## Reading via Sui CLI

For quick checks without writing code:

```bash theme={null}
# Set active environment to testnet
sui client switch --env testnet

# Read USDT/USD price
sui client call \
  --package 0x99847c953ee11ca57b6d602494c914d7d82a2d42c4c8a80307331fead5b22c4a \
  --module price_feed \
  --function get_asset_info \
  --args \
    0x5d2728b862de08de767e4f8841a38c9452d951c0602e77fc47bfbb3ab03f33f0 \
    "[6,202,12,239,97,7,38,63,59,9,165,20,72,97,123,101,146,120,207,247,68,240,231,2,194,36,162,248,140,145,229,13]" \
  --gas-budget 10000000
```

<Note>
  The asset ID is passed as a JSON array of decimal byte values — not as a hex string. The array above is the decimal representation of the USDT/USD asset ID bytes.
</Note>

***

## Converting Prices

Sui uses **positive decimals** — the opposite sign convention from EVM.

| Chain | Decimal value | Formula                                   | Example                                  |
| ----- | ------------- | ----------------------------------------- | ---------------------------------------- |
| EVM   | `-18`         | `price / 10^(-decimal)` = `price / 10^18` | `1000124000000000000 / 10^18 = 1.000124` |
| Sui   | `18`          | `price / 10^decimal` = `price / 10^18`    | `1000124000000000000 / 10^18 = 1.000124` |

The math is the same — only the sign of the `decimal` field differs.

For derived pairs on Sui, the decimal is always `30`:

```typescript theme={null}
// Derived pair result — decimal is always 30 on Sui
const derivedPrice   = pair.derived_price; // e.g. 613000000000000000000000000n
const humanPrice     = Number(derivedPrice) / 10 ** 30;
// 613000000000000000000000000 / 10^30 = 0.000613
// Meaning: 1 CNGN = 0.000613 USDT
```

***

## Adding IFÁ Labs as a Move Dependency

To use the oracle in your own Move package, add it to your `Move.toml`:

```toml theme={null}
[package]
name    = "my_protocol"
edition = "2024"

[dependencies]
Sui        = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/mainnet" }
ifa_oracle = { git = "https://github.com/IFA-Labs/oracle_contract_sui.git", rev = "main" }

[addresses]
my_protocol = "0x0"
```

Then import in your Move source:

```move theme={null}
use ifa_oracle::price_feed::{Self, IfaPriceFeed};
use ifa_oracle::bytes32;
```

***

## Want to Build a Swap or Liquidity Pool Instead?

If you're trying to build a price-aware swap, dust sweeper, or liquidity pool — IFÁ Labs already has a live one on Sui Testnet you can model from or integrate directly with. See [Sui Swap Contract](/sui-swap-overview) for the periphery functions (`deposit_liquidity`, `swap_exact_input`, `sweep`, and more) and how they consume this same `IfaPriceFeed`.

***

## Getting Testnet Tokens

You need testnet SUI to deploy contracts on Sui Testnet. Price reads via `devInspectTransactionBlock` are free and require no tokens.

* **Official Sui Faucet:** [faucet.sui.io](https://faucet.sui.io)
* **IFÁ Labs Faucet:** Claim testnet SUI and WAL directly — see [Testnet Faucet](/testnet-faucet)

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Sui Function Reference" icon="code" href="/sui-function-reference">
    Complete reference for every public function in the IFÁ Labs Sui Move contracts.
  </Card>

  <Card title="Sui Swap Contract" icon="right-left" href="/sui-swap-overview">
    See the oracle in action inside a live liquidity pool.
  </Card>
</CardGroup>

<CardGroup cols={2}>
  <Card title="Supported Assets" icon="coins" href="/supported-assets">
    All supported feeds with asset IDs in both EVM and Sui formats.
  </Card>

  <Card title="Contract Addresses" icon="file-contract" href="/contract-addresses">
    All Sui object IDs and EVM contract addresses in one place.
  </Card>
</CardGroup>
