feat: bridge

This commit is contained in:
bartosz-lipinski 2021-03-01 19:09:40 -06:00
parent 2c04bb5c46
commit e5750fb129
13 changed files with 316 additions and 63 deletions

View File

@ -8,8 +8,10 @@ const { useConnectionConfig } = contexts.Connection;
const { Option } = Select;
// User can choose a collateral they want to use, and then this will display the balance they have in Oyster's lending
// reserve for that collateral type.
// TODO: add way to add new token account
export function SolanaInput(props: {
title: string;
amount?: number | null;
@ -27,7 +29,7 @@ export function SolanaInput(props: {
const [lastAmount, setLastAmount] = useState<string>('');
const renderReserveAccounts = [].map((reserve: any) => {
const renderTokens = [].map((reserve: any) => {
const mint = reserve.info.liquidityMint.toBase58();
const address = reserve.pubkey.toBase58();
const name = getTokenName(tokenMap, mint);
@ -97,7 +99,7 @@ export function SolanaInput(props: {
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{renderReserveAccounts}
{renderTokens}
</Select>
) : (
<TokenDisplay

View File

@ -19,14 +19,16 @@ export const AppLayout = React.memo((props: any) => {
'/faucet': '7',
};
const isRoot = location.pathname !== '/';
const current =
[...Object.keys(paths)].find(key => location.pathname.startsWith(key)) ||
'';
return (
<div className={`App${wormholeReady ? `` : ` wormhole-bg`}`}>
<Wormhole onCreated={() => setWormholeReady(true)}>
<div className={`App`}>
<Wormhole onCreated={() => setWormholeReady(true)} show={isRoot}>
<Layout title={LABELS.APP_TITLE}>
{location.pathname !== '/' && (
{isRoot && (
<Header className="App-Bar">
<div className="app-title">
<h2>WORMHOLE</h2>

View File

@ -21,7 +21,7 @@ export const hasWindow =
* @returns {[]}
*/
export const calculateTorusProperties = (particleSize: number): any => {
let bufferGeometry: BufferGeometry = new TorusGeometry(60, 45, 160, 160);
let bufferGeometry: BufferGeometry = new TorusGeometry(60, 45, 60, 80);
// if normal and uv attributes are not removed,
// mergeVertices() can't consolidate identical vertices

View File

@ -12,7 +12,7 @@ import {
import disc from "./disc.png";
// The individual "particle size".
const PARTICLE_SIZE = 15;
const PARTICLE_SIZE = 10;
/**
* Three JS Point Geometry calculating points around a Torus.
@ -28,7 +28,7 @@ const WormholeGeometry = () => {
const uniforms = React.useMemo(
() => ({
// Adapt the color of the WormholeCanvas here.
color: { value: new Color("gray") },
color: { value: new Color("dimgrey") },
pointTexture: {
value: pointTexture,
},
@ -47,7 +47,7 @@ const WormholeGeometry = () => {
if (mesh.current) {
// x-Axis defines the "top" we're looking at, try e.g. 30.5
mesh.current.rotation.x = 30;
mesh.current.rotation.z += 0.001;
mesh.current.rotation.z += 0.0005;
}
});

View File

@ -12,10 +12,13 @@ import './wormhole.less';
const Wormhole = ({
onCreated,
children,
show,
}: {
onCreated: any;
show: boolean;
children: React.ReactNode;
}) => (
show ? <>{children}</> :
<>
<WormholeCanvas onCreated={onCreated} />
<div className="wormhole-overlay">{children}</div>

View File

@ -0,0 +1,94 @@
import React, { createContext, FunctionComponent, useCallback, useContext, useEffect, useState } from "react";
import Web3 from "web3";
import Web3Modal from "web3modal";
// @ts-ignore
import WalletConnectProvider from "@walletconnect/web3-provider";
// @ts-ignore
import Fortmatic from "fortmatic";
import Torus from "@toruslabs/torus-embed";
import Authereum from "authereum";
import { Bitski } from "bitski";
import { useWallet } from "@oyster/common";
const providerOptions = {
walletconnect: {
package: WalletConnectProvider,
options: {
infuraId: process.env.REACT_APP_INFURA_ID
}
},
torus: {
package: Torus
},
fortmatic: {
package: Fortmatic,
options: {
key: process.env.REACT_APP_FORTMATIC_KEY
}
},
authereum: {
package: Authereum
},
bitski: {
package: Bitski,
options: {
clientId: process.env.REACT_APP_BITSKI_CLIENT_ID,
callbackUrl: window.location.href + "bitski-callback.html"
}
}
};
export interface EthereumContextState {
connect: () => Promise<void>;
web3?: Web3
}
export const EthereumContext = createContext<EthereumContextState>({
connect: async () => { },
});
export const EthereumProvider: FunctionComponent = ({children}) => {
const [web3, setWeb3] = useState<Web3>();
const { connected } = useWallet();
const connect = useCallback(async () => {
const web3Modal = new Web3Modal({
theme: "dark",
network: "mainnet", // optional (TODO: add network selector)
cacheProvider: false, // optional
providerOptions // required
});
const provider = await web3Modal.connect();
provider.on('error', (e: any) => console.error('WS Error', e));
provider.on('end', (e: any) => console.error('WS End', e));
provider.on('disconnect', (error: { code: number; message: string }) => {
console.log(error);
});
provider.on('connect', (info: { chainId: number }) => {
console.log(info);
});
const instance = new Web3(provider);
setWeb3(instance);
}, [setWeb3]);
useEffect(() => {
if(connected) {
connect();
}
}, [connect, connected])
return (
<EthereumContext.Provider value={{ web3, connect }}>
{children}
</EthereumContext.Provider>
);
}
export const useEthereum = () => {
const context = useContext(EthereumContext);
return context;
}

View File

@ -0,0 +1,3 @@
export * from "./market";
export * from "./tokenPair";
export * from "./ethereum";

View File

@ -73,7 +73,7 @@ export const useCurrencyLeg = (defaultMint?: string) => {
);
};
export function CurrencyPairProvider({ children = null as any }) {
export function TokenPairProvider({ children = null as any }) {
const connection = useConnection();
const { tokens } = useConnectionConfig();

View File

@ -1,7 +1,7 @@
import { HashRouter, Route, Switch } from 'react-router-dom';
import React from 'react';
import { contexts } from '@oyster/common';
import { MarketProvider } from './contexts/market';
import { MarketProvider, TokenPairProvider, EthereumProvider } from './contexts';
import { AppLayout } from './components/Layout';
import {
@ -19,17 +19,21 @@ export function Routes() {
<HashRouter basename={'/'}>
<ConnectionProvider>
<WalletProvider>
<AccountsProvider>
<MarketProvider>
<AppLayout>
<Switch>
<Route exact path="/" component={() => <HomeView />} />
<Route path="/move" children={<TransferView />} />
<Route exact path="/faucet" children={<FaucetView />} />
</Switch>
</AppLayout>
</MarketProvider>
</AccountsProvider>
<EthereumProvider>
<AccountsProvider>
<MarketProvider>
<TokenPairProvider>
<AppLayout>
<Switch>
<Route exact path="/" component={() => <HomeView />} />
<Route path="/move" children={<TransferView />} />
<Route exact path="/faucet" children={<FaucetView />} />
</Switch>
</AppLayout>
</TokenPairProvider>
</MarketProvider>
</AccountsProvider>
</EthereumProvider>
</WalletProvider>
</ConnectionProvider>
</HashRouter>

View File

@ -1,17 +1,16 @@
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import { Card } from 'antd';
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
import { LABELS } from '../../constants';
import { contexts, utils, ConnectButton } from '@oyster/common';
import { contexts, utils, ConnectButton, useConnection, useWallet } from '@oyster/common';
import { useHistory, useLocation } from "react-router-dom";
import { Transfer } from '../../components/Transfer';
const { useConnection } = contexts.Connection;
const { useWallet } = contexts.Wallet;
const { notify } = utils;
import { useEthereum } from '../../contexts';
export const TransferView = () => {
const connection = useConnection();
const { wallet } = useWallet();
const { wallet, connected } = useWallet();
const { connect: connectEth } = useEthereum();
const tabStyle: React.CSSProperties = { width: 120 };
const tabList = [
@ -31,6 +30,12 @@ export const TransferView = () => {
},
];
useEffect(() => {
// connectEth();
}, [connected, connectEth])
const location = useLocation();
const history = useHistory();
const activeTab = location.pathname.indexOf("sol") >= 0 ? "sol" : "eth";

View File

@ -1,10 +1,11 @@
import { KnownToken, useLocalStorageState } from '../utils/utils';
import { useLocalStorageState } from '../utils/utils';
import { Account, clusterApiUrl, Connection, Transaction, TransactionInstruction } from '@solana/web3.js';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { notify } from '../utils/notifications';
import { ExplorerLink } from '../components/ExplorerLink';
import LocalTokens from '../config/tokens.json';
import { setProgramIds } from '../utils/ids';
import { KnownToken, TokenListProvider } from '@solana/spl-token-registry';
export type ENV = 'mainnet-beta' | 'testnet' | 'devnet' | 'localnet' | 'lending';
@ -67,13 +68,11 @@ export function ConnectionProvider({ children = undefined as any }) {
const [tokenMap, setTokenMap] = useState<Map<string, KnownToken>>(new Map());
useEffect(() => {
// fetch token files
window
.fetch(`https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/${env}.json`)
.then((res) => {
return res.json();
})
.catch((err) => [])
.then((list: KnownToken[]) => {
new TokenListProvider().resolve(env)
.then((list) => {
const knownMints = [...LocalTokens, ...list].reduce((map, item) => {
map.set(item.mintAddress, item);
return map;

View File

@ -0,0 +1,114 @@
import { MintLayout, AccountLayout, Token } from '@solana/spl-token';
import {
Connection,
PublicKey,
Transaction,
Account,
SystemProgram,
} from '@solana/web3.js';
export const mintNFT = async (
connection: Connection,
wallet: {
publicKey: PublicKey;
signTransaction: (tx: Transaction) => Transaction;
},
// SOL account
owner: PublicKey,
) => {
const TOKEN_PROGRAM_ID = new PublicKey(
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
);
const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
);
const mintAccount = new Account();
const tokenAccount = new Account();
// Allocate memory for the account
const mintRent = await connection.getMinimumBalanceForRentExemption(
MintLayout.span,
);
const accountRent = await connection.getMinimumBalanceForRentExemption(
MintLayout.span,
);
let transaction = new Transaction();
const signers = [mintAccount, tokenAccount];
transaction.recentBlockhash = (
await connection.getRecentBlockhash('max')
).blockhash;
transaction.add(
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: mintAccount.publicKey,
lamports: mintRent,
space: MintLayout.span,
programId: TOKEN_PROGRAM_ID,
}),
);
transaction.add(
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: tokenAccount.publicKey,
lamports: accountRent,
space: AccountLayout.span,
programId: TOKEN_PROGRAM_ID,
}),
);
transaction.add(
Token.createInitMintInstruction(
TOKEN_PROGRAM_ID,
mintAccount.publicKey,
0,
wallet.publicKey,
wallet.publicKey,
),
);
transaction.add(
Token.createInitAccountInstruction(
TOKEN_PROGRAM_ID,
mintAccount.publicKey,
tokenAccount.publicKey,
owner,
),
);
transaction.add(
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
mintAccount.publicKey,
tokenAccount.publicKey,
wallet.publicKey,
[],
1,
),
);
transaction.add(
Token.createSetAuthorityInstruction(
TOKEN_PROGRAM_ID,
mintAccount.publicKey,
null,
wallet.publicKey,
[],
),
);
transaction.setSigners(wallet.publicKey, ...signers.map(s => s.publicKey));
if (signers.length > 0) {
transaction.partialSign(...signers);
}
transaction = await wallet.signTransaction(transaction);
const rawTransaction = transaction.serialize();
let options = {
skipPreflight: true,
commitment: 'singleGossip',
};
const txid = await connection.sendRawTransaction(rawTransaction, options);
return { txid, mint: mintAccount.publicKey, account: tokenAccount.publicKey };
};

View File

@ -5,13 +5,7 @@ import { TokenAccount } from './../models';
import { PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
import { WAD, ZERO } from '../constants';
export interface KnownToken {
tokenSymbol: string;
tokenName: string;
icon: string;
mintAddress: string;
}
import { KnownToken } from '@solana/spl-token-registry';
export type KnownTokenMap = Map<string, KnownToken>;
@ -32,7 +26,7 @@ export function useLocalStorageState(key: string, defaultState?: string) {
});
const setLocalStorageState = useCallback(
(newState) => {
newState => {
const changed = state !== newState;
if (!changed) {
return;
@ -44,7 +38,7 @@ export function useLocalStorageState(key: string, defaultState?: string) {
localStorage.setItem(key, JSON.stringify(newState));
}
},
[state, key]
[state, key],
);
return [state, setLocalStorageState];
@ -55,7 +49,11 @@ export function shortenAddress(address: string, chars = 4): string {
return `${address.slice(0, chars)}...${address.slice(-chars)}`;
}
export function getTokenName(map: KnownTokenMap, mint?: string | PublicKey, shorten = true): string {
export function getTokenName(
map: KnownTokenMap,
mint?: string | PublicKey,
shorten = true,
): string {
const mintAddress = typeof mint === 'string' ? mint : mint?.toBase58();
if (!mintAddress) {
@ -81,8 +79,12 @@ export function getTokenByName(tokenMap: KnownTokenMap, name: string) {
return token;
}
export function getTokenIcon(map: KnownTokenMap, mintAddress?: string | PublicKey): string | undefined {
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58();
export function getTokenIcon(
map: KnownTokenMap,
mintAddress?: string | PublicKey,
): string | undefined {
const address =
typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58();
if (!address) {
return;
}
@ -97,17 +99,22 @@ export function isKnownMint(map: KnownTokenMap, mintAddress: string) {
export const STABLE_COINS = new Set(['USDC', 'wUSDC', 'USDT']);
export function chunks<T>(array: T[], size: number): T[][] {
return Array.apply<number, T[], T[][]>(0, new Array(Math.ceil(array.length / size))).map((_, index) =>
array.slice(index * size, (index + 1) * size)
);
return Array.apply<number, T[], T[][]>(
0,
new Array(Math.ceil(array.length / size)),
).map((_, index) => array.slice(index * size, (index + 1) * size));
}
export function toLamports(account?: TokenAccount | number, mint?: MintInfo): number {
export function toLamports(
account?: TokenAccount | number,
mint?: MintInfo,
): number {
if (!account) {
return 0;
}
const amount = typeof account === 'number' ? account : account.info.amount?.toNumber();
const amount =
typeof account === 'number' ? account : account.info.amount?.toNumber();
const precision = Math.pow(10, mint?.decimals || 0);
return Math.floor(amount * precision);
@ -117,13 +124,21 @@ export function wadToLamports(amount?: BN): BN {
return amount?.div(WAD) || ZERO;
}
export function fromLamports(account?: TokenAccount | number | BN, mint?: MintInfo, rate: number = 1.0): number {
export function fromLamports(
account?: TokenAccount | number | BN,
mint?: MintInfo,
rate: number = 1.0,
): number {
if (!account) {
return 0;
}
const amount = Math.floor(
typeof account === 'number' ? account : BN.isBN(account) ? account.toNumber() : account.info.amount.toNumber()
typeof account === 'number'
? account
: BN.isBN(account)
? account.toNumber()
: account.info.amount.toNumber(),
);
const precision = Math.pow(10, mint?.decimals || 0);
@ -144,8 +159,11 @@ const abbreviateNumber = (number: number, precision: number) => {
return scaled.toFixed(precision) + suffix;
};
export const formatAmount = (val: number, precision: number = 6, abbr: boolean = true) =>
abbr ? abbreviateNumber(val, precision) : val.toFixed(precision);
export const formatAmount = (
val: number,
precision: number = 6,
abbr: boolean = true,
) => (abbr ? abbreviateNumber(val, precision) : val.toFixed(precision));
export function formatTokenAmount(
account?: TokenAccount,
@ -154,13 +172,17 @@ export function formatTokenAmount(
prefix = '',
suffix = '',
precision = 6,
abbr = false
abbr = false,
): string {
if (!account) {
return '';
}
return `${[prefix]}${formatAmount(fromLamports(account, mint, rate), precision, abbr)}${suffix}`;
return `${[prefix]}${formatAmount(
fromLamports(account, mint, rate),
precision,
abbr,
)}${suffix}`;
}
export const formatUSD = new Intl.NumberFormat('en-US', {
@ -190,12 +212,17 @@ export const formatPct = new Intl.NumberFormat('en-US', {
maximumFractionDigits: 2,
});
export function convert(account?: TokenAccount | number, mint?: MintInfo, rate: number = 1.0): number {
export function convert(
account?: TokenAccount | number,
mint?: MintInfo,
rate: number = 1.0,
): number {
if (!account) {
return 0;
}
const amount = typeof account === 'number' ? account : account.info.amount?.toNumber();
const amount =
typeof account === 'number' ? account : account.info.amount?.toNumber();
const precision = Math.pow(10, mint?.decimals || 0);
let result = (amount / precision) * rate;