chore: format

This commit is contained in:
bartosz-lipinski 2021-03-09 17:29:21 -06:00
parent b4a46cfe85
commit 3c2faf5e49
35 changed files with 961 additions and 1261 deletions

View File

@ -1,5 +1,5 @@
{
"name": "@solana/bridge-ethereum",
"name": "@solana/bridge-sdk",
"version": "0.0.1",
"dependencies": {
"@oyster/common": "0.0.1",

View File

@ -296,9 +296,7 @@
"ast": {
"absolutePath": "contracts/token/ERC20/ERC20.sol",
"exportedSymbols": {
"ERC20": [
9194
]
"ERC20": [9194]
},
"id": 9195,
"license": "MIT",
@ -306,12 +304,7 @@
"nodes": [
{
"id": 8689,
"literals": [
"solidity",
"^",
"0.6",
".0"
],
"literals": ["solidity", "^", "0.6", ".0"],
"nodeType": "PragmaDirective",
"src": "33:23:84"
},
@ -399,10 +392,7 @@
"src": "1372:6:84"
}
],
"contractDependencies": [
22,
9773
],
"contractDependencies": [22, 9773],
"contractKind": "contract",
"documentation": {
"id": 8694,
@ -412,11 +402,7 @@
},
"fullyImplemented": true,
"id": 9194,
"linearizedBaseContracts": [
9194,
9773,
22
],
"linearizedBaseContracts": [9194, 9773, 22],
"name": "ERC20",
"nodeType": "ContractDefinition",
"nodes": [
@ -1204,9 +1190,7 @@
"visibility": "public"
},
{
"baseFunctions": [
9704
],
"baseFunctions": [9704],
"body": {
"id": 8779,
"nodeType": "Block",
@ -1300,9 +1284,7 @@
"visibility": "public"
},
{
"baseFunctions": [
9712
],
"baseFunctions": [9712],
"body": {
"id": 8793,
"nodeType": "Block",
@ -1453,9 +1435,7 @@
"visibility": "public"
},
{
"baseFunctions": [
9722
],
"baseFunctions": [9722],
"body": {
"id": 8814,
"nodeType": "Block",
@ -1719,9 +1699,7 @@
"visibility": "public"
},
{
"baseFunctions": [
9732
],
"baseFunctions": [9732],
"body": {
"id": 8832,
"nodeType": "Block",
@ -1928,9 +1906,7 @@
"visibility": "public"
},
{
"baseFunctions": [
9742
],
"baseFunctions": [9742],
"body": {
"id": 8853,
"nodeType": "Block",
@ -2194,9 +2170,7 @@
"visibility": "public"
},
{
"baseFunctions": [
9754
],
"baseFunctions": [9754],
"body": {
"id": 8891,
"nodeType": "Block",
@ -3653,10 +3627,7 @@
"id": 8959,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "7132:7:84",
"typeDescriptions": {
@ -3818,10 +3789,7 @@
"id": 8969,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "7212:7:84",
"typeDescriptions": {
@ -4615,10 +4583,7 @@
"id": 9023,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "7910:7:84",
"typeDescriptions": {
@ -5413,10 +5378,7 @@
"id": 9078,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "8599:7:84",
"typeDescriptions": {
@ -6233,10 +6195,7 @@
"id": 9136,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "9452:7:84",
"typeDescriptions": {
@ -6398,10 +6357,7 @@
"id": 9146,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "9530:7:84",
"typeDescriptions": {

File diff suppressed because it is too large Load Diff

View File

@ -389,9 +389,7 @@
"ast": {
"absolutePath": "/home/hhofstadt/Dev/certus/wormhole/ethereum/contracts/WrappedAsset.sol",
"exportedSymbols": {
"WrappedAsset": [
1728
]
"WrappedAsset": [1728]
},
"id": 1729,
"license": "Apache 2",
@ -399,12 +397,7 @@
"nodes": [
{
"id": 1186,
"literals": [
"solidity",
"^",
"0.6",
".0"
],
"literals": ["solidity", "^", "0.6", ".0"],
"nodeType": "PragmaDirective",
"src": "67:23:3"
},
@ -492,19 +485,12 @@
"src": "337:7:3"
}
],
"contractDependencies": [
1751,
2025
],
"contractDependencies": [1751, 2025],
"contractKind": "contract",
"documentation": null,
"fullyImplemented": true,
"id": 1728,
"linearizedBaseContracts": [
1728,
1751,
2025
],
"linearizedBaseContracts": [1728, 1751, 2025],
"name": "WrappedAsset",
"nodeType": "ContractDefinition",
"nodes": [
@ -697,10 +683,7 @@
"id": 1209,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "549:7:3",
"typeDescriptions": {
@ -1275,10 +1258,7 @@
"id": 1252,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "915:7:3",
"typeDescriptions": {
@ -1568,10 +1548,7 @@
"id": 1273,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "1097:7:3",
"typeDescriptions": {
@ -2319,9 +2296,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1956
],
"baseFunctions": [1956],
"body": {
"id": 1348,
"nodeType": "Block",
@ -2415,9 +2390,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1964
],
"baseFunctions": [1964],
"body": {
"id": 1362,
"nodeType": "Block",
@ -2568,9 +2541,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1974
],
"baseFunctions": [1974],
"body": {
"id": 1383,
"nodeType": "Block",
@ -2834,9 +2805,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1984
],
"baseFunctions": [1984],
"body": {
"id": 1401,
"nodeType": "Block",
@ -3043,9 +3012,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1994
],
"baseFunctions": [1994],
"body": {
"id": 1422,
"nodeType": "Block",
@ -3309,9 +3276,7 @@
"visibility": "public"
},
{
"baseFunctions": [
2006
],
"baseFunctions": [2006],
"body": {
"id": 1460,
"nodeType": "Block",
@ -4768,10 +4733,7 @@
"id": 1528,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "6587:7:3",
"typeDescriptions": {
@ -4933,10 +4895,7 @@
"id": 1538,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "6667:7:3",
"typeDescriptions": {
@ -5641,10 +5600,7 @@
"id": 1586,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "7299:7:3",
"typeDescriptions": {
@ -6297,10 +6253,7 @@
"id": 1632,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "7920:7:3",
"typeDescriptions": {
@ -6975,10 +6928,7 @@
"id": 1681,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "8705:7:3",
"typeDescriptions": {
@ -7140,10 +7090,7 @@
"id": 1691,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "8783:7:3",
"typeDescriptions": {
@ -7603,9 +7550,7 @@
"legacyAST": {
"absolutePath": "/home/hhofstadt/Dev/certus/wormhole/ethereum/contracts/WrappedAsset.sol",
"exportedSymbols": {
"WrappedAsset": [
1728
]
"WrappedAsset": [1728]
},
"id": 1729,
"license": "Apache 2",
@ -7613,12 +7558,7 @@
"nodes": [
{
"id": 1186,
"literals": [
"solidity",
"^",
"0.6",
".0"
],
"literals": ["solidity", "^", "0.6", ".0"],
"nodeType": "PragmaDirective",
"src": "67:23:3"
},
@ -7706,19 +7646,12 @@
"src": "337:7:3"
}
],
"contractDependencies": [
1751,
2025
],
"contractDependencies": [1751, 2025],
"contractKind": "contract",
"documentation": null,
"fullyImplemented": true,
"id": 1728,
"linearizedBaseContracts": [
1728,
1751,
2025
],
"linearizedBaseContracts": [1728, 1751, 2025],
"name": "WrappedAsset",
"nodeType": "ContractDefinition",
"nodes": [
@ -7911,10 +7844,7 @@
"id": 1209,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "549:7:3",
"typeDescriptions": {
@ -8489,10 +8419,7 @@
"id": 1252,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "915:7:3",
"typeDescriptions": {
@ -8782,10 +8709,7 @@
"id": 1273,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "1097:7:3",
"typeDescriptions": {
@ -9533,9 +9457,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1956
],
"baseFunctions": [1956],
"body": {
"id": 1348,
"nodeType": "Block",
@ -9629,9 +9551,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1964
],
"baseFunctions": [1964],
"body": {
"id": 1362,
"nodeType": "Block",
@ -9782,9 +9702,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1974
],
"baseFunctions": [1974],
"body": {
"id": 1383,
"nodeType": "Block",
@ -10048,9 +9966,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1984
],
"baseFunctions": [1984],
"body": {
"id": 1401,
"nodeType": "Block",
@ -10257,9 +10173,7 @@
"visibility": "public"
},
{
"baseFunctions": [
1994
],
"baseFunctions": [1994],
"body": {
"id": 1422,
"nodeType": "Block",
@ -10523,9 +10437,7 @@
"visibility": "public"
},
{
"baseFunctions": [
2006
],
"baseFunctions": [2006],
"body": {
"id": 1460,
"nodeType": "Block",
@ -11982,10 +11894,7 @@
"id": 1528,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "6587:7:3",
"typeDescriptions": {
@ -12147,10 +12056,7 @@
"id": 1538,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "6667:7:3",
"typeDescriptions": {
@ -12855,10 +12761,7 @@
"id": 1586,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "7299:7:3",
"typeDescriptions": {
@ -13511,10 +13414,7 @@
"id": 1632,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "7920:7:3",
"typeDescriptions": {
@ -14189,10 +14089,7 @@
"id": 1681,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "8705:7:3",
"typeDescriptions": {
@ -14354,10 +14251,7 @@
"id": 1691,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "8783:7:3",
"typeDescriptions": {

View File

@ -13,7 +13,9 @@ const resolvePackage = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
webpack: {
configure: (webpackConfig, { env, paths }) => {
paths.appBuild = webpackConfig.output.path = path.resolve('./../../build/bridge');
paths.appBuild = webpackConfig.output.path = path.resolve(
'./../../build/bridge',
);
return webpackConfig;
},
},

View File

@ -1,6 +1,6 @@
import React from "react";
import "./App.less";
import { Routes } from "./routes";
import React from 'react';
import './App.less';
import { Routes } from './routes';
function App() {
return <Routes />;

View File

@ -1,2 +1 @@
export * from './solana';
export * from './ethereum';
export * from './input';

View File

@ -1,14 +1,14 @@
import React, {useState} from 'react';
import {NumericInput, programIds} from '@oyster/common';
import {Card, Select} from 'antd';
import React, { useState } from 'react';
import { NumericInput, programIds } from '@oyster/common';
import { Card, Select } from 'antd';
import './style.less';
import {useEthereum} from '../../contexts';
import {WrappedAssetFactory} from '../../contracts/WrappedAssetFactory';
import {WormholeFactory} from '../../contracts/WormholeFactory';
import {TransferRequestInfo} from '../../models/bridge';
import {TokenDisplay} from '../TokenDisplay'
import { useEthereum } from '../../contexts';
import { WrappedAssetFactory } from '../../contracts/WrappedAssetFactory';
import { WormholeFactory } from '../../contracts/WormholeFactory';
import { TransferRequestInfo } from '../../models/bridge';
import { TokenDisplay } from '../TokenDisplay';
import BN from 'bn.js';
import {ASSET_CHAIN} from "../../models/bridge/constants";
import { ASSET_CHAIN } from '../../models/bridge/constants';
const { Option } = Select;
@ -17,6 +17,7 @@ export function EthereumInput(props: {
hideBalance?: boolean;
asset?: string;
chain?: ASSET_CHAIN;
setAsset: (asset: string) => void;
setInfo: (info: TransferRequestInfo) => void;
@ -27,28 +28,40 @@ export function EthereumInput(props: {
const [lastAmount, setLastAmount] = useState<string>('');
const { tokens, provider } = useEthereum();
const renderReserveAccounts = tokens.filter(t => (t.tags?.indexOf('longList') || -1) < 0).map((token) => {
const mint = token.address;
return (
<Option key={mint} className="multichain-option" value={mint} name={token.symbol} title={token.name}>
<div className="multichain-option-content">
<TokenDisplay asset={props.asset} token={token} chain={ASSET_CHAIN.Ethereum}/>
<div className="multichain-option-name">
<span className={"token-name"}>{token.symbol}</span>
const renderReserveAccounts = tokens
.filter(t => (t.tags?.indexOf('longList') || -1) < 0)
.map(token => {
const mint = token.address;
return (
<Option
key={mint}
className="multichain-option"
value={mint}
name={token.symbol}
title={token.name}
>
<div className="multichain-option-content">
<TokenDisplay
asset={props.asset}
token={token}
chain={props.chain}
/>
<div className="multichain-option-name">
<span className={'token-name'}>{token.symbol}</span>
</div>
</div>
</div>
</Option>
);
});
</Option>
);
});
const updateBalance = async (fromAddress: string) => {
props.setAsset(fromAddress);
if(!provider) {
if (!provider) {
return;
}
const bridgeAddress = programIds().wormhole.bridge;
const bridgeAddress = programIds().wormhole.bridge;
let signer = provider.getSigner();
let e = WrappedAssetFactory.connect(fromAddress, provider);
@ -60,26 +73,30 @@ export function EthereumInput(props: {
let allowance = await e.allowance(addr, bridgeAddress);
let info = {
address: fromAddress,
name: symbol,
balance: balance,
allowance: allowance,
decimals: decimals,
isWrapped: false,
chainID: ASSET_CHAIN.Ethereum,
assetAddress: Buffer.from(fromAddress.slice(2), "hex"),
mint: "",
}
address: fromAddress,
name: symbol,
balance: balance,
allowance: allowance,
decimals: decimals,
isWrapped: false,
chainID: ASSET_CHAIN.Ethereum,
assetAddress: Buffer.from(fromAddress.slice(2), 'hex'),
mint: '',
};
setBalance(new BN(info.balance.toString()).div(new BN(10).pow(new BN(info.decimals))).toNumber());
setBalance(
new BN(info.balance.toString())
.div(new BN(10).pow(new BN(info.decimals)))
.toNumber(),
);
let b = WormholeFactory.connect(bridgeAddress, provider);
let isWrapped = await b.isWrappedAsset(fromAddress)
let isWrapped = await b.isWrappedAsset(fromAddress);
if (isWrapped) {
info.chainID = await e.assetChain()
info.assetAddress = Buffer.from((await e.assetAddress()).slice(2), "hex")
info.isWrapped = true
info.chainID = await e.assetChain();
info.assetAddress = Buffer.from((await e.assetAddress()).slice(2), 'hex');
info.isWrapped = true;
}
props.setInfo(info);
@ -103,10 +120,13 @@ export function EthereumInput(props: {
</div>
)}
</div>
<div className="ccy-input-header" style={{ padding: '0px 10px 5px 7px', height: 80 }}>
<div
className="ccy-input-header"
style={{ padding: '0px 10px 5px 7px', height: 80 }}
>
<NumericInput
value={
parseFloat(lastAmount || '0.00') === props.amount
parseFloat(lastAmount || '0.00') === props.amount
? lastAmount
: props.amount?.toFixed(6)?.toString()
}

View File

@ -1,136 +0,0 @@
import React, {useEffect, useState} from 'react';
import {NumericInput, useConnectionConfig, useMint, useUserAccounts, utils} from '@oyster/common';
import {Card, Select} from 'antd';
import { TokenInfo } from '@uniswap/token-lists';
import './style.less';
import {TokenDisplay} from '../TokenDisplay'
import {ASSET_CHAIN} from "../../models/bridge/constants";
const { Option } = Select;
// TODO: add way to add new token account
export function SolanaInput(props: {
title: string;
toToken?: TokenInfo;
disabled?: boolean;
amount?: number | null;
onInputChange: (value: number | null) => void;
hideBalance?: boolean;
useFirstToken?: boolean;
onMintChange?: (value: string) => void;
}) {
const { tokens, tokenMap } = useConnectionConfig();
const { userAccounts } = useUserAccounts();
const [balance, setBalance] = useState<number>(0);
const [lastAmount, setLastAmount] = useState<string>('');
const [currentMint, setCurrentMint] = useState<string>('')
const mint = useMint(currentMint);
const renderPopularTokens = tokens.map((item) => {
const address = item.address;
return (
<Option key={address} value={address} name={item.symbol} title={address}>
<TokenDisplay
key={address}
asset={address}
token={item}
chain={ASSET_CHAIN.Solana}
/>
</Option>
);
});
useEffect(() => {
if(!currentMint && tokens.length > 0) {
setCurrentMint(tokens[0].address)
}
}, [tokens, currentMint])
useEffect(() => {
const currentAccount = userAccounts?.find(
(a) => a.info.mint.toBase58() === currentMint
);
if (currentAccount && mint) {
setBalance(
currentAccount.info.amount.toNumber() / Math.pow(10, mint.decimals)
);
} else {
setBalance(0);
}
}, [currentMint, mint, userAccounts])
return (
<Card
className="ccy-input to-input"
style={{ borderRadius: 20 }}
bodyStyle={{ padding: 0 }}
>
<div className="ccy-input-header">
<div className="ccy-input-header-left">{props.title}</div>
{!props.hideBalance && (
<div
className="ccy-input-header-right"
onClick={e => props.onInputChange && props.onInputChange(balance)}
>
Balance: {balance.toFixed(6)}
</div>
)}
</div>
<div className="ccy-input-header" style={{ padding: '0px 10px 10px 7px' }}>
<NumericInput
value={
parseFloat(lastAmount || '0.00') === props.amount
? lastAmount
: props.amount?.toFixed(6)?.toString()
}
onChange={(val: string) => {
if (props.onInputChange && parseFloat(val) !== props.amount) {
if (!val || !parseFloat(val)) props.onInputChange(null);
else props.onInputChange(parseFloat(val));
}
setLastAmount(val);
}}
style={{
fontSize: 20,
boxShadow: 'none',
borderColor: 'transparent',
outline: 'transparent',
}}
placeholder="0.00"
/>
<div className="ccy-input-header-right" style={{ display: 'flex' }}>
{!props.disabled ? (
<Select
size="large"
showSearch
style={{ minWidth: 150 }}
placeholder="CCY"
value={currentMint}
onChange={item => {
if (props.onMintChange) props.onMintChange(item);
setCurrentMint(item);
}}
filterOption={(input, option) =>
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{renderPopularTokens}
</Select>
) : ( props.toToken &&
<TokenDisplay
key={props.toToken.address}
asset={props.toToken.address}
token={props.toToken}
chain={ASSET_CHAIN.Solana}
/>
)}
</div>
</div>
</Card>
);
}

View File

@ -26,6 +26,10 @@
box-shadow: none !important;
align-items: center;
}
.ant-select-selection-search {
align-items: center;
display: flex;
}
.ant-select-selection-item {
display: flex;
height: 60px;

View File

@ -3,7 +3,7 @@ import './../../App.less';
import './index.less';
import { Layout } from 'antd';
import { Link, useLocation } from 'react-router-dom';
import metamaskIcon from "../../assets/metamask.svg"
import metamaskIcon from '../../assets/metamask.svg';
import { LABELS } from '../../constants';
import { contexts, AppBar, shortenAddress } from '@oyster/common';
@ -30,31 +30,38 @@ export const AppLayout = React.memo((props: any) => {
'';
return (
<div className={`App`}>
<Wormhole onCreated={() => setWormholeReady(true)} show={true} rotate={isRoot}>
<Wormhole
onCreated={() => setWormholeReady(true)}
show={true}
rotate={isRoot}
>
<Layout title={LABELS.APP_TITLE}>
{isRoot && (
<Header className="App-Bar">
<div className="app-title">
<Link to="/"><h2>WORMHOLE</h2></Link>
<Link to="/">
<h2>WORMHOLE</h2>
</Link>
</div>
<AppBar
useWalletBadge={true}
left={
<>
{accounts[0] && (
<div>
<img
alt={"metamask-icon"}
width={20}
height={20}
src={metamaskIcon}
style={{ marginRight: 8 }}
/>
{shortenAddress(accounts[0], 4)}
</div>
)}
</>
} />
<>
{accounts[0] && (
<div>
<img
alt={'metamask-icon'}
width={20}
height={20}
src={metamaskIcon}
style={{ marginRight: 8 }}
/>
{shortenAddress(accounts[0], 4)}
</div>
)}
</>
}
/>
</Header>
)}
<Content style={{ padding: '0 50px' }}>{props.children}</Content>

View File

@ -11,8 +11,8 @@ export const Settings = () => {
return (
<>
<div style={{ display: "grid" }}>
Network:{" "}
<div style={{ display: 'grid' }}>
Network:{' '}
<Select
onSelect={setEndpoint}
value={endpoint}
@ -24,7 +24,11 @@ export const Settings = () => {
</Select.Option>
))}
</Select>
{connected && <Button type="primary" onClick={disconnect}>Disconnect</Button>}
{connected && (
<Button type="primary" onClick={disconnect}>
Disconnect
</Button>
)}
</div>
</>
);

View File

@ -3,12 +3,32 @@ import { TokenInfo } from '@solana/spl-token-registry';
import { debug } from 'console';
import React from 'react';
import { useEthereum } from '../../contexts';
import { ASSET_CHAIN } from "../../models/bridge/constants";
import { ASSET_CHAIN } from '../../models/bridge/constants';
import './style.less';
export const TokenDisplay = ({ asset, chain, token, logo }: { asset?: string, chain?: ASSET_CHAIN, token?: TokenInfo, logo?: string }) => {
return <div className="token-chain-logo">
export const TokenDisplay = ({
asset,
chain,
token,
logo,
}: {
asset?: string;
chain?: ASSET_CHAIN;
token?: TokenInfo;
logo?: string;
}) => {
return (
<div className="token-chain-logo">
<img className="token-logo" alt="" src={logo || token?.logoURI} />
<img className="chain-logo" alt="" src={chain === ASSET_CHAIN.Ethereum ? '/blockchains/ETH.svg' : '/blockchains/solana.webp'} />
</div>;
}
<img
className="chain-logo"
alt=""
src={
chain === ASSET_CHAIN.Ethereum
? '/blockchains/ETH.svg'
: '/blockchains/solana.webp'
}
/>
</div>
);
};

View File

@ -3,13 +3,19 @@ import { Card, notification, Spin, Button } from 'antd';
import { TokenInfo } from '@uniswap/token-lists';
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
import { LABELS } from '../../constants';
import { contexts, utils, ConnectButton, programIds, formatAmount } from '@oyster/common';
import { useHistory, useLocation } from "react-router-dom";
import { SolanaInput, EthereumInput } from "./../Input";
import {
contexts,
utils,
ConnectButton,
programIds,
formatAmount,
} from '@oyster/common';
import { useHistory, useLocation } from 'react-router-dom';
import { EthereumInput } from './../Input';
import './style.less';
import { ethers } from 'ethers';
import { ASSET_CHAIN } from '../../utils/assets';
import { ASSET_CHAIN, chainToName } from '../../utils/assets';
import { BigNumber } from 'ethers/utils';
import { Erc20Factory } from '../../contracts/Erc20Factory';
import { ProgressUpdate, transfer, TransferRequest } from '../../models/bridge';
@ -18,132 +24,208 @@ import { TokenDisplay } from './../TokenDisplay';
const { useConnection } = contexts.Connection;
const { useWallet } = contexts.Wallet;
const { notify } = utils;
export const typeToIcon = (type: string, isLast: boolean) => {
const style: React.CSSProperties = { marginRight: 5 }
switch(type) {
case "user": return <span style={style}>🪓 </span>;
case "done": return <span style={style}> </span>;
case "error": return <span style={style}> </span>;
case "wait": return isLast ? <Spin /> : <span style={style}> </span>;
default: return null;
const style: React.CSSProperties = { marginRight: 5 };
switch (type) {
case 'user':
return <span style={style}>🪓 </span>;
case 'done':
return <span style={style}> </span>;
case 'error':
return <span style={style}> </span>;
case 'wait':
return isLast ? <Spin /> : <span style={style}> </span>;
default:
return null;
}
}
};
export const Transfer = () => {
const connection = useConnection();
const { wallet } = useWallet();
const { provider, tokenMap } = useEthereum();
const { provider, tokenMap, tokens } = useEthereum();
const [request, setRequest] = useState<TransferRequest>({
// TODO: update based on selected asset
asset: tokens?.[0]?.address,
from: ASSET_CHAIN.Ethereum,
toChain: ASSET_CHAIN.Solana,
});
const [toToken, setToToken] = useState<TokenInfo | undefined>()
const setAssetInformation = (asset:string) => {
request.asset = asset;
setRequest(request)
setToToken(tokenMap.get(request?.asset || ""))
}
const setAssetInformation = (asset: string) => {
setRequest({
...request,
asset,
});
};
return (
<>
<div className="exchange-card">
<EthereumInput
<div className="exchange-card">
<EthereumInput
title="From Ethereum"
setInfo={(info) => { request.info = info }}
asset={request.asset}
setAsset={(asset) => setAssetInformation(asset)}
amount={request.amount}
onInputChange={(amount) => {
request.amount = amount || 0;
setInfo={info => {
request.info = info;
}}
asset={request.asset}
chain={request.from}
setAsset={asset => setAssetInformation(asset)}
amount={request.amount}
onInputChange={amount => {
setRequest({
...request,
amount: amount || 0,
});
}}
/>
<Button type="primary" className="swap-button">
</Button>
<SolanaInput
title="To Solana"
disabled={true}
toToken={toToken}
onInputChange={() => {}}
/>
</div>
<ConnectButton type="primary" onClick={async () => {
if(!wallet || !provider) {
return;
}
<Button
type="primary"
className="swap-button"
disabled={true}
onClick={() => {
const from = request.toChain;
const toChain = request.from;
setRequest({
...request,
from,
toChain,
});
}}
>
</Button>
<EthereumInput
title="To Solana"
setInfo={info => {
request.info = info;
}}
asset={request.asset}
chain={request.toChain}
hideBalance={true}
setAsset={asset => setAssetInformation(asset)}
amount={request.amount}
onInputChange={amount => {
setRequest({
...request,
amount: amount || 0,
});
}}
/>
</div>
<ConnectButton
type="primary"
onClick={async () => {
if (!wallet || !provider) {
return;
}
const NotificationContent = () => {
const [activeSteps, setActiveSteps] = useState<ProgressUpdate[]>([]);
useEffect(() => {
(async () => {
let steps: ProgressUpdate[] = [];
try {
await transfer(connection, wallet, request, provider, (update) => {
if(update.replace) {
steps.pop();
steps = [...steps, update];
} else {
steps = [...steps, update];
}
const NotificationContent = () => {
const [activeSteps, setActiveSteps] = useState<ProgressUpdate[]>(
[],
);
useEffect(() => {
(async () => {
let steps: ProgressUpdate[] = [];
try {
await transfer(
connection,
wallet,
request,
provider,
update => {
if (update.replace) {
steps.pop();
steps = [...steps, update];
} else {
steps = [...steps, update];
}
setActiveSteps(steps);
});
} catch {
// TODO...
}
})();
}, [setActiveSteps]);
setActiveSteps(steps);
},
);
} catch {
// TODO...
}
})();
}, [setActiveSteps]);
return <div>
<div style={{ display: 'flex' }}>
<div>
<h5>{`ETH Mainnet -> Solana Mainnet`}</h5>
<h2>{formatAmount(request.amount || 0, 2)} {request.info?.name}</h2>
return (
<div>
<div style={{ display: 'flex' }}>
<div>
<h5>{`${chainToName(request.from)} Mainnet -> ${chainToName(
request.from,
)} Mainnet`}</h5>
<h2>
{formatAmount(request.amount || 0, 2)}{' '}
{request.info?.name}
</h2>
</div>
<div
style={{
display: 'flex',
marginLeft: 'auto',
marginRight: 10,
}}
>
<TokenDisplay
asset={request.asset}
chain={request.from}
token={tokenMap.get(request.asset || '')}
/>
<span style={{ margin: 15 }}>{'➔'}</span>
<TokenDisplay
asset={request.asset}
chain={request.toChain}
token={tokenMap.get(request.asset || '')}
/>
</div>
</div>
<div style={{ display: 'flex', marginLeft: 'auto', marginRight: 10 }}>
<TokenDisplay asset={request.asset} chain={request.from} token={tokenMap.get(request.asset || '')} />
<span style={{ margin: 15 }}>{'➔'}</span>
<TokenDisplay asset={request.asset} chain={request.toChain} token={tokenMap.get(request.asset || '')} />
<div
style={{
textAlign: 'left',
display: 'flex',
flexDirection: 'column',
}}
>
{(() => {
let group = '';
return activeSteps.map((step, i) => {
let prevGroup = group;
group = step.group;
let newGroup = prevGroup !== group;
return (
<>
{newGroup && <span>{group}</span>}
<span style={{ marginLeft: 15 }}>
{typeToIcon(
step.type,
activeSteps.length - 1 === i,
)}{' '}
{step.message}
</span>
</>
);
});
})()}
</div>
</div>
<div style={{ textAlign: 'left', display: 'flex', flexDirection: 'column' }}>
{(() => {
let group = '';
return activeSteps.map((step, i) => {
let prevGroup = group;
group = step.group;
let newGroup = prevGroup !== group;
return (
<>
{newGroup && <span>{group}</span>}
<span style={{ marginLeft: 15 }}>{typeToIcon(step.type, (activeSteps.length - 1) === i)} {step.message}</span>
</>);
});
})()}
</div>
</div>;
};
);
};
notification.open({
message: '',
duration: 0,
placement: 'bottomLeft',
description: <NotificationContent />,
className: 'custom-class',
style: {
width: 500,
},
});
}}>
Transfer
</ConnectButton>
</>
notification.open({
message: '',
duration: 0,
placement: 'bottomLeft',
description: <NotificationContent />,
className: 'custom-class',
style: {
width: 500,
},
});
}}
>
Transfer
</ConnectButton>
</>
);
};

View File

@ -7,6 +7,9 @@
.swap-button {
border-radius: 2em;
width: 32px;
width: 40px;
height: 40px;
font-size: 20px;
font-weight: bold;
padding-left: 8px;
}

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { PerspectiveCamera } from "@react-three/drei";
import { hasWindow } from "./Utils";
import * as React from 'react';
import { PerspectiveCamera } from '@react-three/drei';
import { hasWindow } from './Utils';
/**
* Creates the perspective Camera for our WormholeCanvas.

View File

@ -9,7 +9,13 @@ import WormholeGeometry from './WormholeGeometry';
* @returns {JSX.Element}
* @constructor
*/
const WormholeCanvas = ({ onCreated, rotate }: { onCreated: any, rotate?: boolean }) => {
const WormholeCanvas = ({
onCreated,
rotate,
}: {
onCreated: any;
rotate?: boolean;
}) => {
return (
<Canvas onCreated={onCreated}>
<Camera />

View File

@ -1,15 +1,15 @@
import * as React from "react";
import { Color, Float32BufferAttribute } from "three";
import type { Mesh, BufferGeometry } from "three";
import { useTexture } from "@react-three/drei";
import { useFrame, useUpdate } from "react-three-fiber";
import * as React from 'react';
import { Color, Float32BufferAttribute } from 'three';
import type { Mesh, BufferGeometry } from 'three';
import { useTexture } from '@react-three/drei';
import { useFrame, useUpdate } from 'react-three-fiber';
import {
calculateTorusProperties,
fragmentShader,
vertexShader,
} from "./Utils";
import disc from "./disc.png";
} from './Utils';
import disc from './disc.png';
// The individual "particle size".
const PARTICLE_SIZE = 10;
@ -28,18 +28,18 @@ const WormholeGeometry = ({ rotate }: { rotate?: boolean }) => {
const uniforms = React.useMemo(
() => ({
// Adapt the color of the WormholeCanvas here.
color: { value: new Color("dimgrey") },
color: { value: new Color('dimgrey') },
pointTexture: {
value: pointTexture,
},
}),
[pointTexture]
[pointTexture],
);
// The calculated torus properties.
const [positionAttribute, colors, sizes] = React.useMemo(
() => calculateTorusProperties(PARTICLE_SIZE),
[]
[],
);
// Rotate mesh around the y axis every frame.
@ -47,7 +47,7 @@ const WormholeGeometry = ({ rotate }: { rotate?: boolean }) => {
if (mesh.current) {
// x-Axis defines the "top" we're looking at, try e.g. 30.5
mesh.current.rotation.x = 30;
if(!rotate) {
if (!rotate) {
mesh.current.rotation.z += 0.0005;
}
}
@ -55,9 +55,9 @@ const WormholeGeometry = ({ rotate }: { rotate?: boolean }) => {
// Calculate the geometry.
const geometry = useUpdate((geo: BufferGeometry) => {
geo.setAttribute("position", positionAttribute);
geo.setAttribute("customColor", new Float32BufferAttribute(colors, 3));
geo.setAttribute("size", new Float32BufferAttribute(sizes, 1));
geo.setAttribute('position', positionAttribute);
geo.setAttribute('customColor', new Float32BufferAttribute(colors, 3));
geo.setAttribute('size', new Float32BufferAttribute(sizes, 1));
}, []);
return (

View File

@ -19,12 +19,14 @@ const Wormhole = ({
show: boolean;
rotate?: boolean;
children: React.ReactNode;
}) => (
!show ? <>{children}</> :
<>
<WormholeCanvas onCreated={onCreated} rotate={rotate} />
<div className="wormhole-overlay">{children}</div>
</>
);
}) =>
!show ? (
<>{children}</>
) : (
<>
<WormholeCanvas onCreated={onCreated} rotate={rotate} />
<div className="wormhole-overlay">{children}</div>
</>
);
export default Wormhole;

View File

@ -1,32 +1,36 @@
import React, {createContext, FunctionComponent, useContext, useEffect} from "react"
import {Connection, PublicKey} from "@solana/web3.js";
import { SolanaBridge} from "../core";
import { useConnection, useConnectionConfig } from "@oyster/common/dist/lib/contexts/connection";
import React, {
createContext,
FunctionComponent,
useContext,
useEffect,
} from 'react';
import { Connection, PublicKey } from '@solana/web3.js';
import { SolanaBridge } from '../core';
import {
useConnection,
useConnectionConfig,
} from '@oyster/common/dist/lib/contexts/connection';
import { utils } from '@oyster/common';
import { MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { WORMHOLE_PROGRAM_ID } from "../utils/ids";
import { MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { WORMHOLE_PROGRAM_ID } from '../utils/ids';
export const BridgeContext = createContext<SolanaBridge | undefined>(undefined);
export const BridgeProvider: FunctionComponent = ({children}) => {
export const BridgeProvider: FunctionComponent = ({ children }) => {
const { endpoint } = useConnectionConfig();
const connection = useConnection();
const programs = utils.programIds();
/// let bridge = new SolanaBridge(endpoint, connection, programs.wormhole.pubkey, programs.token);
/// let bridge = new SolanaBridge(endpoint, connection, programs.wormhole.pubkey, programs.token);
return (
<BridgeContext.Provider value={undefined}>
{children}
</BridgeContext.Provider>
)
}
);
};
export const useBridge = () => {
const bridge = useContext(BridgeContext);
return bridge;
}
};

View File

@ -1,11 +1,11 @@
import {EventEmitter} from "@oyster/common";
import React, {useContext, useEffect, useState} from "react";
import {MarketsContextState} from "./market";
import { EventEmitter } from '@oyster/common';
import React, { useContext, useEffect, useState } from 'react';
import { MarketsContextState } from './market';
export const COINGECKO_POOL_INTERVAL = 10000; // 2 min
export const COINGECKO_API = "https://api.coingecko.com/api/v3/"
export const COINGECKO_COIN_LIST_API = `${COINGECKO_API}coins/list`
export const COINGECKO_COIN_PRICE_API = `${COINGECKO_API}simple/price`
export const COINGECKO_API = 'https://api.coingecko.com/api/v3/';
export const COINGECKO_COIN_LIST_API = `${COINGECKO_API}coins/list`;
export const COINGECKO_COIN_PRICE_API = `${COINGECKO_API}simple/price`;
interface CoinInfo {
id: string;
@ -17,28 +17,30 @@ export interface CoingeckoContextState {
coinList: Map<string, CoinInfo>;
}
const CoingeckoContext = React.createContext<CoingeckoContextState | null>(null);
const CoingeckoContext = React.createContext<CoingeckoContextState | null>(
null,
);
export function CoingeckoProvider({ children = null as any }) {
const [coinList, setCoinList] = useState<Map<string, CoinInfo>>(new Map())
const [coinList, setCoinList] = useState<Map<string, CoinInfo>>(new Map());
useEffect(() => {
(async () => {
const listResponse = await fetch(COINGECKO_COIN_LIST_API);
const coinList: CoinInfo[] = await listResponse.json();
setCoinList(
coinList.reduce((coins, val) => {
coins.set(val.symbol, val);
return coins;
}, new Map<string, CoinInfo>())
)
})();
}, [setCoinList]);
(async () => {
const listResponse = await fetch(COINGECKO_COIN_LIST_API);
const coinList: CoinInfo[] = await listResponse.json();
setCoinList(
coinList.reduce((coins, val) => {
coins.set(val.symbol, val);
return coins;
}, new Map<string, CoinInfo>()),
);
})();
}, [setCoinList]);
return (
<CoingeckoContext.Provider value={{coinList: coinList}}>
<CoingeckoContext.Provider value={{ coinList: coinList }}>
{children}
</CoingeckoContext.Provider>
)
);
}
export const useCoingecko = () => {

View File

@ -1,15 +1,22 @@
import React, { createContext, FunctionComponent, useCallback, useContext, useEffect, useState } from "react";
import React, {
createContext,
FunctionComponent,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
// @ts-ignore
import { useWallet as useEthereumWallet } from "use-wallet";
import { useWallet as useEthereumWallet } from 'use-wallet';
// @ts-ignore
import WalletConnectProvider from "@walletconnect/web3-provider";
import WalletConnectProvider from '@walletconnect/web3-provider';
// @ts-ignore
import Fortmatic from "fortmatic";
import Torus from "@toruslabs/torus-embed";
import { useConnectionConfig, useWallet, ENV } from "@oyster/common";
import Fortmatic from 'fortmatic';
import Torus from '@toruslabs/torus-embed';
import { useConnectionConfig, useWallet, ENV } from '@oyster/common';
import { TokenList, TokenInfo } from '@uniswap/token-lists';
import { ethers } from "ethers";
import { ethers } from 'ethers';
export interface EthereumContextState {
provider?: ethers.providers.Web3Provider;
@ -25,15 +32,15 @@ export const EthereumContext = createContext<EthereumContextState>({
accounts: [''],
});
export const EthereumProvider: FunctionComponent = ({children}) => {
export const EthereumProvider: FunctionComponent = ({ children }) => {
const [accounts, setAccounts] = useState<string[]>(['']);
const [provider, setProvider] = useState<ethers.providers.Web3Provider>();
const { env } = useConnectionConfig();
const { connected } = useWallet();
const wallet = useEthereumWallet();
const [tokens, setTokens] = useState<{
map: Map<string, TokenInfo>,
list: TokenInfo[],
map: Map<string, TokenInfo>;
list: TokenInfo[];
}>({
map: new Map<string, TokenInfo>(),
list: [],
@ -42,26 +49,36 @@ export const EthereumProvider: FunctionComponent = ({children}) => {
useEffect(() => {
(async () => {
const map = new Map<string, TokenInfo>();
const listResponse: TokenList[] = (await Promise.all([
fetch('https://gateway.ipfs.io/ipns/tokens.uniswap.org').then(_ => _.json()),
const listResponse: TokenList[] = await Promise.all([
fetch('https://gateway.ipfs.io/ipns/tokens.uniswap.org').then(_ =>
_.json(),
),
fetch('https://tokenlist.aave.eth.link/').then(_ => _.json()),
fetch('https://tokens.coingecko.com/uniswap/all.json').then(_ => _.json()),
]));
fetch('https://tokens.coingecko.com/uniswap/all.json').then(_ =>
_.json(),
),
]);
listResponse.forEach((list, i) =>
list.tokens.reduce((acc, val) => {
const address = val.address.toLowerCase();
const extraTag = i === 2 && !acc.has(address) ? 'longList' : '';
listResponse.forEach((list, i) => list.tokens.reduce((acc, val) => {
const address = val.address.toLowerCase();
const extraTag = i === 2 && !acc.has(address) ? 'longList' : '';
const item = {
...val,
logoURI: val.logoURI
? val.logoURI?.replace(
'ipfs://',
'https://cloudflare-ipfs.com/ipfs/',
)
: ` https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${val.address}/logo.png `,
tags: val.tags ? [...val.tags, extraTag] : [extraTag],
};
const item = {
...val,
logoURI: val.logoURI ? val.logoURI?.replace('ipfs://', 'https://cloudflare-ipfs.com/ipfs/') : ` https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${val.address}/logo.png `,
tags: val.tags ? [...val.tags, extraTag] : [extraTag]
};
acc.set(address, item);
return acc;
}, map));
acc.set(address, item);
return acc;
}, map),
);
setTokens({
map,
@ -71,26 +88,30 @@ export const EthereumProvider: FunctionComponent = ({children}) => {
}, [setTokens]);
useEffect(() => {
if(connected) {
// @ts-ignore
window.ethereum.enable();
// @ts-ignore
const provider = new ethers.providers.Web3Provider(window.ethereum as any);
const signer = provider.getSigner();
signer.getAddress().then(account => setAccounts([account]));
if (connected) {
// @ts-ignore
window.ethereum.enable();
// @ts-ignore
const provider = new ethers.providers.Web3Provider(
(window as any).ethereum ,
);
const signer = provider.getSigner();
signer.getAddress().then(account => setAccounts([account]));
setProvider(provider);
setProvider(provider);
}
}, [connected])
}, [connected]);
return (
<EthereumContext.Provider value={{ tokens: tokens.list, tokenMap: tokens.map, accounts, provider }}>
<EthereumContext.Provider
value={{ tokens: tokens.list, tokenMap: tokens.map, accounts, provider }}
>
{children}
</EthereumContext.Provider>
);
}
};
export const useEthereum = () => {
const context = useContext(EthereumContext);
return context;
}
};

View File

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

View File

@ -4,13 +4,21 @@ import React, {
useEffect,
useMemo,
useState,
} from "react";
import { MintInfo } from "@solana/spl-token";
import { useHistory, useLocation } from "react-router-dom";
import bs58 from "bs58";
import { TokenAccount } from "@oyster/common";
} from 'react';
import { MintInfo } from '@solana/spl-token';
import { useHistory, useLocation } from 'react-router-dom';
import bs58 from 'bs58';
import { TokenAccount } from '@oyster/common';
import { TokenInfo } from '@solana/spl-token-registry';
import { useConnection, useConnectionConfig, useAccountByMint, useMint, getTokenName, getTokenIcon, convert } from "@oyster/common";
import {
useConnection,
useConnectionConfig,
useAccountByMint,
useMint,
getTokenName,
getTokenIcon,
convert,
} from '@oyster/common';
export interface TokenContextState {
mintAddress: string;
@ -33,7 +41,7 @@ export interface TokenPairContextState {
}
const TokenPairContext = React.createContext<TokenPairContextState | null>(
null
null,
);
const convertAmount = (amount: string, mint?: MintInfo) => {
@ -42,8 +50,8 @@ const convertAmount = (amount: string, mint?: MintInfo) => {
export const useCurrencyLeg = (defaultMint?: string) => {
const { tokenMap } = useConnectionConfig();
const [amount, setAmount] = useState("");
const [mintAddress, setMintAddress] = useState(defaultMint || "");
const [amount, setAmount] = useState('');
const [mintAddress, setMintAddress] = useState(defaultMint || '');
const account = useAccountByMint(mintAddress);
const mint = useMint(mintAddress);
@ -59,18 +67,9 @@ export const useCurrencyLeg = (defaultMint?: string) => {
setMint: setMintAddress,
convertAmount: () => convertAmount(amount, mint),
sufficientBalance: () =>
account !== undefined &&
(convert(account, mint) >= parseFloat(amount))
account !== undefined && convert(account, mint) >= parseFloat(amount),
}),
[
mintAddress,
account,
mint,
amount,
tokenMap,
setAmount,
setMintAddress,
]
[mintAddress, account, mint, amount, tokenMap, setAmount, setMintAddress],
);
};
@ -80,7 +79,7 @@ export function TokenPairProvider({ children = null as any }) {
const history = useHistory();
const location = useLocation();
const [lastTypedAccount, setLastTypedAccount] = useState("");
const [lastTypedAccount, setLastTypedAccount] = useState('');
const base = useCurrencyLeg();
const mintAddressA = base.mintAddress;
@ -96,11 +95,9 @@ export function TokenPairProvider({ children = null as any }) {
useEffect(() => {
const base =
tokens.find((t) => t.address === mintAddressA)?.symbol ||
mintAddressA;
tokens.find(t => t.address === mintAddressA)?.symbol || mintAddressA;
const quote =
tokens.find((t) => t.address === mintAddressB)?.symbol ||
mintAddressB;
tokens.find(t => t.address === mintAddressB)?.symbol || mintAddressB;
document.title = `Swap | Serum (${base}/${quote})`;
}, [mintAddressA, mintAddressB, tokens, location]);
@ -109,13 +106,11 @@ export function TokenPairProvider({ children = null as any }) {
useEffect(() => {
// set history
const base =
tokens.find((t) => t.address === mintAddressA)?.symbol ||
mintAddressA;
tokens.find(t => t.address === mintAddressA)?.symbol || mintAddressA;
const quote =
tokens.find((t) => t.address === mintAddressB)?.symbol ||
mintAddressB;
tokens.find(t => t.address === mintAddressB)?.symbol || mintAddressB;
if (base && quote && location.pathname.indexOf("info") < 0) {
if (base && quote && location.pathname.indexOf('info') < 0) {
history.push({
search: `?pair=${base}-${quote}`,
});
@ -138,21 +133,21 @@ export function TokenPairProvider({ children = null as any }) {
let { defaultBase, defaultQuote } = getDefaultTokens(
tokens,
location.search
location.search,
);
if (!defaultBase || !defaultQuote) {
return;
}
setMintAddressA(
tokens.find((t) => t.symbol === defaultBase)?.address ||
(isValidAddress(defaultBase) ? defaultBase : "") ||
""
tokens.find(t => t.symbol === defaultBase)?.address ||
(isValidAddress(defaultBase) ? defaultBase : '') ||
'',
);
setMintAddressB(
tokens.find((t) => t.symbol === defaultQuote)?.address ||
(isValidAddress(defaultQuote) ? defaultQuote : "") ||
""
tokens.find(t => t.symbol === defaultQuote)?.address ||
(isValidAddress(defaultQuote) ? defaultQuote : '') ||
'',
);
// mintAddressA and mintAddressB are not included here to prevent infinite loop
// eslint-disable-next-line
@ -175,12 +170,12 @@ export function TokenPairProvider({ children = null as any }) {
// TODO: calculate
const result: number | string = 0;
if (typeof result === "string") {
if (typeof result === 'string') {
setDependent(result);
} else if (result !== undefined && Number.isFinite(result)) {
setDependent(result.toFixed(6));
} else {
setDependent("");
setDependent('');
}
}
}, [
@ -228,8 +223,8 @@ const isValidAddress = (address: string) => {
};
function getDefaultTokens(tokens: TokenInfo[], search: string) {
let defaultBase = "SOL";
let defaultQuote = "USDC";
let defaultBase = 'SOL';
let defaultQuote = 'USDC';
const nameToToken = tokens.reduce((map, item) => {
map.set(item.symbol, item);
@ -238,9 +233,9 @@ function getDefaultTokens(tokens: TokenInfo[], search: string) {
if (search) {
const urlParams = new URLSearchParams(search);
const pair = urlParams.get("pair");
const pair = urlParams.get('pair');
if (pair) {
let items = pair.split("-");
let items = pair.split('-');
if (items.length > 1) {
if (nameToToken.has(items[0]) || isValidAddress(items[0])) {

View File

@ -1,21 +1,30 @@
import {useCallback, useEffect, useRef, useState} from "react";
import { useConnection, useConnectionConfig, MintParser, cache, getMultipleAccounts, ParsedAccount, TokenAccountParser} from "@oyster/common";
import {WORMHOLE_PROGRAM_ID} from "../utils/ids";
import {ASSET_CHAIN} from "../utils/assets";
import { useEthereum } from "../contexts";
import { Connection, PublicKey } from "@solana/web3.js";
import { models } from "@oyster/common";
import { AccountInfo, MintInfo, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { useCallback, useEffect, useRef, useState } from 'react';
import {
useConnection,
useConnectionConfig,
MintParser,
cache,
getMultipleAccounts,
ParsedAccount,
TokenAccountParser,
} from '@oyster/common';
import { WORMHOLE_PROGRAM_ID } from '../utils/ids';
import { ASSET_CHAIN } from '../utils/assets';
import { useEthereum } from '../contexts';
import { Connection, PublicKey } from '@solana/web3.js';
import { models } from '@oyster/common';
import { AccountInfo, MintInfo, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { WrappedMetaLayout } from './../models/bridge';
import bs58 from "bs58";
import { COINGECKO_COIN_PRICE_API, COINGECKO_POOL_INTERVAL, useCoingecko } from "../contexts/coingecko";
import bs58 from 'bs58';
import {
COINGECKO_COIN_PRICE_API,
COINGECKO_POOL_INTERVAL,
useCoingecko,
} from '../contexts/coingecko';
export const useEthUserAccount = () => {
const [address, setAddress] = useState('');
// const { web3 } = useEthereum();
return address;
}
};

View File

@ -1,41 +1,56 @@
import {useCallback, useEffect, useRef, useState} from "react";
import { useConnection, useConnectionConfig, MintParser, cache, getMultipleAccounts, ParsedAccount, TokenAccountParser, programIds, formatTokenAmount, fromLamports} from "@oyster/common";
import {WORMHOLE_PROGRAM_ID} from "../utils/ids";
import {ASSET_CHAIN} from "../utils/assets";
import { useEthereum } from "../contexts";
import { Connection, PublicKey } from "@solana/web3.js";
import { models } from "@oyster/common";
import { AccountInfo, MintInfo } from "@solana/spl-token";
import { bridgeAuthorityKey, wrappedAssetMintKey, WrappedMetaLayout } from './../models/bridge';
import bs58 from "bs58";
import { COINGECKO_COIN_PRICE_API, COINGECKO_POOL_INTERVAL, useCoingecko } from "../contexts/coingecko";
import { useCallback, useEffect, useRef, useState } from 'react';
import {
useConnection,
useConnectionConfig,
MintParser,
cache,
getMultipleAccounts,
ParsedAccount,
TokenAccountParser,
programIds,
formatTokenAmount,
fromLamports,
} from '@oyster/common';
import { WORMHOLE_PROGRAM_ID } from '../utils/ids';
import { ASSET_CHAIN } from '../utils/assets';
import { useEthereum } from '../contexts';
import { Connection, PublicKey } from '@solana/web3.js';
import { models } from '@oyster/common';
import { AccountInfo, MintInfo } from '@solana/spl-token';
import {
bridgeAuthorityKey,
wrappedAssetMintKey,
WrappedMetaLayout,
} from './../models/bridge';
import bs58 from 'bs58';
import {
COINGECKO_COIN_PRICE_API,
COINGECKO_POOL_INTERVAL,
useCoingecko,
} from '../contexts/coingecko';
type WrappedAssetMeta = {
chain: number,
decimals: number,
address: string,
mintKey: string,
mint?: ParsedAccount<MintInfo>,
amount: number,
amountInUSD: number,
logo?: string,
symbol?: string,
name?: string,
price?: number,
explorer?: string,
wrappedExplorer?: string,
chain: number;
decimals: number;
address: string;
mintKey: string;
mint?: ParsedAccount<MintInfo>;
amount: number;
amountInUSD: number;
logo?: string;
symbol?: string;
name?: string;
price?: number;
explorer?: string;
wrappedExplorer?: string;
};
const queryWrappedMetaAccounts = async (
authorityKey: PublicKey,
connection: Connection,
setExternalAssets: (arr: WrappedAssetMeta[]) => void
) => {
setExternalAssets: (arr: WrappedAssetMeta[]) => void,
) => {
const filters = [
{
dataSize: WrappedMetaLayout.span,
@ -60,63 +75,75 @@ const queryWrappedMetaAccounts = async (
const assetsByMint = new Map<string, WrappedAssetMeta>();
// aggregate all assets that are not from Solana
resp.result.map((acc: any) => ({
publicKey: new PublicKey(acc.pubkey),
account: {
data: bs58.decode(acc.account.data),
executable: acc.account.executable,
owner: new PublicKey(acc.account.owner),
lamports: acc.account.lamports,
},
})).map((acc: any) => {
if(acc.account.data.length === WrappedMetaLayout.span) {
const metaAccount = WrappedMetaLayout.decode(acc.account.data);
if (metaAccount.chain !== ASSET_CHAIN.Solana) {
const assetAddress: string = new Buffer(metaAccount.address.slice(12)).toString("hex");
resp.result
.map((acc: any) => ({
publicKey: new PublicKey(acc.pubkey),
account: {
data: bs58.decode(acc.account.data),
executable: acc.account.executable,
owner: new PublicKey(acc.account.owner),
lamports: acc.account.lamports,
},
}))
.map((acc: any) => {
if (acc.account.data.length === WrappedMetaLayout.span) {
const metaAccount = WrappedMetaLayout.decode(acc.account.data);
if (metaAccount.chain !== ASSET_CHAIN.Solana) {
const assetAddress: string = new Buffer(
metaAccount.address.slice(12),
).toString('hex');
assets.set(assetAddress, {
chain: metaAccount.chain,
address: assetAddress,
decimals: 9,
mintKey: '',
amount: 0,
amountInUSD: 0,
// TODO: customize per chain
explorer: `https://etherscan.io/address/0x${assetAddress}`
});
assets.set(assetAddress, {
chain: metaAccount.chain,
address: assetAddress,
decimals: 9,
mintKey: '',
amount: 0,
amountInUSD: 0,
// TODO: customize per chain
explorer: `https://etherscan.io/address/0x${assetAddress}`,
});
}
}
}
});
});
// build PDAs for mints
await Promise.all([...assets.keys()].map(async key => {
const meta = assets.get(key);
if(!meta) {
throw new Error('missing key');
}
await Promise.all(
[...assets.keys()].map(async key => {
const meta = assets.get(key);
if (!meta) {
throw new Error('missing key');
}
meta.mintKey = (await wrappedAssetMintKey(programIds().wormhole.pubkey, authorityKey, {
chain: meta.chain,
address: Buffer.from(meta.address, "hex"),
decimals: Math.min(meta.decimals, 9)
})).toBase58();
meta.mintKey = (
await wrappedAssetMintKey(programIds().wormhole.pubkey, authorityKey, {
chain: meta.chain,
address: Buffer.from(meta.address, 'hex'),
decimals: Math.min(meta.decimals, 9),
})
).toBase58();
assetsByMint.set(meta.mintKey, meta);
assetsByMint.set(meta.mintKey, meta);
return meta;
}));
return meta;
}),
);
// query for all mints
const mints = await getMultipleAccounts(connection, [...assetsByMint.keys()], 'singleGossip');
const mints = await getMultipleAccounts(
connection,
[...assetsByMint.keys()],
'singleGossip',
);
// cache mints and listen for changes
mints.keys.forEach((key, index) => {
if(!mints.array[index]) {
if (!mints.array[index]) {
return;
}
const asset = assetsByMint.get(key);
if(!asset) {
if (!asset) {
throw new Error('missing mint');
}
@ -128,50 +155,67 @@ const queryWrappedMetaAccounts = async (
asset.mint = cache.get(key);
asset.wrappedExplorer = `https://explorer.solana.com/address/${asset.mintKey}`;
if(asset.mint) {
if (asset.mint) {
asset.amount =
asset.mint?.info.supply.toNumber() /
Math.pow(10, asset.mint?.info.decimals) || 0;
asset.amount = asset.mint?.info.supply.toNumber() / Math.pow(10, asset.mint?.info.decimals) || 0;
if (!asset.mint) {
throw new Error('missing mint');
}
if(!asset.mint) {
throw new Error('missing mint')
// monitor updates for mints
connection.onAccountChange(asset.mint?.pubkey, acc => {
cache.add(key, acc);
asset.mint = cache.get(key);
asset.amount = asset.mint?.info.supply.toNumber() || 0;
setExternalAssets([...assets.values()]);
});
}
// monitor updates for mints
connection.onAccountChange(asset.mint?.pubkey, (acc) => {
cache.add(key, acc);
asset.mint = cache.get(key);
asset.amount = asset.mint?.info.supply.toNumber() || 0;
setExternalAssets([...assets.values()]);
});
}
setExternalAssets([...assets.values()]);
});
};
const queryCustodyAccounts = async (authorityKey: PublicKey, connection: Connection) => {
const queryCustodyAccounts = async (
authorityKey: PublicKey,
connection: Connection,
) => {
debugger;
const tokenAccounts = await connection.getTokenAccountsByOwner(
authorityKey,
{
const tokenAccounts = await connection
.getTokenAccountsByOwner(authorityKey, {
programId: programIds().token,
}).then(acc => acc.value.map(a => cache.add(a.pubkey, a.account, TokenAccountParser) as ParsedAccount<AccountInfo>));
})
.then(acc =>
acc.value.map(
a =>
cache.add(
a.pubkey,
a.account,
TokenAccountParser,
) as ParsedAccount<AccountInfo>,
),
);
// query for mints
await getMultipleAccounts(connection, tokenAccounts.map(a => a.info.mint.toBase58()), 'single').then(({ keys, array }) => {
await getMultipleAccounts(
connection,
tokenAccounts.map(a => a.info.mint.toBase58()),
'single',
).then(({ keys, array }) => {
keys.forEach((key, index) => {
if(!array[index]) {
if (!array[index]) {
return;
}
return cache.add(key, array[index], MintParser);
})
});
});
return tokenAccounts.map(token => {
const mint = cache.get(token.info.mint) as ParsedAccount<MintInfo>;
const asset = mint.pubkey.toBase58()
const asset = mint.pubkey.toBase58();
return {
address: asset,
chain: ASSET_CHAIN.Solana,
@ -182,7 +226,7 @@ const queryCustodyAccounts = async (authorityKey: PublicKey, connection: Connec
amountInUSD: 0,
explorer: `https://explorer.solana.com/address/${asset}`,
} as WrappedAssetMeta;
})
});
};
export const useWormholeAccounts = () => {
@ -206,22 +250,30 @@ export const useWormholeAccounts = () => {
let authorityKey = await bridgeAuthorityKey(programIds().wormhole.pubkey);
// get all accounts that moved assets from solana to other chains
const custodyAccounts = await queryCustodyAccounts(authorityKey, connection);
const custodyAccounts = await queryCustodyAccounts(
authorityKey,
connection,
);
// query wrapped assets that were imported to solana from other chains
queryWrappedMetaAccounts(authorityKey, connection, (assets) => {
setExternalAssets([...custodyAccounts, ...assets]
.sort((a, b) => a?.symbol?.localeCompare(b.symbol || '') || 0))
queryWrappedMetaAccounts(authorityKey, connection, assets => {
setExternalAssets(
[...custodyAccounts, ...assets].sort(
(a, b) => a?.symbol?.localeCompare(b.symbol || '') || 0,
),
);
}).then(() => setLoading(false));
// TODO: listen to solana accounts for updates
wormholeSubId = connection.onProgramAccountChange(WORMHOLE_PROGRAM_ID, (info) => {
if (info.accountInfo.data.length === WrappedMetaLayout.span) {
// TODO: check if new account and update external assets
}
});
wormholeSubId = connection.onProgramAccountChange(
WORMHOLE_PROGRAM_ID,
info => {
if (info.accountInfo.data.length === WrappedMetaLayout.span) {
// TODO: check if new account and update external assets
}
},
);
})();
return () => {
@ -231,7 +283,7 @@ export const useWormholeAccounts = () => {
const coingeckoTimer = useRef<number>(0);
const dataSourcePriceQuery = useCallback(async () => {
if(externalAssets.length === 0) {
if (externalAssets.length === 0) {
return;
}
@ -240,58 +292,63 @@ export const useWormholeAccounts = () => {
const assetsToQueryNames: WrappedAssetMeta[] = [];
const ids = externalAssets.map(asset => {
// TODO: add different nets/clusters
const ids = externalAssets
.map(asset => {
// TODO: add different nets/clusters
let knownToken = tokenMap.get(asset.mintKey);
if (knownToken) {
asset.logo = knownToken.logoURI;
asset.symbol = knownToken.symbol;
asset.name = knownToken.name;
}
let token = ethTokens.get(`0x${asset.address || ''}`);
if (token) {
asset.logo = token.logoURI;
asset.symbol = token.symbol;
asset.name = token.name;
}
if (asset.symbol) {
let coinInfo = coinList.get(asset.symbol.toLowerCase());
if(coinInfo) {
idToAsset.set(coinInfo.id, [...(idToAsset.get(coinInfo.id) || []), asset]);
addressToId.set(asset.address, coinInfo.id);
return coinInfo.id;
let knownToken = tokenMap.get(asset.mintKey);
if (knownToken) {
asset.logo = knownToken.logoURI;
asset.symbol = knownToken.symbol;
asset.name = knownToken.name;
}
}
}).filter(_ => _);
let token = ethTokens.get(`0x${asset.address || ''}`);
if (token) {
asset.logo = token.logoURI;
asset.symbol = token.symbol;
asset.name = token.name;
}
if (asset.symbol) {
let coinInfo = coinList.get(asset.symbol.toLowerCase());
if (coinInfo) {
idToAsset.set(coinInfo.id, [
...(idToAsset.get(coinInfo.id) || []),
asset,
]);
addressToId.set(asset.address, coinInfo.id);
return coinInfo.id;
}
}
})
.filter(_ => _);
assetsToQueryNames.map(() => {
// TODO: query names using ERC-20?
});
if(ids.length === 0) {
if (ids.length === 0) {
return;
}
const parameters = `?ids=${ids.join(',')}&vs_currencies=usd`;
const resp = await window.fetch(COINGECKO_COIN_PRICE_API+parameters);
const resp = await window.fetch(COINGECKO_COIN_PRICE_API + parameters);
const data = await resp.json();
let totalInUSD = 0;
Object.keys(data).forEach(key => {
let assets = idToAsset.get(key);
if(!assets) {
if (!assets) {
return;
}
assets.forEach(asset => {
asset.price = data[key]?.usd || 1;
asset.amountInUSD = Math.round(asset.amount * (asset.price || 1) * 100) / 100;
asset.amountInUSD =
Math.round(asset.amount * (asset.price || 1) * 100) / 100;
totalInUSD += asset.amountInUSD;
});
});
@ -300,9 +357,9 @@ export const useWormholeAccounts = () => {
coingeckoTimer.current = window.setTimeout(
() => dataSourcePriceQuery(),
COINGECKO_POOL_INTERVAL
COINGECKO_POOL_INTERVAL,
);
}, [externalAssets, setAmountInUSD])
}, [externalAssets, setAmountInUSD]);
useEffect(() => {
if (externalAssets && coinList) {
@ -319,4 +376,4 @@ export const useWormholeAccounts = () => {
externalAssets,
totalInUSD: amountInUSD,
};
}
};

View File

@ -1,15 +1,15 @@
import { HashRouter, Route, Switch } from 'react-router-dom';
import React from 'react';
import { contexts } from '@oyster/common';
import { MarketProvider, TokenPairProvider, EthereumProvider } from './contexts';
import {
MarketProvider,
TokenPairProvider,
EthereumProvider,
} from './contexts';
import { AppLayout } from './components/Layout';
import {
FaucetView,
HomeView,
TransferView,
} from './views';
import {CoingeckoProvider} from "./contexts/coingecko";
import { FaucetView, HomeView, TransferView } from './views';
import { CoingeckoProvider } from './contexts/coingecko';
import { BridgeProvider } from './contexts/bridge';
import { UseWalletProvider } from 'use-wallet';
const { WalletProvider } = contexts.Wallet;
@ -30,9 +30,17 @@ export function Routes() {
<CoingeckoProvider>
<AppLayout>
<Switch>
<Route exact path="/" component={() => <HomeView />} />
<Route
exact
path="/"
component={() => <HomeView />}
/>
<Route path="/move" children={<TransferView />} />
<Route exact path="/faucet" children={<FaucetView />} />
<Route
exact
path="/faucet"
children={<FaucetView />}
/>
</Switch>
</AppLayout>
</CoingeckoProvider>

View File

@ -38,3 +38,12 @@ export enum ASSET_CHAIN {
Solana = 1,
Ethereum = 2,
}
const CHAIN_NAME = {
[ASSET_CHAIN.Solana]: 'Solana',
[ASSET_CHAIN.Ethereum]: 'Ethereum',
};
export const chainToName = (chain?: ASSET_CHAIN) => {
return CHAIN_NAME[chain || ASSET_CHAIN.Ethereum];
};

View File

@ -13,7 +13,7 @@ export const FaucetView = () => {
const airdrop = useCallback(() => {
if (!wallet?.publicKey) {
return;
return;
}
connection
@ -21,28 +21,28 @@ export const FaucetView = () => {
.then(() => {
notify({
message: LABELS.ACCOUNT_FUNDED,
type: "success",
type: 'success',
});
});
}, [wallet, wallet?.publicKey, connection]);
const bodyStyle: React.CSSProperties = {
display: "flex",
display: 'flex',
flex: 1,
justifyContent: "center",
alignItems: "center",
height: "100%",
justifyContent: 'center',
alignItems: 'center',
height: '100%',
};
return (
<div className="flexColumn" style={{ flex: 1 }}>
<Card title={"Faucet"} bodyStyle={bodyStyle} style={{ flex: 1 }}>
<Card title={'Faucet'} bodyStyle={bodyStyle} style={{ flex: 1 }}>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
alignItems: "center",
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
alignItems: 'center',
}}
>
<div className="deposit-input-title" style={{ margin: 10 }}>

View File

@ -1,18 +1,17 @@
import { Table, Col, Row, Statistic, Button } from 'antd';
import React from 'react';
import { GUTTER } from '../../constants';
import { formatNumber, formatUSD, shortenAddress} from '@oyster/common';
import { formatNumber, formatUSD, shortenAddress } from '@oyster/common';
import './itemStyle.less';
import { Link } from 'react-router-dom';
import {useWormholeAccounts} from "../../hooks/useWormholeAccounts";
import { useWormholeAccounts } from '../../hooks/useWormholeAccounts';
import { TokenDisplay } from '../../components/TokenDisplay';
export const HomeView = () => {
const {
loading: loadingLockedAccounts,
externalAssets,
totalInUSD
totalInUSD,
} = useWormholeAccounts();
const columns = [
@ -26,7 +25,12 @@ export const HomeView = () => {
style: {},
},
children: (
<span style={{ display: 'inline-flex', alignItems: 'center' }}>{record.logo && <TokenDisplay logo={record.logo} chain={record.chain} />} {record.symbol}</span>
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
{record.logo && (
<TokenDisplay logo={record.logo} chain={record.chain} />
)}{' '}
{record.symbol}
</span>
),
};
},
@ -56,9 +60,7 @@ export const HomeView = () => {
props: {
style: { textAlign: 'right' },
},
children: (
record.price ? formatUSD.format(record.price) : '--'
),
children: record.price ? formatUSD.format(record.price) : '--',
};
},
},
@ -72,7 +74,9 @@ export const HomeView = () => {
style: {},
},
children: (
<a href={record.explorer} target="_blank">{shortenAddress(text, 6)}</a>
<a href={record.explorer} target="_blank">
{shortenAddress(text, 6)}
</a>
),
};
},
@ -87,11 +91,13 @@ export const HomeView = () => {
style: {},
},
children: (
<a href={record.wrappedExplorer} target="_blank">{shortenAddress(text, 6)}</a>
<a href={record.wrappedExplorer} target="_blank">
{shortenAddress(text, 6)}
</a>
),
};
},
}
},
];
return (
@ -104,7 +110,9 @@ export const HomeView = () => {
>
<Col xs={24} xl={12} className="app-title">
<h1>Wormhole</h1>
<h2><span>Ethereum + Solana Bridge</span></h2>
<h2>
<span>Ethereum + Solana Bridge</span>
</h2>
<Link to="/move">
<Button className="app-action">Get Started</Button>
</Link>
@ -117,7 +125,11 @@ export const HomeView = () => {
/>
</Col>
</Row>
<Table dataSource={externalAssets.filter(a => a.name)} columns={columns} loading={loadingLockedAccounts} />
<Table
dataSource={externalAssets.filter(a => a.name)}
columns={columns}
loading={loadingLockedAccounts}
/>
</div>
);
};

View File

@ -1,3 +1,3 @@
export { HomeView } from "./home";
export { FaucetView } from "./faucet";
export { TransferView } from "./transfer";
export { HomeView } from './home';
export { FaucetView } from './faucet';
export { TransferView } from './transfer';

View File

@ -8,9 +8,9 @@ export const TransferView = () => {
<Card
className="bridge-card"
headStyle={{ padding: 0 }}
bodyStyle={{ position: "relative" }}
>
<Transfer />
bodyStyle={{ position: 'relative' }}
>
<Transfer />
</Card>
</div>
);

View File

@ -1,9 +1,9 @@
declare module "buffer-layout" {
declare module 'buffer-layout' {
const bl: any;
export = bl;
}
declare module "jazzicon" {
declare module 'jazzicon' {
const jazzicon: any;
export = jazzicon;
}