Prompt Wars: NEAR Protocol Recipes
  • NEAR Protocol Recipes
    • NEAR Guest Wallet Widget
    • NEAR and NEP141 Balance Widget
    • Render a Data Table from Contract VIEW Methods
    • Link an Account to Image, Text and Time Metadata with On-Chain Storage
    • Create Time State Machines with Realtime Block Timestamp Comparisons
    • Handle NEP141 Transfer Calls and Track the Target Contract's Balance
    • Handle FunctionCall Transaction Errors
  • Project Setup
    • The User Interface
    • The Rust Contracts
Powered by GitBook
On this page
  1. Project Setup

The Rust Contracts

The content on this page assumes that you already have Rust and Cargo installed in your machine

First, clone the repo:

git clone git@github.com:aufacicenta/pulsemarkets.git prompt-wars

Contract files structure:

.
├── market-factory
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── build.sh
│   └── src
│       ├── callbacks.rs
│       ├── consts.rs
│       ├── contract.rs
│       ├── lib.rs
│       ├── storage.rs
│       ├── tests.rs
│       └── views.rs
└── prompt-wars
    ├── Cargo.lock
    ├── Cargo.toml
    ├── README.md
    ├── build.sh
    └── src
        ├── callbacks.rs
        ├── consts.rs
        ├── contract.rs
        ├── flags.rs
        ├── ft_receiver.rs
        ├── lib.rs
        ├── math.rs
        ├── modifiers.rs
        ├── outcome_token.rs
        ├── storage.rs
        ├── tests.rs
        └── views.rs

You'll want to focus on the contract.rs files most of the time, but storage.rs  is your starting point, for example prompt-wars/src/storage.rs holds all the public structs that the game uses:

use near_sdk::{
    borsh::{self, BorshDeserialize, BorshSerialize},
    collections::{LookupMap, Vector},
    near_bindgen,
    serde::{Deserialize, Serialize},
    AccountId, BorshStorageKey,
};
use shared::OutcomeId;

pub type Timestamp = i64;
pub type WrappedBalance = u128;
pub type OutcomeTokenResult = f32;
pub type ResolutionResult = OutcomeId;

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))]
#[serde(crate = "near_sdk::serde")]
pub struct MarketData {
    // The IPFS reference-image hash of the expected prompts
    pub image_uri: String,
    // Datetime nanos: the market is open
    pub starts_at: Timestamp,
    // Datetime nanos: the market is closed
    pub ends_at: Timestamp,
}

#[near_bindgen]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Market {
    // Market metadata
    pub market: MarketData,
    // NEP141 token metadata
    pub collateral_token: CollateralToken,
    // MArket resolution metadata
    pub resolution: Resolution,
    // Market management account ids metadata
    pub management: Management,
    // Keeps track of Outcomes prices and balances
    pub outcome_tokens: LookupMap<AccountId, OutcomeToken>,
    // Keeps track of the outcome_ids that have bet on the market
    pub players: Vector<AccountId>,
    // Market fees metadata
    pub fees: Fees,
}

#[derive(Serialize, Deserialize)]
pub enum SetPriceOptions {
    Increase,
    Decrease,
}

#[derive(BorshSerialize, BorshDeserialize, Serialize)]
pub struct OutcomeToken {
    // the account id of the outcome_token
    pub outcome_id: OutcomeId,
    // the outcome value, in this case, the prompt submitted to the competition
    pub prompt: String,
    // the outcome value, in this case, the prompt submitted to the competition
    pub output_img_uri: Option<String>,
    // store the result from the image comparison: percentage_diff or pixel_difference
    pub result: Option<OutcomeTokenResult>,
    // total supply of this outcome_token
    pub total_supply: WrappedBalance,
}

#[derive(BorshSerialize, BorshDeserialize, Deserialize, Serialize, Clone)]
pub struct CollateralToken {
    pub id: AccountId,
    pub balance: WrappedBalance,
    pub decimals: u8,
    pub fee_balance: WrappedBalance,
}

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
pub struct Resolution {
    // Time to free up the market
    pub window: Timestamp,
    // Time after the market ends and before the resolution window starts
    pub reveal_window: Timestamp,
    // When the market is resolved, set only by fn resolve
    pub resolved_at: Option<Timestamp>,
    // When the market is resolved, set only by fn resolve
    pub result: Option<ResolutionResult>,
}

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
pub struct Management {
    // Gets sent fees when claiming window is open
    pub dao_account_id: AccountId,
    // Gets back the storage deposit upon self-destruction
    pub market_creator_account_id: AccountId,
    // Set at initialization, the market may be destroyed by the creator to claim the storage deposit
    pub self_destruct_window: Timestamp,
    // Set at initialization, determines when to close bets
    pub buy_sell_threshold: f32,
}

#[derive(BorshSerialize, BorshDeserialize, Deserialize, Serialize, Clone)]
pub struct Fees {
    // Price to charge when creating an outcome token
    pub price: WrappedBalance,
    // Decimal fee to charge upon a bet
    pub fee_ratio: WrappedBalance,
    // When fees got sent to the DAO
    pub claimed_at: Option<Timestamp>,
}

