427 lines
14 KiB
JavaScript
427 lines
14 KiB
JavaScript
import React, { useState, useEffect } from "react";
|
|
import {
|
|
Account,
|
|
PublicKey,
|
|
SYSVAR_CLOCK_PUBKEY,
|
|
SystemProgram,
|
|
Transaction,
|
|
TransactionInstruction,
|
|
} from '@solana/web3.js';
|
|
|
|
import {TokenInstructions} from '@project-serum/serum';
|
|
import BufferLayout from 'buffer-layout';
|
|
import {AccountLayout} from '@solana/spl-token';
|
|
|
|
import { Button, Card, Dropdown, Menu, Select, Popover, Col, Row } from "antd";
|
|
import { NumericInput } from "./numericInput";
|
|
import { Settings } from "./settings";
|
|
import { SettingOutlined } from "@ant-design/icons";
|
|
import { AppBar } from "./appBar";
|
|
import contract_keys from "../contract_keys.json";
|
|
import { markets } from "../markets";
|
|
|
|
|
|
import { useMint } from '../utils/accounts';
|
|
import { fetchAccounts, userTokenAccount } from '../utils/fetchAccounts';
|
|
// For creating a token from usdc
|
|
import issueSet from '../utils/issueSet';
|
|
import { useConnection } from '../utils/connection';
|
|
import { useWallet } from '../utils/wallet';
|
|
import {sendTransaction} from "../utils/utils";
|
|
|
|
|
|
const PROGRAM_ID = new PublicKey(contract_keys.omega_program_id);
|
|
|
|
console.log('PROGRAM_ID', PROGRAM_ID.toString());
|
|
|
|
const QUOTE_CURRENCY = "USDC";
|
|
const QUOTE_CURRENCY_MINT = new PublicKey(contract_keys.quote_mint_pk);
|
|
|
|
console.log('QUOTE_CURRENCY', QUOTE_CURRENCY, QUOTE_CURRENCY_MINT.toString());
|
|
|
|
markets.forEach(m => {
|
|
console.log('MARKET', m.contract_name);
|
|
});
|
|
|
|
|
|
/* INSTRUCTIONS
|
|
* define buffer layouts and factory functions
|
|
*/
|
|
|
|
const MAX_OUTCOMES = 8;
|
|
const DETAILS_BUFFER_LEN = 2048;
|
|
|
|
// TODO fix this layout to be more fully specified
|
|
const OMEGA_CONTRACT_LAYOUT = BufferLayout.struct([
|
|
BufferLayout.nu64('flags'),
|
|
BufferLayout.blob(32, 'oracle'),
|
|
BufferLayout.blob(32, 'quote_mint'),
|
|
BufferLayout.nu64('exp_time'),
|
|
BufferLayout.nu64('auto_exp_time'),
|
|
BufferLayout.blob(32, 'vault'),
|
|
BufferLayout.blob(32, 'signer_key'),
|
|
BufferLayout.nu64('signer_nonce'),
|
|
BufferLayout.blob(32, 'winner'),
|
|
BufferLayout.seq(BufferLayout.blob(32), MAX_OUTCOMES, 'outcomes'),
|
|
BufferLayout.nu64('num_outcomes'),
|
|
BufferLayout.blob(DETAILS_BUFFER_LEN, 'details')
|
|
]);
|
|
|
|
|
|
async function queryMarketContract(conn, contract) {
|
|
const accountInfo = await conn.getParsedAccountInfo(contract, 'singleGossip');
|
|
|
|
const result = OMEGA_CONTRACT_LAYOUT.decode(Buffer.from(accountInfo.value.data));
|
|
console.log('QUERY', contract, result);
|
|
return result;
|
|
};
|
|
|
|
|
|
|
|
const IC_REDEEM_SET = 2;
|
|
const IC_REDEEM_WINNER = 3;
|
|
|
|
const instructionLayout = BufferLayout.struct([
|
|
BufferLayout.u32('instruction'),
|
|
BufferLayout.nu64('quantity'),
|
|
]);
|
|
|
|
function encodeInstructionData(layout, args) {
|
|
let data = Buffer.alloc(1024);
|
|
const encodeLength = layout.encode(args, data);
|
|
return data.slice(0, encodeLength);
|
|
}
|
|
|
|
function RedeemSetInstruction(omegaContract, user, userQuote, vault, omegaSigner, outcomePks, quantity) {
|
|
let keys = [
|
|
{ pubkey: omegaContract, isSigner: false, isWritable: false },
|
|
{ pubkey: user, isSigner: true, isWritable: false },
|
|
{ pubkey: userQuote, isSigner: false, isWritable: true },
|
|
{ pubkey: vault, isSigner: false, isWritable: true },
|
|
{ pubkey: TokenInstructions.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
{ pubkey: omegaSigner, isSigner: false, isWritable: false }
|
|
];
|
|
|
|
for (var i = 0; i < outcomePks.length; i++) {
|
|
keys.push({pubkey: outcomePks[i], isSigner: false, isWritable: true});
|
|
}
|
|
|
|
const data = encodeInstructionData(instructionLayout, {
|
|
instruction: IC_REDEEM_SET,
|
|
quantity
|
|
});
|
|
|
|
return new TransactionInstruction({keys: keys, programId: PROGRAM_ID, data: data});
|
|
}
|
|
|
|
function RedeemWinnerInstruction(omegaContract, user, userQuote, vault, omegaSigner, winnerMint, winnerWallet, quantity) {
|
|
let keys = [
|
|
{ pubkey: omegaContract, isSigner: false, isWritable: false },
|
|
{ pubkey: user, isSigner: true, isWritable: false },
|
|
{ pubkey: userQuote, isSigner: false, isWritable: true },
|
|
{ pubkey: vault, isSigner: false, isWritable: true },
|
|
{ pubkey: TokenInstructions.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
{ pubkey: omegaSigner, isSigner: false, isWritable: false },
|
|
{ pubkey: winnerMint, isSigner: false, isWritable: true},
|
|
{ pubkey: winnerWallet, isSigner: false, isWritable: true},
|
|
{ pubkey: SYSVAR_CLOCK_PUBKEY, isWritable: false, isSigner: false }
|
|
];
|
|
|
|
const data = encodeInstructionData(instructionLayout, {
|
|
instruction: IC_REDEEM_WINNER,
|
|
quantity
|
|
});
|
|
|
|
return new TransactionInstruction({keys: keys, programId: PROGRAM_ID, data: data});
|
|
}
|
|
|
|
|
|
|
|
export const RedeemView = (props) => {
|
|
|
|
let connection = useConnection();
|
|
const { wallet, connected } = useWallet();
|
|
const quoteMint = useMint(contract_keys.quote_mint_pk);
|
|
const [contractData, setContractData] = useState({
|
|
exp_time: 1612137600, // 02/01/2021 00:00 UTC
|
|
decided: false
|
|
});
|
|
const [winnerOutcome, setWinnerOutcome] = useState("");
|
|
|
|
useEffect(() => {
|
|
async function fetchContractData(market) {
|
|
console.log('fetchContractData', market);
|
|
let data = await queryMarketContract(connection, new PublicKey(market.omega_contract_pk));
|
|
let winner = new PublicKey(data.winner);
|
|
let zeroPubkey = new PublicKey(new Uint8Array(32));
|
|
|
|
console.log(new PublicKey(market.omega_contract_pk))
|
|
data['decided'] = !winner.equals(zeroPubkey);
|
|
setContractData(data);
|
|
}
|
|
fetchContractData(markets[0]);
|
|
}, [connection]);
|
|
|
|
|
|
useEffect(() => {
|
|
console.log('contract.exp_time', new Date(contractData.exp_time * 1000));
|
|
console.log('contract.decided', contractData.decided);
|
|
if (contractData.winner) {
|
|
const winner_pk = new PublicKey(contractData.winner).toBase58();
|
|
console.log('winner_pk', winner_pk);
|
|
markets.forEach(m => {
|
|
m.outcomes.forEach(o => {
|
|
if (o.mint_pk === winner_pk) {
|
|
setWinnerOutcome(o.name);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}, [contractData]);
|
|
|
|
async function createTokenAccountTransaction(mintPubkey) {
|
|
const newAccount = new Account();
|
|
const transaction = new Transaction();
|
|
transaction.add(
|
|
SystemProgram.createAccount({
|
|
fromPubkey: wallet.publicKey,
|
|
newAccountPubkey: newAccount.publicKey,
|
|
lamports: await connection.getMinimumBalanceForRentExemption(AccountLayout.span),
|
|
space: AccountLayout.span,
|
|
programId: TokenInstructions.TOKEN_PROGRAM_ID,
|
|
})
|
|
);
|
|
|
|
transaction.add(
|
|
TokenInstructions.initializeAccount({
|
|
account: newAccount.publicKey,
|
|
mint: mintPubkey,
|
|
owner: wallet.publicKey,
|
|
}),
|
|
);
|
|
|
|
return {
|
|
transaction,
|
|
signer: newAccount,
|
|
newAccountPubkey: newAccount.publicKey,
|
|
};
|
|
}
|
|
|
|
function parseAmount(amount) {
|
|
return parseFloat(amount) * Math.pow(10, quoteMint.decimals);
|
|
}
|
|
|
|
async function redeemSet(market, amount) {
|
|
if (!wallet.connected) await wallet.connect();
|
|
console.log('redeemSet', amount);
|
|
const accounts = await fetchAccounts(wallet, connection);
|
|
|
|
let userQuote = await userTokenAccount(accounts, QUOTE_CURRENCY_MINT, wallet, connection);
|
|
let outcomePks = [];
|
|
let outcomeInfos = market["outcomes"];
|
|
let numOutcomes = outcomeInfos.length;
|
|
for (let i = 0; i < numOutcomes; i++) {
|
|
let outcomeMint = new PublicKey(outcomeInfos[i]["mint_pk"]);
|
|
outcomePks.push(outcomeMint);
|
|
let userOutcomeWallet = await userTokenAccount(accounts, outcomeMint, wallet, connection);
|
|
outcomePks.push(userOutcomeWallet);
|
|
console.log(outcomeInfos[i]["name"], outcomeMint, userOutcomeWallet);
|
|
}
|
|
let redeemSetInstruction = RedeemSetInstruction(
|
|
new PublicKey(market.omega_contract_pk),
|
|
wallet.publicKey,
|
|
userQuote,
|
|
new PublicKey(market.quote_vault_pk),
|
|
new PublicKey(market.signer_pk),
|
|
outcomePks,
|
|
amount);
|
|
let transaction = new Transaction();
|
|
transaction.add(redeemSetInstruction);
|
|
|
|
let txid = await sendTransaction({
|
|
transaction,
|
|
wallet,
|
|
signers: [],
|
|
connection,
|
|
sendingMessage: 'sending RedeemSetInstruction...',
|
|
sentMessage: 'RedeemSetInstruction sent',
|
|
successMessage: 'RedeemSetInstruction success'
|
|
});
|
|
console.log('success txid:', txid);
|
|
}
|
|
|
|
async function redeemWinner(market, amount) {
|
|
if (!wallet.connected) await wallet.connect();
|
|
console.log('redeemWinner', amount);
|
|
|
|
const accounts = await fetchAccounts(wallet, connection);
|
|
let winner = new PublicKey(contractData.winner);
|
|
console.log(winner);
|
|
let zeroPubkey = new PublicKey(new Uint8Array(32));
|
|
|
|
if (winner === zeroPubkey) {
|
|
console.log("Contract has not been resolved yet");
|
|
return;
|
|
}
|
|
|
|
let winnerWallet = await userTokenAccount(accounts, winner, wallet, connection);
|
|
let userQuote = await userTokenAccount(accounts, QUOTE_CURRENCY_MINT, wallet, connection);
|
|
|
|
let redeemWinnerInstruction = RedeemWinnerInstruction(
|
|
new PublicKey(market.omega_contract_pk),
|
|
wallet.publicKey,
|
|
userQuote,
|
|
new PublicKey(market.quote_vault_pk),
|
|
new PublicKey(market.signer_pk),
|
|
winner,
|
|
winnerWallet,
|
|
amount);
|
|
|
|
let transaction = new Transaction();
|
|
transaction.add(redeemWinnerInstruction);
|
|
|
|
let txid = await sendTransaction({
|
|
transaction,
|
|
wallet,
|
|
signers: [],
|
|
connection,
|
|
sendingMessage: 'sending RedeemWinnerInstruction...',
|
|
sentMessage: 'RedeemWinnerInstruction sent',
|
|
successMessage: 'RedeemWinnerInstruction success'
|
|
});
|
|
|
|
console.log('success txid:', txid);
|
|
}
|
|
|
|
const colStyle = { padding: "0.5em", width: 512 };
|
|
|
|
const [winnerAmount, setWinnerAmount] = useState("");
|
|
const [redeemAmount, setRedeemAmount] = useState("");
|
|
const [issueAmount, setIssueAmount] = useState("");
|
|
const [winnerMarket, setWinnerMarket] = useState(markets[0]);
|
|
const [redeemMarket, setRedeemMarket] = useState(markets[0]);
|
|
const [issueMarket, setIssueMarket] = useState(markets[0]);
|
|
|
|
const onSelectWinnerMarket = (val) => {
|
|
console.log(`onSelectWinnerMarket ${val}`);
|
|
setWinnerMarket(markets.find(m => m.contract_name === val));
|
|
};
|
|
|
|
const onSelectRedeemMarket = (val) => {
|
|
console.log(`onSelectRedeemMarket ${val}`);
|
|
setRedeemMarket(markets.find(m => m.contract_name === val));
|
|
};
|
|
|
|
const onSelectIssueMarket = (val) => {
|
|
console.log(`onSelectIssueMarket ${val}`);
|
|
setIssueMarket(markets.find(m => m.contract_name === val));
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<AppBar
|
|
right={
|
|
<Popover
|
|
placement="topRight"
|
|
title="Settings"
|
|
content={<Settings />}
|
|
trigger="click"
|
|
>
|
|
<Button
|
|
shape="circle"
|
|
size="large"
|
|
type="text"
|
|
icon={<SettingOutlined />}
|
|
/>
|
|
</Popover>
|
|
}
|
|
/>
|
|
<Row justify="center">
|
|
<div style={colStyle}>
|
|
<Card>
|
|
<h2>Redeem Winner</h2>
|
|
<p>After the oracle has resolved the contract, you may redeem the winning token for equal quantities of USDC.</p>
|
|
<NumericInput
|
|
value={winnerAmount}
|
|
onChange={setWinnerAmount}
|
|
style={{
|
|
"margin-bottom": 10,
|
|
}}
|
|
addonAfter={winnerOutcome}
|
|
placeholder="0.00"
|
|
disabled={!contractData.decided}
|
|
/>
|
|
|
|
|
|
|
|
<Button
|
|
className="trade-button"
|
|
type="primary"
|
|
onClick={connected ? () => redeemWinner(winnerMarket, parseAmount(winnerAmount)) : wallet.connect}
|
|
style={{ width: "100%" }}
|
|
disabled={!contractData.decided}
|
|
>
|
|
{ connected ? "Redeem Tokens" : "Connect Wallet" }
|
|
|
|
</Button>
|
|
</Card>
|
|
</div>
|
|
</Row>
|
|
<Row justify="center">
|
|
<div style={colStyle}>
|
|
<Card>
|
|
<h2>Redeem Set</h2>
|
|
<p>Swap {redeemMarket.outcomes[0].name} and {redeemMarket.outcomes[1].name} for equal quantities of USDC.</p>
|
|
|
|
<NumericInput
|
|
value={redeemAmount}
|
|
onChange={setRedeemAmount}
|
|
style={{
|
|
"margin-bottom": 10,
|
|
}}
|
|
addonAfter={`${redeemMarket.outcomes[0].name} & ${redeemMarket.outcomes[1].name}`}
|
|
placeholder="0.00"
|
|
/>
|
|
<Button
|
|
className="trade-button"
|
|
type="primary"
|
|
onClick={connected ? () => redeemSet(redeemMarket, parseAmount(redeemAmount)) : wallet.connect}
|
|
style={{ width: "100%" }}
|
|
>
|
|
{ connected ? "Redeem Tokens" : "Connect Wallet" }
|
|
|
|
</Button>
|
|
</Card>
|
|
</div>
|
|
</Row>
|
|
<Row justify="center">
|
|
<div style={colStyle}>
|
|
<Card>
|
|
<h2>Issue Set</h2>
|
|
<p>Swap USDC for equal quantities of {issueMarket.contract_name} {issueMarket.outcomes[0].name} and {issueMarket.contract_name} {issueMarket.outcomes[1].name} tokens.</p>
|
|
<NumericInput
|
|
value={issueAmount}
|
|
onChange={setIssueAmount}
|
|
style={{
|
|
"margin-bottom": 10,
|
|
}}
|
|
addonAfter="USDC"
|
|
placeholder="0.00"
|
|
/>
|
|
<Button
|
|
className="trade-button"
|
|
type="primary"
|
|
onClick={connected ? () => issueSet(issueMarket, parseAmount(issueAmount), wallet, connection) : wallet.connect}
|
|
style={{ width: "100%" }}
|
|
>
|
|
{ connected ? "Issue Tokens" : "Connect Wallet" }
|
|
|
|
</Button>
|
|
|
|
</Card>
|
|
</div>
|
|
</Row>
|
|
</>
|
|
);
|
|
};
|