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", "version": "0.0.1",
"dependencies": { "dependencies": {
"@oyster/common": "0.0.1", "@oyster/common": "0.0.1",

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import {EventEmitter} from "@oyster/common"; import { EventEmitter } from '@oyster/common';
import React, {useContext, useEffect, useState} from "react"; import React, { useContext, useEffect, useState } from 'react';
import {MarketsContextState} from "./market"; import { MarketsContextState } from './market';
export const COINGECKO_POOL_INTERVAL = 10000; // 2 min export const COINGECKO_POOL_INTERVAL = 10000; // 2 min
export const COINGECKO_API = "https://api.coingecko.com/api/v3/" export const COINGECKO_API = 'https://api.coingecko.com/api/v3/';
export const COINGECKO_COIN_LIST_API = `${COINGECKO_API}coins/list` export const COINGECKO_COIN_LIST_API = `${COINGECKO_API}coins/list`;
export const COINGECKO_COIN_PRICE_API = `${COINGECKO_API}simple/price` export const COINGECKO_COIN_PRICE_API = `${COINGECKO_API}simple/price`;
interface CoinInfo { interface CoinInfo {
id: string; id: string;
@ -17,28 +17,30 @@ export interface CoingeckoContextState {
coinList: Map<string, CoinInfo>; 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 }) { 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(() => { useEffect(() => {
(async () => { (async () => {
const listResponse = await fetch(COINGECKO_COIN_LIST_API); const listResponse = await fetch(COINGECKO_COIN_LIST_API);
const coinList: CoinInfo[] = await listResponse.json(); const coinList: CoinInfo[] = await listResponse.json();
setCoinList( setCoinList(
coinList.reduce((coins, val) => { coinList.reduce((coins, val) => {
coins.set(val.symbol, val); coins.set(val.symbol, val);
return coins; return coins;
}, new Map<string, CoinInfo>()) }, new Map<string, CoinInfo>()),
) );
})(); })();
}, [setCoinList]); }, [setCoinList]);
return ( return (
<CoingeckoContext.Provider value={{coinList: coinList}}> <CoingeckoContext.Provider value={{ coinList: coinList }}>
{children} {children}
</CoingeckoContext.Provider> </CoingeckoContext.Provider>
) );
} }
export const useCoingecko = () => { 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 // @ts-ignore
import { useWallet as useEthereumWallet } from "use-wallet"; import { useWallet as useEthereumWallet } from 'use-wallet';
// @ts-ignore // @ts-ignore
import WalletConnectProvider from "@walletconnect/web3-provider"; import WalletConnectProvider from '@walletconnect/web3-provider';
// @ts-ignore // @ts-ignore
import Fortmatic from "fortmatic"; import Fortmatic from 'fortmatic';
import Torus from "@toruslabs/torus-embed"; import Torus from '@toruslabs/torus-embed';
import { useConnectionConfig, useWallet, ENV } from "@oyster/common"; import { useConnectionConfig, useWallet, ENV } from '@oyster/common';
import { TokenList, TokenInfo } from '@uniswap/token-lists'; import { TokenList, TokenInfo } from '@uniswap/token-lists';
import { ethers } from "ethers"; import { ethers } from 'ethers';
export interface EthereumContextState { export interface EthereumContextState {
provider?: ethers.providers.Web3Provider; provider?: ethers.providers.Web3Provider;
@ -25,15 +32,15 @@ export const EthereumContext = createContext<EthereumContextState>({
accounts: [''], accounts: [''],
}); });
export const EthereumProvider: FunctionComponent = ({children}) => { export const EthereumProvider: FunctionComponent = ({ children }) => {
const [accounts, setAccounts] = useState<string[]>(['']); const [accounts, setAccounts] = useState<string[]>(['']);
const [provider, setProvider] = useState<ethers.providers.Web3Provider>(); const [provider, setProvider] = useState<ethers.providers.Web3Provider>();
const { env } = useConnectionConfig(); const { env } = useConnectionConfig();
const { connected } = useWallet(); const { connected } = useWallet();
const wallet = useEthereumWallet(); const wallet = useEthereumWallet();
const [tokens, setTokens] = useState<{ const [tokens, setTokens] = useState<{
map: Map<string, TokenInfo>, map: Map<string, TokenInfo>;
list: TokenInfo[], list: TokenInfo[];
}>({ }>({
map: new Map<string, TokenInfo>(), map: new Map<string, TokenInfo>(),
list: [], list: [],
@ -42,26 +49,36 @@ export const EthereumProvider: FunctionComponent = ({children}) => {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const map = new Map<string, TokenInfo>(); const map = new Map<string, TokenInfo>();
const listResponse: TokenList[] = (await Promise.all([ const listResponse: TokenList[] = await Promise.all([
fetch('https://gateway.ipfs.io/ipns/tokens.uniswap.org').then(_ => _.json()), fetch('https://gateway.ipfs.io/ipns/tokens.uniswap.org').then(_ =>
_.json(),
),
fetch('https://tokenlist.aave.eth.link/').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 item = {
const address = val.address.toLowerCase(); ...val,
const extraTag = i === 2 && !acc.has(address) ? 'longList' : ''; 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 = { acc.set(address, item);
...val, return acc;
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 `, }, map),
tags: val.tags ? [...val.tags, extraTag] : [extraTag] );
};
acc.set(address, item);
return acc;
}, map));
setTokens({ setTokens({
map, map,
@ -71,26 +88,30 @@ export const EthereumProvider: FunctionComponent = ({children}) => {
}, [setTokens]); }, [setTokens]);
useEffect(() => { useEffect(() => {
if(connected) { if (connected) {
// @ts-ignore // @ts-ignore
window.ethereum.enable(); window.ethereum.enable();
// @ts-ignore // @ts-ignore
const provider = new ethers.providers.Web3Provider(window.ethereum as any); const provider = new ethers.providers.Web3Provider(
const signer = provider.getSigner(); (window as any).ethereum ,
signer.getAddress().then(account => setAccounts([account])); );
const signer = provider.getSigner();
signer.getAddress().then(account => setAccounts([account]));
setProvider(provider); setProvider(provider);
} }
}, [connected]) }, [connected]);
return ( return (
<EthereumContext.Provider value={{ tokens: tokens.list, tokenMap: tokens.map, accounts, provider }}> <EthereumContext.Provider
value={{ tokens: tokens.list, tokenMap: tokens.map, accounts, provider }}
>
{children} {children}
</EthereumContext.Provider> </EthereumContext.Provider>
); );
} };
export const useEthereum = () => { export const useEthereum = () => {
const context = useContext(EthereumContext); const context = useContext(EthereumContext);
return context; return context;
} };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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