mirror of https://github.com/certusone/oyster.git
WIP commit on proposals, working on a dummy button to create a txn
This commit is contained in:
parent
aab7b80c32
commit
af81a3f644
|
@ -1,12 +1,27 @@
|
||||||
import { useLocalStorageState } from '../utils/utils';
|
import { useLocalStorageState } from '../utils/utils';
|
||||||
import { Account, clusterApiUrl, Connection, Transaction, TransactionInstruction } from '@solana/web3.js';
|
import {
|
||||||
|
Account,
|
||||||
|
clusterApiUrl,
|
||||||
|
Connection,
|
||||||
|
Transaction,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from '@solana/web3.js';
|
||||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { notify } from '../utils/notifications';
|
import { notify } from '../utils/notifications';
|
||||||
import { ExplorerLink } from '../components/ExplorerLink';
|
import { ExplorerLink } from '../components/ExplorerLink';
|
||||||
import { setProgramIds } from '../utils/ids';
|
import { setProgramIds } from '../utils/ids';
|
||||||
import { TokenInfo, TokenListProvider, ENV as ChainId } from '@solana/spl-token-registry';
|
import {
|
||||||
|
TokenInfo,
|
||||||
|
TokenListProvider,
|
||||||
|
ENV as ChainId,
|
||||||
|
} from '@solana/spl-token-registry';
|
||||||
|
|
||||||
export type ENV = 'mainnet-beta' | 'testnet' | 'devnet' | 'localnet' | 'lending';
|
export type ENV =
|
||||||
|
| 'mainnet-beta'
|
||||||
|
| 'testnet'
|
||||||
|
| 'devnet'
|
||||||
|
| 'localnet'
|
||||||
|
| 'lending';
|
||||||
|
|
||||||
export const ENDPOINTS = [
|
export const ENDPOINTS = [
|
||||||
{
|
{
|
||||||
|
@ -19,9 +34,16 @@ export const ENDPOINTS = [
|
||||||
endpoint: clusterApiUrl('testnet'),
|
endpoint: clusterApiUrl('testnet'),
|
||||||
ChainId: ChainId.Testnet,
|
ChainId: ChainId.Testnet,
|
||||||
},
|
},
|
||||||
{ name: 'devnet' as ENV, endpoint: clusterApiUrl('devnet'),
|
{
|
||||||
ChainId: ChainId.Devnet, },
|
name: 'devnet' as ENV,
|
||||||
{ name: 'localnet' as ENV, endpoint: 'http://127.0.0.1:8899', ChainId: ChainId.Devnet, },
|
endpoint: clusterApiUrl('devnet'),
|
||||||
|
ChainId: ChainId.Devnet,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'localnet' as ENV,
|
||||||
|
endpoint: 'http://127.0.0.1:8899',
|
||||||
|
ChainId: ChainId.Devnet,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Oyster Dev' as ENV,
|
name: 'Oyster Dev' as ENV,
|
||||||
endpoint: 'http://oyster-dev.solana.com/',
|
endpoint: 'http://oyster-dev.solana.com/',
|
||||||
|
@ -62,31 +84,47 @@ const ConnectionContext = React.createContext<ConnectionConfig>({
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ConnectionProvider({ children = undefined as any }) {
|
export function ConnectionProvider({ children = undefined as any }) {
|
||||||
const [endpoint, setEndpoint] = useLocalStorageState('connectionEndpts', ENDPOINTS[0].endpoint);
|
const [endpoint, setEndpoint] = useLocalStorageState(
|
||||||
|
'connectionEndpts',
|
||||||
|
ENDPOINTS[0].endpoint,
|
||||||
|
);
|
||||||
|
|
||||||
const [slippage, setSlippage] = useLocalStorageState('slippage', DEFAULT_SLIPPAGE.toString());
|
const [slippage, setSlippage] = useLocalStorageState(
|
||||||
|
'slippage',
|
||||||
|
DEFAULT_SLIPPAGE.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
const connection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
const connection = useMemo(() => new Connection(endpoint, 'recent'), [
|
||||||
const sendConnection = useMemo(() => new Connection(endpoint, 'recent'), [endpoint]);
|
endpoint,
|
||||||
|
]);
|
||||||
|
const sendConnection = useMemo(() => new Connection(endpoint, 'recent'), [
|
||||||
|
endpoint,
|
||||||
|
]);
|
||||||
|
|
||||||
const env = ENDPOINTS.find((end) => end.endpoint === endpoint)?.name || ENDPOINTS[0].name;
|
const env =
|
||||||
|
ENDPOINTS.find(end => end.endpoint === endpoint)?.name || ENDPOINTS[0].name;
|
||||||
|
|
||||||
const [tokens, setTokens] = useState<TokenInfo[]>([]);
|
const [tokens, setTokens] = useState<TokenInfo[]>([]);
|
||||||
const [tokenMap, setTokenMap] = useState<Map<string, TokenInfo>>(new Map());
|
const [tokenMap, setTokenMap] = useState<Map<string, TokenInfo>>(new Map());
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// fetch token files
|
// fetch token files
|
||||||
new TokenListProvider().resolve()
|
new TokenListProvider().resolve().then(container => {
|
||||||
.then((container) => {
|
const list = container
|
||||||
const list = container.excludeByTag("nft").filterByChainId(ENDPOINTS.find((end) => end.endpoint === endpoint)?.ChainId || ChainId.MainnetBeta).getList();
|
.excludeByTag('nft')
|
||||||
|
.filterByChainId(
|
||||||
|
ENDPOINTS.find(end => end.endpoint === endpoint)?.ChainId ||
|
||||||
|
ChainId.MainnetBeta,
|
||||||
|
)
|
||||||
|
.getList();
|
||||||
|
|
||||||
const knownMints = [...list].reduce((map, item) => {
|
const knownMints = [...list].reduce((map, item) => {
|
||||||
map.set(item.address, item);
|
map.set(item.address, item);
|
||||||
return map;
|
return map;
|
||||||
}, new Map<string, TokenInfo>());
|
}, new Map<string, TokenInfo>());
|
||||||
|
|
||||||
setTokenMap(knownMints);
|
setTokenMap(knownMints);
|
||||||
setTokens(list);
|
setTokens(list);
|
||||||
});
|
});
|
||||||
}, [env]);
|
}, [env]);
|
||||||
|
|
||||||
setProgramIds(env);
|
setProgramIds(env);
|
||||||
|
@ -109,7 +147,10 @@ export function ConnectionProvider({ children = undefined as any }) {
|
||||||
}, [connection]);
|
}, [connection]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = sendConnection.onAccountChange(new Account().publicKey, () => {});
|
const id = sendConnection.onAccountChange(
|
||||||
|
new Account().publicKey,
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
return () => {
|
return () => {
|
||||||
sendConnection.removeAccountChangeListener(id);
|
sendConnection.removeAccountChangeListener(id);
|
||||||
};
|
};
|
||||||
|
@ -128,7 +169,7 @@ export function ConnectionProvider({ children = undefined as any }) {
|
||||||
endpoint,
|
endpoint,
|
||||||
setEndpoint,
|
setEndpoint,
|
||||||
slippage: parseFloat(slippage),
|
slippage: parseFloat(slippage),
|
||||||
setSlippage: (val) => setSlippage(val.toString()),
|
setSlippage: val => setSlippage(val.toString()),
|
||||||
connection,
|
connection,
|
||||||
sendConnection,
|
sendConnection,
|
||||||
tokens,
|
tokens,
|
||||||
|
@ -173,7 +214,7 @@ const getErrorForTransaction = async (connection: Connection, txid: string) => {
|
||||||
|
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
if (tx?.meta && tx.meta.logMessages) {
|
if (tx?.meta && tx.meta.logMessages) {
|
||||||
tx.meta.logMessages.forEach((log) => {
|
tx.meta.logMessages.forEach(log => {
|
||||||
const regex = /Error: (.*)/gm;
|
const regex = /Error: (.*)/gm;
|
||||||
let m;
|
let m;
|
||||||
while ((m = regex.exec(log)) !== null) {
|
while ((m = regex.exec(log)) !== null) {
|
||||||
|
@ -200,12 +241,14 @@ export const sendTransaction = async (
|
||||||
awaitConfirmation = true,
|
awaitConfirmation = true,
|
||||||
) => {
|
) => {
|
||||||
let transaction = new Transaction();
|
let transaction = new Transaction();
|
||||||
instructions.forEach((instruction) => transaction.add(instruction));
|
instructions.forEach(instruction => transaction.add(instruction));
|
||||||
transaction.recentBlockhash = (await connection.getRecentBlockhash('max')).blockhash;
|
transaction.recentBlockhash = (
|
||||||
|
await connection.getRecentBlockhash('max')
|
||||||
|
).blockhash;
|
||||||
transaction.setSigners(
|
transaction.setSigners(
|
||||||
// fee payied by the wallet owner
|
// fee payied by the wallet owner
|
||||||
wallet.publicKey,
|
wallet.publicKey,
|
||||||
...signers.map((s) => s.publicKey)
|
...signers.map(s => s.publicKey),
|
||||||
);
|
);
|
||||||
if (signers.length > 0) {
|
if (signers.length > 0) {
|
||||||
transaction.partialSign(...signers);
|
transaction.partialSign(...signers);
|
||||||
|
@ -221,7 +264,10 @@ export const sendTransaction = async (
|
||||||
let slot = 0;
|
let slot = 0;
|
||||||
|
|
||||||
if (awaitConfirmation) {
|
if (awaitConfirmation) {
|
||||||
const confirmation = (await connection.confirmTransaction(txid, options && (options.commitment as any)));
|
const confirmation = await connection.confirmTransaction(
|
||||||
|
txid,
|
||||||
|
options && (options.commitment as any),
|
||||||
|
);
|
||||||
const status = confirmation.value;
|
const status = confirmation.value;
|
||||||
slot = confirmation.context.slot;
|
slot = confirmation.context.slot;
|
||||||
|
|
||||||
|
@ -231,16 +277,18 @@ export const sendTransaction = async (
|
||||||
message: 'Transaction failed...',
|
message: 'Transaction failed...',
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
{errors.map((err) => (
|
{errors.map(err => (
|
||||||
<div>{err}</div>
|
<div>{err}</div>
|
||||||
))}
|
))}
|
||||||
<ExplorerLink address={txid} type='transaction' />
|
<ExplorerLink address={txid} type="transaction" />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error(`Raw transaction ${txid} failed (${JSON.stringify(status)})`);
|
throw new Error(
|
||||||
|
`Raw transaction ${txid} failed (${JSON.stringify(status)})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,9 +90,9 @@ export const PROGRAM_IDS = [
|
||||||
name: 'devnet',
|
name: 'devnet',
|
||||||
timelock: () => ({
|
timelock: () => ({
|
||||||
programAccountId: new PublicKey(
|
programAccountId: new PublicKey(
|
||||||
'9gBhDCCKV7KELLFRY8sAJZXqDmvUfmNzFzpB2b4FUVVr',
|
'4GQdZW6Un8ffGhhVZ1mx2vFjgiBXoGyCqYGugVwCm6G5',
|
||||||
),
|
),
|
||||||
programId: new PublicKey('9iAeqqppjn7g1Jn8o2cQCqU5aQVV3h4q9bbWdKRbeC2w'),
|
programId: new PublicKey('GVRKB2HsYmq1JdQiLSSq776NcahBuMprcCDFud9jcg39'),
|
||||||
}),
|
}),
|
||||||
wormhole: () => ({
|
wormhole: () => ({
|
||||||
pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'),
|
pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'),
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# HOST Public Key used for additional swap fees
|
||||||
|
LEND_HOST_FEE_ADDRESS=''
|
||||||
|
|
||||||
|
# Rewired variables to comply with CRA restrictions
|
||||||
|
REACT_APP_LEND_HOST_FEE_ADDRESS=$LEND_HOST_FEE_ADDRESS
|
|
@ -0,0 +1 @@
|
||||||
|
GENERATE_SOURCEMAP = false
|
|
@ -0,0 +1,23 @@
|
||||||
|
const CracoLessPlugin = require('craco-less');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Handle relative paths to sibling packages
|
||||||
|
const appDirectory = fs.realpathSync(process.cwd());
|
||||||
|
const resolvePackage = relativePath => path.resolve(appDirectory, relativePath);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
plugin: CracoLessPlugin,
|
||||||
|
options: {
|
||||||
|
lessLoaderOptions: {
|
||||||
|
lessOptions: {
|
||||||
|
modifyVars: { '@primary-color': '#2abdd2' },
|
||||||
|
javascriptEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
"name": "proposals",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^4.4.0",
|
||||||
|
"@ant-design/pro-layout": "^6.7.0",
|
||||||
|
"@babel/preset-typescript": "^7.12.13",
|
||||||
|
"@craco/craco": "^5.7.0",
|
||||||
|
"@oyster/common": "0.0.1",
|
||||||
|
"@project-serum/serum": "^0.13.11",
|
||||||
|
"@project-serum/sol-wallet-adapter": "^0.1.4",
|
||||||
|
"@solana/spl-token": "0.0.13",
|
||||||
|
"@solana/spl-token-swap": "0.1.0",
|
||||||
|
"@solana/web3.js": "^0.86.2",
|
||||||
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
|
"@testing-library/react": "^9.5.0",
|
||||||
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
"@types/chart.js": "^2.9.29",
|
||||||
|
"@types/echarts": "^4.9.0",
|
||||||
|
"@types/react-router-dom": "^5.1.6",
|
||||||
|
"@types/testing-library__react": "^10.2.0",
|
||||||
|
"@welldone-software/why-did-you-render": "^6.0.5",
|
||||||
|
"antd": "^4.6.6",
|
||||||
|
"bn.js": "^5.1.3",
|
||||||
|
"bs58": "^4.0.1",
|
||||||
|
"buffer-layout": "^1.2.0",
|
||||||
|
"chart.js": "^2.9.4",
|
||||||
|
"craco-alias": "^2.1.1",
|
||||||
|
"craco-babel-loader": "^0.1.4",
|
||||||
|
"craco-less": "^1.17.0",
|
||||||
|
"echarts": "^4.9.0",
|
||||||
|
"eventemitter3": "^4.0.7",
|
||||||
|
"identicon.js": "^2.3.3",
|
||||||
|
"jazzicon": "^1.5.0",
|
||||||
|
"lodash": "^4.17.20",
|
||||||
|
"react": "16.13.1",
|
||||||
|
"react-dom": "16.13.1",
|
||||||
|
"react-github-btn": "^1.2.0",
|
||||||
|
"react-intl": "^5.10.2",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-scripts": "3.4.3",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prestart": "npm-link-shared ../common/node_modules/ . react",
|
||||||
|
"start": "craco start --verbose",
|
||||||
|
"start:lending": "craco start --verbose",
|
||||||
|
"build": "craco build",
|
||||||
|
"test": "craco test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"localnet:update": "solana-localnet update",
|
||||||
|
"localnet:up": "rm client/util/store/config.json; set -x; solana-localnet down; set -e; solana-localnet up",
|
||||||
|
"localnet:down": "solana-localnet down",
|
||||||
|
"localnet:logs": "solana-localnet logs -f",
|
||||||
|
"predeploy": "git pull --ff-only && yarn && yarn build",
|
||||||
|
"deploy": "gh-pages -d build",
|
||||||
|
"deploy:ar": "arweave deploy-dir build --key-file ",
|
||||||
|
"format:fix": "prettier --write \"**/*.+(js|jsx|ts|tsx|json|css|md)\""
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/solana-labs/oyster"
|
||||||
|
},
|
||||||
|
"homepage": ".",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bn.js": "^5.1.0",
|
||||||
|
"@types/bs58": "^4.0.1",
|
||||||
|
"@types/identicon.js": "^2.3.0",
|
||||||
|
"@types/jest": "^24.9.1",
|
||||||
|
"@types/node": "^12.12.62",
|
||||||
|
"arweave-deploy": "^1.9.1",
|
||||||
|
"gh-pages": "^3.1.0",
|
||||||
|
"prettier": "^2.1.2",
|
||||||
|
"npm-link-shared": "0.5.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-dom": "*"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"react": "16.13.1",
|
||||||
|
"react-dom": "16.13.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/logo.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="Oyster Lending on Solana" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>Oyster Lending | Solana</title>
|
||||||
|
<style type="text/css">
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"
|
||||||
|
integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
src="https://platform.twitter.com/widgets.js"
|
||||||
|
charset="utf-8"
|
||||||
|
></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"short_name": "Swap | Serum",
|
||||||
|
"name": "Swap | Serum",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 216 KiB |
|
@ -0,0 +1,321 @@
|
||||||
|
@import '~antd/dist/antd.dark.less';
|
||||||
|
@import './ant-custom.less';
|
||||||
|
|
||||||
|
body {
|
||||||
|
--row-highlight: @background-color-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-logo {
|
||||||
|
background-image: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjZDgzYWViIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAwIDEwMCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTQwLjM2LDUwLjkxYzAuMDA3LTguMTc0LDMuODM2LTExLjUyNSw3LjA0OC0xMi44OThjNi41NTEtMi44MDEsMTYuNzksMC4xNDEsMjMuODA5LDYuODQyICBjMS4yNDYsMS4xODksMi4zNjEsMi4zMDksMy4zNjUsMy4zNjhjLTUuNjg0LTguMzcyLTE1LjAyNS0xNy41NjYtMjkuMDY0LTE4Ljg1OWMtNy43OTQtMC43MTYtMTMuNzk0LDIuNzk5LTE2LjAzMyw5LjQwOCAgYy0yLjY0OSw3LjgyMSwwLjM0MSwxOS4zMDUsMTEuMTgxLDI2LjEyMmM2LjE1MywzLjg2OSwxMi4zLDYuODY5LDE3LjM0MSw5LjA0NWMtMC41NTEtMC4zNTQtMS4xMDUtMC43MTYtMS42Ni0xLjA5MSAgQzQ1LjczMyw2NS42NjIsNDAuMzU0LDU4LjI4MSw0MC4zNiw1MC45MXoiPjwvcGF0aD48cGF0aCBkPSJNNjAuMDI3LDYzLjc2MWMtMC4wNzgtNC43MTUsMS44OTgtOC4yNSw1LjQyMi05LjY5OGM0LjEzOS0xLjcsOS40OS0wLjAwNCwxMy42MzMsNC4zMjMgIGMwLjY5MSwwLjcyMywxLjMwMywxLjQ1MywxLjg3NSwyLjE4NGMtMS42NzQtMy42OTktNC41MS03Ljk1OC0xMS4xMjEtMTQuMjY5Yy02LjM3MS02LjA4MS0xNS44NzktOC45MTItMjEuNjQyLTYuNDUgIGMtMy44MTIsMS42MjktNS44MjksNS40NTQtNS44MzQsMTEuMDYxYy0wLjAxLDExLjgxNSwxNi4zMTIsMjEuNjQ2LDI1LjA3MiwyNi4wNzJDNjMuNzc1LDczLjc0Niw2MC4xMTUsNjkuMTY4LDYwLjAyNyw2My43NjF6Ij48L3BhdGg+PHBhdGggZD0iTTI3LjU5MSwzOC4xM2MyLjU1Ni03LjU0NSw5LjMzMS0xMS41NjgsMTguMTExLTEwLjc1OGMxMS41MjksMS4wNjEsMjAuMDE1LDcuMTQ4LDI2LjAxMywxMy45MiAgQzYxLjUsMjYuMDU0LDQ4Ljk2MywyMC4zMzksNDguODE3LDIwLjI3NGMtMy4yOTYtMS42ODgtNi43OTctMi41MzEtMTAuNDU3LTIuNTMxYy0xMi43NzQsMC0yMy4xNjcsMTAuNTgtMjMuMTY3LDIzLjU4MyAgYzAsNy45NjEsNC4yMDEsMTUuNTIxLDExLjIzOCwyMC4yMjJjMy43ODksMi41MywxMS40ODgsNS44MjQsMjAuMDQ2LDkuMDM4Yy0yLjI1NC0xLjIxNS00LjU2NC0yLjU0Ny02Ljg3NS00ICBDMjcuODg1LDU5LjIxOSwyNC42OSw0Ni42OTQsMjcuNTkxLDM4LjEzeiI+PC9wYXRoPjxwYXRoIGQ9Ik03Ny42MzcsNTkuNzY5Yy0zLjU2OC0zLjcyOS04LjA1Ny01LjI0Mi0xMS40MjgtMy44NTVjLTIuNzIxLDEuMTE4LTQuMjQ2LDMuOTY3LTQuMTgyLDcuODE0ICBjMC4xNDgsOS4wMzUsMTEuMzEzLDE1LjMxOCwxMy41ODgsMTYuNTkyYzMuNDg5LDEuOTU0LDcuNjI1LDIuMDg3LDcuOTA0LDEuOTM4czAuMjc5LTAuMTQ5LDAuNTMxLTAuNjUxICBjMC42Ni0xLjMwOSwxLjA1My00LjI3NSwwLjM2MS04Ljk2NkM4My43NzcsNjkuNDg5LDgyLjA5Niw2NC40MjcsNzcuNjM3LDU5Ljc2OXoiPjwvcGF0aD48L3N2Zz4=');
|
||||||
|
height: 32px;
|
||||||
|
pointer-events: none;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background-color: black;
|
||||||
|
color: lightgray;
|
||||||
|
padding: 10px 10px;
|
||||||
|
max-height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-spinner {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Banner {
|
||||||
|
min-height: 30px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff704;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
// z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Banner-description {
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav-scroll {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discord {
|
||||||
|
font-size: 30px;
|
||||||
|
color: #7289da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discord:hover {
|
||||||
|
color: #8ea1e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.telegram {
|
||||||
|
color: #32afed;
|
||||||
|
font-size: 28px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 30px;
|
||||||
|
display: flex;
|
||||||
|
width: 27px;
|
||||||
|
height: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.telegram:hover {
|
||||||
|
color: #2789de !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-link {
|
||||||
|
color: #61dafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-buttons {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
gap: 0.3rem;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-wrapper {
|
||||||
|
padding-left: 0.7rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-key {
|
||||||
|
padding: 0.1rem 0.5rem 0.1rem 0.7rem;
|
||||||
|
margin-left: 0.3rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-positive {
|
||||||
|
color: rgba(0, 255, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-negative {
|
||||||
|
color: rgba(255, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-basicLayout {
|
||||||
|
min-height: calc(100% - 30px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
padding: 6px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table {
|
||||||
|
margin: 0px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-inline-collapsed > .ant-menu-item {
|
||||||
|
padding-left: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-options {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-container table > thead > tr th {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-notification {
|
||||||
|
a {
|
||||||
|
color: blue;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-content {
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexColumn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-fill {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-row {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 5px 0px;
|
||||||
|
min-width: 0px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0px;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-box-pack: justify;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.card-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0px;
|
||||||
|
min-width: 0px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-slider {
|
||||||
|
margin: 20px 15px 40px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-sider.ant-layout-sider-collapsed .ant-pro-sider-logo {
|
||||||
|
padding: 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-global-header {
|
||||||
|
.ant-pro-global-header-logo a h1 {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: black !important;
|
||||||
|
color: white !important;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-statistic {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-statistic-content {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-selection-item {
|
||||||
|
.token-balance {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid grey;
|
||||||
|
padding: 0px 10px;
|
||||||
|
margin: 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-balance {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 5px;
|
||||||
|
color: @text-color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class='ant-layout-header'] {
|
||||||
|
height: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-amount-quote {
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-statisitc {
|
||||||
|
.ant-statistic-title {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-statistic-content {
|
||||||
|
max-height: 20px;
|
||||||
|
line-height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-statistic-content-value-int {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-statistic-content-value-decimal {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.bottom-links {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-sider {
|
||||||
|
background: transparent !important;
|
||||||
|
|
||||||
|
.ant-menu {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-amount-quote-stat {
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.exchange-card {
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./App.less";
|
||||||
|
import { Routes } from "./routes";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return <Routes />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
|
@ -0,0 +1,299 @@
|
||||||
|
import {
|
||||||
|
Account,
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
SystemProgram,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from '@solana/web3.js';
|
||||||
|
import { contexts, utils, actions } from '@oyster/common';
|
||||||
|
|
||||||
|
import { AccountLayout, MintLayout } from '@solana/spl-token';
|
||||||
|
import { initTimelockSetInstruction } from '../models/initTimelockSet';
|
||||||
|
import {
|
||||||
|
ConsensusAlgorithm,
|
||||||
|
ExecutionType,
|
||||||
|
TimelockSetLayout,
|
||||||
|
TimelockType,
|
||||||
|
} from '../models/timelock';
|
||||||
|
import { addSignatoryMintInstruction } from '../models/addSignatoryMint';
|
||||||
|
import { addVotingMintInstruction } from '../models/addVotingMint';
|
||||||
|
import { createSign } from 'crypto';
|
||||||
|
|
||||||
|
const { sendTransaction } = contexts.Connection;
|
||||||
|
const { createUninitializedMint } = actions;
|
||||||
|
const { notify } = utils;
|
||||||
|
|
||||||
|
export const createProposal = async (connection: Connection, wallet: any) => {
|
||||||
|
notify({
|
||||||
|
message: 'Initializing Proposal...',
|
||||||
|
description: 'Please wait...',
|
||||||
|
type: 'warn',
|
||||||
|
});
|
||||||
|
const PROGRAM_IDS = utils.programIds();
|
||||||
|
|
||||||
|
let signers: Account[] = [];
|
||||||
|
let instructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
|
const mintRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
MintLayout.span,
|
||||||
|
);
|
||||||
|
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
AccountLayout.span,
|
||||||
|
);
|
||||||
|
|
||||||
|
const timelockRentExempt = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
TimelockSetLayout.span,
|
||||||
|
);
|
||||||
|
const timelockSetKey = new Account();
|
||||||
|
|
||||||
|
const uninitializedTimelockSetInstruction = SystemProgram.createAccount({
|
||||||
|
fromPubkey: wallet.publicKey,
|
||||||
|
newAccountPubkey: timelockSetKey.publicKey,
|
||||||
|
lamports: timelockRentExempt,
|
||||||
|
space: TimelockSetLayout.span,
|
||||||
|
programId: PROGRAM_IDS.timelock.programAccountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
signers.push(timelockSetKey);
|
||||||
|
instructions.push(uninitializedTimelockSetInstruction);
|
||||||
|
|
||||||
|
const adminMint = createUninitializedMint(
|
||||||
|
instructions,
|
||||||
|
wallet.publicKey,
|
||||||
|
mintRentExempt,
|
||||||
|
signers,
|
||||||
|
);
|
||||||
|
|
||||||
|
const adminValidationKey = new Account();
|
||||||
|
const adminValidationInstruction = SystemProgram.createAccount({
|
||||||
|
fromPubkey: wallet.publicKey,
|
||||||
|
newAccountPubkey: adminValidationKey.publicKey,
|
||||||
|
lamports: mintRentExempt,
|
||||||
|
space: MintLayout.span,
|
||||||
|
programId: PROGRAM_IDS.timelock.programAccountId,
|
||||||
|
});
|
||||||
|
instructions.push(adminValidationInstruction);
|
||||||
|
signers.push(adminValidationKey);
|
||||||
|
|
||||||
|
const destinationAdminKey = new Account();
|
||||||
|
const destinationAdminInstruction = SystemProgram.createAccount({
|
||||||
|
fromPubkey: wallet.publicKey,
|
||||||
|
newAccountPubkey: destinationAdminKey.publicKey,
|
||||||
|
lamports: accountRentExempt,
|
||||||
|
space: AccountLayout.span,
|
||||||
|
programId: wallet.publicKey,
|
||||||
|
});
|
||||||
|
instructions.push(destinationAdminInstruction);
|
||||||
|
signers.push(destinationAdminKey);
|
||||||
|
|
||||||
|
instructions.push(
|
||||||
|
initTimelockSetInstruction(
|
||||||
|
timelockSetKey.publicKey,
|
||||||
|
adminMint,
|
||||||
|
adminValidationKey.publicKey,
|
||||||
|
destinationAdminKey.publicKey,
|
||||||
|
wallet.publicKey,
|
||||||
|
{
|
||||||
|
timelockType: TimelockType.CustomSingleSignerV1,
|
||||||
|
consensusAlgorithm: ConsensusAlgorithm.Majority,
|
||||||
|
executionType: ExecutionType.AllOrNothing,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let tx = await sendTransaction(
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
instructions,
|
||||||
|
signers,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
notify({
|
||||||
|
message: 'Proposal created.',
|
||||||
|
type: 'success',
|
||||||
|
description: `Transaction - ${tx}`,
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
/*await createSignatoryMint({
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
mintRentExempt,
|
||||||
|
timelockSetKey,
|
||||||
|
destinationAdminKey,
|
||||||
|
adminMint,
|
||||||
|
accountRentExempt,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createVotingMint({
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
mintRentExempt,
|
||||||
|
timelockSetKey,
|
||||||
|
destinationAdminKey,
|
||||||
|
adminMint,
|
||||||
|
accountRentExempt,
|
||||||
|
});*/
|
||||||
|
};
|
||||||
|
|
||||||
|
interface MintParams {
|
||||||
|
connection: Connection;
|
||||||
|
wallet: any;
|
||||||
|
mintRentExempt: number;
|
||||||
|
timelockSetKey: Account;
|
||||||
|
destinationAdminKey: Account;
|
||||||
|
adminMint: PublicKey;
|
||||||
|
accountRentExempt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSignatoryMint(args: MintParams) {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
mintRentExempt,
|
||||||
|
timelockSetKey,
|
||||||
|
destinationAdminKey,
|
||||||
|
adminMint,
|
||||||
|
accountRentExempt,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
const PROGRAM_IDS = utils.programIds();
|
||||||
|
let signatoryInstructions: TransactionInstruction[] = [];
|
||||||
|
let signatorySigners: Account[] = [];
|
||||||
|
const signatoryMint = createUninitializedMint(
|
||||||
|
signatoryInstructions,
|
||||||
|
wallet.publicKey,
|
||||||
|
mintRentExempt,
|
||||||
|
signatorySigners,
|
||||||
|
);
|
||||||
|
|
||||||
|
const signatoryValidationKey = new Account();
|
||||||
|
const signatoryValidationInstruction = SystemProgram.createAccount({
|
||||||
|
fromPubkey: wallet.publicKey,
|
||||||
|
newAccountPubkey: signatoryValidationKey.publicKey,
|
||||||
|
lamports: mintRentExempt,
|
||||||
|
space: MintLayout.span,
|
||||||
|
programId: PROGRAM_IDS.timelock.programAccountId,
|
||||||
|
});
|
||||||
|
signatoryInstructions.push(signatoryValidationInstruction);
|
||||||
|
signatorySigners.push(signatoryValidationKey);
|
||||||
|
|
||||||
|
const destinationSignatoryKey = new Account();
|
||||||
|
const destinationSignatoryInstruction = SystemProgram.createAccount({
|
||||||
|
fromPubkey: wallet.publicKey,
|
||||||
|
newAccountPubkey: destinationSignatoryKey.publicKey,
|
||||||
|
lamports: accountRentExempt,
|
||||||
|
space: AccountLayout.span,
|
||||||
|
programId: wallet.publicKey,
|
||||||
|
});
|
||||||
|
signatoryInstructions.push(destinationSignatoryInstruction);
|
||||||
|
signatorySigners.push(destinationSignatoryKey);
|
||||||
|
|
||||||
|
signatoryInstructions.push(
|
||||||
|
addSignatoryMintInstruction(
|
||||||
|
timelockSetKey.publicKey,
|
||||||
|
destinationAdminKey.publicKey,
|
||||||
|
adminMint,
|
||||||
|
signatoryMint,
|
||||||
|
signatoryValidationKey.publicKey,
|
||||||
|
destinationSignatoryKey.publicKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
notify({
|
||||||
|
message: 'Adding you as a Signatory...',
|
||||||
|
type: 'warn',
|
||||||
|
description: 'Please wait...',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let tx = await sendTransaction(
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
signatoryInstructions,
|
||||||
|
signatorySigners,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
notify({
|
||||||
|
message: 'Signatory added!',
|
||||||
|
type: 'success',
|
||||||
|
description: `Transaction - ${tx}`,
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createVotingMint(args: MintParams) {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
mintRentExempt,
|
||||||
|
timelockSetKey,
|
||||||
|
destinationAdminKey,
|
||||||
|
adminMint,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
const PROGRAM_IDS = utils.programIds();
|
||||||
|
let votingInstructions: TransactionInstruction[] = [];
|
||||||
|
let votingSigners: Account[] = [];
|
||||||
|
|
||||||
|
const votingMint = createUninitializedMint(
|
||||||
|
votingInstructions,
|
||||||
|
wallet.publicKey,
|
||||||
|
mintRentExempt,
|
||||||
|
votingSigners,
|
||||||
|
);
|
||||||
|
|
||||||
|
const votingValidationKey = new Account();
|
||||||
|
const votingValidationInstruction = SystemProgram.createAccount({
|
||||||
|
fromPubkey: wallet.publicKey,
|
||||||
|
newAccountPubkey: votingValidationKey.publicKey,
|
||||||
|
lamports: mintRentExempt,
|
||||||
|
space: MintLayout.span,
|
||||||
|
programId: PROGRAM_IDS.timelock.programAccountId,
|
||||||
|
});
|
||||||
|
votingInstructions.push(votingValidationInstruction);
|
||||||
|
votingSigners.push(votingValidationKey);
|
||||||
|
|
||||||
|
votingInstructions.push(
|
||||||
|
addVotingMintInstruction(
|
||||||
|
timelockSetKey.publicKey,
|
||||||
|
destinationAdminKey.publicKey,
|
||||||
|
adminMint,
|
||||||
|
votingMint,
|
||||||
|
votingValidationKey.publicKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
notify({
|
||||||
|
message: 'Adding Voting Mint...',
|
||||||
|
type: 'warn',
|
||||||
|
description: 'Please wait...',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let tx = await sendTransaction(
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
votingInstructions,
|
||||||
|
votingSigners,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
notify({
|
||||||
|
message: 'Voting Mint added!',
|
||||||
|
type: 'success',
|
||||||
|
description: `Transaction - ${tx}`,
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@import '~antd/es/style/themes/dark.less';
|
||||||
|
@import "~antd/dist/antd.dark.less";
|
||||||
|
|
||||||
|
@primary-color: #ff00a8;
|
||||||
|
@popover-background: #1a2029;
|
|
@ -0,0 +1,121 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './../../App.less';
|
||||||
|
import { Menu } from 'antd';
|
||||||
|
import {
|
||||||
|
PieChartOutlined,
|
||||||
|
GithubOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
ForkOutlined,
|
||||||
|
// LineChartOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
|
import BasicLayout from '@ant-design/pro-layout';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { LABELS } from '../../constants';
|
||||||
|
import config from './../../../package.json';
|
||||||
|
import { contexts, components } from '@oyster/common';
|
||||||
|
|
||||||
|
const { AppBar } = components;
|
||||||
|
const { useConnectionConfig } = contexts.Connection;
|
||||||
|
|
||||||
|
export const AppLayout = React.memo((props: any) => {
|
||||||
|
const { env } = useConnectionConfig();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const paths: { [key: string]: string } = {
|
||||||
|
'/dashboard': '2',
|
||||||
|
};
|
||||||
|
|
||||||
|
const current =
|
||||||
|
[...Object.keys(paths)].find(key => location.pathname.startsWith(key)) ||
|
||||||
|
'';
|
||||||
|
const defaultKey = paths[current] || '1';
|
||||||
|
const theme = 'light';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<div className="Banner">
|
||||||
|
<div className="Banner-description">{LABELS.AUDIT_WARNING}</div>
|
||||||
|
</div>
|
||||||
|
<BasicLayout
|
||||||
|
title={LABELS.APP_TITLE}
|
||||||
|
footerRender={() => (
|
||||||
|
<div className="footer" title={LABELS.FOOTER}>
|
||||||
|
{LABELS.FOOTER}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
navTheme={theme}
|
||||||
|
headerTheme={theme}
|
||||||
|
theme={theme}
|
||||||
|
layout="mix"
|
||||||
|
fixSiderbar={true}
|
||||||
|
primaryColor="#d83aeb"
|
||||||
|
logo={<div className="App-logo" />}
|
||||||
|
rightContentRender={() => <AppBar />}
|
||||||
|
links={[]}
|
||||||
|
menuContentRender={() => {
|
||||||
|
return (
|
||||||
|
<div className="links">
|
||||||
|
<Menu
|
||||||
|
theme={theme}
|
||||||
|
defaultSelectedKeys={[defaultKey]}
|
||||||
|
mode="inline"
|
||||||
|
>
|
||||||
|
<Menu.Item key="1" icon={<HomeOutlined />}>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: '/',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{LABELS.MENU_HOME}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="2" icon={<PieChartOutlined />}>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: '/dashboard',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{LABELS.MENU_DASHBOARD}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
<Menu
|
||||||
|
theme={theme}
|
||||||
|
defaultSelectedKeys={[defaultKey]}
|
||||||
|
selectable={false}
|
||||||
|
mode="inline"
|
||||||
|
className="bottom-links"
|
||||||
|
>
|
||||||
|
<Menu.Item key="16" icon={<ForkOutlined />}>
|
||||||
|
<a
|
||||||
|
title="Fork"
|
||||||
|
href={`${config.repository.url}/fork`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Fork
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
,
|
||||||
|
<Menu.Item key="15" icon={<GithubOutlined />}>
|
||||||
|
<a
|
||||||
|
title="Gtihub"
|
||||||
|
href={config.repository.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Github
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</BasicLayout>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './labels';
|
||||||
|
export * from './style';
|
|
@ -0,0 +1,15 @@
|
||||||
|
export const LABELS = {
|
||||||
|
CONNECT_LABEL: 'Connect Wallet',
|
||||||
|
AUDIT_WARNING:
|
||||||
|
'Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.',
|
||||||
|
FOOTER:
|
||||||
|
'This page was produced by the Solana Foundation ("SF") for internal educational and inspiration purposes only. SF does not encourage, induce or sanction the deployment, integration or use of Oyster or any similar application (including its code) in violation of applicable laws or regulations and hereby prohibits any such deployment, integration or use. Anyone using this code or a derivation thereof must comply with applicable laws and regulations when releasing related software.',
|
||||||
|
MENU_HOME: 'Home',
|
||||||
|
MENU_DASHBOARD: 'Dashboard',
|
||||||
|
APP_TITLE: 'Oyster Proposals',
|
||||||
|
CONNECT_BUTTON: 'Connect',
|
||||||
|
WALLET_TOOLTIP: 'Wallet public key',
|
||||||
|
WALLET_BALANCE: 'Wallet balance',
|
||||||
|
SETTINGS_TOOLTIP: 'Settings',
|
||||||
|
DASHBOARD_INFO: 'Connect to a wallet to view proposals.',
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const GUTTER = [16, { xs: 8, sm: 16, md: 16, lg: 16 }] as any;
|
||||||
|
|
||||||
|
export const SMALL_STATISTIC: React.CSSProperties = {
|
||||||
|
fontSize: 10,
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
import "./wdyr";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import "./index.css";
|
||||||
|
import App from "./App";
|
||||||
|
import * as serviceWorker from "./serviceWorker";
|
||||||
|
ReactDOM.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
document.getElementById("root")
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||||
|
serviceWorker.unregister();
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "Oyster Proposals",
|
||||||
|
"short_name": "Oyster Proposals",
|
||||||
|
"display": "standalone",
|
||||||
|
"start_url": "./",
|
||||||
|
"theme_color": "#002140",
|
||||||
|
"background_color": "#001529",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/icon-192x192.png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/icon-128x128.png",
|
||||||
|
"sizes": "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/icon-512x512.png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from '@solana/web3.js';
|
||||||
|
import { utils } from '@oyster/common';
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
import { TimelockConfig, TimelockInstruction } from './timelock';
|
||||||
|
|
||||||
|
/// Adds signatory mint to new timelock set. Gives signatory token to admin caller.
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Uninitialized Timelock set account
|
||||||
|
/// 1. `[writable]` Initialized Admin Token account
|
||||||
|
/// 2. `[writable]` Initialized Admin mint account
|
||||||
|
/// 3. `[writable]` Uninitialized Signatory Mint account
|
||||||
|
/// 4. `[writable]` Uninitialized Signatory Validation account
|
||||||
|
/// 5. `[writable]` Uninitialized Destination account for first signatory token
|
||||||
|
/// 6. `[]` Timelock Program
|
||||||
|
/// 7. '[]` Token program id
|
||||||
|
/// 8. `[]` Rent sysvar
|
||||||
|
export const addSignatoryMintInstruction = (
|
||||||
|
timelockSetAccount: PublicKey,
|
||||||
|
adminAccount: PublicKey,
|
||||||
|
adminMintAccount: PublicKey,
|
||||||
|
signatoryMintAccount: PublicKey,
|
||||||
|
signatoryValidationAccount: PublicKey,
|
||||||
|
destinationSignatoryAccount: PublicKey,
|
||||||
|
): TransactionInstruction => {
|
||||||
|
const PROGRAM_IDS = utils.programIds();
|
||||||
|
|
||||||
|
const dataLayout = BufferLayout.struct([]);
|
||||||
|
|
||||||
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
|
dataLayout.encode(
|
||||||
|
{
|
||||||
|
instruction: TimelockInstruction.AddSignatoryMint,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
{ pubkey: timelockSetAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: adminAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: adminMintAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: signatoryMintAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: signatoryValidationAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: destinationSignatoryAccount, isSigner: true, isWritable: true },
|
||||||
|
{
|
||||||
|
pubkey: PROGRAM_IDS.timelock.programAccountId,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
|
||||||
|
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: PROGRAM_IDS.timelock.programId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,60 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from '@solana/web3.js';
|
||||||
|
import { utils } from '@oyster/common';
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
import { TimelockConfig, TimelockInstruction } from './timelock';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
|
||||||
|
/// Adds voting mint to new timelock set.
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Uninitialized Timelock set account
|
||||||
|
/// 1. `[writable]` Initialized Admin Token account
|
||||||
|
/// 2. `[writable]` Initialized Admin mint account
|
||||||
|
/// 3. `[writable]` Uninitialized Voting Mint account
|
||||||
|
/// 4. `[writable]` Uninitialized Voting Validation account
|
||||||
|
/// 5. `[]` Timelock Program
|
||||||
|
/// 6. '[]` Token program id
|
||||||
|
/// 7. `[]` Rent sysvar
|
||||||
|
export const addVotingMintInstruction = (
|
||||||
|
timelockSetAccount: PublicKey,
|
||||||
|
adminAccount: PublicKey,
|
||||||
|
adminMintAccount: PublicKey,
|
||||||
|
votingMintAccount: PublicKey,
|
||||||
|
votingValidationAccount: PublicKey,
|
||||||
|
): TransactionInstruction => {
|
||||||
|
const PROGRAM_IDS = utils.programIds();
|
||||||
|
|
||||||
|
const dataLayout = BufferLayout.struct([]);
|
||||||
|
|
||||||
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
|
dataLayout.encode(
|
||||||
|
{
|
||||||
|
instruction: TimelockInstruction.AddSignatoryMint,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
{ pubkey: timelockSetAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: adminAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: adminMintAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: votingMintAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: votingValidationAccount, isSigner: true, isWritable: true },
|
||||||
|
{
|
||||||
|
pubkey: PROGRAM_IDS.timelock.programAccountId,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
|
||||||
|
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: PROGRAM_IDS.timelock.programId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from '@solana/web3.js';
|
||||||
|
import { utils } from '@oyster/common';
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
import { TimelockConfig, TimelockInstruction } from './timelock';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
|
||||||
|
/// Initializes a new empty Timelocked set of Instructions that will be executed at various slots in the future in draft mode.
|
||||||
|
/// Grants Admin token to caller.
|
||||||
|
///
|
||||||
|
/// 1. `[writable]` Uninitialized Timelock set account
|
||||||
|
/// 2. `[writable]` Uninitialized Admin Mint account
|
||||||
|
/// 3. `[writable]` Uninitialized Admin Validation account
|
||||||
|
/// 4. `[writable]` Uninitialized Destination account for first admin token
|
||||||
|
/// 5. `[]` Wallet pubkey - owner of destination account for first admin token
|
||||||
|
/// 6. `[]` Timelock Program
|
||||||
|
/// 7. '[]` Token program id
|
||||||
|
/// 8. `[]` Rent sysvar
|
||||||
|
export const initTimelockSetInstruction = (
|
||||||
|
timelockSetAccount: PublicKey,
|
||||||
|
adminMintAccount: PublicKey,
|
||||||
|
adminValidationAccount: PublicKey,
|
||||||
|
destinationAdminAccount: PublicKey,
|
||||||
|
wallet: PublicKey,
|
||||||
|
timelockConfig: TimelockConfig,
|
||||||
|
): TransactionInstruction => {
|
||||||
|
const PROGRAM_IDS = utils.programIds();
|
||||||
|
|
||||||
|
const dataLayout = BufferLayout.struct([
|
||||||
|
BufferLayout.u8('instruction'),
|
||||||
|
BufferLayout.u8('consensusAlgorithm'),
|
||||||
|
BufferLayout.u8('executionType'),
|
||||||
|
BufferLayout.u8('timelockType'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
|
dataLayout.encode(
|
||||||
|
{
|
||||||
|
instruction: TimelockInstruction.InitTimelockSet,
|
||||||
|
consensusAlgorithm: new BN(timelockConfig.consensusAlgorithm),
|
||||||
|
executionType: new BN(timelockConfig.executionType),
|
||||||
|
timelockType: new BN(timelockConfig.timelockType),
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
{ pubkey: timelockSetAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: adminMintAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: adminValidationAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: destinationAdminAccount, isSigner: true, isWritable: true },
|
||||||
|
{ pubkey: wallet, isSigner: true, isWritable: false },
|
||||||
|
{
|
||||||
|
pubkey: PROGRAM_IDS.timelock.programAccountId,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
|
||||||
|
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: PROGRAM_IDS.timelock.programId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,158 @@
|
||||||
|
import * as Layout from '../utils/layout';
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { AccountInfo, PublicKey } from '@solana/web3.js';
|
||||||
|
|
||||||
|
export enum TimelockInstruction {
|
||||||
|
InitTimelockSet = 1,
|
||||||
|
AddSignatoryMint = 11,
|
||||||
|
AddVotingMint = 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimelockConfig {
|
||||||
|
consensusAlgorithm: ConsensusAlgorithm;
|
||||||
|
executionType: ExecutionType;
|
||||||
|
timelockType: TimelockType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ConsensusAlgorithm {
|
||||||
|
Majority = 0,
|
||||||
|
SuperMajority = 1,
|
||||||
|
FullConsensus = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExecutionType {
|
||||||
|
AllOrNothing = 0,
|
||||||
|
AnyAboveVoteFinishSlot = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TimelockType {
|
||||||
|
CustomSingleSignerV1 = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TimelockStateStatus {
|
||||||
|
/// Draft
|
||||||
|
Draft = 0,
|
||||||
|
/// Taking votes
|
||||||
|
Voting = 1,
|
||||||
|
|
||||||
|
/// Votes complete, in execution phase
|
||||||
|
Executing = 2,
|
||||||
|
|
||||||
|
/// Completed, can be rebooted
|
||||||
|
Completed = 3,
|
||||||
|
|
||||||
|
/// Deleted
|
||||||
|
Deleted = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimelockState {
|
||||||
|
status: TimelockStateStatus;
|
||||||
|
totalVotingTokensMinted: BN;
|
||||||
|
timelockTransactions: PublicKey[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimelockSetLayout: typeof BufferLayout.Structure = BufferLayout.struct(
|
||||||
|
[
|
||||||
|
BufferLayout.u8('version'),
|
||||||
|
Layout.publicKey('signatoryMint'),
|
||||||
|
Layout.publicKey('adminMint'),
|
||||||
|
Layout.publicKey('votingMint'),
|
||||||
|
Layout.publicKey('signatoryValidation'),
|
||||||
|
Layout.publicKey('adminValidation'),
|
||||||
|
Layout.publicKey('votingValidation'),
|
||||||
|
BufferLayout.u8('timelockStateStatus'),
|
||||||
|
Layout.uint64('totalVotingTokensMinted'),
|
||||||
|
Layout.publicKey('timelockTxn1'),
|
||||||
|
Layout.publicKey('timelockTxn2'),
|
||||||
|
Layout.publicKey('timelockTxn3'),
|
||||||
|
Layout.publicKey('timelockTxn4'),
|
||||||
|
Layout.publicKey('timelockTxn5'),
|
||||||
|
Layout.publicKey('timelockTxn6'),
|
||||||
|
Layout.publicKey('timelockTxn7'),
|
||||||
|
Layout.publicKey('timelockTxn8'),
|
||||||
|
Layout.publicKey('timelockTxn9'),
|
||||||
|
Layout.publicKey('timelockTxn10'),
|
||||||
|
BufferLayout.u8('consensusAlgorithm'),
|
||||||
|
BufferLayout.u8('executionType'),
|
||||||
|
BufferLayout.u8('timelockType'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface TimelockSet {
|
||||||
|
/// Version of the struct
|
||||||
|
version: number;
|
||||||
|
|
||||||
|
/// Mint that creates signatory tokens of this instruction
|
||||||
|
/// If there are outstanding signatory tokens, then cannot leave draft state. Signatories must burn tokens (ie agree
|
||||||
|
/// to move instruction to voting state) and bring mint to net 0 tokens outstanding. Each signatory gets 1 (serves as flag)
|
||||||
|
signatoryMint: PublicKey;
|
||||||
|
|
||||||
|
/// Admin ownership mint. One token is minted, can be used to grant admin status to a new person.
|
||||||
|
adminMint: PublicKey;
|
||||||
|
|
||||||
|
/// Mint that creates voting tokens of this instruction
|
||||||
|
votingMint: PublicKey;
|
||||||
|
|
||||||
|
/// Used to validate signatory tokens in a round trip transfer
|
||||||
|
signatoryValidation: PublicKey;
|
||||||
|
|
||||||
|
/// Used to validate admin tokens in a round trip transfer
|
||||||
|
adminValidation: PublicKey;
|
||||||
|
|
||||||
|
/// Used to validate voting tokens in a round trip transfer
|
||||||
|
votingValidation: PublicKey;
|
||||||
|
|
||||||
|
/// Reserve state
|
||||||
|
state: TimelockState;
|
||||||
|
|
||||||
|
/// configuration values
|
||||||
|
config: TimelockConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimelockSetParser = (
|
||||||
|
pubKey: PublicKey,
|
||||||
|
info: AccountInfo<Buffer>,
|
||||||
|
) => {
|
||||||
|
const buffer = Buffer.from(info.data);
|
||||||
|
const data = TimelockSetLayout.decode(buffer);
|
||||||
|
|
||||||
|
const details = {
|
||||||
|
pubkey: pubKey,
|
||||||
|
account: {
|
||||||
|
...info,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
version: data.version,
|
||||||
|
signatoryMint: data.signatoryMint,
|
||||||
|
adminMint: data.adminMint,
|
||||||
|
votingMint: data.votingMint,
|
||||||
|
signatoryValidation: data.signatoryValidation,
|
||||||
|
adminValidation: data.adminValidation,
|
||||||
|
votingValidation: data.votingValidation,
|
||||||
|
state: {
|
||||||
|
status: TimelockStateStatus[data.timelockStateStatus],
|
||||||
|
totalVotingTokensMinted: data.totalVotingTokensMinted,
|
||||||
|
timelockTransactions: [
|
||||||
|
data.timelockTxn1,
|
||||||
|
data.timelockTxn2,
|
||||||
|
data.timelockTxn3,
|
||||||
|
data.timelockTxn4,
|
||||||
|
data.timelockTxn5,
|
||||||
|
data.timelockTxn6,
|
||||||
|
data.timelockTxn7,
|
||||||
|
data.timelockTxn8,
|
||||||
|
data.timelockTxn9,
|
||||||
|
data.timelockTxn10,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
consensusAlgorithm: data.consensusAlgorithm,
|
||||||
|
executionType: data.executionType,
|
||||||
|
timelockType: data.timelockType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return details;
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="react-scripts" />
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||||
|
import React from 'react';
|
||||||
|
import { contexts } from '@oyster/common';
|
||||||
|
import { AppLayout } from './components/Layout';
|
||||||
|
|
||||||
|
import { DashboardView, HomeView } from './views';
|
||||||
|
const { WalletProvider } = contexts.Wallet;
|
||||||
|
const { ConnectionProvider } = contexts.Connection;
|
||||||
|
const { AccountsProvider } = contexts.Accounts;
|
||||||
|
|
||||||
|
export function Routes() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HashRouter basename={'/'}>
|
||||||
|
<ConnectionProvider>
|
||||||
|
<WalletProvider>
|
||||||
|
<AccountsProvider>
|
||||||
|
<AppLayout>
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/" component={() => <HomeView />} />
|
||||||
|
<Route exact path="/dashboard" children={<DashboardView />} />
|
||||||
|
</Switch>
|
||||||
|
</AppLayout>
|
||||||
|
</AccountsProvider>
|
||||||
|
</WalletProvider>
|
||||||
|
</ConnectionProvider>
|
||||||
|
</HashRouter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
// This optional code is used to register a service worker.
|
||||||
|
// register() is not called by default.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on subsequent visits to a page, after all the
|
||||||
|
// existing tabs open on the page have been closed, since previously cached
|
||||||
|
// resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
type Config = {
|
||||||
|
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||||
|
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function register(config?: Config) {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (isLocalhost) {
|
||||||
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl, config);
|
||||||
|
|
||||||
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
|
// service worker/PWA documentation.
|
||||||
|
navigator.serviceWorker.ready.then(() => {
|
||||||
|
console.log(
|
||||||
|
'This web app is being served cache-first by a service ' +
|
||||||
|
'worker. To learn more, visit https://bit.ly/CRA-PWA',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is not localhost. Just register service worker
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl: string, config?: Config) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
if (installingWorker == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the updated precached content has been fetched,
|
||||||
|
// but the previous service worker will still serve the older
|
||||||
|
// content until all client tabs are closed.
|
||||||
|
console.log(
|
||||||
|
'New content is available and will be used when all ' +
|
||||||
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onUpdate) {
|
||||||
|
config.onUpdate(registration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onSuccess) {
|
||||||
|
config.onSuccess(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl, {
|
||||||
|
headers: { 'Service-Worker': 'script' },
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready
|
||||||
|
.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
|
@ -0,0 +1,9 @@
|
||||||
|
declare module 'buffer-layout' {
|
||||||
|
const bl: any;
|
||||||
|
export = bl;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'jazzicon' {
|
||||||
|
const jazzicon: any;
|
||||||
|
export = jazzicon;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module '@project-serum/sol-wallet-adapter' {
|
||||||
|
const adapter: any;
|
||||||
|
export = adapter;
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout for a public key
|
||||||
|
*/
|
||||||
|
export const publicKey = (property = 'publicKey'): unknown => {
|
||||||
|
const publicKeyLayout = BufferLayout.blob(32, property);
|
||||||
|
|
||||||
|
const _decode = publicKeyLayout.decode.bind(publicKeyLayout);
|
||||||
|
const _encode = publicKeyLayout.encode.bind(publicKeyLayout);
|
||||||
|
|
||||||
|
publicKeyLayout.decode = (buffer: Buffer, offset: number) => {
|
||||||
|
const data = _decode(buffer, offset);
|
||||||
|
return new PublicKey(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
publicKeyLayout.encode = (key: PublicKey, buffer: Buffer, offset: number) => {
|
||||||
|
return _encode(key.toBuffer(), buffer, offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
return publicKeyLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout for a 64bit unsigned value
|
||||||
|
*/
|
||||||
|
export const uint64 = (property = 'uint64'): unknown => {
|
||||||
|
const layout = BufferLayout.blob(8, property);
|
||||||
|
|
||||||
|
const _decode = layout.decode.bind(layout);
|
||||||
|
const _encode = layout.encode.bind(layout);
|
||||||
|
|
||||||
|
layout.decode = (buffer: Buffer, offset: number) => {
|
||||||
|
const data = _decode(buffer, offset);
|
||||||
|
return new BN(
|
||||||
|
[...data]
|
||||||
|
.reverse()
|
||||||
|
.map(i => `00${i.toString(16)}`.slice(-2))
|
||||||
|
.join(''),
|
||||||
|
16,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.encode = (num: BN, buffer: Buffer, offset: number) => {
|
||||||
|
const a = num.toArray().reverse();
|
||||||
|
let b = Buffer.from(a);
|
||||||
|
if (b.length !== 8) {
|
||||||
|
const zeroPad = Buffer.alloc(8);
|
||||||
|
b.copy(zeroPad);
|
||||||
|
b = zeroPad;
|
||||||
|
}
|
||||||
|
return _encode(b, buffer, offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: wrap in BN (what about decimals?)
|
||||||
|
export const uint128 = (property = 'uint128'): unknown => {
|
||||||
|
const layout = BufferLayout.blob(16, property);
|
||||||
|
|
||||||
|
const _decode = layout.decode.bind(layout);
|
||||||
|
const _encode = layout.encode.bind(layout);
|
||||||
|
|
||||||
|
layout.decode = (buffer: Buffer, offset: number) => {
|
||||||
|
const data = _decode(buffer, offset);
|
||||||
|
return new BN(
|
||||||
|
[...data]
|
||||||
|
.reverse()
|
||||||
|
.map(i => `00${i.toString(16)}`.slice(-2))
|
||||||
|
.join(''),
|
||||||
|
16,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.encode = (num: BN, buffer: Buffer, offset: number) => {
|
||||||
|
const a = num.toArray().reverse();
|
||||||
|
let b = Buffer.from(a);
|
||||||
|
if (b.length !== 16) {
|
||||||
|
const zeroPad = Buffer.alloc(16);
|
||||||
|
b.copy(zeroPad);
|
||||||
|
b = zeroPad;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _encode(b, buffer, offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout for a Rust String type
|
||||||
|
*/
|
||||||
|
export const rustString = (property = 'string'): unknown => {
|
||||||
|
const rsl = BufferLayout.struct(
|
||||||
|
[
|
||||||
|
BufferLayout.u32('length'),
|
||||||
|
BufferLayout.u32('lengthPadding'),
|
||||||
|
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'),
|
||||||
|
],
|
||||||
|
property,
|
||||||
|
);
|
||||||
|
const _decode = rsl.decode.bind(rsl);
|
||||||
|
const _encode = rsl.encode.bind(rsl);
|
||||||
|
|
||||||
|
rsl.decode = (buffer: Buffer, offset: number) => {
|
||||||
|
const data = _decode(buffer, offset);
|
||||||
|
return data.chars.toString('utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
rsl.encode = (str: string, buffer: Buffer, offset: number) => {
|
||||||
|
const data = {
|
||||||
|
chars: Buffer.from(str, 'utf8'),
|
||||||
|
};
|
||||||
|
return _encode(data, buffer, offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
return rsl;
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Card, Col, Row } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { GUTTER, LABELS } from '../../constants';
|
||||||
|
import { contexts } from '@oyster/common';
|
||||||
|
import './style.less';
|
||||||
|
const { useWallet } = contexts.Wallet;
|
||||||
|
|
||||||
|
export const DashboardView = () => {
|
||||||
|
const { connected } = useWallet();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dashboard-container">
|
||||||
|
<Row gutter={GUTTER}></Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,84 @@
|
||||||
|
@import '~antd/es/style/themes/dark.less';
|
||||||
|
|
||||||
|
.dashboard-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: @text-color;
|
||||||
|
|
||||||
|
& > :nth-child(n) {
|
||||||
|
flex: 15%;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
flex: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :last-child {
|
||||||
|
flex: 200px
|
||||||
|
}
|
||||||
|
|
||||||
|
border-bottom: 1px solid @border-color-split;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :last-child {
|
||||||
|
span {
|
||||||
|
font-size: 10px;
|
||||||
|
color: gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header {
|
||||||
|
|
||||||
|
& > :nth-child(n) {
|
||||||
|
flex: 15%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
text-align: left;
|
||||||
|
flex: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :last-child {
|
||||||
|
flex: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.dashboard-splash {
|
||||||
|
max-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Row } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { GUTTER } from '../../constants';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import { createProposal } from '../../actions/createProposal';
|
||||||
|
import { contexts } from '@oyster/common';
|
||||||
|
const { useWallet } = contexts.Wallet;
|
||||||
|
const { useConnection } = contexts.Connection;
|
||||||
|
export const HomeView = () => {
|
||||||
|
const wallet = useWallet();
|
||||||
|
const connection = useConnection();
|
||||||
|
return (
|
||||||
|
<div className="flexColumn">
|
||||||
|
<Row gutter={GUTTER} className="home-info-row">
|
||||||
|
<Button onClick={() => createProposal(connection, wallet.wallet)}>
|
||||||
|
Click me
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { HomeView } from './home';
|
||||||
|
export { DashboardView } from './dashboard';
|
|
@ -0,0 +1,8 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||||
|
whyDidYouRender(React, {
|
||||||
|
trackAllPureComponents: true,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"typeRoots": ["../../types"]
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
declare module "buffer-layout" {
|
||||||
|
const bl: any;
|
||||||
|
export = bl;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "jazzicon" {
|
||||||
|
const jazzicon: any;
|
||||||
|
export = jazzicon;
|
||||||
|
}
|
Loading…
Reference in New Issue