From 2a0eba0ce2bd7acd9fead7c3b987167a0cfd07fd Mon Sep 17 00:00:00 2001 From: Mikael Lindlof Date: Thu, 19 Nov 2020 23:07:48 +0000 Subject: [PATCH] Add keplr wallet --- script/react.sh | 8 ++- web/.env | 5 +- web/docker-compose.yaml | 4 ++ web/src/App.tsx | 48 ++-------------- web/src/config.ts | 26 ++++----- web/src/keplrWallet.js | 123 ++++++++++++++++++++++++++++++++++++++++ web/src/localWallet.ts | 43 ++++++++++++++ 7 files changed, 197 insertions(+), 60 deletions(-) create mode 100644 web/src/keplrWallet.js create mode 100644 web/src/localWallet.ts diff --git a/script/react.sh b/script/react.sh index e3b5390..5d595f8 100755 --- a/script/react.sh +++ b/script/react.sh @@ -15,4 +15,10 @@ secretcli tx compute instantiate "$CODE" "{}" --from a --amount 1000000uscrt --l echo Contract $CONTRACT cd web -REACT_APP_LCD_URL=http://localhost:1338 REACT_APP_CONTRACT=$CONTRACT docker-compose up --build + +export REACT_APP_CHAIN_ID=enigma-pub-testnet-3 +export REACT_APP_CHAIN_NAME="Secret localhost" +export REACT_APP_LCD_URL=http://localhost:1338 +export REACT_APP_RPC_URL=http://localhost:26657 +export REACT_APP_CONTRACT="$CONTRACT" +docker-compose up --build diff --git a/web/.env b/web/.env index eb34a39..7373802 100644 --- a/web/.env +++ b/web/.env @@ -1,3 +1,6 @@ -REACT_APP_LCD_URL=https://bootstrap.secrettestnet.io/ +REACT_APP_CHAIN_ID=holodeck-2 +REACT_APP_CHAIN_NAME=Secret Holodeck 2 +REACT_APP_LCD_URL=https://bootstrap.secrettestnet.io +REACT_APP_RPC_URL=http://bootstrap.secrettestnet.io:26657 REACT_APP_CONTRACT=secret17c7j5xgylhty8jq5lqnuaqd68ndazs022mqcuu REACT_APP_FAUCET=https://faucet.secrettestnet.io/ diff --git a/web/docker-compose.yaml b/web/docker-compose.yaml index 8b0341c..1c026af 100644 --- a/web/docker-compose.yaml +++ b/web/docker-compose.yaml @@ -5,6 +5,10 @@ services: build: context: . environment: + - 'REACT_APP_CHAIN_ID=${REACT_APP_CHAIN_ID}' + - 'REACT_APP_CHAIN_NAME=${REACT_APP_CHAIN_NAME}' + - 'REACT_APP_LCD_URL=${REACT_APP_LCD_URL}' + - 'REACT_APP_RPC_URL=${REACT_APP_RPC_URL}' - 'REACT_APP_CONTRACT=${REACT_APP_CONTRACT}' ports: - '3000:3000' diff --git a/web/src/App.tsx b/web/src/App.tsx index 2344090..fdf1117 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Button } from '@material-ui/core'; import * as SecretJS from 'secretjs'; -import * as bip39 from 'bip39'; import { useLocalStorage } from './utils'; import * as Msg from './msg'; import Config from './config'; @@ -13,6 +12,8 @@ import Banner from './Banner'; import Grid from '@material-ui/core/Grid'; import CircularProgress from '@material-ui/core/CircularProgress'; import GameTicker from './components/GameTicker'; +import localWallet from './localWallet'; +import keplr from './keplrWallet'; const config = Config(); @@ -21,7 +22,8 @@ export const App: React.FC = () => { const [game, setGame] = useLocalStorage('game', undefined); const { enqueueSnackbar } = useSnackbar(); useEffect(() => { - initClient(setClient); + //localWallet(config.lcdUrl, setClient); + keplr(config.chainId, config.chainName, config.lcdUrl, config.rpcUrl, setClient); }, []); return ( @@ -116,46 +118,4 @@ const claimInactivity = async ( } }; -const initClient = async (setClient: Function) => { - let mnemonic = localStorage.getItem('mnemonic'); - if (!mnemonic) { - mnemonic = bip39.generateMnemonic(); - localStorage.setItem('mnemonic', mnemonic); - } - - let tx_encryption_seed: Uint8Array; - const tx_encryption_seed_storage = localStorage.getItem('tx_encryption_seed'); - if (tx_encryption_seed_storage) { - tx_encryption_seed = Uint8Array.from(JSON.parse(`[${tx_encryption_seed_storage}]`)); - } else { - tx_encryption_seed = SecretJS.EnigmaUtils.GenerateNewSeed(); - localStorage.setItem('tx_encryption_seed', tx_encryption_seed.toString()); - } - - const signingPen = await SecretJS.Secp256k1Pen.fromMnemonic(mnemonic); - const walletAddress = SecretJS.pubkeyToAddress( - SecretJS.encodeSecp256k1Pubkey(signingPen.pubkey), - 'secret', - ); - - const secretJsClient = new SecretJS.SigningCosmWasmClient( - config.lcdUrl, - walletAddress, - (signBytes) => signingPen.sign(signBytes), - tx_encryption_seed, - { - init: { - amount: [{ amount: '250000', denom: 'uscrt' }], - gas: '250000', - }, - exec: { - amount: [{ amount: '250000', denom: 'uscrt' }], - gas: '250000', - }, - }, - ); - setClient(secretJsClient); - return secretJsClient; -}; - export default App; diff --git a/web/src/config.ts b/web/src/config.ts index 60838f1..e0ef748 100644 --- a/web/src/config.ts +++ b/web/src/config.ts @@ -1,27 +1,25 @@ interface Config { + readonly chainId: string; + readonly chainName: string; readonly lcdUrl: string; + readonly rpcUrl: string; readonly contract: string; readonly faucetUrl: string | undefined; } export default (): Config => { - const lcdUrl: string = lcdUrlFromEnv(); - const contract: string = contractFromEnv(); return { - lcdUrl, - contract, + chainId: requiredEnv('REACT_APP_CHAIN_ID'), + chainName: requiredEnv('REACT_APP_CHAIN_NAME'), + lcdUrl: requiredEnv('REACT_APP_LCD_URL'), + rpcUrl: requiredEnv('REACT_APP_RPC_URL'), + contract: requiredEnv('REACT_APP_CONTRACT'), faucetUrl: process.env.REACT_APP_FAUCET, }; }; -const lcdUrlFromEnv = (): string => { - const reactAppContract = process.env.REACT_APP_LCD_URL; - if (!reactAppContract) throw new Error('REACT_APP_LCD_URL not configured'); - return reactAppContract; -}; - -const contractFromEnv = (): string => { - const reactAppContract = process.env.REACT_APP_CONTRACT; - if (!reactAppContract) throw new Error('REACT_APP_CONTRACT not configured'); - return reactAppContract; +const requiredEnv = (key: string): string => { + const val = process.env[key]; + if (!val) throw new Error(`${key} not configured`); + return val; }; diff --git a/web/src/keplrWallet.js b/web/src/keplrWallet.js new file mode 100644 index 0000000..2d3dacf --- /dev/null +++ b/web/src/keplrWallet.js @@ -0,0 +1,123 @@ +import * as SecretJS from 'secretjs'; + +export default async (chainId, chainName, lcdUrl, rpcUrl, setClient) => { + // Keplr extension injects the offline signer that is compatible with cosmJS. + // You can get this offline signer from `window.getOfflineSigner(chainId:string)` after load event. + // And it also injects the helper function to `window.keplr`. + // If `window.getOfflineSigner` or `window.keplr` is null, Keplr extension may be not installed on browser. + if (!window.getOfflineSigner || !window.keplr) { + alert('Please install keplr extension'); + } else { + if (window.keplr.experimentalSuggestChain) { + try { + await window.keplr.experimentalSuggestChain({ + // Chain-id of the Cosmos SDK chain. + chainId, + // The name of the chain to be displayed to the user. + chainName, + // RPC endpoint of the chain. + rpc: rpcUrl, + // REST endpoint of the chain. + rest: lcdUrl, + // Staking coin information + stakeCurrency: { + // Coin denomination to be displayed to the user. + coinDenom: 'SCRT', + // Actual denom (i.e. uatom, uscrt) used by the blockchain. + coinMinimalDenom: 'uscrt', + // # of decimal points to convert minimal denomination to user-facing denomination. + coinDecimals: 6, + // (Optional) Keplr can show the fiat value of the coin if a coingecko id is provided. + // You can get id from https://api.coingecko.com/api/v3/coins/list if it is listed. + // coinGeckoId: "" + }, + // (Optional) If you have a wallet webpage used to stake the coin then provide the url to the website in `walletUrlForStaking`. + // The 'stake' button in Keplr extension will link to the webpage. + // walletUrlForStaking: "", + // The BIP44 path. + bip44: { + // You can only set the coin type of BIP44. + // 'Purpose' is fixed to 44. + coinType: 118, + }, + // Bech32 configuration to show the address to user. + // This field is the interface of + // { + // bech32PrefixAccAddr: string; + // bech32PrefixAccPub: string; + // bech32PrefixValAddr: string; + // bech32PrefixValPub: string; + // bech32PrefixConsAddr: string; + // bech32PrefixConsPub: string; + // } + bech32Config: { + bech32PrefixAccAddr: 'secret', + bech32PrefixAccPub: 'secretpub', + bech32PrefixValAddr: 'secretvaloper', + bech32PrefixValPub: 'secretvaloperpub', + bech32PrefixConsAddr: 'secretvalcons', + bech32PrefixConsPub: 'secretvalconspub', + }, + // List of all coin/tokens used in this chain. + currencies: [ + { + coinDenom: 'SCRT', + coinMinimalDenom: 'uscrt', + coinDecimals: 6, + }, + ], + // List of coin/tokens used as a fee token in this chain. + feeCurrencies: [ + { + coinDenom: 'SCRT', + coinMinimalDenom: 'uscrt', + coinDecimals: 6, + }, + ], + // (Optional) The number of the coin type. + // This field is only used to fetch the address from ENS. + // Ideally, it is recommended to be the same with BIP44 path's coin type. + // However, some early chains may choose to use the Cosmos Hub BIP44 path of '118'. + // So, this is separated to support such chains. + coinType: 118, + // (Optional) This is used to set the fee of the transaction. + // If this field is not provided, Keplr extension will set the default gas price as (low: 0.01, average: 0.025, high: 0.04). + // Currently, Keplr doesn't support dynamic calculation of the gas prices based on on-chain data. + // Make sure that the gas prices are higher than the minimum gas prices accepted by chain validators and RPC/REST endpoint. + gasPriceStep: { + low: 0.01, + average: 0.025, + high: 0.04, + }, + }); + } catch { + alert('Failed to suggest the chain'); + } + } else { + alert('Please use the recent version of keplr extension'); + } + } + + // You should request Keplr to enable the wallet. + // This method will ask the user whether or not to allow access if they haven't visited this website. + // Also, it will request user to unlock the wallet if the wallet is locked. + // If you don't request enabling before usage, there is no guarantee that other methods will work. + await window.keplr.enable(chainId); + + const offlineSigner = window.getOfflineSigner(chainId); + + // You can get the address/public keys by `getAccounts` method. + // It can return the array of address/public key. + // But, currently, Keplr extension manages only one address/public key pair. + // XXX: This line is needed to set the sender address for SigningCosmosClient. + const accounts = await offlineSigner.getAccounts(); + + // Initialize the gaia api with the offline signer that is injected by Keplr extension. + const secretJsClient = new SecretJS.SigningCosmWasmClient( + lcdUrl, + accounts[0].address, + offlineSigner, + ); + + setClient(secretJsClient); +}; diff --git a/web/src/localWallet.ts b/web/src/localWallet.ts new file mode 100644 index 0000000..f2d1af4 --- /dev/null +++ b/web/src/localWallet.ts @@ -0,0 +1,43 @@ +import * as SecretJS from 'secretjs'; +import * as bip39 from 'bip39'; + +export default async (lcdUrl: string, setClient: Function) => { + let mnemonic = localStorage.getItem('mnemonic'); + if (!mnemonic) { + mnemonic = bip39.generateMnemonic(); + localStorage.setItem('mnemonic', mnemonic); + } + + let tx_encryption_seed: Uint8Array; + const tx_encryption_seed_storage = localStorage.getItem('tx_encryption_seed'); + if (tx_encryption_seed_storage) { + tx_encryption_seed = Uint8Array.from(JSON.parse(`[${tx_encryption_seed_storage}]`)); + } else { + tx_encryption_seed = SecretJS.EnigmaUtils.GenerateNewSeed(); + localStorage.setItem('tx_encryption_seed', tx_encryption_seed.toString()); + } + + const signingPen = await SecretJS.Secp256k1Pen.fromMnemonic(mnemonic); + const walletAddress = SecretJS.pubkeyToAddress( + SecretJS.encodeSecp256k1Pubkey(signingPen.pubkey), + 'secret', + ); + + const secretJsClient = new SecretJS.SigningCosmWasmClient( + lcdUrl, + walletAddress, + (signBytes) => signingPen.sign(signBytes), + tx_encryption_seed, + { + init: { + amount: [{ amount: '250000', denom: 'uscrt' }], + gas: '250000', + }, + exec: { + amount: [{ amount: '250000', denom: 'uscrt' }], + gas: '250000', + }, + }, + ); + setClient(secretJsClient); +};