├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── task.md
└── workflows
│ ├── build.yaml
│ ├── prettier.yml
│ └── test.yaml
├── .gitignore
├── .nvmrc
├── .prettierrc.json
├── Dockerfile
├── LEGAL.txt
├── LICENSE
├── README.md
├── config-overrides.js
├── package-lock.json
├── package.json
├── public
├── index.html
├── relayerExample.json
└── robots.txt
├── src
├── App.js
├── ErrorBoundary.js
├── __tests__
│ ├── sdk.js
│ └── unit.ts
├── components
│ ├── AlgorandWalletKey.tsx
│ ├── AptosConnectWalletDialog.tsx
│ ├── AptosWalletKey.tsx
│ ├── Attest
│ │ ├── Create.tsx
│ │ ├── CreatePreview.tsx
│ │ ├── Send.tsx
│ │ ├── SendPreview.tsx
│ │ ├── Source.tsx
│ │ ├── SourcePreview.tsx
│ │ ├── Target.tsx
│ │ ├── TargetPreview.tsx
│ │ ├── WaitingForWalletMessage.tsx
│ │ └── index.tsx
│ ├── ButtonWithLoader.tsx
│ ├── ChainSelect.tsx
│ ├── ChainSelectArrow.tsx
│ ├── ChainWarningMessage.tsx
│ ├── EthereumSignerKey.tsx
│ ├── EvmConnectWalletDialog.tsx
│ ├── FeeMethodSelector.tsx
│ ├── Footer.tsx
│ ├── HeaderText.tsx
│ ├── InjectiveConnectWalletDialog.tsx
│ ├── InjectiveWalletKey.tsx
│ ├── KeyAndBalance.tsx
│ ├── LowBalanceWarning.tsx
│ ├── NFT
│ │ ├── Redeem.tsx
│ │ ├── RedeemPreview.tsx
│ │ ├── Send.tsx
│ │ ├── SendPreview.tsx
│ │ ├── Source.tsx
│ │ ├── SourcePreview.tsx
│ │ ├── Target.tsx
│ │ ├── TargetPreview.tsx
│ │ ├── WaitingForWalletMessage.tsx
│ │ └── index.tsx
│ ├── NFTOriginVerifier.tsx
│ ├── NearWalletKey.tsx
│ ├── NumberTextField.tsx
│ ├── Recovery.tsx
│ ├── RelaySelector.tsx
│ ├── SeiConnectWalletDialog.tsx
│ ├── SeiWalletKey.tsx
│ ├── ShowTx.tsx
│ ├── SmartAddress.tsx
│ ├── SolanaConnectWalletDialog.tsx
│ ├── SolanaCreateAssociatedAddress.tsx
│ ├── SolanaTPSWarning.tsx
│ ├── SolanaWalletKey.tsx
│ ├── StepDescription.tsx
│ ├── SuiConnectWalletDialog.tsx
│ ├── SuiWalletKey.tsx
│ ├── TerraConnectWalletDialog.tsx
│ ├── TerraFeeDenomPicker.tsx
│ ├── TerraWalletKey.tsx
│ ├── ToggleConnectedButton.tsx
│ ├── TokenOriginVerifier.tsx
│ ├── TokenSelectors
│ │ ├── AlgoTokenPicker.tsx
│ │ ├── AptosTokenPicker.tsx
│ │ ├── EvmTokenPicker.tsx
│ │ ├── InjectiveTokenPicker.tsx
│ │ ├── NFTViewer.tsx
│ │ ├── NearTokenPicker.tsx
│ │ ├── OffsetButton.tsx
│ │ ├── RefreshButtonWrapper.tsx
│ │ ├── SeiTokenPicker.tsx
│ │ ├── SolanaTokenPicker.tsx
│ │ ├── SourceTokenSelector.tsx
│ │ ├── SuiTokenPicker.tsx
│ │ ├── TerraTokenPicker.tsx
│ │ ├── TokenPicker.tsx
│ │ └── XplaTokenPicker.tsx
│ ├── TransactionProgress.tsx
│ ├── Transfer
│ │ ├── AddToMetamask.tsx
│ │ ├── PendingVAAWarning.tsx
│ │ ├── Redeem.tsx
│ │ ├── RedeemPreview.tsx
│ │ ├── RegisterNowButton.tsx
│ │ ├── Send.tsx
│ │ ├── SendConfirmationDialog.tsx
│ │ ├── SendPreview.tsx
│ │ ├── Source.tsx
│ │ ├── SourceAssetWarning.tsx
│ │ ├── SourcePreview.tsx
│ │ ├── Target.tsx
│ │ ├── TargetPreview.tsx
│ │ ├── TransferLimitedWarning.tsx
│ │ ├── WaitingForWalletMessage.tsx
│ │ └── index.tsx
│ ├── USDC
│ │ └── index.tsx
│ ├── UnwrapNative.tsx
│ ├── WithdrawTokensTerra.tsx
│ ├── XplaConnectWalletDialog.tsx
│ └── XplaWalletKey.tsx
├── config.ts
├── contexts
│ ├── AlgorandWalletContext.tsx
│ ├── AptosWalletContext.tsx
│ ├── EthereumProviderContext.tsx
│ ├── InjectiveWalletContext.tsx
│ ├── NearWalletContext.tsx
│ ├── SolanaWalletContext.tsx
│ ├── SuiWalletContext.tsx
│ ├── TerraWalletContext.tsx
│ └── XplaWalletContext.tsx
├── hooks
│ ├── useAcalaRelayerInfo.ts
│ ├── useAlgoMetadata.ts
│ ├── useAllowance.ts
│ ├── useAptosMetadata.ts
│ ├── useAptosNativeBalance.ts
│ ├── useAttestSignedVAA.ts
│ ├── useCheckIfWormholeWrapped.ts
│ ├── useCopyToClipboard.tsx
│ ├── useEthereumMigratorInformation.tsx
│ ├── useEvmMetadata.ts
│ ├── useFetchForeignAsset.ts
│ ├── useFetchTargetAsset.ts
│ ├── useGetIsTransferCompleted.ts
│ ├── useGetSourceParsedTokenAccounts.ts
│ ├── useGetTargetParsedTokenAccounts.ts
│ ├── useHandleAttest.tsx
│ ├── useHandleCreateWrapped.tsx
│ ├── useHandleNFTRedeem.tsx
│ ├── useHandleNFTTransfer.tsx
│ ├── useHandleRedeem.tsx
│ ├── useHandleTransfer.tsx
│ ├── useInjectiveMetadata.ts
│ ├── useInjectiveNativeBalances.ts
│ ├── useIsTransferLimited.ts
│ ├── useIsWalletReady.ts
│ ├── useMetadata.ts
│ ├── useMetaplexData.ts
│ ├── useNFTSignedVAA.ts
│ ├── useNFTTargetAddress.ts
│ ├── useNativeSuiBalance.ts
│ ├── useNearMetadata.ts
│ ├── useOriginalAsset.ts
│ ├── useRelayerInfo.ts
│ ├── useRelayersAvailable.ts
│ ├── useSeiMetadata.ts
│ ├── useSeiNativeBalances.ts
│ ├── useSolanaTokenMap.ts
│ ├── useSuiMetadata.ts
│ ├── useSyncTargetAddress.ts
│ ├── useTerraMetadata.ts
│ ├── useTerraNativeBalances.ts
│ ├── useTerraTokenMap.ts
│ ├── useTransactionFees.tsx
│ ├── useTransferSignedVAA.ts
│ ├── useTransferTargetAddress.ts
│ ├── useXplaMetadata.ts
│ └── useXplaNativeBalances.ts
├── icons
│ ├── Discord.svg
│ ├── Docs.svg
│ ├── Github.svg
│ ├── Medium.svg
│ ├── Telegram.svg
│ ├── Twitter.svg
│ ├── acala.svg
│ ├── algorand.svg
│ ├── aptos.svg
│ ├── arbitrum.svg
│ ├── aurora.svg
│ ├── avax.svg
│ ├── base.svg
│ ├── bnb.svg
│ ├── bsc.svg
│ ├── celo.svg
│ ├── eth.svg
│ ├── fantom.svg
│ ├── injective.svg
│ ├── karura.svg
│ ├── keplr.svg
│ ├── klaytn.svg
│ ├── metamask-fox.svg
│ ├── moonbeam.svg
│ ├── near.svg
│ ├── neon.svg
│ ├── oasis-network-rose-logo.svg
│ ├── optimism.svg
│ ├── polygon.svg
│ ├── sei.svg
│ ├── solana.svg
│ ├── sui.svg
│ ├── terra.svg
│ ├── terra2.svg
│ ├── usdc.svg
│ ├── walletconnect.svg
│ ├── wormhole.svg
│ └── xpla.svg
├── index.js
├── muiTheme.js
├── react-app-env.d.ts
├── store
│ ├── attestSlice.ts
│ ├── feeSlice.ts
│ ├── helpers.ts
│ ├── index.ts
│ ├── nftSlice.ts
│ ├── selectors.ts
│ ├── tokenSlice.ts
│ ├── transferSlice.ts
│ ├── usdcSelectors.ts
│ └── usdcSlice.ts
└── utils
│ ├── SolanaPriceStore.ts
│ ├── algorand.ts
│ ├── aptos.ts
│ ├── balancePretty.ts
│ ├── consts.ts
│ ├── ethereum.ts
│ ├── getSignedVAAWithRetry.ts
│ ├── injective.ts
│ ├── karura.ts
│ ├── metaMaskChainParameters.ts
│ ├── metaplex.ts
│ ├── near.ts
│ ├── parseError.ts
│ ├── pushToClipboard.ts
│ ├── sei.ts
│ ├── sleep.ts
│ ├── solana.ts
│ ├── sort.ts
│ ├── sui.ts
│ ├── terra.ts
│ └── xpla.ts
└── tsconfig.json
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Submit a bug ticket if you have an issue. If you're a user, check out the Wormhole Discord server below for faster assistance.
4 | title: ''
5 | labels: 'bug'
6 | assignees: ''
7 | ---
8 |
9 |
10 | ## Description and context
11 |
12 |
13 |
14 | ## Steps to reproduce
15 |
16 |
17 |
18 | 1.
19 | 2.
20 | 3.
21 |
22 | ## Experienced behavior
23 |
24 |
25 |
26 | ## Expected behavior
27 |
28 |
29 |
30 | ## Solution recommendation
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Wormhole Official Discord
4 | url: https://discord.gg/wormholecrypto
5 | about: If you're a user, this is the fastest way to get help. Do not give your wallet private key or mnemonic words to anyone.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/task.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Task
3 | about: Create a regular work item to be picked up by a contributor.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Description and context
11 |
12 |
13 |
14 |
15 |
16 | ## Definition of done
17 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | build-and-deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout 🛎️
11 | uses: actions/checkout@v3
12 | - uses: actions/setup-node@v3
13 | with:
14 | node-version: 16
15 | cache: "npm"
16 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
17 | run: |
18 | npm ci
19 | IFS='/' read -a strarr <<< $GITHUB_REPOSITORY && sed -i "`wc -l < package.json`i\,\"homepage\":\"https://${strarr[0]}.github.io/${strarr[1]}\"" package.json
20 | npm run build
21 | - name: Deploy 🚀
22 | uses: JamesIves/github-pages-deploy-action@v4.2.5
23 | with:
24 | branch: gh-pages # The branch the action should deploy to.
25 | folder: build # The folder the action should deploy.
26 |
--------------------------------------------------------------------------------
/.github/workflows/prettier.yml:
--------------------------------------------------------------------------------
1 | name: Prettier Check
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | prettier:
7 | runs-on: "ubuntu-latest"
8 | steps:
9 | - name: Checkout repository
10 | uses: actions/checkout@v3
11 | - name: Run prettier
12 | run: npx prettier --check ./src
13 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on:
3 | pull_request:
4 | jobs:
5 | test:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v3
9 | - uses: actions/setup-node@v3
10 | with:
11 | node-version: 16
12 | cache: "npm"
13 | - run: npm ci
14 | - run: npm run build
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # ethereum contracts
26 | /contracts
27 | /src/ethers-contracts
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16.20.0
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": false,
7 | "quoteProps": "as-needed",
8 | "jsxSingleQuote": false,
9 | "trailingComma": "es5",
10 | "bracketSpacing": true,
11 | "bracketSameLine": false,
12 | "arrowParens": "always"
13 | }
14 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
2 |
3 | # Derivative of ethereum/Dockerfile, look there for an explanation on how it works.
4 | FROM node:16-alpine@sha256:f21f35732964a96306a84a8c4b5a829f6d3a0c5163237ff4b6b8b34f8d70064b
5 |
6 | RUN mkdir -p /app
7 | WORKDIR /app
8 |
9 | COPY bridge_ui/package.json bridge_ui/package-lock.json ./
10 | RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
11 | npm ci
12 | COPY bridge_ui .
13 |
--------------------------------------------------------------------------------
/LEGAL.txt:
--------------------------------------------------------------------------------
1 | The information and materials available through this repo (collectively, “Materials”) are available on an “AI IS” basis without warranties of any kind, either express or implied, including, but not limited to, warranties of merchantability, title, fitness for a particular purpose and non-infringement. You assume all risks associated with using the Materials, and digital assets and decentralized systems generally, including but not limited to, that: (a) digital assets are highly volatile; (b) using digital assets is inherently risky due to both features of such assets and the potential unauthorized acts of third parties; (c) you may not have ready access to digital assets; and (d) there is a risk of losing your digital assets or those owned by a third party. You agree that you will have no recourse against anyone else for any losses due to the use of the Materials. For example, these losses may arise from or relate to: (i) incorrect information; (ii) software or network failures; (iii) corrupted files; (iv) unauthorized access; (v) errors, mistakes, or inaccuracies; or (vi) third-party activities.
2 |
3 | You are solely responsible for using the Materials in compliance with applicable laws and regulations, including but not limited to, export control or sanctions laws of any applicable jurisdiction. You should be aware that U.S. export control and sanctions laws prohibit U.S. persons (and other persons that are subject to such laws) from transacting with persons in certain countries and territories or that are on the Specially Designated Nationals list. Accordingly, you must avoid providing the Materials to sanctioned persons because such activity could be a violation of U.S. export controls and sanctions law.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 Wormhole Project Contributors
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Example Token Bridge UI · [](https://github.com/wormhole-foundation/example-token-bridge-ui/blob/main/LICENSE) 
2 |
3 | This app serves as a testnet and local devnet UI for the example token bridge contracts.
4 |
5 | View at https://wormhole-foundation.github.io/example-token-bridge-ui/
6 |
7 | ## Install
8 |
9 | ```bash
10 | npm ci
11 | ```
12 |
13 | ## Develop
14 |
15 | If using the node version specified in `.nvmrc`, run with
16 |
17 | ```bash
18 | npm start
19 | ```
20 |
21 | If on latest LTS (v18.16.0), run with
22 |
23 | ```bash
24 | NODE_OPTIONS=--openssl-legacy-provider npm start
25 | ```
26 |
27 | *Note: the above issue should be resolved after updating to the latest mui + react versions*
28 |
29 | ## Build
30 |
31 | ```bash
32 | npm run build
33 | ```
34 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const { ProvidePlugin } = require("webpack");
2 |
3 | module.exports = function override(config, env) {
4 | return {
5 | ...config,
6 | module: {
7 | ...config.module,
8 | rules: [
9 | ...config.module.rules,
10 | {
11 | test: /\.m?js$/,
12 | enforce: "pre",
13 | use: ["source-map-loader"],
14 | resolve: {
15 | fullySpecified: false,
16 | fallback: {
17 | "react/jsx-runtime": "react/jsx-runtime.js",
18 | "react/jsx-dev-runtime": "react/jsx-dev-runtime.js",
19 | },
20 | },
21 | },
22 | {
23 | test: /\.wasm$/,
24 | type: "webassembly/async",
25 | },
26 | ],
27 | },
28 | plugins: [
29 | ...config.plugins,
30 | new ProvidePlugin({
31 | Buffer: ["buffer", "Buffer"],
32 | process: "process/browser",
33 | }),
34 | ],
35 | resolve: {
36 | ...config.resolve,
37 | fallback: {
38 | assert: "assert",
39 | buffer: "buffer",
40 | console: "console-browserify",
41 | constants: "constants-browserify",
42 | crypto: "crypto-browserify",
43 | domain: "domain-browser",
44 | events: "events",
45 | fs: false,
46 | http: "stream-http",
47 | https: "https-browserify",
48 | os: "os-browserify/browser",
49 | path: "path-browserify",
50 | punycode: "punycode",
51 | process: "process/browser",
52 | querystring: "querystring-es3",
53 | stream: "stream-browserify",
54 | _stream_duplex: "readable-stream/duplex",
55 | _stream_passthrough: "readable-stream/passthrough",
56 | _stream_readable: "readable-stream/readable",
57 | _stream_transform: "readable-stream/transform",
58 | _stream_writable: "readable-stream/writable",
59 | string_decoder: "string_decoder",
60 | sys: "util",
61 | timers: "timers-browserify",
62 | tty: "tty-browserify",
63 | url: "url",
64 | util: "util",
65 | vm: "vm-browserify",
66 | zlib: "browserify-zlib",
67 | },
68 | },
69 | experiments: {
70 | asyncWebAssembly: true,
71 | },
72 | ignoreWarnings: [/Failed to parse source map/],
73 | };
74 | };
75 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
15 | Token Bridge
16 |
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/public/relayerExample.json:
--------------------------------------------------------------------------------
1 | {
2 | "supportedTokens": [
3 | {
4 | "chainId": 1,
5 | "address": "So11111111111111111111111111111111111111112",
6 | "coingeckoId": "solana"
7 | },
8 | {
9 | "chainId": 2,
10 | "address": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E",
11 | "coingeckoId": "ethereum"
12 | },
13 | {
14 | "chainId": 3,
15 | "address": "uluna",
16 | "coingeckoId": "terra-luna"
17 | },
18 | {
19 | "chainId": 4,
20 | "address": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E",
21 | "coingeckoId": "binancecoin"
22 | }
23 | ],
24 | "relayers": [{ "name": "localhostRelayer", "url": "http://localhost:4201" }]
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import { Typography } from "@material-ui/core";
2 | import React from "react";
3 |
4 | export default class ErrorBoundary extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = { hasError: false };
8 | }
9 |
10 | static getDerivedStateFromError(error) {
11 | return { hasError: true };
12 | }
13 |
14 | componentDidCatch(error, errorInfo) {
15 | console.error(error, errorInfo);
16 | }
17 |
18 | render() {
19 | if (this.state.hasError) {
20 | return (
21 |
22 | An unexpected error has occurred. Please refresh the page.
23 |
24 | );
25 | }
26 |
27 | return this.props.children;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/__tests__/sdk.js:
--------------------------------------------------------------------------------
1 | const { describe, expect, it } = require("@jest/globals");
2 | const fs = require("fs");
3 |
4 | describe("SDK installation", () => {
5 | it("does not import from file path", () => {
6 | const packageFile = fs.readFileSync("./package.json");
7 | const packageObj = JSON.parse(packageFile.toString());
8 |
9 | const sdkInstallation =
10 | packageObj?.dependencies?.["@certusone/wormhole-sdk"];
11 | expect(sdkInstallation && !sdkInstallation.includes("file")).toBe(true);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/__tests__/unit.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "@jest/globals";
2 | import { balancePretty } from "../utils/balancePretty";
3 |
4 | describe("Unit Tests", () => {
5 | describe("balancePretty() tests", () => {
6 | it("9.99 => 9.99", () => {
7 | expect(balancePretty("9.99")).toBe("9.99");
8 | });
9 | it("123456.789 => 123456.78", () => {
10 | expect(balancePretty("123456.789")).toBe("123456.7");
11 | });
12 | it("1234567.8912 => 1.23 M", () => {
13 | expect(balancePretty("1234567.891")).toBe("1.23 M");
14 | });
15 | it("123999.8912 => 1.23 M", () => {
16 | expect(balancePretty("1239999.8912")).toBe("1.23 M");
17 | });
18 | it("981234567.8912 => 981.23 M", () => {
19 | expect(balancePretty("981234567.891")).toBe("981.23 M");
20 | });
21 | it("9876543210.8912 => 9.87 B", () => {
22 | expect(balancePretty("9876543210.8912")).toBe("9.87 B");
23 | });
24 | it("219876543210.8912 => 219.87 B", () => {
25 | expect(balancePretty("219876543210.8912")).toBe("219.87 B");
26 | });
27 | it("219876543210 => 219.87 B", () => {
28 | expect(balancePretty("219876543210")).toBe("219.87 B");
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/components/AlgorandWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useAlgorandContext } from "../contexts/AlgorandWalletContext";
2 | import ToggleConnectedButton from "./ToggleConnectedButton";
3 |
4 | const AlgorandWalletKey = () => {
5 | const { connect, disconnect, accounts } = useAlgorandContext();
6 |
7 | return (
8 | <>
9 |
15 | >
16 | );
17 | };
18 |
19 | export default AlgorandWalletKey;
20 |
--------------------------------------------------------------------------------
/src/components/AptosWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from "react";
2 | import { useAptosContext } from "../contexts/AptosWalletContext";
3 | import AptosConnectWalletDialog from "./AptosConnectWalletDialog";
4 | import ToggleConnectedButton from "./ToggleConnectedButton";
5 |
6 | const AptosWalletKey = () => {
7 | const { connected, disconnect, account } = useAptosContext();
8 | const address = account?.address?.toString();
9 | const [isDialogOpen, setIsDialogOpen] = useState(false);
10 |
11 | const connect = useCallback(() => {
12 | setIsDialogOpen(true);
13 | }, [setIsDialogOpen]);
14 |
15 | const closeDialog = useCallback(() => {
16 | setIsDialogOpen(false);
17 | }, [setIsDialogOpen]);
18 | return (
19 | <>
20 |
26 |
27 | >
28 | );
29 | };
30 |
31 | export default AptosWalletKey;
32 |
--------------------------------------------------------------------------------
/src/components/Attest/Create.tsx:
--------------------------------------------------------------------------------
1 | import { isTerraChain } from "@certusone/wormhole-sdk";
2 | import { CircularProgress, makeStyles } from "@material-ui/core";
3 | import { useSelector } from "react-redux";
4 | import useFetchForeignAsset from "../../hooks/useFetchForeignAsset";
5 | import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped";
6 | import useIsWalletReady from "../../hooks/useIsWalletReady";
7 | import {
8 | selectAttestSourceAsset,
9 | selectAttestSourceChain,
10 | selectAttestTargetChain,
11 | } from "../../store/selectors";
12 | import ButtonWithLoader from "../ButtonWithLoader";
13 | import KeyAndBalance from "../KeyAndBalance";
14 | import TerraFeeDenomPicker from "../TerraFeeDenomPicker";
15 | import WaitingForWalletMessage from "./WaitingForWalletMessage";
16 |
17 | const useStyles = makeStyles((theme) => ({
18 | alignCenter: {
19 | margin: "0 auto",
20 | display: "block",
21 | textAlign: "center",
22 | },
23 | spacer: {
24 | height: theme.spacing(2),
25 | },
26 | }));
27 |
28 | function Create() {
29 | const classes = useStyles();
30 | const targetChain = useSelector(selectAttestTargetChain);
31 | const originAsset = useSelector(selectAttestSourceAsset);
32 | const originChain = useSelector(selectAttestSourceChain);
33 | const { isReady, statusMessage } = useIsWalletReady(targetChain);
34 | const foreignAssetInfo = useFetchForeignAsset(
35 | originChain,
36 | originAsset,
37 | targetChain
38 | );
39 | const shouldUpdate = foreignAssetInfo.data?.doesExist;
40 | const foreignAddress = foreignAssetInfo.data?.address;
41 | const error = foreignAssetInfo.error || statusMessage;
42 | const { handleClick, disabled, showLoader } = useHandleCreateWrapped(
43 | shouldUpdate || false,
44 | foreignAddress
45 | );
46 |
47 | return (
48 | <>
49 |
50 | {isTerraChain(targetChain) && (
51 |
52 | )}
53 | {foreignAssetInfo.isFetching ? (
54 | <>
55 |
56 |
57 | >
58 | ) : (
59 | <>
60 |
66 | {shouldUpdate ? "Update" : "Create"}
67 |
68 |
69 | >
70 | )}
71 | >
72 | );
73 | }
74 |
75 | export default Create;
76 |
--------------------------------------------------------------------------------
/src/components/Attest/CreatePreview.tsx:
--------------------------------------------------------------------------------
1 | import { Link, makeStyles, Typography } from "@material-ui/core";
2 | import { useCallback } from "react";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import {
5 | selectAttestCreateTx,
6 | selectAttestTargetChain,
7 | } from "../../store/selectors";
8 | import { reset } from "../../store/attestSlice";
9 | import ButtonWithLoader from "../ButtonWithLoader";
10 | import ShowTx from "../ShowTx";
11 | import { useHistory } from "react-router";
12 | import { getHowToAddToTokenListUrl } from "../../utils/consts";
13 | import { Alert } from "@material-ui/lab";
14 |
15 | const useStyles = makeStyles((theme) => ({
16 | description: {
17 | textAlign: "center",
18 | },
19 | alert: {
20 | marginTop: theme.spacing(1),
21 | },
22 | }));
23 |
24 | export default function CreatePreview() {
25 | const { push } = useHistory();
26 | const classes = useStyles();
27 | const dispatch = useDispatch();
28 | const targetChain = useSelector(selectAttestTargetChain);
29 | const createTx = useSelector(selectAttestCreateTx);
30 | const handleResetClick = useCallback(() => {
31 | dispatch(reset());
32 | }, [dispatch]);
33 | const handleReturnClick = useCallback(() => {
34 | dispatch(reset());
35 | push("/transfer");
36 | }, [dispatch, push]);
37 |
38 | const explainerString =
39 | "Success! The create wrapped transaction was submitted.";
40 | const howToAddToTokenListUrl = getHowToAddToTokenListUrl(targetChain);
41 |
42 | return (
43 | <>
44 |
49 | {explainerString}
50 |
51 | {createTx ? : null}
52 | {howToAddToTokenListUrl ? (
53 |
54 | Remember to add the token to the{" "}
55 |
60 | token list
61 |
62 | {"."}
63 |
64 | ) : null}
65 |
66 | Attest Another Token!
67 |
68 |
69 | Return to Transfer
70 |
71 | >
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/Attest/SendPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useSelector } from "react-redux";
3 | import {
4 | selectAttestSourceChain,
5 | selectAttestAttestTx,
6 | } from "../../store/selectors";
7 | import ShowTx from "../ShowTx";
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | description: {
11 | textAlign: "center",
12 | },
13 | tx: {
14 | marginTop: theme.spacing(1),
15 | textAlign: "center",
16 | },
17 | viewButton: {
18 | marginTop: theme.spacing(1),
19 | },
20 | }));
21 |
22 | export default function SendPreview() {
23 | const classes = useStyles();
24 | const sourceChain = useSelector(selectAttestSourceChain);
25 | const attestTx = useSelector(selectAttestAttestTx);
26 |
27 | const explainerString = "The token has been attested!";
28 |
29 | return (
30 | <>
31 |
36 | {explainerString}
37 |
38 | {attestTx ? : null}
39 | >
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Attest/Source.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, TextField } from "@material-ui/core";
2 | import { useCallback } from "react";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import {
5 | incrementStep,
6 | setSourceAsset,
7 | setSourceChain,
8 | } from "../../store/attestSlice";
9 | import {
10 | selectAttestIsSourceComplete,
11 | selectAttestShouldLockFields,
12 | selectAttestSourceAsset,
13 | selectAttestSourceChain,
14 | } from "../../store/selectors";
15 | import { CHAINS } from "../../utils/consts";
16 | import ButtonWithLoader from "../ButtonWithLoader";
17 | import ChainSelect from "../ChainSelect";
18 | import KeyAndBalance from "../KeyAndBalance";
19 | import LowBalanceWarning from "../LowBalanceWarning";
20 |
21 | const useStyles = makeStyles((theme) => ({
22 | transferField: {
23 | marginTop: theme.spacing(5),
24 | },
25 | }));
26 |
27 | function Source() {
28 | const classes = useStyles();
29 | const dispatch = useDispatch();
30 | const sourceChain = useSelector(selectAttestSourceChain);
31 | const sourceAsset = useSelector(selectAttestSourceAsset);
32 | const isSourceComplete = useSelector(selectAttestIsSourceComplete);
33 | const shouldLockFields = useSelector(selectAttestShouldLockFields);
34 | const handleSourceChange = useCallback(
35 | (event) => {
36 | dispatch(setSourceChain(event.target.value));
37 | },
38 | [dispatch]
39 | );
40 | const handleAssetChange = useCallback(
41 | (event) => {
42 | dispatch(setSourceAsset(event.target.value));
43 | },
44 | [dispatch]
45 | );
46 | const handleNextClick = useCallback(() => {
47 | dispatch(incrementStep());
48 | }, [dispatch]);
49 | return (
50 | <>
51 |
60 |
61 |
70 |
71 |
76 | Next
77 |
78 | >
79 | );
80 | }
81 |
82 | export default Source;
83 |
--------------------------------------------------------------------------------
/src/components/Attest/SourcePreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useSelector } from "react-redux";
3 | import {
4 | selectAttestSourceAsset,
5 | selectAttestSourceChain,
6 | } from "../../store/selectors";
7 | import { CHAINS_BY_ID } from "../../utils/consts";
8 | import SmartAddress from "../SmartAddress";
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | description: {
12 | textAlign: "center",
13 | },
14 | }));
15 |
16 | export default function SourcePreview() {
17 | const classes = useStyles();
18 | const sourceChain = useSelector(selectAttestSourceChain);
19 | const sourceAsset = useSelector(selectAttestSourceAsset);
20 |
21 | const explainerContent =
22 | sourceChain && sourceAsset ? (
23 | <>
24 | You will attest
25 |
26 | on {CHAINS_BY_ID[sourceChain].name}
27 | >
28 | ) : (
29 | ""
30 | );
31 |
32 | return (
33 |
38 | {explainerContent}
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Attest/Target.tsx:
--------------------------------------------------------------------------------
1 | import { isEVMChain } from "@certusone/wormhole-sdk";
2 | import { makeStyles, Typography } from "@material-ui/core";
3 | import { Alert } from "@material-ui/lab";
4 | import { useCallback, useMemo } from "react";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { GasEstimateSummary } from "../../hooks/useTransactionFees";
7 | import { incrementStep, setTargetChain } from "../../store/attestSlice";
8 | import {
9 | selectAttestIsTargetComplete,
10 | selectAttestShouldLockFields,
11 | selectAttestSourceChain,
12 | selectAttestTargetChain,
13 | } from "../../store/selectors";
14 | import { CHAINS, CHAINS_BY_ID } from "../../utils/consts";
15 | import ButtonWithLoader from "../ButtonWithLoader";
16 | import ChainSelect from "../ChainSelect";
17 | import KeyAndBalance from "../KeyAndBalance";
18 | import LowBalanceWarning from "../LowBalanceWarning";
19 |
20 | const useStyles = makeStyles((theme) => ({
21 | alert: {
22 | marginTop: theme.spacing(1),
23 | marginBottom: theme.spacing(1),
24 | },
25 | }));
26 |
27 | function Target() {
28 | const classes = useStyles();
29 | const dispatch = useDispatch();
30 | const sourceChain = useSelector(selectAttestSourceChain);
31 | const chains = useMemo(
32 | () => CHAINS.filter((c) => c.id !== sourceChain),
33 | [sourceChain]
34 | );
35 | const targetChain = useSelector(selectAttestTargetChain);
36 | const isTargetComplete = useSelector(selectAttestIsTargetComplete);
37 | const shouldLockFields = useSelector(selectAttestShouldLockFields);
38 | const handleTargetChange = useCallback(
39 | (event) => {
40 | dispatch(setTargetChain(event.target.value));
41 | },
42 | [dispatch]
43 | );
44 | const handleNextClick = useCallback(() => {
45 | dispatch(incrementStep());
46 | }, [dispatch]);
47 | return (
48 | <>
49 |
58 |
59 |
60 |
61 | You will have to pay transaction fees on{" "}
62 | {CHAINS_BY_ID[targetChain].name} to attest this token.{" "}
63 |
64 | {isEVMChain(targetChain) && (
65 |
69 | )}
70 |
71 |
72 |
77 | Next
78 |
79 | >
80 | );
81 | }
82 |
83 | export default Target;
84 |
--------------------------------------------------------------------------------
/src/components/Attest/TargetPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useSelector } from "react-redux";
3 | import { selectAttestTargetChain } from "../../store/selectors";
4 | import { CHAINS_BY_ID } from "../../utils/consts";
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | description: {
8 | textAlign: "center",
9 | },
10 | }));
11 |
12 | export default function TargetPreview() {
13 | const classes = useStyles();
14 | const targetChain = useSelector(selectAttestTargetChain);
15 |
16 | const explainerString = `to ${CHAINS_BY_ID[targetChain].name}`;
17 |
18 | return (
19 |
24 | {explainerString}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Attest/WaitingForWalletMessage.tsx:
--------------------------------------------------------------------------------
1 | import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
2 | import { makeStyles, Typography } from "@material-ui/core";
3 | import { useSelector } from "react-redux";
4 | import {
5 | selectAttestAttestTx,
6 | selectAttestCreateTx,
7 | selectAttestIsCreating,
8 | selectAttestIsSending,
9 | selectAttestTargetChain,
10 | } from "../../store/selectors";
11 | import { WAITING_FOR_WALLET_AND_CONF } from "../Transfer/WaitingForWalletMessage";
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | message: {
15 | color: theme.palette.warning.light,
16 | marginTop: theme.spacing(1),
17 | textAlign: "center",
18 | },
19 | }));
20 |
21 | export default function WaitingForWalletMessage() {
22 | const classes = useStyles();
23 | const isSending = useSelector(selectAttestIsSending);
24 | const attestTx = useSelector(selectAttestAttestTx);
25 | const targetChain = useSelector(selectAttestTargetChain);
26 | const isCreating = useSelector(selectAttestIsCreating);
27 | const createTx = useSelector(selectAttestCreateTx);
28 | const showWarning = (isSending && !attestTx) || (isCreating && !createTx);
29 | return showWarning ? (
30 |
31 | {WAITING_FOR_WALLET_AND_CONF}{" "}
32 | {targetChain === CHAIN_ID_SOLANA && isCreating
33 | ? "Note: there will be several transactions"
34 | : null}
35 |
36 | ) : null;
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/ButtonWithLoader.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | CircularProgress,
4 | makeStyles,
5 | Typography,
6 | } from "@material-ui/core";
7 | import { ReactChild } from "react";
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | root: {
11 | position: "relative",
12 | },
13 | button: {
14 | marginTop: theme.spacing(2),
15 | width: "100%",
16 | },
17 | loader: {
18 | position: "absolute",
19 | bottom: 0,
20 | left: "50%",
21 | marginLeft: -12,
22 | marginBottom: 6,
23 | },
24 | error: {
25 | marginTop: theme.spacing(1),
26 | textAlign: "center",
27 | },
28 | }));
29 |
30 | export default function ButtonWithLoader({
31 | disabled,
32 | onClick,
33 | showLoader,
34 | error,
35 | children,
36 | }: {
37 | disabled?: boolean;
38 | onClick: () => void;
39 | showLoader?: boolean;
40 | error?: string;
41 | children: ReactChild;
42 | }) {
43 | const classes = useStyles();
44 | return (
45 | <>
46 |
47 |
56 | {showLoader ? (
57 |
62 | ) : null}
63 |
64 | {error ? (
65 |
66 | {error}
67 |
68 | ) : null}
69 | >
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/ChainSelect.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ListItemIcon,
3 | ListItemText,
4 | makeStyles,
5 | MenuItem,
6 | OutlinedTextFieldProps,
7 | TextField,
8 | } from "@material-ui/core";
9 | import clsx from "clsx";
10 | import { ChainInfo } from "../utils/consts";
11 |
12 | const useStyles = makeStyles((theme) => ({
13 | select: {
14 | "& .MuiSelect-root": {
15 | display: "flex",
16 | alignItems: "center",
17 | },
18 | },
19 | listItemIcon: {
20 | minWidth: 40,
21 | },
22 | icon: {
23 | height: 24,
24 | maxWidth: 24,
25 | },
26 | }));
27 |
28 | const createChainMenuItem = ({ id, name, logo }: ChainInfo, classes: any) => (
29 |
35 | );
36 |
37 | interface ChainSelectProps extends OutlinedTextFieldProps {
38 | chains: ChainInfo[];
39 | }
40 |
41 | export default function ChainSelect({ chains, ...rest }: ChainSelectProps) {
42 | const classes = useStyles();
43 |
44 | return (
45 |
46 | {chains.map((chain) => createChainMenuItem(chain, classes))}
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/ChainSelectArrow.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton } from "@material-ui/core";
2 | import { ArrowForward, SwapHoriz } from "@material-ui/icons";
3 | import { useState } from "react";
4 |
5 | export default function ChainSelectArrow({
6 | onClick,
7 | disabled,
8 | }: {
9 | onClick: () => void;
10 | disabled: boolean;
11 | }) {
12 | const [showSwap, setShowSwap] = useState(false);
13 |
14 | return (
15 | {
18 | setShowSwap(true);
19 | }}
20 | onMouseLeave={() => {
21 | setShowSwap(false);
22 | }}
23 | disabled={disabled}
24 | >
25 | {showSwap ? : }
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/ChainWarningMessage.tsx:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@certusone/wormhole-sdk";
2 | import { Link, makeStyles, Typography } from "@material-ui/core";
3 | import { Alert } from "@material-ui/lab";
4 | import { useMemo } from "react";
5 | import { CHAIN_CONFIG_MAP } from "../config";
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | alert: {
9 | marginTop: theme.spacing(1),
10 | marginBottom: theme.spacing(1),
11 | },
12 | }));
13 |
14 | export default function ChainWarningMessage({ chainId }: { chainId: ChainId }) {
15 | const classes = useStyles();
16 |
17 | const warningMessage = useMemo(() => {
18 | return CHAIN_CONFIG_MAP[chainId]?.warningMessage;
19 | }, [chainId]);
20 |
21 | if (warningMessage === undefined) {
22 | return null;
23 | }
24 |
25 | return (
26 |
27 | {warningMessage.text}
28 | {warningMessage.link ? (
29 |
30 |
31 | {warningMessage.link.text}
32 |
33 |
34 | ) : null}
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/EthereumSignerKey.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from "react";
2 | import { Typography } from "@material-ui/core";
3 | import { useEthereumProvider } from "../contexts/EthereumProviderContext";
4 | import ToggleConnectedButton from "./ToggleConnectedButton";
5 | import EvmConnectWalletDialog from "./EvmConnectWalletDialog";
6 | import { ChainId } from "@certusone/wormhole-sdk";
7 |
8 | const EthereumSignerKey = ({ chainId }: { chainId: ChainId }) => {
9 | const { disconnect, signerAddress, providerError } = useEthereumProvider();
10 |
11 | const [isDialogOpen, setIsDialogOpen] = useState(false);
12 |
13 | const openDialog = useCallback(() => {
14 | setIsDialogOpen(true);
15 | }, [setIsDialogOpen]);
16 |
17 | const closeDialog = useCallback(() => {
18 | setIsDialogOpen(false);
19 | }, [setIsDialogOpen]);
20 |
21 | return (
22 | <>
23 |
29 |
34 | {providerError ? (
35 |
36 | {providerError}
37 |
38 | ) : null}
39 | >
40 | );
41 | };
42 |
43 | export default EthereumSignerKey;
44 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { Button, makeStyles, Typography } from "@material-ui/core";
2 |
3 | const useStyles = makeStyles((theme) => ({
4 | footer: {
5 | textAlign: "center",
6 | borderTop: "1px solid #585587",
7 | position: "relative",
8 | maxWidth: 1100,
9 | margin: "80px auto 0px",
10 | paddingTop: theme.spacing(2),
11 | paddingBottom: theme.spacing(6.5),
12 | [theme.breakpoints.up("md")]: {
13 | paddingBottom: theme.spacing(12),
14 | },
15 | },
16 | button: {
17 | textTransform: "none",
18 | margin: theme.spacing(1),
19 | },
20 | }));
21 |
22 | export default function Footer() {
23 | const classes = useStyles();
24 | return (
25 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/HeaderText.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import clsx from "clsx";
3 | import { ReactChild } from "react";
4 |
5 | const useStyles = makeStyles((theme) => ({
6 | centeredContainer: {
7 | marginBottom: theme.spacing(4),
8 | textAlign: "center",
9 | width: "100%",
10 | },
11 | linearGradient: {
12 | WebkitBackgroundClip: "text",
13 | backgroundClip: "text",
14 | WebkitTextFillColor: "transparent",
15 | MozBackgroundClip: "text",
16 | MozTextFillColor: "transparent",
17 | },
18 | subtitle: {
19 | marginTop: theme.spacing(2),
20 | },
21 | }));
22 |
23 | export default function HeaderText({
24 | children,
25 | white,
26 | small,
27 | subtitle,
28 | }: {
29 | children: ReactChild;
30 | white?: boolean;
31 | small?: boolean;
32 | subtitle?: ReactChild;
33 | }) {
34 | const classes = useStyles();
35 | return (
36 |
37 |
42 | {children}
43 |
44 | {subtitle ? (
45 |
46 | {subtitle}
47 |
48 | ) : null}
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/InjectiveWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from "react";
2 | import { useInjectiveContext } from "../contexts/InjectiveWalletContext";
3 | import InjectiveConnectWalletDialog from "./InjectiveConnectWalletDialog";
4 | import ToggleConnectedButton from "./ToggleConnectedButton";
5 |
6 | const InjectiveWalletKey = () => {
7 | const { disconnect, address } = useInjectiveContext();
8 | const [isDialogOpen, setIsDialogOpen] = useState(false);
9 |
10 | const connect = useCallback(() => {
11 | setIsDialogOpen(true);
12 | }, [setIsDialogOpen]);
13 |
14 | const closeDialog = useCallback(() => {
15 | setIsDialogOpen(false);
16 | }, [setIsDialogOpen]);
17 |
18 | return (
19 | <>
20 |
26 |
30 | >
31 | );
32 | };
33 |
34 | export default InjectiveWalletKey;
35 |
--------------------------------------------------------------------------------
/src/components/KeyAndBalance.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ChainId,
3 | CHAIN_ID_ALGORAND,
4 | CHAIN_ID_APTOS,
5 | CHAIN_ID_INJECTIVE,
6 | CHAIN_ID_NEAR,
7 | CHAIN_ID_SOLANA,
8 | CHAIN_ID_SUI,
9 | CHAIN_ID_XPLA,
10 | isEVMChain,
11 | isTerraChain,
12 | CHAIN_ID_SEI,
13 | } from "@certusone/wormhole-sdk";
14 | import AlgorandWalletKey from "./AlgorandWalletKey";
15 | import AptosWalletKey from "./AptosWalletKey";
16 | import EthereumSignerKey from "./EthereumSignerKey";
17 | import InjectiveWalletKey from "./InjectiveWalletKey";
18 | import NearWalletKey from "./NearWalletKey";
19 | import SolanaWalletKey from "./SolanaWalletKey";
20 | import SuiWalletKey from "./SuiWalletKey";
21 | import TerraWalletKey from "./TerraWalletKey";
22 | import XplaWalletKey from "./XplaWalletKey";
23 | import SeiWalletKey from "./SeiWalletKey";
24 |
25 | function KeyAndBalance({ chainId }: { chainId: ChainId }) {
26 | if (isEVMChain(chainId)) {
27 | return ;
28 | }
29 | if (chainId === CHAIN_ID_SOLANA) {
30 | return ;
31 | }
32 | if (isTerraChain(chainId)) {
33 | return ;
34 | }
35 | if (chainId === CHAIN_ID_ALGORAND) {
36 | return ;
37 | }
38 | if (chainId === CHAIN_ID_XPLA) {
39 | return ;
40 | }
41 | if (chainId === CHAIN_ID_APTOS) {
42 | return ;
43 | }
44 | if (chainId === CHAIN_ID_INJECTIVE) {
45 | return ;
46 | }
47 | if (chainId === CHAIN_ID_SEI) {
48 | return ;
49 | }
50 | if (chainId === CHAIN_ID_NEAR) {
51 | return ;
52 | }
53 | if (chainId === CHAIN_ID_SUI) {
54 | return ;
55 | }
56 | return null;
57 | }
58 |
59 | export default KeyAndBalance;
60 |
--------------------------------------------------------------------------------
/src/components/LowBalanceWarning.tsx:
--------------------------------------------------------------------------------
1 | import { ChainId, isTerraChain } from "@certusone/wormhole-sdk";
2 | import { makeStyles, Typography } from "@material-ui/core";
3 | import { Alert } from "@material-ui/lab";
4 | import { useSelector } from "react-redux";
5 | import useIsWalletReady from "../hooks/useIsWalletReady";
6 | import useTransactionFees from "../hooks/useTransactionFees";
7 | import { selectTransferUseRelayer } from "../store/selectors";
8 | import { getDefaultNativeCurrencySymbol } from "../utils/consts";
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | alert: {
12 | marginTop: theme.spacing(1),
13 | marginBottom: theme.spacing(1),
14 | },
15 | }));
16 |
17 | function LowBalanceWarning({ chainId }: { chainId: ChainId }) {
18 | const classes = useStyles();
19 | const { isReady } = useIsWalletReady(chainId);
20 | const transactionFeeWarning = useTransactionFees(chainId);
21 | const relayerSelected = !!useSelector(selectTransferUseRelayer);
22 | const terraChain = isTerraChain(chainId);
23 |
24 | const displayWarning =
25 | isReady &&
26 | !relayerSelected &&
27 | (terraChain || transactionFeeWarning.balanceString) &&
28 | transactionFeeWarning.isSufficientBalance === false;
29 |
30 | const warningMessage = terraChain
31 | ? "This wallet may not have sufficient funds to pay for the upcoming transaction fees."
32 | : `This wallet has a very low ${getDefaultNativeCurrencySymbol(
33 | chainId
34 | )} balance and may not be able to pay for the upcoming transaction fees.`;
35 |
36 | const content = (
37 |
38 | {warningMessage}
39 | {!terraChain ? (
40 |
41 | {"Current balance: " + transactionFeeWarning.balanceString}
42 |
43 | ) : null}
44 |
45 | );
46 |
47 | return displayWarning ? content : null;
48 | }
49 |
50 | export default LowBalanceWarning;
51 |
--------------------------------------------------------------------------------
/src/components/NFT/Redeem.tsx:
--------------------------------------------------------------------------------
1 | import { isTerraChain } from "@certusone/wormhole-sdk";
2 | import { useSelector } from "react-redux";
3 | import { useHandleNFTRedeem } from "../../hooks/useHandleNFTRedeem";
4 | import useIsWalletReady from "../../hooks/useIsWalletReady";
5 | import { selectNFTTargetChain } from "../../store/selectors";
6 | import ButtonWithLoader from "../ButtonWithLoader";
7 | import KeyAndBalance from "../KeyAndBalance";
8 | import StepDescription from "../StepDescription";
9 | import TerraFeeDenomPicker from "../TerraFeeDenomPicker";
10 | import WaitingForWalletMessage from "./WaitingForWalletMessage";
11 |
12 | function Redeem() {
13 | const { handleClick, disabled, showLoader } = useHandleNFTRedeem();
14 | const targetChain = useSelector(selectNFTTargetChain);
15 | const { isReady, statusMessage } = useIsWalletReady(targetChain);
16 | return (
17 | <>
18 | Receive the NFT on the target chain
19 |
20 | {isTerraChain(targetChain) && (
21 |
22 | )}
23 |
29 | Redeem
30 |
31 |
32 | >
33 | );
34 | }
35 |
36 | export default Redeem;
37 |
--------------------------------------------------------------------------------
/src/components/NFT/RedeemPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useCallback } from "react";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { selectNFTRedeemTx, selectNFTTargetChain } from "../../store/selectors";
5 | import { reset } from "../../store/nftSlice";
6 | import ButtonWithLoader from "../ButtonWithLoader";
7 | import ShowTx from "../ShowTx";
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | description: {
11 | textAlign: "center",
12 | },
13 | }));
14 |
15 | export default function RedeemPreview() {
16 | const classes = useStyles();
17 | const dispatch = useDispatch();
18 | const targetChain = useSelector(selectNFTTargetChain);
19 | const redeemTx = useSelector(selectNFTRedeemTx);
20 | const handleResetClick = useCallback(() => {
21 | dispatch(reset());
22 | }, [dispatch]);
23 |
24 | const explainerString =
25 | "Success! The redeem transaction was submitted. The NFT will become available once the transaction confirms.";
26 |
27 | return (
28 | <>
29 |
34 | {explainerString}
35 |
36 | {redeemTx ? : null}
37 |
38 | Transfer Another NFT!
39 |
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/NFT/Send.tsx:
--------------------------------------------------------------------------------
1 | import { isTerraChain } from "@certusone/wormhole-sdk";
2 | import { Alert } from "@material-ui/lab";
3 | import { useSelector } from "react-redux";
4 | import { useHandleNFTTransfer } from "../../hooks/useHandleNFTTransfer";
5 | import useIsWalletReady from "../../hooks/useIsWalletReady";
6 | import {
7 | selectNFTSourceWalletAddress,
8 | selectNFTSourceChain,
9 | selectNFTTargetError,
10 | selectNFTTransferTx,
11 | selectNFTIsSendComplete,
12 | } from "../../store/selectors";
13 | import { CHAINS_BY_ID } from "../../utils/consts";
14 | import ButtonWithLoader from "../ButtonWithLoader";
15 | import KeyAndBalance from "../KeyAndBalance";
16 | import ShowTx from "../ShowTx";
17 | import StepDescription from "../StepDescription";
18 | import TerraFeeDenomPicker from "../TerraFeeDenomPicker";
19 | import TransactionProgress from "../TransactionProgress";
20 | import WaitingForWalletMessage from "./WaitingForWalletMessage";
21 |
22 | function Send() {
23 | const { handleClick, disabled, showLoader } = useHandleNFTTransfer();
24 | const sourceChain = useSelector(selectNFTSourceChain);
25 | const error = useSelector(selectNFTTargetError);
26 | const { isReady, statusMessage, walletAddress } =
27 | useIsWalletReady(sourceChain);
28 | const sourceWalletAddress = useSelector(selectNFTSourceWalletAddress);
29 | const transferTx = useSelector(selectNFTTransferTx);
30 | const isSendComplete = useSelector(selectNFTIsSendComplete);
31 | //The chain ID compare is handled implicitly, as the isWalletReady hook should report !isReady if the wallet is on the wrong chain.
32 | const isWrongWallet =
33 | sourceWalletAddress &&
34 | walletAddress &&
35 | sourceWalletAddress !== walletAddress;
36 | const isDisabled = !isReady || isWrongWallet || disabled;
37 | const errorMessage = isWrongWallet
38 | ? "A different wallet is connected than in Step 1."
39 | : statusMessage || error || undefined;
40 | return (
41 | <>
42 |
43 | Transfer the NFT to the Wormhole Token Bridge.
44 |
45 |
46 | {isTerraChain(sourceChain) && (
47 |
48 | )}
49 |
50 | This will initiate the transfer on {CHAINS_BY_ID[sourceChain].name} and
51 | wait for finalization. If you navigate away from this page before
52 | completing Step 4, you will have to perform the recovery workflow to
53 | complete the transfer.
54 |
55 |
61 | Transfer
62 |
63 |
64 | {transferTx ? : null}
65 |
70 | >
71 | );
72 | }
73 |
74 | export default Send;
75 |
--------------------------------------------------------------------------------
/src/components/NFT/SendPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useSelector } from "react-redux";
3 | import {
4 | selectNFTSourceChain,
5 | selectNFTTransferTx,
6 | } from "../../store/selectors";
7 | import ShowTx from "../ShowTx";
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | description: {
11 | textAlign: "center",
12 | },
13 | tx: {
14 | marginTop: theme.spacing(1),
15 | textAlign: "center",
16 | },
17 | viewButton: {
18 | marginTop: theme.spacing(1),
19 | },
20 | }));
21 |
22 | export default function SendPreview() {
23 | const classes = useStyles();
24 | const sourceChain = useSelector(selectNFTSourceChain);
25 | const transferTx = useSelector(selectNFTTransferTx);
26 |
27 | const explainerString = "The NFT has been sent!";
28 |
29 | return (
30 | <>
31 |
36 | {explainerString}
37 |
38 | {transferTx ? : null}
39 | >
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/NFT/SourcePreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useSelector } from "react-redux";
3 | import {
4 | selectNFTSourceChain,
5 | selectNFTSourceParsedTokenAccount,
6 | } from "../../store/selectors";
7 | import { CHAINS_BY_ID } from "../../utils/consts";
8 | import SmartAddress from "../SmartAddress";
9 | import NFTViewer from "../TokenSelectors/NFTViewer";
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | description: {
13 | textAlign: "center",
14 | },
15 | }));
16 |
17 | export default function SourcePreview() {
18 | const classes = useStyles();
19 | const sourceChain = useSelector(selectNFTSourceChain);
20 | const sourceParsedTokenAccount = useSelector(
21 | selectNFTSourceParsedTokenAccount
22 | );
23 |
24 | const explainerContent =
25 | sourceChain && sourceParsedTokenAccount ? (
26 | <>
27 | You will transfer 1 NFT of
28 |
32 | from
33 |
37 | on {CHAINS_BY_ID[sourceChain].name}
38 | >
39 | ) : (
40 | ""
41 | );
42 |
43 | return (
44 | <>
45 |
50 | {explainerContent}
51 |
52 | {sourceParsedTokenAccount ? (
53 |
54 | ) : null}
55 | >
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/NFT/TargetPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useSelector } from "react-redux";
3 | import {
4 | selectNFTTargetAddressHex,
5 | selectNFTTargetChain,
6 | } from "../../store/selectors";
7 | import { hexToNativeString } from "@certusone/wormhole-sdk";
8 | import { CHAINS_BY_ID } from "../../utils/consts";
9 | import SmartAddress from "../SmartAddress";
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | description: {
13 | textAlign: "center",
14 | },
15 | }));
16 |
17 | export default function TargetPreview() {
18 | const classes = useStyles();
19 | const targetChain = useSelector(selectNFTTargetChain);
20 | const targetAddress = useSelector(selectNFTTargetAddressHex);
21 | const targetAddressNative = hexToNativeString(targetAddress, targetChain);
22 |
23 | const explainerContent =
24 | targetChain && targetAddressNative ? (
25 | <>
26 | to
27 |
28 | on {CHAINS_BY_ID[targetChain].name}
29 | >
30 | ) : (
31 | ""
32 | );
33 |
34 | return (
35 |
40 | {explainerContent}
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/NFT/WaitingForWalletMessage.tsx:
--------------------------------------------------------------------------------
1 | import { CHAIN_ID_SOLANA, isEVMChain } from "@certusone/wormhole-sdk";
2 | import { makeStyles, Typography } from "@material-ui/core";
3 | import { useSelector } from "react-redux";
4 | import {
5 | selectNFTIsRedeeming,
6 | selectNFTIsSending,
7 | selectNFTRedeemTx,
8 | selectNFTSourceChain,
9 | selectNFTTargetChain,
10 | selectNFTTransferTx,
11 | } from "../../store/selectors";
12 | import { WAITING_FOR_WALLET_AND_CONF } from "../Transfer/WaitingForWalletMessage";
13 |
14 | const useStyles = makeStyles((theme) => ({
15 | message: {
16 | color: theme.palette.warning.light,
17 | marginTop: theme.spacing(1),
18 | textAlign: "center",
19 | },
20 | }));
21 |
22 | export default function WaitingForWalletMessage() {
23 | const classes = useStyles();
24 | const sourceChain = useSelector(selectNFTSourceChain);
25 | const isSending = useSelector(selectNFTIsSending);
26 | const transferTx = useSelector(selectNFTTransferTx);
27 | const targetChain = useSelector(selectNFTTargetChain);
28 | const isRedeeming = useSelector(selectNFTIsRedeeming);
29 | const redeemTx = useSelector(selectNFTRedeemTx);
30 | const showWarning = (isSending && !transferTx) || (isRedeeming && !redeemTx);
31 | return showWarning ? (
32 |
33 | {WAITING_FOR_WALLET_AND_CONF}{" "}
34 | {targetChain === CHAIN_ID_SOLANA && isRedeeming
35 | ? "Note: there will be several transactions"
36 | : isEVMChain(sourceChain) && isSending
37 | ? "Note: there will be two transactions"
38 | : null}
39 |
40 | ) : null;
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/NearWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useNearContext } from "../contexts/NearWalletContext";
2 | import ToggleConnectedButton from "./ToggleConnectedButton";
3 |
4 | const NearWalletKey = () => {
5 | const { connect, disconnect, accountId: activeAccount } = useNearContext();
6 |
7 | return (
8 |
14 | );
15 | };
16 |
17 | export default NearWalletKey;
18 |
--------------------------------------------------------------------------------
/src/components/NumberTextField.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | InputAdornment,
4 | TextField,
5 | TextFieldProps,
6 | } from "@material-ui/core";
7 |
8 | export default function NumberTextField({
9 | onMaxClick,
10 | ...props
11 | }: TextFieldProps & { onMaxClick?: () => void }) {
12 | return (
13 |
19 |
26 |
27 | ) : undefined,
28 | ...(props?.InputProps || {}),
29 | }}
30 | >
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/RelaySelector.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | CircularProgress,
3 | makeStyles,
4 | MenuItem,
5 | TextField,
6 | Typography,
7 | } from "@material-ui/core";
8 | import { useCallback } from "react";
9 | import useRelayersAvailable, { Relayer } from "../hooks/useRelayersAvailable";
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | mainContainer: {
13 | textAlign: "center",
14 | },
15 | }));
16 |
17 | export default function RelaySelector({
18 | selectedValue,
19 | onChange,
20 | }: {
21 | selectedValue: Relayer | null;
22 | onChange: (newValue: Relayer | null) => void;
23 | }) {
24 | const classes = useStyles();
25 | const availableRelayers = useRelayersAvailable(true);
26 |
27 | const loader = (
28 |
29 |
30 | Loading available relayers
31 |
32 | );
33 |
34 | const onChangeWrapper = useCallback(
35 | (event) => {
36 | console.log(event, "event in selector");
37 | event.target.value
38 | ? onChange(
39 | availableRelayers?.data?.relayers?.find(
40 | (x) => x.url === event.target.value
41 | ) || null
42 | )
43 | : onChange(null);
44 | },
45 | [onChange, availableRelayers]
46 | );
47 |
48 | console.log("selectedValue in relay selector", selectedValue);
49 |
50 | const selector = (
51 |
58 | {availableRelayers.data?.relayers?.map((item) => (
59 |
62 | ))}
63 |
64 | );
65 |
66 | const error = (
67 |
68 | No relayers are available at this time.
69 |
70 | );
71 |
72 | return (
73 |
74 | {availableRelayers.data?.relayers?.length
75 | ? selector
76 | : availableRelayers.isFetching
77 | ? loader
78 | : error}
79 |
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/SeiConnectWalletDialog.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Dialog,
3 | DialogTitle,
4 | Divider,
5 | IconButton,
6 | List,
7 | ListItem,
8 | ListItemText,
9 | makeStyles,
10 | } from "@material-ui/core";
11 | import CloseIcon from "@material-ui/icons/Close";
12 | import { useWallet } from "@sei-js/react";
13 | import { useCallback } from "react";
14 |
15 | const useStyles = makeStyles((theme) => ({
16 | flexTitle: {
17 | display: "flex",
18 | alignItems: "center",
19 | "& > div": {
20 | flexGrow: 1,
21 | marginRight: theme.spacing(4),
22 | },
23 | "& > button": {
24 | marginRight: theme.spacing(-1),
25 | },
26 | },
27 | icon: {
28 | height: 24,
29 | width: 24,
30 | },
31 | }));
32 |
33 | const WalletOptions = ({
34 | walletInfo,
35 | connect,
36 | isInstalled,
37 | onClose,
38 | }: {
39 | walletInfo: string;
40 | connect: (wallet: any) => void;
41 | isInstalled: boolean;
42 | onClose: () => void;
43 | }) => {
44 | const name = `${walletInfo.charAt(0).toUpperCase()}${walletInfo.slice(1)}`;
45 |
46 | const handleClick = useCallback(() => {
47 | connect(walletInfo);
48 | onClose();
49 | }, [connect, onClose, walletInfo]);
50 |
51 | return (
52 |
53 | {isInstalled ? name : `Install ${name}`}
54 |
55 | );
56 | };
57 |
58 | const SeiConnectWalletDialog = ({
59 | isOpen,
60 | onClose,
61 | }: {
62 | isOpen: boolean;
63 | onClose: () => void;
64 | }) => {
65 | const {
66 | connect,
67 | installedWallets: installedWalletKeys,
68 | supportedWallets: supportedWalletKeys,
69 | } = useWallet();
70 | const classes = useStyles();
71 |
72 | const installedWallets = installedWalletKeys.map((walletInfo) => (
73 |
80 | ));
81 |
82 | const undetectedWallets = supportedWalletKeys
83 | .filter((walletInfo) => !installedWalletKeys.includes(walletInfo))
84 | .map((walletInfo) => (
85 |
92 | ));
93 |
94 | return (
95 |
112 | );
113 | };
114 |
115 | export default SeiConnectWalletDialog;
116 |
--------------------------------------------------------------------------------
/src/components/SeiWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useWallet } from "@sei-js/react";
2 | import { useCallback, useState } from "react";
3 | import SeiConnectWalletDialog from "./SeiConnectWalletDialog";
4 | import ToggleConnectedButton from "./ToggleConnectedButton";
5 |
6 | const SeiWalletKey = () => {
7 | const { disconnect, accounts } = useWallet();
8 | const address = accounts.length ? accounts[0].address : null;
9 | const [isDialogOpen, setIsDialogOpen] = useState(false);
10 |
11 | const connect = useCallback(() => {
12 | setIsDialogOpen(true);
13 | }, [setIsDialogOpen]);
14 |
15 | const closeDialog = useCallback(() => {
16 | setIsDialogOpen(false);
17 | }, [setIsDialogOpen]);
18 |
19 | return (
20 | <>
21 |
27 |
28 | >
29 | );
30 | };
31 |
32 | export default SeiWalletKey;
33 |
--------------------------------------------------------------------------------
/src/components/SolanaTPSWarning.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core";
2 | import { Alert } from "@material-ui/lab";
3 | import { Connection } from "@solana/web3.js";
4 | import numeral from "numeral";
5 | import { useEffect, useState } from "react";
6 | import { SOLANA_HOST } from "../utils/consts";
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | alert: {
10 | marginTop: theme.spacing(1),
11 | marginBottom: theme.spacing(1),
12 | },
13 | }));
14 |
15 | export default function SolanaTPSWarning() {
16 | const classes = useStyles();
17 | const [tps, setTps] = useState(null);
18 | useEffect(() => {
19 | let cancelled = false;
20 | let interval = setInterval(() => {
21 | (async () => {
22 | try {
23 | const connection = new Connection(SOLANA_HOST);
24 | const samples = await connection.getRecentPerformanceSamples(1);
25 | if (samples.length >= 1) {
26 | let short = samples
27 | .filter((sample) => sample.numTransactions !== 0)
28 | .map(
29 | (sample) => sample.numTransactions / sample.samplePeriodSecs
30 | );
31 | const avgTps = short[0];
32 | if (!cancelled) {
33 | setTps(avgTps);
34 | }
35 | }
36 | } catch (e) {}
37 | })();
38 | }, 5000);
39 | return () => {
40 | cancelled = true;
41 | clearInterval(interval);
42 | };
43 | }, []);
44 | return tps !== null && tps < 1500 ? (
45 | {`WARNING! The Solana Transactions Per Second (TPS) is below 1500. This is a sign of network congestion. Proceed with caution as you may have difficulty submitting transactions and the guardians may have difficulty witnessing them (this could lead to processing delays). Current TPS: ${numeral(
50 | tps
51 | ).format("0,0")}`}
52 | ) : null;
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/SolanaWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useState } from "react";
2 | import { useSolanaWallet } from "../contexts/SolanaWalletContext";
3 | import SolanaConnectWalletDialog from "./SolanaConnectWalletDialog";
4 | import ToggleConnectedButton from "./ToggleConnectedButton";
5 |
6 | const SolanaWalletKey = () => {
7 | const { publicKey, wallet, disconnect } = useSolanaWallet();
8 | const [isDialogOpen, setIsDialogOpen] = useState(false);
9 |
10 | const openDialog = useCallback(() => {
11 | setIsDialogOpen(true);
12 | }, [setIsDialogOpen]);
13 |
14 | const closeDialog = useCallback(() => {
15 | setIsDialogOpen(false);
16 | }, [setIsDialogOpen]);
17 |
18 | const publicKeyBase58 = useMemo(() => {
19 | return publicKey?.toBase58() || "";
20 | }, [publicKey]);
21 |
22 | return (
23 | <>
24 |
31 |
32 | >
33 | );
34 | };
35 |
36 | export default SolanaWalletKey;
37 |
--------------------------------------------------------------------------------
/src/components/StepDescription.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { ReactChild } from "react";
3 |
4 | const useStyles = makeStyles((theme) => ({
5 | description: {
6 | marginBottom: theme.spacing(4),
7 | },
8 | }));
9 |
10 | export default function StepDescription({
11 | children,
12 | }: {
13 | children: ReactChild;
14 | }) {
15 | const classes = useStyles();
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/SuiConnectWalletDialog.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Dialog,
3 | DialogTitle,
4 | IconButton,
5 | List,
6 | ListItem,
7 | ListItemIcon,
8 | ListItemText,
9 | makeStyles,
10 | } from "@material-ui/core";
11 | import CloseIcon from "@material-ui/icons/Close";
12 | import { useWallet } from "@suiet/wallet-kit";
13 | import { useCallback } from "react";
14 |
15 | const useStyles = makeStyles((theme) => ({
16 | flexTitle: {
17 | display: "flex",
18 | alignItems: "center",
19 | "& > div": {
20 | flexGrow: 1,
21 | marginRight: theme.spacing(4),
22 | },
23 | "& > button": {
24 | marginRight: theme.spacing(-1),
25 | },
26 | },
27 | icon: {
28 | height: 24,
29 | width: 24,
30 | },
31 | }));
32 |
33 | const WalletOptions = ({
34 | name,
35 | select,
36 | onClose,
37 | icon,
38 | }: {
39 | name: string;
40 | select: (name: string) => void;
41 | onClose: () => void;
42 | icon: string;
43 | }) => {
44 | const classes = useStyles();
45 | const handleClick = useCallback(() => {
46 | select(name);
47 | onClose();
48 | }, [select, onClose, name]);
49 |
50 | return (
51 |
52 |
53 |
54 |
55 | {name}
56 |
57 | );
58 | };
59 |
60 | const SuiConnectWalletDialog = ({
61 | isOpen,
62 | onClose,
63 | }: {
64 | isOpen: boolean;
65 | onClose: () => void;
66 | }) => {
67 | const { allAvailableWallets, select } = useWallet();
68 | const classes = useStyles();
69 | const filteredConnections = allAvailableWallets.map(({ name, iconUrl }) => (
70 |
77 | ));
78 | return (
79 |
90 | );
91 | };
92 |
93 | export default SuiConnectWalletDialog;
94 |
--------------------------------------------------------------------------------
/src/components/SuiWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from "react";
2 | import SuiConnectWalletDialog from "./SuiConnectWalletDialog";
3 | import ToggleConnectedButton from "./ToggleConnectedButton";
4 | import { useWallet } from "@suiet/wallet-kit";
5 |
6 | const SuiWalletKey = () => {
7 | const { connected, address, disconnect } = useWallet();
8 | const [isDialogOpen, setIsDialogOpen] = useState(false);
9 |
10 | const connect = useCallback(() => {
11 | setIsDialogOpen(true);
12 | }, [setIsDialogOpen]);
13 |
14 | const closeDialog = useCallback(() => {
15 | setIsDialogOpen(false);
16 | }, [setIsDialogOpen]);
17 |
18 | const onDisconnect = useCallback(() => {
19 | disconnect().catch((e) => console.error(e));
20 | }, [disconnect]);
21 |
22 | return (
23 | <>
24 |
30 |
31 | >
32 | );
33 | };
34 |
35 | export default SuiWalletKey;
36 |
--------------------------------------------------------------------------------
/src/components/TerraWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useConnectedWallet, useWallet } from "@terra-money/wallet-provider";
2 | import { useCallback, useState } from "react";
3 | import TerraConnectWalletDialog from "./TerraConnectWalletDialog";
4 | import ToggleConnectedButton from "./ToggleConnectedButton";
5 |
6 | const TerraWalletKey = () => {
7 | const wallet = useWallet();
8 | const connectedWallet = useConnectedWallet();
9 |
10 | const [isDialogOpen, setIsDialogOpen] = useState(false);
11 |
12 | const connect = useCallback(() => {
13 | setIsDialogOpen(true);
14 | }, [setIsDialogOpen]);
15 |
16 | const closeDialog = useCallback(() => {
17 | setIsDialogOpen(false);
18 | }, [setIsDialogOpen]);
19 |
20 | return (
21 | <>
22 |
28 |
29 | >
30 | );
31 | };
32 |
33 | export default TerraWalletKey;
34 |
--------------------------------------------------------------------------------
/src/components/ToggleConnectedButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button, makeStyles, Tooltip } from "@material-ui/core";
2 | import { LinkOff } from "@material-ui/icons";
3 |
4 | const useStyles = makeStyles((theme) => ({
5 | button: {
6 | display: "flex",
7 | margin: `${theme.spacing(1)}px auto`,
8 | width: "100%",
9 | maxWidth: 400,
10 | },
11 | icon: {
12 | height: 24,
13 | width: 24,
14 | },
15 | }));
16 |
17 | const ToggleConnectedButton = ({
18 | connect,
19 | disconnect,
20 | connected,
21 | pk,
22 | walletIcon,
23 | }: {
24 | connect(): any;
25 | disconnect(): any;
26 | connected: boolean;
27 | pk: string;
28 | walletIcon?: string;
29 | }) => {
30 | const classes = useStyles();
31 | const is0x = pk.startsWith("0x");
32 | return connected ? (
33 |
34 |
43 | ) : (
44 |
45 | )
46 | }
47 | >
48 | Disconnect {pk.substring(0, is0x ? 6 : 3)}...
49 | {pk.substr(pk.length - (is0x ? 4 : 3))}
50 |
51 |
52 | ) : (
53 |
62 | );
63 | };
64 |
65 | export default ToggleConnectedButton;
66 |
--------------------------------------------------------------------------------
/src/components/TokenSelectors/OffsetButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button, makeStyles } from "@material-ui/core";
2 | import { ReactChild } from "react";
3 |
4 | const useStyles = makeStyles((theme) => ({
5 | offsetButton: { display: "block", marginLeft: "auto", marginTop: 8 },
6 | }));
7 |
8 | export default function OffsetButton({
9 | onClick,
10 | disabled,
11 | children,
12 | }: {
13 | onClick: () => void;
14 | disabled?: boolean;
15 | children: ReactChild;
16 | }) {
17 | const classes = useStyles();
18 | return (
19 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/TokenSelectors/RefreshButtonWrapper.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | createStyles,
3 | IconButton,
4 | makeStyles,
5 | Tooltip,
6 | } from "@material-ui/core";
7 | import RefreshIcon from "@material-ui/icons/Refresh";
8 |
9 | const useStyles = makeStyles(() =>
10 | createStyles({
11 | inlineContentWrapper: {
12 | display: "inline-block",
13 | flexGrow: 1,
14 | },
15 | flexWrapper: {
16 | "& > *": {
17 | margin: ".5rem",
18 | },
19 | display: "flex",
20 | alignItems: "center",
21 | },
22 | })
23 | );
24 |
25 | export default function RefreshButtonWrapper({
26 | children,
27 | callback,
28 | }: {
29 | children: JSX.Element;
30 | callback: () => any;
31 | }) {
32 | const classes = useStyles();
33 |
34 | const refreshWrapper = (
35 |
36 |
{children}
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 |
45 | return refreshWrapper;
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Transfer/PendingVAAWarning.tsx:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@certusone/wormhole-sdk";
2 | import { Link, makeStyles } from "@material-ui/core";
3 | import { Alert } from "@material-ui/lab";
4 | import { CHAINS_BY_ID } from "../../utils/consts";
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | alert: {
8 | marginTop: theme.spacing(1),
9 | marginBottom: theme.spacing(1),
10 | },
11 | }));
12 |
13 | const PendingVAAWarning = ({ sourceChain }: { sourceChain: ChainId }) => {
14 | const classes = useStyles();
15 | const chainName = CHAINS_BY_ID[sourceChain]?.name || "unknown";
16 | const message = `The daily notional value limit for transfers on ${chainName} has been exceeded. As
17 | a result, the VAA for this transfer is pending. If you have any questions,
18 | please open a support ticket on `;
19 | return (
20 |
21 | {message}
22 |
27 | https://discord.gg/wormholecrypto
28 |
29 | {"."}
30 |
31 | );
32 | };
33 |
34 | export default PendingVAAWarning;
35 |
--------------------------------------------------------------------------------
/src/components/Transfer/RedeemPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useCallback } from "react";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import {
5 | selectTransferRedeemTx,
6 | selectTransferTargetChain,
7 | } from "../../store/selectors";
8 | import { reset } from "../../store/transferSlice";
9 | import ButtonWithLoader from "../ButtonWithLoader";
10 | import ShowTx from "../ShowTx";
11 | import AddToMetamask from "./AddToMetamask";
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | description: {
15 | textAlign: "center",
16 | marginBottom: theme.spacing(2),
17 | },
18 | }));
19 |
20 | export default function RedeemPreview({
21 | overrideExplainerString,
22 | }: {
23 | overrideExplainerString?: string;
24 | }) {
25 | const classes = useStyles();
26 | const dispatch = useDispatch();
27 | const targetChain = useSelector(selectTransferTargetChain);
28 | const redeemTx = useSelector(selectTransferRedeemTx);
29 | const handleResetClick = useCallback(() => {
30 | dispatch(reset());
31 | }, [dispatch]);
32 |
33 | const explainerString =
34 | overrideExplainerString ||
35 | "Success! The redeem transaction was submitted. The tokens will become available once the transaction confirms.";
36 |
37 | return (
38 | <>
39 |
44 | {explainerString}
45 |
46 | {redeemTx ? : null}
47 |
48 |
49 | Transfer More Tokens!
50 |
51 | >
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/Transfer/SendPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { useSelector } from "react-redux";
3 | import {
4 | selectTransferSourceChain,
5 | selectTransferTransferTx,
6 | } from "../../store/selectors";
7 | import ShowTx from "../ShowTx";
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | description: {
11 | textAlign: "center",
12 | },
13 | tx: {
14 | marginTop: theme.spacing(1),
15 | textAlign: "center",
16 | },
17 | viewButton: {
18 | marginTop: theme.spacing(1),
19 | },
20 | }));
21 |
22 | export default function SendPreview() {
23 | const classes = useStyles();
24 | const sourceChain = useSelector(selectTransferSourceChain);
25 | const transferTx = useSelector(selectTransferTransferTx);
26 |
27 | const explainerString = "The tokens have entered the bridge!";
28 |
29 | return (
30 | <>
31 |
36 | {explainerString}
37 |
38 | {transferTx ? : null}
39 | >
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Transfer/SourceAssetWarning.tsx:
--------------------------------------------------------------------------------
1 | import { ChainId, CHAIN_ID_POLYGON, isEVMChain } from "@certusone/wormhole-sdk";
2 | import { makeStyles, Typography } from "@material-ui/core";
3 | import { Alert } from "@material-ui/lab";
4 | import { POLYGON_TERRA_WRAPPED_TOKENS } from "../../utils/consts";
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | container: {
8 | marginTop: theme.spacing(2),
9 | marginBottom: theme.spacing(2),
10 | },
11 | alert: {
12 | marginTop: theme.spacing(1),
13 | marginBottom: theme.spacing(1),
14 | },
15 | }));
16 |
17 | function PolygonTerraWrappedWarning() {
18 | const classes = useStyles();
19 | return (
20 |
21 |
22 | This is a Shuttle-wrapped asset from Polygon! Transferring it will
23 | result in a double wrapped (Portal-wrapped Shuttle-wrapped) asset, which
24 | has no liquid markets.
25 |
26 |
27 | );
28 | }
29 |
30 | export default function SoureAssetWarning({
31 | sourceChain,
32 | sourceAsset,
33 | }: {
34 | sourceChain?: ChainId;
35 | sourceAsset?: string;
36 | originChain?: ChainId;
37 | targetChain?: ChainId;
38 | targetAsset?: string;
39 | }) {
40 | if (!(sourceChain && sourceAsset)) {
41 | return null;
42 | }
43 |
44 | const searchableAddress = isEVMChain(sourceChain)
45 | ? sourceAsset.toLowerCase()
46 | : sourceAsset;
47 | const showPolygonTerraWrappedWarning =
48 | sourceChain === CHAIN_ID_POLYGON &&
49 | POLYGON_TERRA_WRAPPED_TOKENS.includes(searchableAddress);
50 |
51 | return (
52 | <>
53 | {showPolygonTerraWrappedWarning ? : null}
54 | >
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/Transfer/SourcePreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import numeral from "numeral";
3 | import { useSelector } from "react-redux";
4 | import {
5 | selectSourceWalletAddress,
6 | selectTransferAmount,
7 | selectTransferRelayerFee,
8 | selectTransferSourceChain,
9 | selectTransferSourceParsedTokenAccount,
10 | } from "../../store/selectors";
11 | import { CHAINS_BY_ID } from "../../utils/consts";
12 | import SmartAddress from "../SmartAddress";
13 |
14 | const useStyles = makeStyles((theme) => ({
15 | description: {
16 | textAlign: "center",
17 | },
18 | }));
19 |
20 | export default function SourcePreview() {
21 | const classes = useStyles();
22 | const sourceChain = useSelector(selectTransferSourceChain);
23 | const sourceParsedTokenAccount = useSelector(
24 | selectTransferSourceParsedTokenAccount
25 | );
26 | const sourceWalletAddress = useSelector(selectSourceWalletAddress);
27 | const sourceAmount = useSelector(selectTransferAmount);
28 | const relayerFee = useSelector(selectTransferRelayerFee);
29 |
30 | const explainerContent =
31 | sourceChain && sourceParsedTokenAccount ? (
32 | <>
33 |
34 | You will transfer {sourceAmount}{" "}
35 | {relayerFee
36 | ? `(+~${numeral(relayerFee).format("0.00")} relayer fee)`
37 | : ""}
38 |
39 |
44 | {sourceWalletAddress ? (
45 | <>
46 | from
47 |
48 | >
49 | ) : null}
50 | on {CHAINS_BY_ID[sourceChain].name}
51 | >
52 | ) : (
53 | ""
54 | );
55 |
56 | return (
57 | <>
58 |
63 | {explainerContent}
64 |
65 | >
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/Transfer/TargetPreview.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Typography } from "@material-ui/core";
2 | import { CHAINS_BY_ID } from "../../utils/consts";
3 | import SmartAddress from "../SmartAddress";
4 | import { useTargetInfo } from "./Target";
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | description: {
8 | textAlign: "center",
9 | },
10 | }));
11 |
12 | export default function TargetPreview() {
13 | const classes = useStyles();
14 | const {
15 | targetChain,
16 | readableTargetAddress,
17 | targetAsset,
18 | symbol,
19 | tokenName,
20 | logo,
21 | } = useTargetInfo();
22 |
23 | const explainerContent =
24 | targetChain && readableTargetAddress ? (
25 | <>
26 | {targetAsset ? (
27 | <>
28 | and receive
29 |
37 | >
38 | ) : null}
39 | to
40 |
41 | on {CHAINS_BY_ID[targetChain].name}
42 | >
43 | ) : (
44 | ""
45 | );
46 |
47 | return (
48 |
53 | {explainerContent}
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/Transfer/TransferLimitedWarning.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core";
2 | import { Alert } from "@material-ui/lab";
3 | import { IsTransferLimitedResult } from "../../hooks/useIsTransferLimited";
4 | import {
5 | CHAINS_BY_ID,
6 | USD_NUMBER_FORMATTER as USD_FORMATTER,
7 | } from "../../utils/consts";
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | alert: {
11 | marginTop: theme.spacing(1),
12 | marginBottom: theme.spacing(1),
13 | },
14 | }));
15 |
16 | const TransferLimitedWarning = ({
17 | isTransferLimited,
18 | }: {
19 | isTransferLimited: IsTransferLimitedResult;
20 | }) => {
21 | const classes = useStyles();
22 | if (
23 | isTransferLimited.isLimited &&
24 | isTransferLimited.reason &&
25 | isTransferLimited.limits
26 | ) {
27 | const chainName =
28 | CHAINS_BY_ID[isTransferLimited.limits.chainId]?.name || "unknown";
29 | const message =
30 | isTransferLimited.reason === "EXCEEDS_MAX_NOTIONAL"
31 | ? `This transfer's estimated notional value would exceed the notional value limit for transfers on ${chainName} (${USD_FORMATTER.format(
32 | isTransferLimited.limits.chainNotionalLimit
33 | )}) and may be subject to a 24 hour delay.`
34 | : isTransferLimited.reason === "EXCEEDS_LARGE_TRANSFER_LIMIT"
35 | ? `This transfer's estimated notional value may exceed the notional value for large transfers on ${chainName} (${USD_FORMATTER.format(
36 | isTransferLimited.limits.chainBigTransactionSize
37 | )}) and may be subject to a 24 hour delay.`
38 | : isTransferLimited.reason === "EXCEEDS_REMAINING_NOTIONAL"
39 | ? `This transfer's estimated notional value may exceed the remaining notional value available for transfers on ${chainName} (${USD_FORMATTER.format(
40 | isTransferLimited.limits.chainRemainingAvailableNotional
41 | )}) and may be subject to a delay.`
42 | : "";
43 | return (
44 |
45 | {message}
46 |
47 | );
48 | }
49 | return null;
50 | };
51 |
52 | export default TransferLimitedWarning;
53 |
--------------------------------------------------------------------------------
/src/components/Transfer/WaitingForWalletMessage.tsx:
--------------------------------------------------------------------------------
1 | import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
2 | import { makeStyles, Typography } from "@material-ui/core";
3 | import { useSelector } from "react-redux";
4 | import {
5 | selectTransferIsApproving,
6 | selectTransferIsRedeeming,
7 | selectTransferIsSending,
8 | selectTransferRedeemTx,
9 | selectTransferTargetChain,
10 | selectTransferTransferTx,
11 | } from "../../store/selectors";
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | message: {
15 | color: theme.palette.warning.light,
16 | marginTop: theme.spacing(1),
17 | textAlign: "center",
18 | },
19 | }));
20 |
21 | export const WAITING_FOR_WALLET_AND_CONF =
22 | "Waiting for wallet approval (likely in a popup) and confirmation...";
23 |
24 | export default function WaitingForWalletMessage() {
25 | const classes = useStyles();
26 | const isApproving = useSelector(selectTransferIsApproving);
27 | const isSending = useSelector(selectTransferIsSending);
28 | const transferTx = useSelector(selectTransferTransferTx);
29 | const targetChain = useSelector(selectTransferTargetChain);
30 | const isRedeeming = useSelector(selectTransferIsRedeeming);
31 | const redeemTx = useSelector(selectTransferRedeemTx);
32 | const showWarning =
33 | isApproving || (isSending && !transferTx) || (isRedeeming && !redeemTx);
34 | return showWarning ? (
35 |
36 | {WAITING_FOR_WALLET_AND_CONF}{" "}
37 | {targetChain === CHAIN_ID_SOLANA && isRedeeming
38 | ? "Note: there will be several transactions"
39 | : null}
40 |
41 | ) : null;
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/XplaWalletKey.tsx:
--------------------------------------------------------------------------------
1 | import { useConnectedWallet, useWallet } from "@xpla/wallet-provider";
2 | import { useCallback, useState } from "react";
3 | import XplaConnectWalletDialog from "./XplaConnectWalletDialog";
4 | import ToggleConnectedButton from "./ToggleConnectedButton";
5 |
6 | const XplaWalletKey = () => {
7 | const wallet = useWallet();
8 | const connectedWallet = useConnectedWallet();
9 |
10 | const [isDialogOpen, setIsDialogOpen] = useState(false);
11 |
12 | const connect = useCallback(() => {
13 | setIsDialogOpen(true);
14 | }, [setIsDialogOpen]);
15 |
16 | const closeDialog = useCallback(() => {
17 | setIsDialogOpen(false);
18 | }, [setIsDialogOpen]);
19 |
20 | return (
21 | <>
22 |
28 |
29 | >
30 | );
31 | };
32 |
33 | export default XplaWalletKey;
34 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@certusone/wormhole-sdk";
2 |
3 | export type DisableTransfers = boolean | "to" | "from";
4 |
5 | export interface WarningMessage {
6 | text: string;
7 | link?: {
8 | url: string;
9 | text: string;
10 | };
11 | }
12 |
13 | export interface ChainConfig {
14 | disableTransfers?: DisableTransfers;
15 | warningMessage?: WarningMessage;
16 | }
17 |
18 | export type ChainConfigMap = {
19 | [key in ChainId]?: ChainConfig;
20 | };
21 |
22 | export const CHAIN_CONFIG_MAP: ChainConfigMap = {
23 | // [CHAIN_ID_POLYGON]: {
24 | // disableTransfers: true,
25 | // warningMessage: {
26 | // text: "Polygon is currently experiencing partial downtime. As a precautionary measure, Wormhole Network and Portal have paused Polygon support until the network has been fully restored.",
27 | // link: {
28 | // url: "https://twitter.com/0xPolygonDevs",
29 | // text: "Follow @0xPolygonDevs for updates",
30 | // },
31 | // },
32 | // } as ChainConfig,
33 | };
34 |
--------------------------------------------------------------------------------
/src/contexts/AlgorandWalletContext.tsx:
--------------------------------------------------------------------------------
1 | import MyAlgoConnect, { Accounts } from "@randlabs/myalgo-connect";
2 | import {
3 | createContext,
4 | ReactChildren,
5 | useCallback,
6 | useContext,
7 | useMemo,
8 | useState,
9 | } from "react";
10 |
11 | interface IAlgorandContext {
12 | connect(): void;
13 | disconnect(): void;
14 | accounts: Accounts[];
15 | }
16 |
17 | const AlgorandContext = createContext({
18 | connect: () => {},
19 | disconnect: () => {},
20 | accounts: [],
21 | });
22 |
23 | export const AlgorandContextProvider = ({
24 | children,
25 | }: {
26 | children: ReactChildren;
27 | }) => {
28 | const myAlgoConnect = useMemo(() => new MyAlgoConnect(), []);
29 | const [accounts, setAccounts] = useState([]);
30 | const connect = useCallback(() => {
31 | let cancelled = false;
32 | (async () => {
33 | const accounts = await myAlgoConnect.connect({
34 | shouldSelectOneAccount: true,
35 | });
36 | if (!cancelled) {
37 | setAccounts(accounts);
38 | }
39 | })();
40 | return () => {
41 | cancelled = true;
42 | };
43 | }, [myAlgoConnect]);
44 | const disconnect = useCallback(() => {
45 | setAccounts([]);
46 | }, []);
47 | const value = useMemo(
48 | () => ({
49 | connect,
50 | disconnect,
51 | accounts,
52 | }),
53 | [connect, disconnect, accounts]
54 | );
55 |
56 | return (
57 |
58 | {children}
59 |
60 | );
61 | };
62 |
63 | export const useAlgorandContext = () => {
64 | return useContext(AlgorandContext);
65 | };
66 |
--------------------------------------------------------------------------------
/src/contexts/AptosWalletContext.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | AptosSnapAdapter,
3 | AptosWalletAdapter,
4 | BitkeepWalletAdapter,
5 | BloctoWalletAdapter,
6 | FewchaWalletAdapter,
7 | FletchWalletAdapter,
8 | MartianWalletAdapter,
9 | NightlyWalletAdapter,
10 | PontemWalletAdapter,
11 | RiseWalletAdapter,
12 | SpikaWalletAdapter,
13 | TokenPocketWalletAdapter,
14 | useWallet,
15 | WalletProvider,
16 | } from "@manahippo/aptos-wallet-adapter";
17 | import { ReactChildren, useMemo } from "react";
18 |
19 | export const useAptosContext = useWallet;
20 |
21 | export const AptosWalletProvider = ({
22 | children,
23 | }: {
24 | children: ReactChildren;
25 | }) => {
26 | const wallets = useMemo(
27 | () => [
28 | new AptosWalletAdapter(),
29 | new MartianWalletAdapter(),
30 | new RiseWalletAdapter(),
31 | new NightlyWalletAdapter(),
32 | new PontemWalletAdapter(),
33 | new FletchWalletAdapter(),
34 | new FewchaWalletAdapter(),
35 | new SpikaWalletAdapter(),
36 | new AptosSnapAdapter(),
37 | new BitkeepWalletAdapter(),
38 | new TokenPocketWalletAdapter(),
39 | new BloctoWalletAdapter(),
40 | ],
41 | []
42 | );
43 | return (
44 | {
47 | console.log("wallet errors: ", error);
48 | }}
49 | >
50 | {children}
51 |
52 | );
53 | };
54 |
55 | export default AptosWalletProvider;
56 |
--------------------------------------------------------------------------------
/src/contexts/SolanaWalletContext.tsx:
--------------------------------------------------------------------------------
1 | import { Adapter, WalletAdapterNetwork } from "@solana/wallet-adapter-base";
2 | import {
3 | ConnectionProvider,
4 | WalletProvider,
5 | useWallet,
6 | } from "@solana/wallet-adapter-react";
7 | import {
8 | PhantomWalletAdapter,
9 | SolflareWalletAdapter,
10 | SolletWalletAdapter,
11 | CloverWalletAdapter,
12 | Coin98WalletAdapter,
13 | SlopeWalletAdapter,
14 | SolongWalletAdapter,
15 | TorusWalletAdapter,
16 | SolletExtensionWalletAdapter,
17 | ExodusWalletAdapter,
18 | BackpackWalletAdapter,
19 | NightlyWalletAdapter,
20 | BloctoWalletAdapter,
21 | } from "@solana/wallet-adapter-wallets";
22 | import { FC, useMemo } from "react";
23 | import { CLUSTER, SOLANA_HOST } from "../utils/consts";
24 |
25 | export const SolanaWalletProvider: FC = (props) => {
26 | const wallets = useMemo(() => {
27 | const wallets: Adapter[] = [
28 | new PhantomWalletAdapter(),
29 | new SolflareWalletAdapter(),
30 | new BackpackWalletAdapter(),
31 | new NightlyWalletAdapter(),
32 | new SolletWalletAdapter(),
33 | new SolletExtensionWalletAdapter(),
34 | new CloverWalletAdapter(),
35 | new Coin98WalletAdapter(),
36 | new SlopeWalletAdapter(),
37 | new SolongWalletAdapter(),
38 | new TorusWalletAdapter(),
39 | new ExodusWalletAdapter(),
40 | ];
41 | if (CLUSTER === "testnet") {
42 | wallets.push(
43 | new BloctoWalletAdapter({ network: WalletAdapterNetwork.Devnet })
44 | );
45 | }
46 | return wallets;
47 | }, []);
48 |
49 | return (
50 |
51 |
52 | {props.children}
53 |
54 |
55 | );
56 | };
57 |
58 | export const useSolanaWallet = useWallet;
59 |
--------------------------------------------------------------------------------
/src/contexts/SuiWalletContext.tsx:
--------------------------------------------------------------------------------
1 | import { WalletProvider } from "@suiet/wallet-kit";
2 | import { ReactChildren } from "react";
3 |
4 | // export const useSuiContext = () => {
5 | // const [accounts, setAccounts] = useState([]);
6 | // const { select, wallets, connected, disconnect, getAccounts } = useWallet();
7 |
8 | // useEffect(() => {
9 | // let isCancelled = false;
10 | // if (wallet) {
11 | // wallet.getAccounts().then((accounts) => {
12 | // if (!isCancelled) {
13 | // setAccounts(accounts);
14 | // }
15 | // });
16 | // }
17 | // return () => {
18 | // isCancelled = true;
19 | // };
20 | // }, [wallet]);
21 |
22 | // return {
23 | // wallet,
24 | // accounts,
25 | // select,
26 | // wallets,
27 | // connected,
28 | // disconnect,
29 | // getAccounts,
30 | // };
31 | // };
32 |
33 | export const SuiWalletProvider = ({
34 | children,
35 | }: {
36 | children: ReactChildren;
37 | }) => {
38 | return {children};
39 | };
40 |
41 | export default SuiWalletProvider;
42 |
--------------------------------------------------------------------------------
/src/contexts/TerraWalletContext.tsx:
--------------------------------------------------------------------------------
1 | import { NetworkInfo, WalletProvider } from "@terra-money/wallet-provider";
2 | import { ReactChildren } from "react";
3 |
4 | const testnet: NetworkInfo = {
5 | name: "testnet",
6 | chainID: "pisco-1",
7 | lcd: "https://pisco-lcd.terra.dev",
8 | walletconnectID: 0,
9 | };
10 |
11 | const walletConnectChainIds: Record = {
12 | 0: testnet,
13 | };
14 |
15 | export const TerraWalletProvider = ({
16 | children,
17 | }: {
18 | children: ReactChildren;
19 | }) => {
20 | return (
21 |
25 | {children}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/contexts/XplaWalletContext.tsx:
--------------------------------------------------------------------------------
1 | import { NetworkInfo, WalletProvider } from "@xpla/wallet-provider";
2 | import { ReactChildren } from "react";
3 |
4 | const testnet: NetworkInfo = {
5 | name: "testnet",
6 | chainID: "cube_47-5",
7 | lcd: "https://cube-lcd.xpla.dev",
8 | walletconnectID: 0,
9 | };
10 |
11 | const walletConnectChainIds: Record = {
12 | 0: testnet,
13 | };
14 |
15 | export const XplaWalletProvider = ({
16 | children,
17 | }: {
18 | children: ReactChildren;
19 | }) => {
20 | return (
21 |
25 | {children}
26 |
27 | );
28 | };
29 |
30 | export default XplaWalletProvider;
31 |
--------------------------------------------------------------------------------
/src/hooks/useAlgoMetadata.ts:
--------------------------------------------------------------------------------
1 | import { Algodv2 } from "algosdk";
2 | import { useEffect, useMemo, useState } from "react";
3 | import { DataWrapper } from "../store/helpers";
4 | import { ALGORAND_HOST, ALGO_DECIMALS } from "../utils/consts";
5 |
6 | export type AlgoMetadata = {
7 | symbol?: string;
8 | tokenName?: string;
9 | decimals: number;
10 | };
11 |
12 | export const fetchSingleMetadata = async (
13 | address: string,
14 | algodClient: Algodv2
15 | ): Promise => {
16 | const assetId = parseInt(address);
17 | if (assetId === 0) {
18 | return {
19 | tokenName: "Algo",
20 | symbol: "ALGO",
21 | decimals: ALGO_DECIMALS,
22 | };
23 | }
24 | const assetInfo = await algodClient.getAssetByID(assetId).do();
25 | return {
26 | tokenName: assetInfo.params.name,
27 | symbol: assetInfo.params["unit-name"],
28 | decimals: assetInfo.params.decimals,
29 | };
30 | };
31 |
32 | const fetchAlgoMetadata = async (addresses: string[]) => {
33 | const algodClient = new Algodv2(
34 | ALGORAND_HOST.algodToken,
35 | ALGORAND_HOST.algodServer,
36 | ALGORAND_HOST.algodPort
37 | );
38 | const promises: Promise[] = [];
39 | addresses.forEach((address) => {
40 | promises.push(fetchSingleMetadata(address, algodClient));
41 | });
42 | const resultsArray = await Promise.all(promises);
43 | const output = new Map();
44 | addresses.forEach((address, index) => {
45 | output.set(address, resultsArray[index]);
46 | });
47 |
48 | return output;
49 | };
50 |
51 | function useAlgoMetadata(
52 | addresses: string[]
53 | ): DataWrapper