#[derive(BorshStorageKey, BorshSerialize)]
pub enum StorageKeys {
    OutcomeTokens,
    StakingFees,
    MarketCreatorFees,
}

#[derive(Serialize, Deserialize)]
pub struct CreateOutcomeTokenArgs {
    // the outcome value, in this case, the prompt submitted to the competition
    pub prompt: String,
}

#[derive(Serialize, Deserialize)]
pub enum Payload {
    CreateOutcomeTokenArgs(CreateOutcomeTokenArgs),
}

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
pub struct Ix {
    pub address: [u8; 32],
}

Note that these structs, have their corresponding Typescript definitions:

app/src/providers/near/contracts/prompt-wars/prompt-wars.types.ts
type Timestamp = number;
type WrappedBalance = number;
export type AccountId = string;
export type OutcomeId = AccountId;
export type OutcomeTokenResult = number;

export type Prompt = {
  value: string;
  negative_prompt?: string;
};

export type ResolutionResult = AccountId;

export type MarketData = {
  image_uri: string;
  starts_at: Timestamp;
  ends_at: Timestamp;
};

export type Fees = {
  price: WrappedBalance;
  fee_ratio: WrappedBalance;
  claiming_window?: Timestamp;
};

export type Management = {
  dao_account_id: AccountId;
  self_destruct_window: Timestamp;
  buy_sell_threshold: number;
  market_creator_account_id?: AccountId;
};

export type CollateralToken = {
  id: string;
  balance: number;
  decimals: number;
  fee_balance: WrappedBalance;
};

export type Resolution = {
  window: Timestamp;
  reveal_window: Timestamp;
  resolved_at?: Timestamp;
  result?: string;
};

export type OutcomeToken = {
  outcome_id: OutcomeId;
  prompt: string;
  total_supply: WrappedBalance;
  result?: OutcomeTokenResult;
  output_img_uri?: string;
};

export type PromptWarsMarketContractValues = {
  market: MarketData;
  resolution: Resolution;
  fees: Fees;
  management: Management;
  collateralToken: CollateralToken;
  outcomeIds: Array<OutcomeId>;
  isResolved: boolean;
  isOpen: boolean;
  isOver: boolean;
  isRevealWindowExpired: boolean;
  isResolutionWindowExpired: boolean;
  isExpiredUnresolved: boolean;

  status: PromptWarsMarketContractStatus;
};

export enum PromptWarsMarketContractStatus {
  LOADING = "Loading",
  OPEN = "Open",
  REVEALING = "Revealing",
  RESOLVING = "Resolving",
  RESOLVED = "Resolved",
  UNRESOLVED = "Unresolved",
  CLOSED = "Closed",
}

export type GetOutcomeTokenArgs = {
  outcome_id: OutcomeId;
};

export type PromptWarsMarketContractMethods = {
  get_market_data: () => Promise<MarketData>;
  get_resolution_data: () => Promise<Resolution>;
  get_fee_data: () => Promise<Fees>;
  get_management_data: () => Promise<Management>;
  get_collateral_token_metadata: () => Promise<CollateralToken>;
  get_outcome_token: (args: GetOutcomeTokenArgs) => Promise<OutcomeToken>;
  get_outcome_ids: () => Promise<Array<AccountId>>;
  get_block_timestamp: () => Promise<Timestamp>;
  resolved_at: () => Promise<Timestamp>;
  balance_of: (outcome_id: OutcomeId) => Promise<WrappedBalance>;
  get_amount_mintable: (amount: WrappedBalance) => Promise<Array<WrappedBalance>>;
  get_amount_payable_unresolved: () => Promise<Array<WrappedBalance>>;
  get_amount_payable_resolved: () => Promise<Array<WrappedBalance>>;
  get_precision_decimals: () => Promise<WrappedBalance>;
  // flags
  is_resolved: () => Promise<boolean>;
  is_open: () => Promise<boolean>;
  is_over: () => Promise<boolean>;
  is_reveal_window_expired: () => Promise<boolean>;
  is_resolution_window_expired: () => Promise<boolean>;
  is_self_destruct_window_expired: () => Promise<boolean>;
  is_expired_unresolved: () => Promise<boolean>;
  // mutators
  sell: (args: { name: string; args: string }, gas?: number, amount?: string | null) => Promise<boolean>;
  reveal: (
    args: { outcome_id: OutcomeId; result: OutcomeTokenResult },
    gas?: number,
    amount?: string | null,
  ) => Promise<boolean>;
  resolve: (args: { name: string; args: string }, gas?: number, amount?: string | null) => Promise<boolean>;
};

Testing Rust contracts

Both prompt-wars and market-factory have a tests.rs file. If you make changes to any of these, you may check for errors by running:

cargo test -- --nocapture 

This command should result in 100% passing tests:

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests promptwars
PreviousThe User Interface

Last updated 1 year ago