support multiple wallets
This commit is contained in:
parent
de620e9a3f
commit
da3072b867
|
@ -1,4 +1,9 @@
|
|||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'
|
||||
import {
|
||||
AccountInfo,
|
||||
Connection,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
} from '@solana/web3.js'
|
||||
import Wallet from '@project-serum/sol-wallet-adapter'
|
||||
import { Market, OpenOrders } from '@project-serum/serum'
|
||||
import { Event } from '@project-serum/serum/lib/queue'
|
||||
|
@ -186,7 +191,6 @@ export interface MarginAccountContextValues {
|
|||
}
|
||||
|
||||
// Type declaration for the margin accounts for the mango group
|
||||
|
||||
export type mangoTokenAccounts = {
|
||||
mango_group: string
|
||||
accounts: TokenAccount[]
|
||||
|
@ -199,3 +203,18 @@ export interface KnownToken {
|
|||
icon?: string
|
||||
mintAddress: string
|
||||
}
|
||||
|
||||
export const DEFAULT_PUBLIC_KEY = new PublicKey(
|
||||
'11111111111111111111111111111111'
|
||||
)
|
||||
|
||||
export interface WalletAdapter {
|
||||
publicKey: PublicKey
|
||||
autoApprove: boolean
|
||||
connected: boolean
|
||||
signTransaction: (transaction: Transaction) => Promise<Transaction>
|
||||
signAllTransactions: (transaction: Transaction[]) => Promise<Transaction[]>
|
||||
connect: () => any
|
||||
disconnect: () => any
|
||||
on<T>(event: string, fn: () => void): this
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import useWallet from '../hooks/useWallet'
|
|||
import ThemeSwitch from './ThemeSwitch'
|
||||
import UiLock from './UiLock'
|
||||
import { useRouter } from 'next/router'
|
||||
import WalletSelect from './WalletSelect'
|
||||
|
||||
const Code = styled.code`
|
||||
border: 1px solid hsla(0, 0%, 39.2%, 0.2);
|
||||
|
@ -44,14 +45,14 @@ const TopBar = () => {
|
|||
<div className="flex items-center pr-1">
|
||||
{asPath === '/' ? <UiLock className="mr-4" /> : null}
|
||||
<ThemeSwitch />
|
||||
<div className="hidden sm:ml-4 sm:flex sm:items-center">
|
||||
<div className="border border-th-primary hover:bg-th-primary rounded-md ">
|
||||
<div className="hidden sm:ml-4 sm:flex">
|
||||
<div className="flex items-center border border-th-primary rounded-md">
|
||||
<button
|
||||
onClick={handleConnectDisconnect}
|
||||
className="px-4 py-2 focus:outline-none text-th-primary hover:text-th-fgd-1 font-semibold text-bas"
|
||||
className="p-2 focus:outline-none text-th-primary hover:text-th-fgd-1 hover:bg-th-primary font-semibold"
|
||||
>
|
||||
{connected ? (
|
||||
<div onClick={wallet.disconnect}>
|
||||
<div>
|
||||
<span>Disconnect: </span>
|
||||
<Code
|
||||
className={`text-xs p-1 text-th-fgd-1 font-extralight`}
|
||||
|
@ -65,6 +66,11 @@ const TopBar = () => {
|
|||
'Connect Wallet'
|
||||
)}
|
||||
</button>
|
||||
{!connected && (
|
||||
<div className="relative h-full">
|
||||
<WalletSelect />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -76,11 +82,11 @@ const TopBar = () => {
|
|||
aria-expanded="false"
|
||||
onClick={() => setShowMenu((showMenu) => !showMenu)}
|
||||
>
|
||||
<span className={`sr-only`}>Open main menu</span>
|
||||
<span className="sr-only">Open main menu</span>
|
||||
{showMenu ? (
|
||||
<XIcon className={`h-5 w-5 text-th-primary`} />
|
||||
<XIcon className="h-5 w-5 text-th-primary" />
|
||||
) : (
|
||||
<MenuIcon className={`h-5 w-5 text-th-primary`} />
|
||||
<MenuIcon className="h-5 w-5 text-th-primary" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@ import Switch from './Switch'
|
|||
export default function TradeForm() {
|
||||
const { baseCurrency, quoteCurrency, market, marketAddress } = useMarket()
|
||||
const set = useMangoStore((s) => s.set)
|
||||
const { connected } = useMangoStore((s) => s.wallet)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const { connection, cluster } = useConnection()
|
||||
const { side, baseSize, quoteSize, price, tradeType } = useMangoStore(
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { DotsHorizontalIcon, CheckCircleIcon } from '@heroicons/react/outline'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { WALLET_PROVIDERS } from '../hooks/useWallet'
|
||||
|
||||
export default function WalletSelect() {
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const { providerUrl } = useMangoStore((s) => s.wallet)
|
||||
|
||||
const handleSelectProvider = (url) => {
|
||||
setMangoStore((state) => {
|
||||
state.wallet.providerUrl = url
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button className="p-2 h-full border-l border-th-primary focus:outline-none text-th-primary hover:text-th-fgd-1 hover:bg-th-primary cursor-pointer">
|
||||
<DotsHorizontalIcon className="h-5 w-5 text-th-primary" />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
show={open}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
static
|
||||
className="z-40 absolute right-0 w-48 mt-2 origin-top-right border border-th-primary bg-th-bkg-2 divide-y divide-th-fgd-1 rounded-md shadow-lg outline-none"
|
||||
>
|
||||
{WALLET_PROVIDERS.map(({ name, url }) => (
|
||||
<Menu.Item key={name}>
|
||||
<button
|
||||
className="p-4 w-full text-left flex items-center hover:text-th-primary"
|
||||
onClick={() => handleSelectProvider(url)}
|
||||
>
|
||||
{providerUrl === url ? (
|
||||
<CheckCircleIcon className="h-4 w-4 mr-2" />
|
||||
) : null}{' '}
|
||||
{name}
|
||||
</button>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
)
|
||||
}
|
|
@ -18,13 +18,9 @@ const WithdrawModal = ({ isOpen, onClose }) => {
|
|||
const { connection, programId } = useConnection()
|
||||
const walletAccounts = useMangoStore((s) => s.wallet.balances)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const withdrawAccounts = useMemo(
|
||||
() =>
|
||||
walletAccounts.filter((acc) =>
|
||||
const withdrawAccounts = useMemo(() => walletAccounts.filter((acc) =>
|
||||
Object.values(symbols).includes(acc.account.mint.toString())
|
||||
),
|
||||
[symbols, walletAccounts]
|
||||
)
|
||||
),[symbols, walletAccounts])
|
||||
const [selectedAccount, setSelectedAccount] = useState(withdrawAccounts[0])
|
||||
const mintAddress = useMemo(() => selectedAccount?.account.mint.toString(), [
|
||||
selectedAccount,
|
||||
|
|
|
@ -1,39 +1,69 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import Wallet from '@project-serum/sol-wallet-adapter'
|
||||
import useLocalStorageState from './useLocalStorageState'
|
||||
import useMangoStore, { INITIAL_STATE } from '../stores/useMangoStore'
|
||||
import { notify } from '../utils/notifications'
|
||||
import {
|
||||
PhantomWalletAdapter,
|
||||
SolletExtensionAdapter,
|
||||
} from '../utils/wallet-adapters'
|
||||
|
||||
const ASSET_URL =
|
||||
'https://cdn.jsdelivr.net/gh/solana-labs/oyster@main/assets/wallets'
|
||||
|
||||
export const WALLET_PROVIDERS = [
|
||||
{ name: 'sollet.io', url: 'https://www.sollet.io' },
|
||||
{
|
||||
name: 'Sollet.io',
|
||||
url: 'https://www.sollet.io',
|
||||
icon: `${ASSET_URL}/sollet.svg`,
|
||||
},
|
||||
{
|
||||
name: 'Sollet Extension',
|
||||
url: 'https://www.sollet.io/extension',
|
||||
icon: `${ASSET_URL}/sollet.svg`,
|
||||
adapter: SolletExtensionAdapter as any,
|
||||
},
|
||||
{
|
||||
name: 'Phantom',
|
||||
url: 'https://www.phantom.app',
|
||||
icon: `https://www.phantom.app/img/logo.png`,
|
||||
adapter: PhantomWalletAdapter,
|
||||
},
|
||||
]
|
||||
|
||||
const ENDPOINT = process.env.CLUSTER ? process.env.CLUSTER : 'mainnet-beta'
|
||||
|
||||
export default function useWallet() {
|
||||
const setMangoStore = useMangoStore((state) => state.set)
|
||||
const { current: wallet, connected } = useMangoStore((state) => state.wallet)
|
||||
const { current: wallet, connected, providerUrl } = useMangoStore(
|
||||
(state) => state.wallet
|
||||
)
|
||||
const endpoint = useMangoStore((state) => state.connection.endpoint)
|
||||
const fetchWalletBalances = useMangoStore(
|
||||
(s) => s.actions.fetchWalletBalances
|
||||
)
|
||||
const [savedProviderUrl] = useLocalStorageState(
|
||||
const [savedProviderUrl, setSavedProviderUrl] = useLocalStorageState(
|
||||
'walletProvider',
|
||||
'https://www.sollet.io'
|
||||
)
|
||||
const providerUrl = savedProviderUrl
|
||||
? savedProviderUrl
|
||||
: 'https://www.sollet.io'
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet) return
|
||||
console.log('creating wallet', endpoint)
|
||||
if (providerUrl !== savedProviderUrl) {
|
||||
setSavedProviderUrl(providerUrl || savedProviderUrl)
|
||||
setMangoStore((state) => {
|
||||
state.wallet.providerUrl = providerUrl || savedProviderUrl
|
||||
})
|
||||
}
|
||||
}, [providerUrl, savedProviderUrl, setSavedProviderUrl])
|
||||
|
||||
useEffect(() => {
|
||||
if (!providerUrl) return
|
||||
const provider = WALLET_PROVIDERS.find(({ url }) => url === providerUrl)
|
||||
const newWallet = new (provider?.adapter || Wallet)(providerUrl, endpoint)
|
||||
console.log('wallet', newWallet)
|
||||
|
||||
const newWallet = new Wallet(providerUrl, ENDPOINT)
|
||||
setMangoStore((state) => {
|
||||
state.wallet.current = newWallet
|
||||
})
|
||||
}, [endpoint, connected])
|
||||
}, [endpoint, providerUrl, setMangoStore])
|
||||
|
||||
useEffect(() => {
|
||||
if (!wallet) return
|
||||
|
@ -64,16 +94,20 @@ export default function useWallet() {
|
|||
})
|
||||
})
|
||||
return () => {
|
||||
wallet.disconnect()
|
||||
if (wallet && wallet.connected) {
|
||||
console.log('DISCONNECTING')
|
||||
|
||||
wallet.disconnect()
|
||||
}
|
||||
setMangoStore((state) => {
|
||||
state.wallet = INITIAL_STATE.WALLET
|
||||
})
|
||||
}
|
||||
}, [wallet])
|
||||
}, [wallet, setMangoStore])
|
||||
|
||||
useEffect(() => {
|
||||
fetchWalletBalances()
|
||||
}, [connected, fetchWalletBalances])
|
||||
|
||||
return { wallet, connected }
|
||||
return { connected, wallet }
|
||||
}
|
||||
|
|
|
@ -10,8 +10,7 @@ import {
|
|||
} from '@blockworks-foundation/mango-client'
|
||||
import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'
|
||||
import { Wallet } from '@project-serum/sol-wallet-adapter'
|
||||
import { EndpointInfo } from '../@types/types'
|
||||
import { EndpointInfo, WalletAdapter } from '../@types/types'
|
||||
import { getOwnedTokenAccounts } from '../utils/tokens'
|
||||
|
||||
export const ENDPOINTS: EndpointInfo[] = [
|
||||
|
@ -34,6 +33,7 @@ const DEFAULT_MANGO_GROUP_NAME = 'BTC_ETH_USDT'
|
|||
|
||||
export const INITIAL_STATE = {
|
||||
WALLET: {
|
||||
providerUrl: null,
|
||||
connected: false,
|
||||
current: null,
|
||||
balances: [],
|
||||
|
@ -95,8 +95,9 @@ interface MangoStore extends State {
|
|||
tradeType: 'Market' | 'Limit'
|
||||
}
|
||||
wallet: {
|
||||
providerUrl: string
|
||||
connected: boolean
|
||||
current: Wallet
|
||||
current: WalletAdapter | undefined
|
||||
balances: Array<{ account: any; publicKey: PublicKey }>
|
||||
srmAccountsForOwner: any[]
|
||||
contributedSrm: number
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './phantom'
|
||||
export * from './sollet-extension'
|
|
@ -0,0 +1,102 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
import { PublicKey, Transaction } from '@solana/web3.js'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../../@types/types'
|
||||
|
||||
type PhantomEvent = 'disconnect' | 'connect'
|
||||
type PhantomRequestMethod =
|
||||
| 'connect'
|
||||
| 'disconnect'
|
||||
| 'signTransaction'
|
||||
| 'signAllTransactions'
|
||||
|
||||
interface PhantomProvider {
|
||||
publicKey?: PublicKey
|
||||
isConnected?: boolean
|
||||
autoApprove?: boolean
|
||||
signTransaction: (transaction: Transaction) => Promise<Transaction>
|
||||
signAllTransactions: (transactions: Transaction[]) => Promise<Transaction[]>
|
||||
connect: () => Promise<void>
|
||||
disconnect: () => Promise<void>
|
||||
on: (event: PhantomEvent, handler: (args: any) => void) => void
|
||||
request: (method: PhantomRequestMethod, params: any) => Promise<any>
|
||||
listeners: (event: PhantomEvent) => (() => void)[]
|
||||
}
|
||||
|
||||
export class PhantomWalletAdapter
|
||||
extends EventEmitter
|
||||
implements WalletAdapter {
|
||||
constructor() {
|
||||
super()
|
||||
this.connect = this.connect.bind(this)
|
||||
}
|
||||
|
||||
private get _provider(): PhantomProvider | undefined {
|
||||
if ((window as any)?.solana?.isPhantom) {
|
||||
return (window as any).solana
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
private _handleConnect = (...args) => {
|
||||
this.emit('connect', ...args)
|
||||
}
|
||||
|
||||
private _handleDisconnect = (...args) => {
|
||||
this.emit('disconnect', ...args)
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this._provider?.isConnected || false
|
||||
}
|
||||
|
||||
get autoApprove() {
|
||||
return this._provider?.autoApprove || false
|
||||
}
|
||||
|
||||
async signAllTransactions(
|
||||
transactions: Transaction[]
|
||||
): Promise<Transaction[]> {
|
||||
if (!this._provider) {
|
||||
return transactions
|
||||
}
|
||||
|
||||
return this._provider.signAllTransactions(transactions)
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this._provider?.publicKey || DEFAULT_PUBLIC_KEY
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
if (!this._provider) {
|
||||
return transaction
|
||||
}
|
||||
|
||||
return this._provider.signTransaction(transaction)
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (!this._provider) {
|
||||
window.open('https://phantom.app/', '_blank')
|
||||
notify({
|
||||
message: 'Connection Error',
|
||||
description: 'Please install Phantom wallet',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this._provider.listeners('connect').length) {
|
||||
this._provider?.on('connect', this._handleConnect)
|
||||
}
|
||||
if (!this._provider.listeners('disconnect').length) {
|
||||
this._provider?.on('disconnect', this._handleDisconnect)
|
||||
}
|
||||
return this._provider?.connect()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._provider) {
|
||||
this._provider.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import Wallet from '@project-serum/sol-wallet-adapter'
|
||||
import { notify } from '../../utils/notifications'
|
||||
|
||||
export function SolletExtensionAdapter(_, network) {
|
||||
const sollet = (window as any).sollet
|
||||
if (sollet) {
|
||||
return new Wallet(sollet, network)
|
||||
}
|
||||
|
||||
return {
|
||||
on: () => {},
|
||||
connect: () => {
|
||||
notify({
|
||||
message: 'Sollet Extension Error',
|
||||
description: 'Please install the Sollet Extension for Chrome',
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue