Render a Data Table from Contract VIEW Methods
Iterate over contract addresses and fetch their data to render a data table. Learn this useful React hook pattern to implement this in your own dApp.
Last updated
Iterate over contract addresses and fetch their data to render a data table. Learn this useful React hook pattern to implement this in your own dApp.
Last updated
Getting Started
Follow the Project Setup instructions to start the project in your local machine and try the recipes yourself. You can also clone or navigate the repo to get just what you need.
For Prompt Wars, this table is rendered in the /previous-rounds
URL. It will load this page component:
Let's dive into pulse.promptWars.getLatestMarketId()
:
As you can see, it is creating a guest connection to get data from a Rust NEAR contract get_markets_list
VIEW method.
This contract is a games factory implementation that stores only the game IDs after their contract accounts are created:
The VIEW methods are in the same contract suite, but in this file:
This way, we are able to get an array of game IDs which we'll use to fetch their storage metadata.
Pay attention to pub struct MarketData
and its type definitions. We should have VIEW and CHANGE methods to modify some of these properties and get them later:
We add VIEW methods to set these properties, for example get_market_data
:
Some of the storage values are set when the contract is initialized, meant to NOT be modified later:
Run sh build.sh
to build the factory contract:
and then deploy it:
near deploy --wasmFile target/wasm32-unknown-unknown/release/market_factory.wasm --accountId $NEAR_PROMPT_WARS_FACTORY_ACCOUNT_ID
Now that there's an on-chain factory contract, we should be able to create new games on the server side:
When a new game is created, the factory contract should return the game IDs with get_markets_list
as we explained earlier. Once you can get a game ID, you should also be able to get its metadata:
Finally, we should be able to render a table component with the contract values.
Notice how the table row component is wrapped with a context controller:
{markets.map((marketId) => (
<NearPromptWarsMarketContractContextController marketId={marketId} key={marketId}>
<PreviousRoundsTableRow marketId={marketId} />
</NearPromptWarsMarketContractContextController>
))}
useNearPromptWarsMarketContractContext
hook will help us to organize the VIEW methods implementation better and render the data in the td
elements:
Lastly, this is the implementation of a Promise.all
call to get all the on-chain values needed for the table and other game info components:
import { GetServerSidePropsContext, NextPage } from "next";
import { i18n, useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Head from "next/head";
import { DashboardLayout } from "layouts/dashboard-layout/DashboardLayout";
import { AccountId } from "providers/near/contracts/market/market.types";
import pulse from "providers/pulse";
import { PreviousRoundsContainer } from "app/prompt-wars/previous-rounds/PreviousRoundsContainer";
import { PromptWarsMarketFactory } from "providers/near/contracts/prompt-wars-market-factory/contract";
const Index: NextPage<{ marketId: AccountId; markets: Array<AccountId> }> = ({ marketId, markets }) => {
const { t } = useTranslation("head");
return (
<DashboardLayout marketId={marketId}>
<Head>
<title>{t("head.og.title")}</title>
<meta name="description" content={t("head.og.description")} />
<meta property="og:title" content={t("head.og.title")} />
<meta property="og:description" content={t("head.og.description")} />
<meta property="og:url" content="https://app.pulsemarkets.org/" />
</Head>
<PreviousRoundsContainer markets={markets} />
</DashboardLayout>
);
};
export const getServerSideProps = async ({ locale }: GetServerSidePropsContext) => {
await i18n?.reloadResources();
const marketId = await pulse.promptWars.getLatestMarketId();
const markets = await PromptWarsMarketFactory.get_markets_list();
markets?.reverse();
return {
props: {
...(await serverSideTranslations(locale!, ["common", "head", "prompt-wars"])),
marketId,
markets,
},
};
};
export default Index;
import { MarketFactoryContract } from "providers/near/contracts/market-factory";
export default async () => {
const marketFactory = await MarketFactoryContract.loadFromGuestConnection();
const marketsList = await marketFactory.get_markets_list();
if (!marketsList) {
throw new Error("ERR_FAILED_TO_FETCH_MARKETS");
}
const latestMarketId = marketsList.pop();
return latestMarketId;
};
use near_sdk::{
collections::Vector, env, json_types::Base64VecU8, near_bindgen, serde_json, serde_json::Value,
AccountId, Promise,
};
use std::default::Default;
use crate::consts::*;
use crate::ext_self;
use crate::storage::*;
impl Default for MarketFactory {
fn default() -> Self {
Self {
markets: Vector::new(b"d".to_vec()),
}
}
}
#[near_bindgen]
impl MarketFactory {
#[payable]
pub fn create_market(&mut self, name: AccountId, args: Base64VecU8) -> Promise {
let market_account_id: AccountId = format!("{}.{}", name, env::current_account_id())
.parse()
.unwrap();
let mut init_args: Value = serde_json::from_slice(&args.0.as_slice()).unwrap();
init_args["management"]["market_creator_account_id"] =
Value::String(env::signer_account_id().to_string());
let collateral_token_account_id: AccountId = init_args["collateral_token"]["id"]
.as_str()
.unwrap()
.parse()
.unwrap();
// @TODO if this promise fails, the funds (attached_deposit) are not returned to the signer
let create_market_promise = Promise::new(market_account_id.clone())
.create_account()
.deploy_contract(MARKET_CODE.to_vec())
.transfer(env::attached_deposit() - STORAGE_DEPOSIT_BOND)
.function_call(
"new".to_string(),
init_args.to_string().into_bytes(),
0,
GAS_FOR_CREATE_MARKET,
);
let create_market_callback = ext_self::ext(env::current_account_id())
.with_attached_deposit(0)
.with_static_gas(GAS_FOR_CREATE_MARKET_CALLBACK)
.on_create_market_callback(market_account_id, collateral_token_account_id);
create_market_promise.then(create_market_callback)
}
}
use near_sdk::{near_bindgen, AccountId};
use crate::storage::*;
#[near_bindgen]
impl MarketFactory {
pub fn get_markets_list(&self) -> Vec<AccountId> {
self.markets.to_vec()
}
pub fn get_markets_count(&self) -> u64 {
self.markets.len()
}
pub fn get_markets(&self, from_index: u64, limit: u64) -> Vec<AccountId> {
let elements = &self.markets;
(from_index..std::cmp::min(from_index + limit, elements.len()))
.filter_map(|index| elements.get(index))
.collect()
}
}
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],
}
use crate::math;
use near_sdk::{env, log, near_bindgen, AccountId};
use num_format::ToFormattedString;
use shared::OutcomeId;
use crate::{storage::*, FORMATTED_STRING_LOCALE};
#[near_bindgen]
impl Market {
pub fn get_market_data(&self) -> MarketData {
self.market.clone()
}
pub fn get_resolution_data(&self) -> Resolution {
self.resolution.clone()
}
pub fn get_fee_data(&self) -> Fees {
self.fees.clone()
}
pub fn get_management_data(&self) -> Management {
self.management.clone()
}
pub fn get_collateral_token_metadata(&self) -> CollateralToken {
self.collateral_token.clone()
}
pub fn get_outcome_token(&self, outcome_id: &OutcomeId) -> OutcomeToken {
match self.outcome_tokens.get(outcome_id) {
Some(token) => token,
None => env::panic_str("ERR_INVALID_OUTCOME_ID"),
}
}
pub fn get_outcome_ids(&self) -> Vec<AccountId> {
self.players.to_vec()
}
pub fn get_block_timestamp(&self) -> Timestamp {
env::block_timestamp().try_into().unwrap()
}
pub fn resolved_at(&self) -> Timestamp {
match self.resolution.resolved_at {
Some(timestamp) => timestamp,
None => env::panic_str("ERR_RESOLVED_AT"),
}
}
pub fn balance_of(&self, outcome_id: &OutcomeId) -> WrappedBalance {
self.get_outcome_token(outcome_id).get_balance_of()
}
pub fn get_amount_mintable(&self, amount: WrappedBalance) -> (WrappedBalance, WrappedBalance) {
let fee = self.calc_percentage(amount, self.get_fee_data().fee_ratio);
let amount_mintable = amount - fee;
(amount_mintable, fee)
}
// The player gets his deposit back, minus fees
pub fn get_amount_payable_unresolved(
&self,
outcome_id: OutcomeId,
) -> (WrappedBalance, WrappedBalance) {
let amount = self.balance_of(&outcome_id);
// This balance is already minus fees
let collateral_token_balance = self.collateral_token.balance;
let outcome_token_balance = self.balance_of(&outcome_id);
if amount > outcome_token_balance {
env::panic_str("ERR_GET_AMOUNT_PAYABLE_UNRESOLVED_INVALID_AMOUNT");
}
log!(
"get_amount_payable_unresolved - EXPIRED_UNRESOLVED -- selling: {}, collateral_token_balance: {}, amount_payable: {}",
amount.to_formatted_string(&FORMATTED_STRING_LOCALE),
collateral_token_balance.to_formatted_string(&FORMATTED_STRING_LOCALE),
amount.to_formatted_string(&FORMATTED_STRING_LOCALE)
);
(amount, 0)
}
// Winner takes all! This method calculates only after resolution
pub fn get_amount_payable_resolved(&self) -> (WrappedBalance, WrappedBalance) {
// This balance is already minus fees
let collateral_token_balance = self.collateral_token.balance;
// In this game, winner takes all
let amount_payable = collateral_token_balance;
// 100% of the bag baby!
let weight = 1;
log!(
"get_amount_payable - RESOLVED -- ct_balance: {}, weight: {}, amount_payable: {}",
collateral_token_balance.to_formatted_string(&FORMATTED_STRING_LOCALE),
weight.to_formatted_string(&FORMATTED_STRING_LOCALE),
amount_payable.to_formatted_string(&FORMATTED_STRING_LOCALE)
);
(amount_payable, weight)
}
pub fn get_precision_decimals(&self) -> WrappedBalance {
let precision = format!(
"{:0<p$}",
10,
p = self.collateral_token.decimals as usize + 1
);
precision.parse().unwrap()
}
pub fn calc_percentage(&self, amount: WrappedBalance, bps: WrappedBalance) -> WrappedBalance {
math::complex_div_u128(
self.get_precision_decimals(),
math::complex_mul_u128(self.get_precision_decimals(), amount, bps),
math::complex_mul_u128(1, self.get_precision_decimals(), 100),
)
}
}
use near_sdk::collections::Vector;
use near_sdk::{
collections::LookupMap, env, ext_contract, json_types::U128, log, near_bindgen, AccountId,
Promise,
};
use num_format::ToFormattedString;
use shared::OutcomeId;
use std::default::Default;
use near_contract_standards::fungible_token::core::ext_ft_core;
use crate::consts::*;
use crate::storage::*;
#[ext_contract(ext_self)]
trait Callbacks {
fn on_ft_transfer_callback(
&mut self,
amount_payable: WrappedBalance,
outcome_id: OutcomeId,
) -> String;
fn on_claim_fees_resolved_callback(
&mut self,
payee: AccountId,
amount_paid: WrappedBalance,
) -> Option<Timestamp>;
fn on_claim_balance_self_destruct_callback(
&mut self,
payee: AccountId,
amount_payable: WrappedBalance,
) -> Option<Timestamp>;
}
#[ext_contract(ext_feed_parser)]
trait SwitchboardFeedParser {
fn aggregator_read(&self, msg: String) -> Promise;
}
impl Default for Market {
fn default() -> Self {
env::panic_str("ERR_MARKET_NOT_INITIALIZED")
}
}
#[near_bindgen]
impl Market {
#[init]
pub fn new(
market: MarketData,
management: Management,
collateral_token: CollateralToken,
) -> Self {
if env::state_exists() {
env::panic_str("ERR_ALREADY_INITIALIZED");
}
let starts_at: Timestamp = env::block_timestamp().try_into().unwrap();
let ends_at: Timestamp = starts_at + EVENT_PERIOD_NANOS;
let reveal_window = ends_at + STAGE_PERIOD_NANOS;
let resolution_window = reveal_window + STAGE_PERIOD_NANOS;
// 7 days
let self_destruct_window = resolution_window + 604_800_000_000_000;
Self {
market: MarketData {
starts_at,
ends_at,
..market
},
outcome_tokens: LookupMap::new(StorageKeys::OutcomeTokens),
players: Vector::new(b"p"),
resolution: Resolution {
window: resolution_window,
reveal_window,
resolved_at: None,
result: None,
},
fees: Fees {
price: CREATE_OUTCOME_TOKEN_PRICE,
fee_ratio: FEE_RATIO,
claimed_at: None,
},
collateral_token: CollateralToken {
balance: 0,
fee_balance: 0,
// @TODO collateral_token_decimals should be set by a cross-contract call to ft_metadata
..collateral_token
},
management: Management {
self_destruct_window,
buy_sell_threshold: BUY_SELL_THRESHOLD,
..management
},
}
}
#[private]
#[payable]
pub fn create_outcome_token(
&mut self,
sender_id: AccountId,
amount: WrappedBalance,
payload: CreateOutcomeTokenArgs,
) -> WrappedBalance {
self.assert_is_open();
self.assert_is_not_resolved();
self.assert_price(amount);
let outcome_id = sender_id;
let prompt = payload.prompt;
match self.outcome_tokens.get(&outcome_id) {
Some(_token) => env::panic_str("ERR_CREATE_OUTCOME_TOKEN_outcome_id_EXIST"),
None => {
self.internal_create_outcome_token(outcome_id, prompt, amount);
}
}
amount
}
#[payable]
pub fn sell(&mut self) -> WrappedBalance {
if self.is_expired_unresolved() {
return self.internal_sell_unresolved();
}
self.assert_is_resolved();
return self.internal_sell_resolved();
}
// During the resolution period, this method is called by the server (owner), setting the OutcomeTokenResult of each player
pub fn reveal(
&mut self,
outcome_id: OutcomeId,
result: OutcomeTokenResult,
output_img_uri: String,
) {
self.assert_only_owner();
self.assert_is_reveal_window_open();
self.assert_is_not_resolved();
self.assert_is_valid_outcome(&outcome_id);
self.assert_is_resolution_window_open();
let mut outcome_token = self.outcome_tokens.get(&outcome_id).unwrap();
outcome_token.set_result(result);
outcome_token.set_output_img_uri(output_img_uri);
self.outcome_tokens.insert(&outcome_id, &outcome_token);
log!("reveal: outcome_id: {}, result: {}", outcome_id, result);
}
// Gets the players accounts,
// gets their outcome token results if they revealed on time
// sorts the results, closest to 0 is first, closest to 0 wins
#[payable]
pub fn resolve(&mut self) {
self.assert_only_owner();
self.assert_is_not_resolved();
self.assert_is_resolution_window_open();
if self.get_outcome_ids().is_empty() {
self.resolution.resolved_at = Some(self.get_block_timestamp());
log!("resolve, market ended with 0 participants");
return;
}
let separator = "=".to_string();
let mut results: Vector<String> = Vector::new(b"r");
for player in self.get_outcome_ids().clone() {
let outcome_token = self.outcome_tokens.get(&player).unwrap();
if outcome_token.result.is_none() {
log!(
"resolve â PLAYER_DID_NOT_REVEAL â outcome_id: {}, result: {}",
outcome_token.outcome_id,
outcome_token.result.unwrap_or(0.0)
);
} else {
let result_str = outcome_token.result.unwrap().to_string();
results.push(&(player.to_string() + &separator + &result_str));
}
}
log!("resolve, unsorted results: {:?}", results.to_vec());
let mut sort = results.to_vec();
sort.sort_by(|a, b| {
let result_a = a.split(&separator).last().unwrap();
let result_b = b.split(&separator).last().unwrap();
return result_a.partial_cmp(result_b).unwrap();
});
log!("resolve, sorted results: {:?}", sort);
// @TODO what happens if 2 or more players have exactly the same result?
let winner = &sort[0];
let result = winner.split(&separator).next().unwrap();
log!("resolve, result: {}", result);
self.internal_set_resolution_result(AccountId::new_unchecked(result.to_string()));
}
pub fn claim_fees(&mut self) {
self.assert_is_resolution_window_expired();
self.assert_fees_not_claimed();
let payee = self.management.dao_account_id.clone();
let amount_payable = self.collateral_token.fee_balance;
if amount_payable == 0 {
self.fees.claimed_at = Some(self.get_block_timestamp());
env::panic_str("ERR_CLAIM_FEES_AMOUNT_PAYABLE_IS_ZERO");
}
let ft_transfer_promise = ext_ft_core::ext(self.collateral_token.id.clone())
.with_attached_deposit(FT_TRANSFER_BOND)
.with_static_gas(GAS_FT_TRANSFER)
.ft_transfer(payee.clone(), U128::from(amount_payable), None);
let ft_transfer_callback_promise = ext_self::ext(env::current_account_id())
.with_attached_deposit(0)
.with_static_gas(GAS_FT_TRANSFER_CALLBACK)
.on_claim_fees_resolved_callback(payee, amount_payable);
ft_transfer_promise.then(ft_transfer_callback_promise);
}
pub fn self_destruct(&mut self) {
self.assert_only_owner();
self.assert_is_resolution_window_expired();
if !self.is_self_destruct_window_expired() {
env::panic_str("ERR_SELF_DESTRUCT_WINDOW_NOT_EXPIRED");
}
if self.get_outcome_ids().is_empty() {
env::panic_str("ERR_NO_OUTCOME_IDS");
}
if !self.fees.claimed_at.is_some() || self.collateral_token.fee_balance > 0 {
env::panic_str("ERR_SELF_DESTRUCT_FEES_UNCLAIMED");
}
let amount_payable = self.collateral_token.balance;
if amount_payable == 0 {
Promise::new(env::current_account_id())
.delete_account(self.management.market_creator_account_id.clone());
return;
}
let payee = self.management.dao_account_id.clone();
let ft_transfer_promise = ext_ft_core::ext(self.collateral_token.id.clone())
.with_attached_deposit(FT_TRANSFER_BOND)
.with_static_gas(GAS_FT_TRANSFER)
.ft_transfer(payee.clone(), U128::from(amount_payable), None);
let ft_transfer_callback_promise = ext_self::ext(env::current_account_id())
.with_attached_deposit(0)
.with_static_gas(GAS_FT_TRANSFER_CALLBACK)
.on_claim_balance_self_destruct_callback(payee, amount_payable);
ft_transfer_promise.then(ft_transfer_callback_promise);
}
#[private]
pub fn update_collateral_token_balance(&mut self, amount: WrappedBalance) -> WrappedBalance {
self.collateral_token.balance = amount;
log!(
"update_collateral_token_balance: {}",
amount.to_formatted_string(&FORMATTED_STRING_LOCALE)
);
self.collateral_token.balance
}
#[private]
pub fn update_collateral_token_fee_balance(
&mut self,
amount: WrappedBalance,
) -> WrappedBalance {
self.collateral_token.fee_balance += amount;
log!(
"update_collateral_token_fee_balance, in: {}, total: {}",
amount.to_formatted_string(&FORMATTED_STRING_LOCALE),
self.collateral_token
.fee_balance
.to_formatted_string(&FORMATTED_STRING_LOCALE)
);
self.collateral_token.fee_balance
}
}
impl Market {
// Mint a new token for the player
fn internal_create_outcome_token(
&mut self,
outcome_id: OutcomeId,
prompt: String,
amount: WrappedBalance,
) -> WrappedBalance {
let (amount_mintable, fee) = self.get_amount_mintable(amount);
let outcome_token = OutcomeToken::new(&outcome_id, prompt, amount_mintable);
self.players.push(&outcome_id);
self.outcome_tokens.insert(&outcome_id, &outcome_token);
self.update_collateral_token_balance(self.collateral_token.balance + amount_mintable);
self.update_collateral_token_fee_balance(fee);
self.outcome_tokens.insert(&outcome_id, &outcome_token);
log!("CREATE_OUTCOME_TOKEN amount: {}, fee_ratio: {}, fee_result: {}, outcome_id: {}, sender_id: {}, ot_supply: {}, amount_mintable: {}, ct_balance: {}, fee_balance: {}",
amount.to_formatted_string(&FORMATTED_STRING_LOCALE),
self.fees.fee_ratio.to_formatted_string(&FORMATTED_STRING_LOCALE),
fee.to_formatted_string(&FORMATTED_STRING_LOCALE),
outcome_token.outcome_id,
outcome_id,
outcome_token.total_supply().to_formatted_string(&FORMATTED_STRING_LOCALE),
amount_mintable.to_formatted_string(&FORMATTED_STRING_LOCALE),
self.collateral_token.balance.to_formatted_string(&FORMATTED_STRING_LOCALE),
self.collateral_token.fee_balance.to_formatted_string(&FORMATTED_STRING_LOCALE),
);
amount_mintable
}
fn internal_sell_unresolved(&mut self) -> WrappedBalance {
let payee = env::signer_account_id();
let (amount_payable, _weight) = self.get_amount_payable_unresolved(payee.clone());
self.internal_transfer(payee, amount_payable)
}
fn internal_sell_resolved(&mut self) -> WrappedBalance {
let payee = self.resolution.result.clone().unwrap();
self.assert_is_winner(&payee);
let (amount_payable, _weight) = self.get_amount_payable_resolved();
self.internal_transfer(payee, amount_payable)
}
fn internal_transfer(
&mut self,
payee: AccountId,
amount_payable: WrappedBalance,
) -> WrappedBalance {
if amount_payable <= 0 {
env::panic_str("ERR_CANT_SELL_A_LOSING_OUTCOME");
}
let outcome_token = self.get_outcome_token(&payee);
log!(
"TRANSFER amount: {}, account_id: {}, supply: {}, is_resolved: {}, ct_balance: {}, amount_payable: {}",
amount_payable.to_formatted_string(&FORMATTED_STRING_LOCALE),
payee,
outcome_token.total_supply().to_formatted_string(&FORMATTED_STRING_LOCALE),
self.is_resolved(),
self.collateral_token.balance.to_formatted_string(&FORMATTED_STRING_LOCALE),
amount_payable.to_formatted_string(&FORMATTED_STRING_LOCALE),
);
let ft_transfer_promise = ext_ft_core::ext(self.collateral_token.id.clone())
.with_attached_deposit(FT_TRANSFER_BOND)
.with_static_gas(GAS_FT_TRANSFER)
.ft_transfer(payee.clone(), U128::from(amount_payable), None);
let ft_transfer_callback_promise = ext_self::ext(env::current_account_id())
.with_attached_deposit(0)
.with_static_gas(GAS_FT_TRANSFER_CALLBACK)
.on_ft_transfer_callback(amount_payable, outcome_token.outcome_id);
ft_transfer_promise.then(ft_transfer_callback_promise);
amount_payable
}
fn internal_set_resolution_result(&mut self, result: ResolutionResult) {
self.resolution.result = Some(result);
self.resolution.resolved_at = Some(self.get_block_timestamp());
log!(
"internal_set_resolution_result, result: {:?}",
self.resolution.result.clone().unwrap()
);
}
}
#!/bin/bash
RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release
wasm-opt -Oz --signext-lowering ./target/wasm32-unknown-unknown/release/market_factory.wasm -o ./target/wasm32-unknown-unknown/release/market_factory.wasm
cp ./target/wasm32-unknown-unknown/release/market_factory.wasm res/
import clsx from "clsx";
import { NearPromptWarsMarketContractContextController } from "context/near/prompt-wars-market-contract/NearPromptWarsMarketContractContextController";
import { PreviousRoundsTableRow } from "../previous-rounds-table-row/PreviousRoundsTableRow";
import { Typography } from "ui/typography/Typography";
import styles from "./PreviousRoundsTable.module.scss";
import { PreviousRoundsTableProps } from "./PreviousRoundsTable.types";
export const PreviousRoundsTable: React.FC<PreviousRoundsTableProps> = ({ className, markets }) => (
<div className={clsx(styles["previous-rounds-table__responsive"], className)}>
<table className={clsx(styles["previous-rounds-table"], className)}>
<thead>
<tr>
<th>
<Typography.Description>Image</Typography.Description>
</th>
<th>
<Typography.Description>Winner</Typography.Description>
</th>
<th>
<Typography.Description>No. of Players</Typography.Description>
</th>
<th>
<Typography.Description>Started at</Typography.Description>
</th>
<th>
<Typography.Description>Ended at</Typography.Description>
</th>
<th>
<Typography.Description>Status</Typography.Description>
</th>
<th>{null}</th>
</tr>
</thead>
<tbody>
{markets.map((marketId) => (
<NearPromptWarsMarketContractContextController marketId={marketId} key={marketId}>
<PreviousRoundsTableRow marketId={marketId} />
</NearPromptWarsMarketContractContextController>
))}
</tbody>
</table>
</div>
);
import clsx from "clsx";
import { useEffect } from "react";
import { Typography } from "ui/typography/Typography";
import { useNearPromptWarsMarketContractContext } from "context/near/prompt-wars-market-contract/useNearPromptWarsMarketContractContext";
import ipfs from "providers/ipfs";
import { useRoutes } from "hooks/useRoutes/useRoutes";
import date from "providers/date";
import styles from "./PreviousRoundsTableRow.module.scss";
import { PreviousRoundsTableRowProps } from "./PreviousRoundsTableRow.types";
export const PreviousRoundsTableRow: React.FC<PreviousRoundsTableRowProps> = ({ className, marketId }) => {
const { fetchMarketContractValues, marketContractValues } = useNearPromptWarsMarketContractContext();
const routes = useRoutes();
useEffect(() => {
fetchMarketContractValues();
}, [marketId]);
if (!marketContractValues) return null;
return (
<tr className={clsx(styles["previous-rounds-table-row"], className)}>
<td>
<img
src={ipfs.asHttpsURL(marketContractValues?.market.image_uri)}
className={clsx(styles["previous-rounds-table-row__thumbnail"])}
alt="market thumbnail"
/>
</td>
<td>
<Typography.Description flat>{marketContractValues?.resolution.result}</Typography.Description>
</td>
<td>
<Typography.Description flat>{marketContractValues?.outcomeIds.length}</Typography.Description>
</td>
<td>
<Typography.Description flat>
{date.fromTimestampWithOffset(marketContractValues?.market.starts_at, 0)}
</Typography.Description>
</td>
<td>
<Typography.Description flat>
{date.fromTimestampWithOffset(marketContractValues?.market.ends_at, 0)}
</Typography.Description>
</td>
<td>
{marketContractValues?.isResolved ? (
<Typography.Description flat className={clsx(styles["previous-rounds-table-row__resolved"])}>
resolved
</Typography.Description>
) : (
<Typography.Description flat>unresolved</Typography.Description>
)}
</td>
<td>
<Typography.Link
className={clsx(styles["previous-rounds-table-row__link"])}
href={routes.dashboard.promptWars.market({ marketId })}
>
See details
</Typography.Link>
</td>
</tr>
);
};
import { Contract } from "near-api-js";
import * as nearAPI from "near-api-js";
import { BN } from "bn.js";
import { FinalExecutionOutcome, Wallet } from "@near-wallet-selector/core";
import { TypedError } from "near-api-js/lib/providers";
import near from "providers/near";
import date from "providers/date";
import {
AccountId,
GetOutcomeTokenArgs,
OutcomeId,
OutcomeTokenResult,
PromptWarsMarketContractMethods,
PromptWarsMarketContractValues,
} from "./prompt-wars.types";
import { CHANGE_METHODS, VIEW_METHODS } from "./constants";
export class PromptWarsMarketContract {
values: PromptWarsMarketContractValues | undefined;
contract: Contract & PromptWarsMarketContractMethods;
contractAddress: AccountId;
constructor(contract: Contract & PromptWarsMarketContractMethods) {
this.contract = contract;
this.contractAddress = contract.contractId;
}
static async loadFromGuestConnection(contractAddress: AccountId) {
try {
const connection = await nearAPI.connect({
keyStore: new nearAPI.keyStores.InMemoryKeyStore(),
headers: {},
...near.getConfig(),
});
const account = await connection.account(near.getConfig().guestWalletId);
const contractMethods = { viewMethods: VIEW_METHODS, changeMethods: CHANGE_METHODS };
const contract = near.initContract<PromptWarsMarketContractMethods>(account, contractAddress, contractMethods);
return new PromptWarsMarketContract(contract);
} catch (error) {
console.log(error);
throw error;
}
}
static async loadFromWalletConnection(
connection: nearAPI.WalletConnection,
contractAddress: string,
): Promise<[PromptWarsMarketContract, Contract & PromptWarsMarketContractMethods]> {
const account = await connection.account();
const contractMethods = { viewMethods: VIEW_METHODS, changeMethods: CHANGE_METHODS };
const contract = near.initContract<PromptWarsMarketContractMethods>(account, contractAddress, contractMethods);
return [new PromptWarsMarketContract(contract), contract];
}
static async sell(wallet: Wallet, contractAddress: AccountId) {
try {
const gas = new BN("300000000000000");
const deposit = "0";
const response = await wallet.signAndSendTransaction({
receiverId: contractAddress,
actions: [
{
type: "FunctionCall",
params: {
methodName: "sell",
args: {},
gas: gas.toString(),
deposit,
},
},
],
});
near.unwrapFinalExecutionOutcome(response as FinalExecutionOutcome);
} catch (error) {
console.log(error);
throw new Error("ERR_MARKET_CONTRACT_SELL");
}
}
static async reveal(
contractId: AccountId,
outcome_id: OutcomeId,
result: OutcomeTokenResult,
output_img_uri: string,
) {
console.log(`revealing Prompt Wars prompt result for with account ${near.getConfig().serverWalletId}`);
const connection = await near.getPrivateKeyConnection();
const account = await connection.account(near.getConfig().serverWalletId);
const methodName = "reveal";
const gas = new BN("300000000000000");
const attachedDeposit = new BN("0");
const args = { outcome_id, result, output_img_uri };
await account.functionCall({
contractId,
methodName,
args,
gas,
attachedDeposit,
});
}
static async resolve(contractId: AccountId) {
console.log(`resolving Prompt Wars with account ${near.getConfig().serverWalletId}`);
const connection = await near.getPrivateKeyConnection();
const account = await connection.account(near.getConfig().serverWalletId);
const methodName = "resolve";
const gas = new BN("300000000000000");
const attachedDeposit = new BN("0");
const args = {};
await account.functionCall({
contractId,
methodName,
args,
gas,
attachedDeposit,
});
}
static async sellResolved(contractId: AccountId) {
console.log(`calling sell resolved Prompt Wars with account ${near.getConfig().serverWalletId}`);
const connection = await near.getPrivateKeyConnection();
const account = await connection.account(near.getConfig().serverWalletId);
const methodName = "sell";
const gas = new BN("300000000000000");
const attachedDeposit = new BN("0");
const args = {};
await account.functionCall({
contractId,
methodName,
args,
gas,
attachedDeposit,
});
}
static async claimFees(contractId: AccountId) {
console.log(`calling claim_fees Prompt Wars with account ${near.getConfig().serverWalletId}`);
const connection = await near.getPrivateKeyConnection();
const account = await connection.account(near.getConfig().serverWalletId);
const methodName = "claim_fees";
const gas = new BN("300000000000000");
const attachedDeposit = new BN("0");
const args = {};
await account.functionCall({
contractId,
methodName,
args,
gas,
attachedDeposit,
});
}
static async selfDestruct(contractId: AccountId) {
console.log(`calling self_destruct Prompt Wars with account ${near.getConfig().serverWalletId}`);
const connection = await near.getPrivateKeyConnection();
const account = await connection.account(near.getConfig().serverWalletId);
const methodName = "self_destruct";
const gas = new BN("300000000000000");
const attachedDeposit = new BN("0");
const args = {};
await account.functionCall({
contractId,
methodName,
args,
gas,
attachedDeposit,
});
}
async get_market_data() {
try {
const result = await this.contract.get_market_data();
return {
...result,
starts_at: date.extractNanoseconds(result.starts_at),
ends_at: date.extractNanoseconds(result.ends_at),
};
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_MARKET_DATA");
}
}
async get_resolution_data() {
try {
const result = await this.contract.get_resolution_data();
return {
...result,
window: date.extractNanoseconds(result.window),
resolved_at: result.resolved_at ? date.extractNanoseconds(result.resolved_at) : undefined,
reveal_window: date.extractNanoseconds(result.reveal_window),
};
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_RESOLUTION_DATA");
}
}
async get_fee_data() {
try {
const result = await this.contract.get_fee_data();
return {
...result,
claiming_window: result.claiming_window ? date.extractNanoseconds(result.claiming_window) : undefined,
};
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_FEE_DATA");
}
}
async get_management_data() {
try {
const result = await this.contract.get_management_data();
return {
...result,
self_destruct_window: date.extractNanoseconds(result.self_destruct_window),
};
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_MANAGEMENT_DATA");
}
}
async get_collateral_token_metadata() {
try {
const result = await this.contract.get_collateral_token_metadata();
return result;
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_COLLATERAL_TOKEN_METADATA");
}
}
async get_outcome_ids() {
try {
const result = await this.contract.get_outcome_ids();
return result.sort((a, b) => a.localeCompare(b));
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_OUTCOME_IDS");
}
}
async get_outcome_token(args: GetOutcomeTokenArgs) {
try {
const result = await this.contract.get_outcome_token(args);
return result;
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_OUTCOME_TOKEN");
}
}
async get_block_timestamp() {
try {
const result = await this.contract.get_block_timestamp();
return date.extractNanoseconds(result);
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_GET_BLOCK_TIMESTAMP");
}
}
async is_resolved() {
try {
const result = await this.contract.is_resolved();
return result;
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_IS_RESOLVED");
}
}
async is_open() {
try {
const result = await this.contract.is_open();
return result;
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_IS_OPEN");
}
}
async is_over() {
try {
const result = await this.contract.is_over();
return result;
} catch (error) {
console.log(error);
if ((error as TypedError)?.type === "AccountDoesNotExist") {
return true;
}
throw new Error("ERR_PW_MARKET_CONTRACT_IS_OVER");
}
}
async is_reveal_window_expired() {
try {
const result = await this.contract.is_reveal_window_expired();
return result;
} catch (error) {
console.log(error);
if ((error as TypedError)?.type === "AccountDoesNotExist") {
return true;
}
throw new Error("ERR_PW_MARKET_CONTRACT_IS_REVEAL_WINDOW_EXPIRED");
}
}
async is_resolution_window_expired() {
try {
const result = await this.contract.is_resolution_window_expired();
return result;
} catch (error) {
console.log(error);
if ((error as TypedError)?.type === "AccountDoesNotExist") {
return true;
}
throw new Error("ERR_PW_MARKET_CONTRACT_IS_RESOLUTION_WINDOW_EXPIRED");
}
}
async is_self_destruct_window_expired() {
try {
const result = await this.contract.is_self_destruct_window_expired();
return result;
} catch (error) {
console.log(error);
if ((error as TypedError)?.type === "AccountDoesNotExist") {
return true;
}
throw new Error("ERR_PW_MARKET_CONTRACT_IS_SELF_DESTRUCT_WINDOW_EXPIRED");
}
}
async is_expired_unresolved() {
try {
const result = await this.contract.is_expired_unresolved();
return result;
} catch (error) {
console.log(error);
throw new Error("ERR_PW_MARKET_CONTRACT_IS_EXPIRED_UNRESOLVED");
}
}
}
import { v4 as uuidv4 } from "uuid";
import { NextApiRequest, NextApiResponse } from "next";
import logger from "providers/logger";
import { DeployPromptWarsMarketContractArgs } from "providers/near/contracts/prompt-wars-market-factory/prompt-wars-market-factory.types";
import pulse from "providers/pulse";
import { CollateralToken, Management, MarketData } from "providers/near/contracts/prompt-wars/prompt-wars.types";
import near from "providers/near";
import { PromptWarsMarketFactory } from "providers/near/contracts/prompt-wars-market-factory/contract";
import { PromptWarsMarketContract } from "providers/near/contracts/prompt-wars/contract";
import ipfs from "providers/ipfs";
import { routes } from "hooks/useRoutes/useRoutes";
export default async function Fn(_request: NextApiRequest, response: NextApiResponse) {
try {
const latestMarketId = await pulse.promptWars.getLatestMarketId();
if (await pulse.promptWars.isMarketActive(latestMarketId!)) {
throw new Error("ERR_LATEST_MARKET_IS_STILL_ACTIVE");
}
const image_uri = await ipfs.getFileAsIPFSUrl("https://source.unsplash.com/random/512x512");
const market: MarketData = {
image_uri,
// Set to 0, it will be set in the contract initialization
starts_at: 0,
// Set to 0, it will be set in the contract initialization
ends_at: 0,
};
const management: Management = {
dao_account_id: near.getConfig().marketDaoAccountId,
market_creator_account_id: near.getConfig().serverWalletId,
// Set to 0, it will be set in the contract initialization
self_destruct_window: 0,
// Set to 0, it will be set in the contract initialization
buy_sell_threshold: 0,
};
const { accountId, decimals } = pulse.getDefaultCollateralToken();
const collateral_token: CollateralToken = {
id: accountId,
decimals,
balance: 0,
fee_balance: 0,
};
const promptWarsMarketArgs: DeployPromptWarsMarketContractArgs = {
market,
management,
collateral_token,
};
const id = `pw-${uuidv4().slice(0, 4)}`;
await PromptWarsMarketFactory.createMarket(id, promptWarsMarketArgs);
logger.info({ promptWarsMarketArgs, id });
const marketId = `${id}.${near.getConfig().factoryWalletId}`;
const marketContract = await PromptWarsMarketContract.loadFromGuestConnection(marketId);
const marketData = await marketContract.get_market_data();
const resolution = await marketContract.get_resolution_data();
let ms = marketData.ends_at - marketData.starts_at;
const revealEndpoint = routes.api.promptWars.reveal();
logger.info(`setting timeout to call the reveal API endpoint ${revealEndpoint} for market ${marketId} in ${ms} ms`);
setTimeout(async () => {
try {
logger.info(`calling the reveal API endpoint ${revealEndpoint} for market ${marketId}`);
await fetch(revealEndpoint);
} catch (error) {
logger.error(error);
}
}, ms);
ms = resolution.reveal_window - marketData.starts_at;
const resolveEndpoint = routes.api.promptWars.resolve();
logger.info(
`setting timeout to call the resolution API endpoint ${resolveEndpoint} for market ${marketId} in ${ms} ms`,
);
setTimeout(async () => {
try {
logger.info(`calling resolution API endpoint ${resolveEndpoint} for market ${marketId}`);
await fetch(resolveEndpoint);
} catch (error) {
logger.error(error);
}
}, ms);
response.status(200).json({ promptWarsMarketArgs, id });
} catch (error) {
logger.error(error);
response.status(500).json({ error: (error as Error).message });
}
}
import React, { useState } from "react";
import { setTimeout } from "timers";
import { useRouter } from "next/router";
import { useToastContext } from "hooks/useToastContext/useToastContext";
import { Typography } from "ui/typography/Typography";
import { PromptWarsMarketContract } from "providers/near/contracts/prompt-wars/contract";
import {
GetOutcomeTokenArgs,
Prompt,
PromptWarsMarketContractStatus,
PromptWarsMarketContractValues,
} from "providers/near/contracts/prompt-wars/prompt-wars.types";
import { useWalletStateContext } from "context/wallet/state/useWalletStateContext";
import { FungibleTokenContract } from "providers/near/contracts/fungible-token/contract";
import currency from "providers/currency";
import { useRoutes } from "hooks/useRoutes/useRoutes";
import { ErrorCode } from "providers/near/error-codes";
import {
NearPromptWarsMarketContractContextContextActions,
NearPromptWarsMarketContractContextControllerProps,
} from "./NearPromptWarsMarketContractContextContext.types";
import { NearPromptWarsMarketContractContext } from "./NearPromptWarsMarketContractContext";
let marketContract: PromptWarsMarketContract;
export const NearPromptWarsMarketContractContextController = ({
children,
marketId,
}: NearPromptWarsMarketContractContextControllerProps) => {
const [marketContractValues, setMarketContractValues] = useState<PromptWarsMarketContractValues>();
const [actions, setActions] = useState<NearPromptWarsMarketContractContextContextActions>({
fetchMarketContractValues: {
isLoading: false,
},
ftTransferCall: {
success: false,
isLoading: false,
},
create: {
isLoading: false,
},
});
const routes = useRoutes();
const router = useRouter();
const toast = useToastContext();
const walletStateContext = useWalletStateContext();
const getMarketStatus = (values: PromptWarsMarketContractValues): PromptWarsMarketContractStatus => {
if (!values) {
return PromptWarsMarketContractStatus.LOADING;
}
if (values?.isOpen) {
return PromptWarsMarketContractStatus.OPEN;
}
if (values?.isOver && values.isResolved) {
return PromptWarsMarketContractStatus.RESOLVED;
}
if (values?.isOver && !values.isRevealWindowExpired) {
return PromptWarsMarketContractStatus.REVEALING;
}
if (values?.isOver && !values.isResolutionWindowExpired) {
return PromptWarsMarketContractStatus.RESOLVING;
}
if (values?.isOver && values.isExpiredUnresolved) {
return PromptWarsMarketContractStatus.UNRESOLVED;
}
return PromptWarsMarketContractStatus.CLOSED;
};
const fetchMarketContractValues = async () => {
setActions((prev) => ({
...prev,
fetchMarketContractValues: {
isLoading: true,
},
}));
try {
// Wait 1 second to allow flags to change
setTimeout(async () => {
try {
const contract = await PromptWarsMarketContract.loadFromGuestConnection(marketId);
const [
market,
resolution,
fees,
management,
collateralToken,
outcomeIds,
isResolved,
isOpen,
isOver,
isRevealWindowExpired,
isResolutionWindowExpired,
isExpiredUnresolved,
] = await Promise.all([
contract.get_market_data(),
contract.get_resolution_data(),
contract.get_fee_data(),
contract.get_management_data(),
contract.get_collateral_token_metadata(),
contract.get_outcome_ids(),
contract.is_resolved(),
contract.is_open(),
contract.is_over(),
contract.is_reveal_window_expired(),
contract.is_resolution_window_expired(),
contract.is_expired_unresolved(),
]);
const values: PromptWarsMarketContractValues = {
market,
resolution,
fees,
management,
collateralToken,
outcomeIds,
isResolved,
isOpen,
isOver,
isRevealWindowExpired,
isResolutionWindowExpired,
isExpiredUnresolved,
status: PromptWarsMarketContractStatus.LOADING,
};
const status = getMarketStatus(values);
values.status = status;
setMarketContractValues(values);
} catch (error) {
console.log(error);
}
}, 1000);
} catch {
toast.trigger({
variant: "error",
withTimeout: true,
title: "Failed to fetch market data",
children: <Typography.Text>Try refreshing the page, or check your internet connection.</Typography.Text>,
});
}
setActions((prev) => ({
...prev,
fetchMarketContractValues: {
isLoading: false,
},
}));
};
const assertWalletConnection = () => {
if (!walletStateContext.isConnected) {
toast.trigger({
variant: "info",
withTimeout: true,
title: "Wallet is not connected",
children: <Typography.Text>Check your internet connection, your NEAR balance and try again.</Typography.Text>,
});
throw new Error("ERR_USE_NEAR_MARKET_CONTRACT_INVALID_WALLET_CONNECTION");
}
};
const ftTransferCall = async (prompt: Prompt) => {
if (!marketContractValues) {
return;
}
try {
assertWalletConnection();
setActions((prev) => ({
...prev,
ftTransferCall: {
...prev.ftTransferCall,
isLoading: true,
},
}));
const amount = marketContractValues.fees.price.toString();
const msg = JSON.stringify({ CreateOutcomeTokenArgs: { prompt: JSON.stringify(prompt) } });
await FungibleTokenContract.ftTransferCall(
walletStateContext.context.wallet!,
marketContractValues.collateralToken.id!,
{
receiver_id: marketId,
amount,
msg,
},
);
setActions((prev) => ({
...prev,
ftTransferCall: {
...prev.ftTransferCall,
isLoading: false,
success: true,
},
}));
toast.trigger({
variant: "confirmation",
withTimeout: true,
title: "Your prompt was successfully submitted",
children: (
<Typography.Text>{`Transferred USDT ${currency.convert.toDecimalsPrecisionString(
amount,
marketContractValues.collateralToken.decimals!,
)} to ${marketId}`}</Typography.Text>
),
});
fetchMarketContractValues();
} catch (error) {
if ((error as Error).message === ErrorCode.ERR_FungibleTokenContract_ftTransferCall) {
toast.trigger({
variant: "error",
title: "Failed to make transfer call",
children: <Typography.Text>You already transferred to this match.</Typography.Text>,
});
} else {
toast.trigger({
variant: "error",
title: "Failed to make transfer call",
children: (
<Typography.Text>Check your internet connection, connect your wallet and try again.</Typography.Text>
),
});
}
setActions((prev) => ({
...prev,
ftTransferCall: {
...prev.ftTransferCall,
isLoading: false,
success: true,
},
}));
}
};
const sell = async () => {
try {
assertWalletConnection();
await PromptWarsMarketContract.sell(walletStateContext.context.wallet!, marketId);
toast.trigger({
variant: "confirmation",
withTimeout: false,
title: "Success",
children: <Typography.Text>Check your new wallet balance.</Typography.Text>,
});
} catch {
toast.trigger({
variant: "error",
withTimeout: true,
title: "Failed to call sell method",
children: (
<Typography.Text>Check your internet connection, your NEAR wallet connection and try again.</Typography.Text>
),
});
}
};
const getOutcomeToken = async (args: GetOutcomeTokenArgs) => {
try {
if (!marketContract) {
marketContract = await PromptWarsMarketContract.loadFromGuestConnection(marketId);
}
const outcomeToken = await marketContract.get_outcome_token(args);
return outcomeToken;
} catch (error) {
console.log(error);
}
return undefined;
};
const create = async () => {
setActions((prev) => ({
...prev,
create: {
isLoading: true,
},
}));
try {
const response = await fetch(routes.api.promptWars.create());
if (!response.ok) {
throw new Error("ERR_USE_NEAR_PROMPT_WARS_MARKET_CONTRACT_CREATE_FAILED");
}
router.push(routes.dashboard.promptWars.home());
} catch (error) {
console.log(error);
toast.trigger({
variant: "error",
withTimeout: false,
title: "Failed to create a new market",
children: <Typography.Text>The server must have run out of funds. Please try again later.</Typography.Text>,
});
}
setActions((prev) => ({
...prev,
create: {
isLoading: false,
},
}));
};
const props = {
fetchMarketContractValues,
marketContractValues,
ftTransferCall,
sell,
getOutcomeToken,
actions,
marketId,
create,
};
return (
<NearPromptWarsMarketContractContext.Provider value={props}>
{children}
</NearPromptWarsMarketContractContext.Provider>
);
};