From be5a61811157e33748e907929ebcdf516d7fd4f2 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 11 Apr 2022 16:14:16 +1000 Subject: [PATCH] add a LocalStorage wallet for electron dev-use Signed-off-by: Sven Dowideit --- src/renderer/App.tsx | 4 +- src/renderer/components/AccountView.tsx | 65 +++++++-- src/renderer/components/ProgramChange.tsx | 18 ++- src/renderer/components/TransferSolButton.tsx | 9 +- src/renderer/data/accounts/getAccount.ts | 3 +- src/renderer/wallet-adapter/localstorage.ts | 138 ++++++++++++++---- 6 files changed, 190 insertions(+), 47 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 34bf3f87..bcaf8cf9 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -289,9 +289,9 @@ export const GlobalContainer: FC = () => { new LedgerWalletAdapter(), // new SolletWalletAdapter({ network }), // new SolletExtensionWalletAdapter({ network }), - new LocalStorageWalletAdapter(), + new LocalStorageWalletAdapter({ endpoint }), ], - [] + [endpoint] ); return ( diff --git a/src/renderer/components/AccountView.tsx b/src/renderer/components/AccountView.tsx index 1e221adb..0a6021e2 100644 --- a/src/renderer/components/AccountView.tsx +++ b/src/renderer/components/AccountView.tsx @@ -1,9 +1,10 @@ -import { useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { faTerminal } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Container from 'react-bootstrap/Container'; import ButtonGroup from 'react-bootstrap/ButtonGroup'; import ButtonToolbar from 'react-bootstrap/ButtonToolbar'; +import * as sol from '@solana/web3.js'; import { useInterval, useAppSelector } from '../hooks'; import analytics from '../common/analytics'; @@ -37,28 +38,64 @@ const explorerURL = (net: Net, address: string) => { } }; -function AccountView(props: { pubKey: string | undefined }) { +function AccountView(props: { pubKey: string }) { const { pubKey } = props; const { net } = useAppSelector(selectValidatorNetworkState); - const [account, setSelectedAccountInfo] = useState( - undefined - ); + if (!pubKey) { + pubKey = '12341234123412341234'; + } - useInterval(() => { + const [account, setSelectedAccountInfo] = useState({ + net, + pubKey, + accountId: new sol.PublicKey(pubKey), + accountInfo: sol.AccountInfo < Buffer > {}, + solDelta: 0, + count: 0, + maxDelta: 0, + programID: '', + }); + + const updateAccount = useCallback(() => { + let ok = false; if (pubKey) { getAccount(net, pubKey) - .then((a) => setSelectedAccountInfo(a)) + .then((res) => { + // eslint-disable-next-line promise/always-return + if (res) { + setSelectedAccountInfo(res); + ok = true; + } + }) /* eslint-disable no-console */ .catch(console.log); - } else { - setSelectedAccountInfo(undefined); } - }, 666); + if (!ok) { + const offChainAccount: AccountInfo = { + net, + pubKey, + accountId: new sol.PublicKey(pubKey), + accountInfo: sol.AccountInfo < Buffer > {}, + solDelta: 0, + count: 0, + maxDelta: 0, + programID: '', + }; + setSelectedAccountInfo(offChainAccount); + } + }, [net, pubKey]); - if (!account) { - return <>No account selected; - } + useEffect(() => { + updateAccount(); + }, [net, pubKey, updateAccount]); + + useInterval(() => { + updateAccount(); + }, 666); + // if (!account) { + // return <>No account selected; + // } const humanName = getHumanName(account); return ( @@ -89,7 +126,7 @@ function AccountView(props: { pubKey: string | undefined }) { - + diff --git a/src/renderer/components/ProgramChange.tsx b/src/renderer/components/ProgramChange.tsx index 6af76d6a..f6acd938 100644 --- a/src/renderer/components/ProgramChange.tsx +++ b/src/renderer/components/ProgramChange.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, useCallback } from 'react'; +import * as sol from '@solana/web3.js'; import { faStar } from '@fortawesome/free-solid-svg-icons'; import * as faRegular from '@fortawesome/free-regular-svg-icons'; @@ -24,18 +25,31 @@ export function ProgramChange(props: { const [change, setChangeInfo] = useState(undefined); const updateAccount = useCallback(() => { + let ok = false; if (pubKey) { getAccount(net, pubKey) .then((res) => { // eslint-disable-next-line promise/always-return if (res) { setChangeInfo(res); + ok = true; } }) /* eslint-disable no-console */ .catch(console.log); - } else { - setChangeInfo(undefined); + } + if (!ok) { + const offChainAccount: AccountInfo = { + net, + pubKey, + accountId: new sol.PublicKey(pubKey), + accountInfo: sol.AccountInfo < Buffer > {}, + solDelta: 0, + count: 0, + maxDelta: 0, + programID: '', + }; + setChangeInfo(offChainAccount); } }, [net, pubKey]); diff --git a/src/renderer/components/TransferSolButton.tsx b/src/renderer/components/TransferSolButton.tsx index d45a10dd..70dc047e 100644 --- a/src/renderer/components/TransferSolButton.tsx +++ b/src/renderer/components/TransferSolButton.tsx @@ -107,7 +107,14 @@ function TransferSolPopover(props: { pubKey: string | undefined }) { { pending: 'Transfer submitted', success: 'Transfer succeeded 👌', - error: 'Transfer failed 🤯', + // error: 'Transfer failed 🤯', + error: { + render({ data }) { + console.log('eror', data); + // When the promise reject, data will contains the error + return 'error'; + }, + }, } ); }} diff --git a/src/renderer/data/accounts/getAccount.ts b/src/renderer/data/accounts/getAccount.ts index 44687de5..d0a6a61d 100644 --- a/src/renderer/data/accounts/getAccount.ts +++ b/src/renderer/data/accounts/getAccount.ts @@ -96,6 +96,7 @@ export const renderData = (account: AccountInfo | undefined) => { // TODO: this should look up a persistent key: string map export const getHumanName = (key: AccountInfo | sol.PublicKey) => { /* eslint-disable no-console */ - console.log(key); + // console.log(key); + return ''; }; diff --git a/src/renderer/wallet-adapter/localstorage.ts b/src/renderer/wallet-adapter/localstorage.ts index af8ad271..5b78b7cc 100644 --- a/src/renderer/wallet-adapter/localstorage.ts +++ b/src/renderer/wallet-adapter/localstorage.ts @@ -1,5 +1,8 @@ /* eslint-disable no-underscore-dangle */ // code style from solana-labs/wallet-adapter + +/* eslint-disable max-classes-per-file */ + import { BaseMessageSignerWalletAdapter, EventEmitter, @@ -18,14 +21,7 @@ import { WalletSignTransactionError, WalletWindowClosedError, } from '@solana/wallet-adapter-base'; -import { - Keypair, - Connection, - PublicKey, - SendOptions, - Transaction, - TransactionSignature, -} from '@solana/web3.js'; +import * as sol from '@solana/web3.js'; import * as bip39 from 'bip39'; import { saveState, loadState } from '../data/localstorage'; @@ -38,18 +34,97 @@ interface LocalStorageWallet extends EventEmitter { isLocalStorage?: boolean; publicKey?: { toBytes(): Uint8Array }; isConnected: boolean; - signTransaction(transaction: Transaction): Promise; - signAllTransactions(transactions: Transaction[]): Promise; + signTransaction(transaction: sol.Transaction): Promise; + signAllTransactions( + transactions: sol.Transaction[] + ): Promise; signAndSendTransaction( - transaction: Transaction, - options?: SendOptions - ): Promise<{ signature: TransactionSignature }>; + transaction: sol.Transaction, + options?: sol.SendOptions + ): Promise<{ signature: sol.TransactionSignature }>; signMessage(message: Uint8Array): Promise<{ signature: Uint8Array }>; connect(): Promise; disconnect(): Promise; _handleDisconnect(...args: unknown[]): unknown; } +class LocalStorageWalletProvider implements LocalStorageWallet { + isLocalStorage = true; + + isConnected = false; + + endpoint: string; + + account: sol.Keypair; + + constructor(config: LocalStorageWalletAdapterConfig) { + this.endpoint = config.endpoint; + this.account = config.account; + } + + signTransaction = async (transaction: sol.Transaction) => { + transaction.partialSign(this.account); + return transaction; + }; + + signAllTransactions = async ( + transactions: sol.Transaction[] + ): Promise => { + return transactions; + }; + + signAndSendTransaction = async ( + transaction: sol.Transaction, + options?: sol.SendOptions + ): Promise<{ signature: sol.TransactionSignature }> => { + const connection = new sol.Connection(this.endpoint); + + transaction.recentBlockhash = ( + await connection.getRecentBlockhash('max') + ).blockhash; + // transaction.setSigners( + // // fee payed by the wallet owner + // this.publicKey, + // ...signers.map((s) => s.publicKey) + // ); + + // if (signers.length > 0) { + // transaction.partialSign(...signers); + // } + + try { + console.log('go'); + const signedTransaction = await this.signTransaction(transaction); + console.log(signedTransaction); + const rawTransaction = signedTransaction.serialize(); + console.log(rawTransaction); + const sig = await connection.sendRawTransaction( + rawTransaction, + options + // { + // skipPreflight, + // preflightCommitment: 'single', + // } + ); + console.log(sig); + console.log('done'); + return { signature: sig }; + } catch (e) { + console.log('catch', e); + } + }; + + signMessage = async ( + message: Uint8Array + ): Promise<{ signature: Uint8Array }> => {}; + + connect = async (): Promise => {}; + + disconnect = async (): Promise => {}; + + _handleDisconnect = (): void => {}; +} + interface LocalStorageWindow extends Window { solana?: LocalStorageWallet; } @@ -57,7 +132,8 @@ interface LocalStorageWindow extends Window { declare const window: LocalStorageWindow; export interface LocalStorageWalletAdapterConfig { - unused: string; + endpoint: string; + account: sol.Keypair; } export const LocalStorageWalletName = @@ -73,25 +149,28 @@ export class LocalStorageWalletAdapter extends BaseMessageSignerWalletAdapter { private _wallet: LocalStorageWallet | null; - private _keyPair: Keypair | null = null; + private _keyPair: sol.Keypair | null = null; - private _publicKey: PublicKey | null; + private _publicKey: sol.PublicKey | null; private _connecting: boolean; private _readyState: WalletReadyState = WalletReadyState.Installed; - constructor(config: LocalStorageWalletAdapterConfig = {}) { + private _config: LocalStorageWalletAdapterConfig; + + constructor(config: LocalStorageWalletAdapterConfig) { super(); this._connecting = false; this._wallet = null; this._publicKey = null; + this._config = config; this._readyState = WalletReadyState.Installed; this.emit('readyStateChange', this._readyState); } - get publicKey(): PublicKey | null { + get publicKey(): sol.PublicKey | null { return this._publicKey; } @@ -115,7 +194,7 @@ export class LocalStorageWalletAdapter extends BaseMessageSignerWalletAdapter { this._connecting = true; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this._wallet = window!.solana!; + // this._wallet = window!.solana!; if (!this._publicKey) { // LocalStorage is always connected @@ -125,7 +204,7 @@ export class LocalStorageWalletAdapter extends BaseMessageSignerWalletAdapter { try { const seed = await bip39.mnemonicToSeed(loaded); // this._keyPair = Keypair.generate(); - this._keyPair = Keypair.fromSeed(seed.slice(0, 32)); + this._keyPair = sol.Keypair.fromSeed(seed.slice(0, 32)); } catch (e) { console.log(`Error loading LocalStorage wallet: ${e}`); } @@ -134,10 +213,13 @@ export class LocalStorageWalletAdapter extends BaseMessageSignerWalletAdapter { const mnemonic = bip39.generateMnemonic(); const seed = await bip39.mnemonicToSeed(mnemonic); // this._keyPair = Keypair.generate(); - this._keyPair = Keypair.fromSeed(seed.slice(0, 32)); + this._keyPair = sol.Keypair.fromSeed(seed.slice(0, 32)); saveState('WorkbenchLocalStoreWallet', mnemonic); } this._publicKey = this._keyPair.publicKey; + this._config.account = this._keyPair; + this._wallet = new LocalStorageWalletProvider(this._config); + this._wallet.isConnected = true; // set this._PublicKey... this.emit('connect', this._publicKey); @@ -162,10 +244,10 @@ export class LocalStorageWalletAdapter extends BaseMessageSignerWalletAdapter { } async sendTransaction( - transaction: Transaction, - connection: Connection, + transaction: sol.Transaction, + connection: sol.Connection, options?: SendTransactionOptions - ): Promise { + ): Promise { try { const wallet = this._wallet; // LocalStorage doesn't handle partial signers, so if they are provided, don't use `signAndSendTransaction` @@ -191,7 +273,9 @@ export class LocalStorageWalletAdapter extends BaseMessageSignerWalletAdapter { return super.sendTransaction(transaction, connection, options); } - async signTransaction(transaction: Transaction): Promise { + async signTransaction( + transaction: sol.Transaction + ): Promise { try { const wallet = this._wallet; if (!wallet) throw new WalletNotConnectedError(); @@ -208,8 +292,8 @@ export class LocalStorageWalletAdapter extends BaseMessageSignerWalletAdapter { } async signAllTransactions( - transactions: Transaction[] - ): Promise { + transactions: sol.Transaction[] + ): Promise { try { const wallet = this._wallet; if (!wallet) throw new WalletNotConnectedError();