From b1a237db999f49c3242fcd63605762517f54e17b Mon Sep 17 00:00:00 2001 From: Evan Gray <56235822+evan-gray@users.noreply.github.com> Date: Sun, 1 Aug 2021 23:21:28 -0400 Subject: [PATCH] bridge_ui: solana balances, begin wasm integration Change-Id: Ifde4ec688ff678f46c1606e519fc36c0ece68fd4 --- bridge_ui/craco.config.js | 28 +++ bridge_ui/package-lock.json | 271 ++++++++++++++++++++- bridge_ui/package.json | 14 +- bridge_ui/src/components/KeyAndBalance.tsx | 50 ++++ bridge_ui/src/components/Transfer.tsx | 81 ++++-- bridge_ui/src/hooks/useEthereumBalance.ts | 61 +++-- bridge_ui/src/hooks/useSolanaBalance.ts | 47 ++++ bridge_ui/src/utils/consts.ts | 2 + bridge_ui/src/utils/transferFrom.ts | 41 +++- generate-wasm.sh | 12 +- 10 files changed, 554 insertions(+), 53 deletions(-) create mode 100644 bridge_ui/craco.config.js create mode 100644 bridge_ui/src/components/KeyAndBalance.tsx create mode 100644 bridge_ui/src/hooks/useSolanaBalance.ts diff --git a/bridge_ui/craco.config.js b/bridge_ui/craco.config.js new file mode 100644 index 000000000..9ccca4fce --- /dev/null +++ b/bridge_ui/craco.config.js @@ -0,0 +1,28 @@ +const { addBeforeLoader, loaderByName } = require("@craco/craco"); + +module.exports = { + webpack: { + configure: (webpackConfig) => { + const wasmExtensionRegExp = /\.wasm$/; + webpackConfig.resolve.extensions.push(".wasm"); + + webpackConfig.module.rules.forEach((rule) => { + (rule.oneOf || []).forEach((oneOf) => { + if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) { + oneOf.exclude.push(wasmExtensionRegExp); + } + }); + }); + + const wasmLoader = { + test: /\.wasm$/, + include: /node_modules\/(bridge|token-bridge)/, + loaders: ["wasm-loader"], + }; + + addBeforeLoader(webpackConfig, loaderByName("file-loader"), wasmLoader); + + return webpackConfig; + }, + }, +}; diff --git a/bridge_ui/package-lock.json b/bridge_ui/package-lock.json index 5b68c3414..77839986b 100644 --- a/bridge_ui/package-lock.json +++ b/bridge_ui/package-lock.json @@ -5,7 +5,9 @@ "requires": true, "packages": { "": { + "name": "test_ui", "version": "0.1.0", + "hasInstallScript": true, "dependencies": { "@material-ui/core": "^4.12.2", "@metamask/detect-provider": "^1.2.0", @@ -13,16 +15,20 @@ "@solana/wallet-base": "^0.0.1", "@solana/web3.js": "^1.22.0", "@typechain/ethers-v5": "^7.0.1", + "bridge": "file:rust_modules\\core", "ethers": "^5.4.1", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-scripts": "4.0.3" + "react-scripts": "4.0.3", + "token-bridge": "file:rust_modules\\token" }, "devDependencies": { + "@craco/craco": "^6.2.0", "@openzeppelin/contracts": "^4.2.0", "@truffle/hdwallet-provider": "^1.4.1", "copy-dir": "^1.3.0", - "truffle": "^5.4.1" + "truffle": "^5.4.1", + "wasm-loader": "^1.3.0" } }, "node_modules/@apollo/client": { @@ -2005,6 +2011,27 @@ "ieee754": "^1.1.13" } }, + "node_modules/@craco/craco": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.2.0.tgz", + "integrity": "sha512-kLc4GSdgR9D5JiZmSxtzbvBKcUFSJqMXImRjjYf5pacwiyAs3XfQwai7T+pExfLQNUnytgkL8jRFUJeYrkVr7g==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "webpack-merge": "^4.2.2" + }, + "bin": { + "craco": "bin/craco.js" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "react-scripts": "^4.0.0" + } + }, "node_modules/@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -10222,6 +10249,10 @@ "node": ">=8" } }, + "node_modules/bridge": { + "resolved": "rust_modules/core", + "link": true + }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -34482,6 +34513,10 @@ "node": ">=0.6" } }, + "node_modules/token-bridge": { + "resolved": "rust_modules/token", + "link": true + }, "node_modules/tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -35551,6 +35586,72 @@ "makeerror": "1.0.x" } }, + "node_modules/wasm-dce": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz", + "integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.0.0-beta.39", + "@babel/traverse": "^7.0.0-beta.39", + "@babel/types": "^7.0.0-beta.39", + "babylon": "^7.0.0-beta.39", + "webassembly-interpreter": "0.0.30" + } + }, + "node_modules/wasm-dce/node_modules/babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", + "dev": true, + "peer": true, + "bin": { + "babylon": "bin/babylon.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/wasm-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wasm-loader/-/wasm-loader-1.3.0.tgz", + "integrity": "sha512-R4s75XH+o8qM+WaRrAU9S2rbAMDzob18/S3V8R9ZoFpZkPWLAohWWlzWAp1ybeTkOuuku/X1zJtxiV0pBYxZww==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "wasm-dce": "^1.0.0" + }, + "peerDependencies": { + "wasm-dce": "1.x" + } + }, + "node_modules/wasm-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/wasm-loader/node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", @@ -36335,6 +36436,46 @@ "xhr-request-promise": "^0.1.2" } }, + "node_modules/webassembly-floating-point-hex-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/webassembly-floating-point-hex-parser/-/webassembly-floating-point-hex-parser-0.1.2.tgz", + "integrity": "sha512-TUf1H++8U10+stJbFydnvrpG5Sznz5Rilez/oZlV5zI0C/e4cSxd8rALAJ8VpTvjVWxLmL3SVSJUK6Ap9AoiNg==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/webassembly-interpreter": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/webassembly-interpreter/-/webassembly-interpreter-0.0.30.tgz", + "integrity": "sha512-+Jdy2piEvz9T5j751mOE8+rBO12p+nNW6Fg4kJZ+zP1oUfsm+151sbAbM8AFxWTURmWCGP+r8Lxwfv3pzN1bCQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0-beta.36", + "long": "^3.2.0", + "webassembly-floating-point-hex-parser": "0.1.2" + }, + "bin": { + "wasm": "lib/bin/repl.js", + "wasm2wast": "lib/bin/wasm2wast.js", + "wasmast": "lib/bin/wasmast.js", + "wasmdump": "lib/bin/wasmdump.js", + "wasmrun": "lib/bin/wasmrun.js", + "wastast": "lib/bin/wastast.js" + } + }, + "node_modules/webassembly-interpreter/node_modules/long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -37076,6 +37217,15 @@ "node": ">= 4.0.0" } }, + "node_modules/webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", @@ -38362,6 +38512,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, "optional": true + }, + "rust_modules/core": { + "name": "bridge", + "version": "0.1.0" + }, + "rust_modules/token": { + "name": "token-bridge", + "version": "0.1.0" } }, "dependencies": { @@ -39743,6 +39901,18 @@ } } }, + "@craco/craco": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.2.0.tgz", + "integrity": "sha512-kLc4GSdgR9D5JiZmSxtzbvBKcUFSJqMXImRjjYf5pacwiyAs3XfQwai7T+pExfLQNUnytgkL8jRFUJeYrkVr7g==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "webpack-merge": "^4.2.2" + } + }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -46437,6 +46607,9 @@ "fill-range": "^7.0.1" } }, + "bridge": { + "version": "file:rust_modules/core" + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -66128,6 +66301,9 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "token-bridge": { + "version": "file:rust_modules/token" + }, "tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -66999,6 +67175,60 @@ "makeerror": "1.0.x" } }, + "wasm-dce": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz", + "integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.0.0-beta.39", + "@babel/traverse": "^7.0.0-beta.39", + "@babel/types": "^7.0.0-beta.39", + "babylon": "^7.0.0-beta.39", + "webassembly-interpreter": "0.0.30" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", + "dev": true, + "peer": true + } + } + }, + "wasm-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wasm-loader/-/wasm-loader-1.3.0.tgz", + "integrity": "sha512-R4s75XH+o8qM+WaRrAU9S2rbAMDzob18/S3V8R9ZoFpZkPWLAohWWlzWAp1ybeTkOuuku/X1zJtxiV0pBYxZww==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", @@ -67692,6 +67922,34 @@ } } }, + "webassembly-floating-point-hex-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/webassembly-floating-point-hex-parser/-/webassembly-floating-point-hex-parser-0.1.2.tgz", + "integrity": "sha512-TUf1H++8U10+stJbFydnvrpG5Sznz5Rilez/oZlV5zI0C/e4cSxd8rALAJ8VpTvjVWxLmL3SVSJUK6Ap9AoiNg==", + "dev": true, + "peer": true + }, + "webassembly-interpreter": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/webassembly-interpreter/-/webassembly-interpreter-0.0.30.tgz", + "integrity": "sha512-+Jdy2piEvz9T5j751mOE8+rBO12p+nNW6Fg4kJZ+zP1oUfsm+151sbAbM8AFxWTURmWCGP+r8Lxwfv3pzN1bCQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.0.0-beta.36", + "long": "^3.2.0", + "webassembly-floating-point-hex-parser": "0.1.2" + }, + "dependencies": { + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true, + "peer": true + } + } + }, "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -68494,6 +68752,15 @@ } } }, + "webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, "webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", diff --git a/bridge_ui/package.json b/bridge_ui/package.json index 791a355ca..fecff3617 100644 --- a/bridge_ui/package.json +++ b/bridge_ui/package.json @@ -9,16 +9,18 @@ "@solana/wallet-base": "^0.0.1", "@solana/web3.js": "^1.22.0", "@typechain/ethers-v5": "^7.0.1", + "bridge": "file:rust_modules\\core", "ethers": "^5.4.1", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-scripts": "4.0.3" + "react-scripts": "4.0.3", + "token-bridge": "file:rust_modules\\token" }, "scripts": { "postinstall": "npm run build-contracts", - "start": "react-scripts start", - "build": "npm run build-contracts && react-scripts build", - "test": "npm run build-contracts && react-scripts test", + "start": "craco start", + "build": "npm run build-contracts && craco build", + "test": "npm run build-contracts && craco test", "eject": "react-scripts eject", "build-contracts": "npm run build --prefix ../ethereum && node scripts/copyContracts.js && typechain --target=ethers-v5 --out-dir=src/ethers-contracts contracts/*.json" }, @@ -41,9 +43,11 @@ ] }, "devDependencies": { + "@craco/craco": "^6.2.0", "@openzeppelin/contracts": "^4.2.0", "@truffle/hdwallet-provider": "^1.4.1", "copy-dir": "^1.3.0", - "truffle": "^5.4.1" + "truffle": "^5.4.1", + "wasm-loader": "^1.3.0" } } diff --git a/bridge_ui/src/components/KeyAndBalance.tsx b/bridge_ui/src/components/KeyAndBalance.tsx new file mode 100644 index 000000000..670a33ef4 --- /dev/null +++ b/bridge_ui/src/components/KeyAndBalance.tsx @@ -0,0 +1,50 @@ +import { Typography } from "@material-ui/core"; +import { useEthereumProvider } from "../contexts/EthereumProviderContext"; +import { useSolanaWallet } from "../contexts/SolanaWalletContext"; +import useEthereumBalance from "../hooks/useEthereumBalance"; +import useSolanaBalance from "../hooks/useSolanaBalance"; +import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts"; +import EthereumSignerKey from "./EthereumSignerKey"; +import SolanaWalletKey from "./SolanaWalletKey"; + +function KeyAndBalance({ + chainId, + tokenAddress, +}: { + chainId: ChainId; + tokenAddress?: string; +}) { + // TODO: more generic way to get balance + const provider = useEthereumProvider(); + const ethBalance = useEthereumBalance( + tokenAddress, + provider, + chainId === CHAIN_ID_ETH + ); + const { wallet: solWallet } = useSolanaWallet(); + const solPK = solWallet?.publicKey; + const solBalance = useSolanaBalance( + tokenAddress, + solPK, + chainId === CHAIN_ID_SOLANA + ); + if (chainId === CHAIN_ID_ETH) { + return ( + <> + + {ethBalance} + + ); + } + if (chainId === CHAIN_ID_SOLANA) { + return ( + <> + + {solBalance} + + ); + } + return null; +} + +export default KeyAndBalance; diff --git a/bridge_ui/src/components/Transfer.tsx b/bridge_ui/src/components/Transfer.tsx index 239e26845..c7893e034 100644 --- a/bridge_ui/src/components/Transfer.tsx +++ b/bridge_ui/src/components/Transfer.tsx @@ -10,16 +10,21 @@ import { useCallback, useState } from "react"; import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useSolanaWallet } from "../contexts/SolanaWalletContext"; import useEthereumBalance from "../hooks/useEthereumBalance"; +import useSolanaBalance from "../hooks/useSolanaBalance"; import { ChainId, CHAINS, + CHAINS_BY_ID, CHAIN_ID_ETH, CHAIN_ID_SOLANA, ETH_TEST_TOKEN_ADDRESS, + SOL_TEST_TOKEN_ADDRESS, } from "../utils/consts"; -import transferFrom from "../utils/transferFrom"; -import EthereumSignerKey from "./EthereumSignerKey"; -import SolanaWalletKey from "./SolanaWalletKey"; +import transferFrom, { + transferFromEth, + transferFromSolana, +} from "../utils/transferFrom"; +import KeyAndBalance from "./KeyAndBalance"; const useStyles = makeStyles((theme) => ({ transferBox: { @@ -57,6 +62,13 @@ function Transfer() { const handleFromChange = useCallback( (event) => { setFromChain(event.target.value); + // TODO: remove or check env - for testing purposes + if (event.target.value === CHAIN_ID_ETH) { + setAssetAddress(ETH_TEST_TOKEN_ADDRESS); + } + if (event.target.value === CHAIN_ID_SOLANA) { + setAssetAddress(SOL_TEST_TOKEN_ADDRESS); + } if (toChain === event.target.value) { setToChain(fromChain); } @@ -68,6 +80,13 @@ function Transfer() { setToChain(event.target.value); if (fromChain === event.target.value) { setFromChain(toChain); + // TODO: remove or check env - for testing purposes + if (toChain === CHAIN_ID_ETH) { + setAssetAddress(ETH_TEST_TOKEN_ADDRESS); + } + if (toChain === CHAIN_ID_SOLANA) { + setAssetAddress(SOL_TEST_TOKEN_ADDRESS); + } } }, [fromChain, toChain] @@ -79,22 +98,56 @@ function Transfer() { setAmount(event.target.value); }, []); const provider = useEthereumProvider(); - const ethBalance = useEthereumBalance(assetAddress, provider); const { wallet } = useSolanaWallet(); - const solPK = wallet?.publicKey?.toBytes(); + const solPK = wallet?.publicKey; // TODO: dynamically get "to" wallet const handleClick = useCallback(() => { + // TODO: more generic way of calling these if (transferFrom[fromChain]) { - transferFrom[fromChain](provider, assetAddress, amount, toChain, solPK); + if ( + fromChain === CHAIN_ID_ETH && + transferFrom[fromChain] === transferFromEth + ) { + transferFromEth( + provider, + assetAddress, + amount, + toChain, + solPK?.toBytes() + ); + } + if ( + fromChain === CHAIN_ID_SOLANA && + transferFrom[fromChain] === transferFromSolana + ) { + transferFromSolana( + solPK?.toString(), + assetAddress, + amount, + provider, + toChain + ); + } } }, [fromChain, provider, solPK, assetAddress, amount, toChain]); // update this as we develop, just setting expectations with the button state + const ethBalance = useEthereumBalance( + assetAddress, + provider, + fromChain === CHAIN_ID_ETH + ); + const solBalance = useSolanaBalance( + assetAddress, + solPK, + fromChain === CHAIN_ID_SOLANA + ); + const balance = Number(ethBalance) || Number(solBalance); const isTransferImplemented = !!transferFrom[fromChain]; const isProviderConnected = !!provider; const isRecipientAvailable = !!solPK; const isAddressDefined = !!assetAddress; const isAmountPositive = Number(amount) > 0; // TODO: this needs per-chain, bn parsing - const isBalanceAtLeastAmount = Number(ethBalance) >= Number(amount); // TODO: ditto + const isBalanceAtLeastAmount = balance >= Number(amount); // TODO: ditto const canAttemptTransfer = isTransferImplemented && isProviderConnected && @@ -106,7 +159,7 @@ function Transfer() {
- To + From ))} - - {ethBalance} + - From + To {CHAINS.map(({ id, name }) => ( @@ -134,7 +186,8 @@ function Transfer() { ))} - + {/* TODO: determine "to" token address */} + {!isTransferImplemented - ? `Transfer is not yet implemented for ${CHAINS[fromChain]}` + ? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}` : !isProviderConnected ? "The source wallet is not connected" : !isRecipientAvailable @@ -175,8 +228,6 @@ function Transfer() { ? "The amount must be positive" : !isBalanceAtLeastAmount ? "The amount may not be greater than the balance" - : !isBalanceAtLeastAmount - ? "The amount may not be greater than the balance" : ""} )} diff --git a/bridge_ui/src/hooks/useEthereumBalance.ts b/bridge_ui/src/hooks/useEthereumBalance.ts index 336716e12..1e48cea8a 100644 --- a/bridge_ui/src/hooks/useEthereumBalance.ts +++ b/bridge_ui/src/hooks/useEthereumBalance.ts @@ -3,35 +3,46 @@ import { formatUnits } from "ethers/lib/utils"; import { useEffect, useState } from "react"; import { TokenImplementation__factory } from "../ethers-contracts"; -function useEthereumBalance(address: string, provider?: ethers.providers.Web3Provider) { +function useEthereumBalance( + address: string | undefined, + provider: ethers.providers.Web3Provider | undefined, + shouldCalculate?: boolean +) { //TODO: should this check allowance too or subtract allowance? - const [balance, setBalance] = useState('') - useEffect(()=>{ - if (!address || !provider) { - setBalance('') - return + const [balance, setBalance] = useState(""); + useEffect(() => { + if (!address || !provider || !shouldCalculate) { + setBalance(""); + return; } - let cancelled = false + let cancelled = false; const token = TokenImplementation__factory.connect(address, provider); - token.decimals().then((decimals) => { - console.log(decimals); - provider - ?.getSigner() - .getAddress() - .then((pk) => { - console.log(pk) - token.balanceOf(pk).then((n) => { - if (!cancelled) { - setBalance(formatUnits(n,decimals)) - } - }); + token + .decimals() + .then((decimals) => { + console.log(decimals); + provider + ?.getSigner() + .getAddress() + .then((pk) => { + console.log(pk); + token.balanceOf(pk).then((n) => { + if (!cancelled) { + setBalance(formatUnits(n, decimals)); + } + }); + }); + }) + .catch(() => { + if (!cancelled) { + setBalance(""); + } }); - }); return () => { - cancelled = true - } - },[address, provider]) - return balance + cancelled = true; + }; + }, [address, provider, shouldCalculate]); + return balance; } -export default useEthereumBalance \ No newline at end of file +export default useEthereumBalance; diff --git a/bridge_ui/src/hooks/useSolanaBalance.ts b/bridge_ui/src/hooks/useSolanaBalance.ts new file mode 100644 index 000000000..6bb8a3aa4 --- /dev/null +++ b/bridge_ui/src/hooks/useSolanaBalance.ts @@ -0,0 +1,47 @@ +import { Connection, PublicKey } from "@solana/web3.js"; +import { useEffect, useState } from "react"; +import { SOLANA_HOST } from "../utils/consts"; + +function useSolanaBalance( + tokenAddress: string | undefined, + ownerAddress: PublicKey | null | undefined, + shouldCalculate?: boolean +) { + //TODO: should connection happen in a context? + const [balance, setBalance] = useState(""); + useEffect(() => { + if (!tokenAddress || !ownerAddress || !shouldCalculate) { + setBalance(""); + return; + } + let cancelled = false; + const connection = new Connection(SOLANA_HOST); + connection + .getParsedTokenAccountsByOwner(ownerAddress, { + mint: new PublicKey(tokenAddress), + }) + .then(({ value }) => { + if (!cancelled) { + if (value.length) { + console.log(value[0].account.data.parsed); + setBalance( + value[0].account.data.parsed?.info?.tokenAmount?.uiAmountString + ); + } else { + setBalance("0"); + } + } + }) + .catch(() => { + if (!cancelled) { + setBalance(""); + } + }); + return () => { + cancelled = true; + }; + }, [tokenAddress, ownerAddress, shouldCalculate]); + return balance; +} + +export default useSolanaBalance; diff --git a/bridge_ui/src/utils/consts.ts b/bridge_ui/src/utils/consts.ts index f0e431fd7..3d8b3a894 100644 --- a/bridge_ui/src/utils/consts.ts +++ b/bridge_ui/src/utils/consts.ts @@ -25,6 +25,8 @@ export const CHAINS = [ name: 'Terra' }, ] +export type ChainsById = {[key in ChainId]: ChainInfo} +export const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain)=>{obj[chain.id]=chain;return obj},{} as ChainsById) export const SOLANA_HOST = 'http://localhost:8899' export const ETH_TEST_TOKEN_ADDRESS = "0x0290FB167208Af455bB137780163b7B7a9a10C16" export const ETH_TOKEN_BRIDGE_ADDRESS = "0xe982e462b094850f12af94d21d470e21be9d0e9c" diff --git a/bridge_ui/src/utils/transferFrom.ts b/bridge_ui/src/utils/transferFrom.ts index 8e23dbd62..d93db6da4 100644 --- a/bridge_ui/src/utils/transferFrom.ts +++ b/bridge_ui/src/utils/transferFrom.ts @@ -1,12 +1,12 @@ import { ethers } from "ethers"; -import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { arrayify, formatUnits, parseUnits } from "ethers/lib/utils"; import { Bridge__factory, TokenImplementation__factory } from "../ethers-contracts"; -import { ChainId, CHAIN_ID_ETH, ETH_TOKEN_BRIDGE_ADDRESS } from "./consts"; +import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA, ETH_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS } from "./consts"; // TODO: this should probably be extended from the context somehow so that the signatures match // TODO: allow for / handle cancellation? // TODO: overall better input checking and error handling -function transferFromEth(provider: ethers.providers.Web3Provider | undefined, tokenAddress: string, amount: string, recipientChain: ChainId, recipientAddress: Uint8Array | undefined) { +export function transferFromEth(provider: ethers.providers.Web3Provider | undefined, tokenAddress: string, amount: string, recipientChain: ChainId, recipientAddress: Uint8Array | undefined) { if (!provider || !recipientAddress) return; const signer = provider.getSigner(); if (!signer) return; @@ -59,8 +59,41 @@ function transferFromEth(provider: ethers.providers.Web3Provider | undefined, to }); } +// TODO: need to check transfer native vs transfer wrapped +// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts) +export function transferFromSolana(fromAddress: string | undefined, tokenAddress: string, amount: string, targetProvider: ethers.providers.Web3Provider | undefined, targetChain: ChainId) { + if (!fromAddress || !targetProvider) return; + const targetSigner = targetProvider.getSigner(); + if (!targetSigner) return; + targetSigner.getAddress().then(targetAddressStr => { + const targetAddress = arrayify(targetAddressStr) + const nonceConst = Math.random() * 100000; + const nonceBuffer = Buffer.alloc(4); + nonceBuffer.writeUInt32LE(nonceConst, 0); + const nonce = nonceBuffer.readUInt32LE(0) + // TODO: check decimals + // should we avoid BigInt? + const amountParsed = BigInt(amount) + const fee = BigInt(0) // for now, this won't do anything, we may add later + console.log('bridge:',SOL_TOKEN_BRIDGE_ADDRESS) + console.log('from:',fromAddress) + console.log('token:',tokenAddress) + console.log('nonce:',nonce) + console.log('amount:',amountParsed) + console.log('fee:',fee) + console.log('target:',targetAddressStr,targetAddress) + console.log('chain:',targetChain) + // TODO: program_id vs bridge_id? + import("token-bridge").then(({transfer_native_ix})=>{ + const ix = transfer_native_ix(SOL_TOKEN_BRIDGE_ADDRESS,SOL_TOKEN_BRIDGE_ADDRESS,fromAddress,fromAddress,tokenAddress,nonce,amountParsed,fee,targetAddress,targetChain) + console.log(ix) + }) + }) +} + const transferFrom = { - [CHAIN_ID_ETH]: transferFromEth + [CHAIN_ID_ETH]: transferFromEth, + [CHAIN_ID_SOLANA]: transferFromSolana } export default transferFrom \ No newline at end of file diff --git a/generate-wasm.sh b/generate-wasm.sh index 3f5d044dc..3343c994c 100755 --- a/generate-wasm.sh +++ b/generate-wasm.sh @@ -10,12 +10,20 @@ set -euo pipefail -v $(pwd)/../bridge_ui/rust_modules/core:/usr/src/bridge/bridge/program/pkg \ -e EMITTER_ADDRESS=11111111111111111111111111111115 \ localhost/certusone/wormhole-wasmpack:latest \ + /usr/local/cargo/bin/wasm-pack build --target bundler -- --features wasm + docker run --rm -it --workdir /usr/src/bridge/bridge/program \ + -v $(pwd)/../clients/solana/pkg:/usr/src/bridge/bridge/program/pkg \ + -e EMITTER_ADDRESS=11111111111111111111111111111115 \ + localhost/certusone/wormhole-wasmpack:latest \ /usr/local/cargo/bin/wasm-pack build --target nodejs -- --features wasm - cp $(pwd)/../bridge_ui/rust_modules/core/. $(pwd)/../clients/solana/pkg/ -R docker run --rm -it --workdir /usr/src/bridge/modules/token_bridge/program \ -v $(pwd)/../bridge_ui/rust_modules/token:/usr/src/bridge/modules/token_bridge/program/pkg \ -e EMITTER_ADDRESS=11111111111111111111111111111115 \ localhost/certusone/wormhole-wasmpack:latest \ + /usr/local/cargo/bin/wasm-pack build --target bundler -- --features wasm + docker run --rm -it --workdir /usr/src/bridge/modules/token_bridge/program \ + -v $(pwd)/../clients/token_bridge/pkg:/usr/src/bridge/modules/token_bridge/program/pkg \ + -e EMITTER_ADDRESS=11111111111111111111111111111115 \ + localhost/certusone/wormhole-wasmpack:latest \ /usr/local/cargo/bin/wasm-pack build --target nodejs -- --features wasm - cp $(pwd)/../bridge_ui/rust_modules/. $(pwd)/../clients/token_bridge/pkg -R )