Add wallet selector and ledger support (#60)
This commit is contained in:
parent
4351dd0abf
commit
d4a65b837f
|
@ -6,6 +6,7 @@
|
|||
"dependencies": {
|
||||
"@ant-design/icons": "^4.2.1",
|
||||
"@craco/craco": "^5.6.4",
|
||||
"@ledgerhq/hw-transport-webusb": "^5.41.0",
|
||||
"@project-serum/associated-token": "0.1.0",
|
||||
"@project-serum/awesome-serum": "1.0.1",
|
||||
"@project-serum/pool": "^0.2.0",
|
||||
|
@ -77,6 +78,8 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ledgerhq__hw-transport": "^4.21.3",
|
||||
"@types/ledgerhq__hw-transport-webusb": "^4.70.1",
|
||||
"gh-pages": "^3.1.0",
|
||||
"git-format-staged": "^2.1.0",
|
||||
"husky": "^4.2.5",
|
||||
|
|
|
@ -10,7 +10,8 @@ import {
|
|||
getSelectedTokenAccountForMint,
|
||||
MarketProvider,
|
||||
useBalances,
|
||||
useCustomMarkets, useLocallyStoredFeeDiscountKey,
|
||||
useCustomMarkets,
|
||||
useLocallyStoredFeeDiscountKey,
|
||||
useMarket,
|
||||
useTokenAccounts,
|
||||
} from '../utils/markets';
|
||||
|
@ -23,7 +24,7 @@ import FloatingElement from './layout/FloatingElement';
|
|||
import WalletConnect from './WalletConnect';
|
||||
import { SwapOutlined } from '@ant-design/icons';
|
||||
import { CustomMarketInfo } from '../utils/types';
|
||||
import Wallet from '@project-serum/sol-wallet-adapter';
|
||||
import { WalletAdapter } from '../wallet-adapters';
|
||||
|
||||
const { Option } = Select;
|
||||
const { Title } = Typography;
|
||||
|
@ -173,7 +174,7 @@ function ConvertFormSubmit({
|
|||
setSize: (newSize: number | undefined) => void;
|
||||
fromToken: string;
|
||||
toToken: string;
|
||||
wallet: Wallet;
|
||||
wallet?: WalletAdapter;
|
||||
customMarkets: CustomMarketInfo[];
|
||||
}) {
|
||||
const { market } = useMarket();
|
||||
|
@ -181,7 +182,9 @@ function ConvertFormSubmit({
|
|||
const balances = useBalances();
|
||||
const [fromAmount, setFromAmount] = useState<number | undefined>();
|
||||
const [toAmount, setToAmount] = useState<number | undefined>();
|
||||
const { storedFeeDiscountKey: feeDiscountKey } = useLocallyStoredFeeDiscountKey();
|
||||
const {
|
||||
storedFeeDiscountKey: feeDiscountKey,
|
||||
} = useLocallyStoredFeeDiscountKey();
|
||||
|
||||
const connection = useConnection();
|
||||
const sendConnection = useSendConnection();
|
||||
|
@ -269,6 +272,10 @@ function ConvertFormSubmit({
|
|||
|
||||
setIsConverting(true);
|
||||
try {
|
||||
if (!wallet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await placeOrder({
|
||||
side,
|
||||
price: parsedPrice,
|
||||
|
|
|
@ -5,9 +5,11 @@ import { LinkOutlined } from '@ant-design/icons';
|
|||
export default function LinkAddress({
|
||||
title,
|
||||
address,
|
||||
shorten = false,
|
||||
}: {
|
||||
title?: undefined | any;
|
||||
address: string;
|
||||
shorten?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
|
@ -18,8 +20,9 @@ export default function LinkAddress({
|
|||
href={'https://explorer.solana.com/address/' + address}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{address}
|
||||
{shorten ? `${address.slice(0, 4)}...${address.slice(-4)}` : address}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -52,6 +52,15 @@ export default function StandaloneBalancesDisplay() {
|
|||
balances && balances.find((b) => b.coin === quoteCurrency);
|
||||
|
||||
async function onSettleFunds() {
|
||||
if (!wallet) {
|
||||
notify({
|
||||
message: 'Wallet not connected',
|
||||
description: 'wallet is undefined',
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!market) {
|
||||
notify({
|
||||
message: 'Error settling funds',
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import logo from '../assets/logo.svg';
|
||||
import styled from 'styled-components';
|
||||
import { useWallet, WALLET_PROVIDERS } from '../utils/wallet';
|
||||
import { useWallet } from '../utils/wallet';
|
||||
import { ENDPOINTS, useConnectionConfig } from '../utils/connection';
|
||||
import Settings from './Settings';
|
||||
import CustomClusterEndpointDialog from './CustomClusterEndpointDialog';
|
||||
|
@ -51,7 +51,7 @@ const EXTERNAL_LINKS = {
|
|||
};
|
||||
|
||||
export default function TopBar() {
|
||||
const { connected, wallet, providerUrl, setProvider } = useWallet();
|
||||
const { connected, wallet } = useWallet();
|
||||
const {
|
||||
endpoint,
|
||||
endpointInfo,
|
||||
|
@ -321,15 +321,6 @@ export default function TopBar() {
|
|||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Select onSelect={setProvider} value={providerUrl}>
|
||||
{WALLET_PROVIDERS.map(({ name, url }) => (
|
||||
<Select.Option value={url} key={url}>
|
||||
{name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<WalletConnect />
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,9 @@ import {
|
|||
useMarkPrice,
|
||||
useSelectedOpenOrdersAccount,
|
||||
useSelectedBaseCurrencyAccount,
|
||||
useSelectedQuoteCurrencyAccount, useFeeDiscountKeys, useLocallyStoredFeeDiscountKey,
|
||||
useSelectedQuoteCurrencyAccount,
|
||||
useFeeDiscountKeys,
|
||||
useLocallyStoredFeeDiscountKey,
|
||||
} from '../utils/markets';
|
||||
import { useWallet } from '../utils/wallet';
|
||||
import { notify } from '../utils/notifications';
|
||||
|
@ -64,7 +66,9 @@ export default function TradeForm({
|
|||
const sendConnection = useSendConnection();
|
||||
const markPrice = useMarkPrice();
|
||||
useFeeDiscountKeys();
|
||||
const { storedFeeDiscountKey: feeDiscountKey } = useLocallyStoredFeeDiscountKey();
|
||||
const {
|
||||
storedFeeDiscountKey: feeDiscountKey,
|
||||
} = useLocallyStoredFeeDiscountKey();
|
||||
|
||||
const [postOnly, setPostOnly] = useState(false);
|
||||
const [ioc, setIoc] = useState(false);
|
||||
|
@ -85,6 +89,8 @@ export default function TradeForm({
|
|||
market?.minOrderSize && getDecimalCount(market.minOrderSize);
|
||||
let priceDecimalCount = market?.tickSize && getDecimalCount(market.tickSize);
|
||||
|
||||
const publicKey = wallet?.publicKey;
|
||||
|
||||
useEffect(() => {
|
||||
setChangeOrderRef && setChangeOrderRef(doChangeOrder);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@ -103,17 +109,14 @@ export default function TradeForm({
|
|||
useEffect(() => {
|
||||
const warmUpCache = async () => {
|
||||
try {
|
||||
if (!wallet || !wallet.publicKey || !market) {
|
||||
if (!wallet || !publicKey || !market) {
|
||||
console.log(`Skipping refreshing accounts`);
|
||||
return;
|
||||
}
|
||||
const startTime = getUnixTs();
|
||||
console.log(`Refreshing accounts for ${market.address}`);
|
||||
await market?.findOpenOrdersAccountsForOwner(
|
||||
sendConnection,
|
||||
wallet.publicKey,
|
||||
);
|
||||
await market?.findBestFeeDiscountKey(sendConnection, wallet.publicKey);
|
||||
await market?.findOpenOrdersAccountsForOwner(sendConnection, publicKey);
|
||||
await market?.findBestFeeDiscountKey(sendConnection, publicKey);
|
||||
const endTime = getUnixTs();
|
||||
console.log(
|
||||
`Finished refreshing accounts for ${market.address} after ${
|
||||
|
@ -127,7 +130,7 @@ export default function TradeForm({
|
|||
warmUpCache();
|
||||
const id = setInterval(warmUpCache, 30_000);
|
||||
return () => clearInterval(id);
|
||||
}, [market, sendConnection, wallet, wallet.publicKey]);
|
||||
}, [market, sendConnection, wallet, publicKey]);
|
||||
|
||||
const onSetBaseSize = (baseSize: number | undefined) => {
|
||||
setBaseSize(baseSize);
|
||||
|
@ -242,6 +245,10 @@ export default function TradeForm({
|
|||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (!wallet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await placeOrder({
|
||||
side,
|
||||
price,
|
||||
|
@ -252,7 +259,7 @@ export default function TradeForm({
|
|||
wallet,
|
||||
baseCurrencyAccount: baseCurrencyAccount?.pubkey,
|
||||
quoteCurrencyAccount: quoteCurrencyAccount?.pubkey,
|
||||
feeDiscountPubkey: feeDiscountKey
|
||||
feeDiscountPubkey: feeDiscountKey,
|
||||
});
|
||||
refreshCache(tuple('getTokenAccounts', wallet, connected));
|
||||
setPrice(undefined);
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
widget,
|
||||
ChartingLibraryWidgetOptions,
|
||||
IChartingLibraryWidget,
|
||||
ResolutionString
|
||||
ResolutionString,
|
||||
} from '../../charting_library'; // Make sure to follow step 1 of the README
|
||||
import { useMarket } from '../../utils/markets';
|
||||
import { BONFIDA_DATA_FEED } from '../../utils/bonfidaConnector';
|
||||
|
|
|
@ -36,6 +36,10 @@ export default function OpenOrderTable({
|
|||
async function cancel(order) {
|
||||
setCancelId(order?.orderId);
|
||||
try {
|
||||
if (!wallet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await cancelOrder({
|
||||
order,
|
||||
market: order.market,
|
||||
|
|
|
@ -35,6 +35,15 @@ export default function WalletBalancesTable({
|
|||
async function onSettleFunds() {
|
||||
setSettlingFunds(true);
|
||||
try {
|
||||
if (!wallet) {
|
||||
notify({
|
||||
message: 'Wallet not connected',
|
||||
description: 'Wallet not connected',
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tokenAccounts || !tokenAccountsConnected) {
|
||||
notify({
|
||||
message: 'Error settling funds',
|
||||
|
|
|
@ -1,34 +1,24 @@
|
|||
import React from 'react';
|
||||
import { Button, Popover } from 'antd';
|
||||
import { InfoCircleOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Dropdown, Menu } from 'antd';
|
||||
import { useWallet } from '../utils/wallet';
|
||||
import LinkAddress from './LinkAddress';
|
||||
|
||||
export default function WalletConnect() {
|
||||
const { connected, wallet } = useWallet();
|
||||
const publicKey = wallet?.publicKey?.toBase58();
|
||||
const { connected, wallet, select, connect, disconnect } = useWallet();
|
||||
const publicKey = (connected && wallet?.publicKey?.toBase58()) || '';
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
{connected && <LinkAddress shorten={true} address={publicKey} />}
|
||||
<Menu.Item key="3" onClick={select}>
|
||||
Change Wallet
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
type="text"
|
||||
size="large"
|
||||
onClick={connected ? wallet.disconnect : wallet.connect}
|
||||
style={{ color: '#2abdd2' }}
|
||||
>
|
||||
<UserOutlined />
|
||||
{!connected ? 'Connect wallet' : 'Disconnect'}
|
||||
</Button>
|
||||
{connected && (
|
||||
<Popover
|
||||
content={<LinkAddress address={publicKey} />}
|
||||
placement="bottomRight"
|
||||
title="Wallet public key"
|
||||
trigger="click"
|
||||
>
|
||||
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
|
||||
</Popover>
|
||||
)}
|
||||
</React.Fragment>
|
||||
<Dropdown.Button onClick={connected ? disconnect : connect} overlay={menu}>
|
||||
{connected ? 'Disconnect' : 'Connect'}
|
||||
</Dropdown.Button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ export default function NewPoolPage() {
|
|||
}, [programId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (connected) {
|
||||
if (connected && wallet) {
|
||||
setAdminAddress(wallet.publicKey.toBase58());
|
||||
}
|
||||
}, [wallet, connected]);
|
||||
|
@ -91,7 +91,7 @@ export default function NewPoolPage() {
|
|||
(adminAddress || !adminControlled);
|
||||
|
||||
async function onSubmit() {
|
||||
if (!canSubmit) {
|
||||
if (!canSubmit || !wallet) {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
|
|
|
@ -65,7 +65,7 @@ function PauseUnpauseTab({ poolInfo }: TabParams) {
|
|||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
async function sendPause() {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
|
@ -85,7 +85,7 @@ function PauseUnpauseTab({ poolInfo }: TabParams) {
|
|||
}
|
||||
|
||||
async function sendUnpause() {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
|
@ -130,7 +130,7 @@ function AddAssetTab({ poolInfo }: TabParams) {
|
|||
mintAddress,
|
||||
);
|
||||
const transaction = new Transaction();
|
||||
if (!(await connection.getAccountInfo(vaultAddress))) {
|
||||
if (!(await connection.getAccountInfo(vaultAddress)) && wallet) {
|
||||
transaction.add(
|
||||
await createAssociatedTokenAccount(
|
||||
wallet.publicKey,
|
||||
|
@ -208,6 +208,10 @@ function DepositTab({ poolInfo }: TabParams) {
|
|||
const [onSubmit, submitting] = useOnSubmitHandler(
|
||||
'depositing to pool',
|
||||
async () => {
|
||||
if (!wallet) {
|
||||
throw new Error('Wallet is not connected');
|
||||
}
|
||||
|
||||
const mintAddress = new PublicKey(address);
|
||||
const vaultAddress = poolInfo.state.assets.find((asset) =>
|
||||
asset.mint.equals(mintAddress),
|
||||
|
@ -316,6 +320,10 @@ function WithdrawTab({ poolInfo }: TabParams) {
|
|||
const [onSubmit, submitting] = useOnSubmitHandler(
|
||||
'withdrawing from pool',
|
||||
async () => {
|
||||
if (!wallet) {
|
||||
throw new Error('Wallet is not connected');
|
||||
}
|
||||
|
||||
const mintAddress = new PublicKey(address);
|
||||
const vaultAddress = poolInfo.state.assets.find((asset) =>
|
||||
asset.mint.equals(mintAddress),
|
||||
|
@ -472,7 +480,7 @@ function useOnSubmitHandler(
|
|||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
throw new Error('Wallet not connected');
|
||||
}
|
||||
const [transaction, signers] = await makeTransaction();
|
||||
|
|
|
@ -95,7 +95,7 @@ function CreateRedeemTab({ poolInfo, mintInfo, tab }: CreateRedeemInnerPanel) {
|
|||
|
||||
async function onSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (!action || !basket || !connected || !canSubmit) {
|
||||
if (!action || !basket || !connected || !canSubmit || !wallet) {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
|
|
|
@ -49,7 +49,7 @@ export default function PoolPage() {
|
|||
);
|
||||
const { wallet } = useWallet();
|
||||
|
||||
if (poolInfo && mintInfo) {
|
||||
if (poolInfo && mintInfo && wallet) {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
|
|
|
@ -423,7 +423,7 @@ export function useOpenOrdersAccounts(fast = false) {
|
|||
const { connected, wallet } = useWallet();
|
||||
const connection = useConnection();
|
||||
async function getOpenOrdersAccounts() {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
return null;
|
||||
}
|
||||
if (!market) {
|
||||
|
@ -456,7 +456,7 @@ export function useTokenAccounts(): [
|
|||
const { connected, wallet } = useWallet();
|
||||
const connection = useConnection();
|
||||
async function getTokenAccounts() {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
return null;
|
||||
}
|
||||
return await getTokenAccountInfo(connection, wallet.publicKey);
|
||||
|
@ -603,7 +603,7 @@ export function useFeeDiscountKeys(): [
|
|||
const connection = useConnection();
|
||||
const { setStoredFeeDiscountKey } = useLocallyStoredFeeDiscountKey();
|
||||
let getFeeDiscountKeys = async () => {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
return null;
|
||||
}
|
||||
if (!market) {
|
||||
|
@ -660,7 +660,7 @@ export function useFillsForAllMarkets(limit = 100) {
|
|||
let marketData;
|
||||
for (marketData of allMarkets) {
|
||||
const { market, marketName } = marketData;
|
||||
if (!market) {
|
||||
if (!market || !wallet) {
|
||||
return fills;
|
||||
}
|
||||
const openOrdersAccounts = await market.findOpenOrdersAccountsForOwner(
|
||||
|
@ -710,7 +710,7 @@ export function useAllOpenOrdersAccounts() {
|
|||
].map((stringProgramId) => new PublicKey(stringProgramId));
|
||||
|
||||
const getAllOpenOrdersAccounts = async () => {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
return [];
|
||||
}
|
||||
return (
|
||||
|
@ -819,7 +819,7 @@ export const useAllOpenOrders = (): {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (connected) {
|
||||
if (connected && wallet) {
|
||||
const getAllOpenOrders = async () => {
|
||||
setLoaded(false);
|
||||
const _openOrders: { orders: Order[]; marketAddress: string }[] = [];
|
||||
|
@ -852,7 +852,7 @@ export const useAllOpenOrders = (): {
|
|||
};
|
||||
getAllOpenOrders();
|
||||
}
|
||||
}, [connected, wallet, refresh]);
|
||||
}, [connection, connected, wallet, refresh]);
|
||||
return {
|
||||
openOrders: openOrders,
|
||||
loaded: loaded,
|
||||
|
@ -1042,7 +1042,7 @@ export function useGetOpenOrdersForDeprecatedMarkets(): {
|
|||
.map((market) => market.address.toBase58());
|
||||
|
||||
async function getOpenOrdersForDeprecatedMarkets() {
|
||||
if (!connected) {
|
||||
if (!connected || !wallet) {
|
||||
return null;
|
||||
}
|
||||
if (!marketsList) {
|
||||
|
|
|
@ -31,6 +31,10 @@ export function PreferencesProvider({ children }) {
|
|||
const autoSettle = async () => {
|
||||
const markets = (marketList || []).map((m) => m.market);
|
||||
try {
|
||||
if (!wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Auto settling');
|
||||
await settleAllFunds({
|
||||
connection,
|
||||
|
|
|
@ -21,12 +21,12 @@ import {
|
|||
TOKEN_MINTS,
|
||||
TokenInstructions,
|
||||
} from '@project-serum/serum';
|
||||
import Wallet from '@project-serum/sol-wallet-adapter';
|
||||
import { SelectedTokenAccounts, TokenAccount } from './types';
|
||||
import { Order } from '@project-serum/serum/lib/market';
|
||||
import { Buffer } from 'buffer';
|
||||
import assert from 'assert';
|
||||
import { struct } from 'superstruct';
|
||||
import { WalletAdapter } from '../wallet-adapters';
|
||||
|
||||
export async function createTokenAccountTransaction({
|
||||
connection,
|
||||
|
@ -34,7 +34,7 @@ export async function createTokenAccountTransaction({
|
|||
mintPublicKey,
|
||||
}: {
|
||||
connection: Connection;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
mintPublicKey: PublicKey;
|
||||
}): Promise<{
|
||||
transaction: Transaction;
|
||||
|
@ -76,7 +76,7 @@ export async function settleFunds({
|
|||
market: Market;
|
||||
openOrders: OpenOrders;
|
||||
connection: Connection;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
baseCurrencyAccount: TokenAccount;
|
||||
quoteCurrencyAccount: TokenAccount;
|
||||
}): Promise<string | undefined> {
|
||||
|
@ -174,7 +174,7 @@ export async function settleAllFunds({
|
|||
selectedTokenAccounts,
|
||||
}: {
|
||||
connection: Connection;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
tokenAccounts: TokenAccount[];
|
||||
markets: Market[];
|
||||
selectedTokenAccounts?: SelectedTokenAccounts;
|
||||
|
@ -289,7 +289,7 @@ export async function settleAllFunds({
|
|||
export async function cancelOrder(params: {
|
||||
market: Market;
|
||||
connection: Connection;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
order: Order;
|
||||
}) {
|
||||
return cancelOrders({ ...params, orders: [params.order] });
|
||||
|
@ -302,7 +302,7 @@ export async function cancelOrders({
|
|||
orders,
|
||||
}: {
|
||||
market: Market;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
connection: Connection;
|
||||
orders: Order[];
|
||||
}) {
|
||||
|
@ -339,7 +339,7 @@ export async function placeOrder({
|
|||
orderType: 'ioc' | 'postOnly' | 'limit';
|
||||
market: Market | undefined | null;
|
||||
connection: Connection;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
baseCurrencyAccount: PublicKey | undefined;
|
||||
quoteCurrencyAccount: PublicKey | undefined;
|
||||
feeDiscountPubkey: PublicKey | undefined;
|
||||
|
@ -480,7 +480,7 @@ export async function listMarket({
|
|||
dexProgramId,
|
||||
}: {
|
||||
connection: Connection;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
baseMint: PublicKey;
|
||||
quoteMint: PublicKey;
|
||||
baseLotSize: number;
|
||||
|
@ -637,7 +637,7 @@ export async function sendTransaction({
|
|||
timeout = DEFAULT_TIMEOUT,
|
||||
}: {
|
||||
transaction: Transaction;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
signers?: Array<Account>;
|
||||
connection: Connection;
|
||||
sendingMessage?: string;
|
||||
|
@ -668,7 +668,7 @@ export async function signTransaction({
|
|||
connection,
|
||||
}: {
|
||||
transaction: Transaction;
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
signers?: Array<Account>;
|
||||
connection: Connection;
|
||||
}) {
|
||||
|
@ -691,7 +691,7 @@ export async function signTransactions({
|
|||
transaction: Transaction;
|
||||
signers?: Array<Account>;
|
||||
}[];
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter;
|
||||
connection: Connection;
|
||||
}) {
|
||||
const blockhash = (await connection.getRecentBlockhash('max')).blockhash;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { AccountInfo, Connection, PublicKey } 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';
|
||||
import { Order } from '@project-serum/serum/lib/market';
|
||||
import { WalletAdapter } from '../wallet-adapters';
|
||||
|
||||
export interface ConnectionContextValues {
|
||||
endpoint: string;
|
||||
|
@ -14,11 +14,12 @@ export interface ConnectionContextValues {
|
|||
}
|
||||
|
||||
export interface WalletContextValues {
|
||||
wallet: Wallet;
|
||||
wallet: WalletAdapter | undefined;
|
||||
connected: boolean;
|
||||
providerUrl: string;
|
||||
setProviderUrl: (newProviderUrl: string) => void;
|
||||
providerName: string;
|
||||
select: () => void;
|
||||
}
|
||||
|
||||
export interface MarketInfo {
|
||||
|
|
|
@ -28,7 +28,9 @@ export function floorToDecimal(
|
|||
value: number,
|
||||
decimals: number | undefined | null,
|
||||
) {
|
||||
return decimals ? Math.floor(value * 10 ** decimals) / 10 ** decimals : Math.floor(value);
|
||||
return decimals
|
||||
? Math.floor(value * 10 ** decimals) / 10 ** decimals
|
||||
: Math.floor(value);
|
||||
}
|
||||
|
||||
export function roundToDecimal(
|
||||
|
@ -39,10 +41,18 @@ export function roundToDecimal(
|
|||
}
|
||||
|
||||
export function getDecimalCount(value): number {
|
||||
if (!isNaN(value) && Math.floor(value) !== value && value.toString().includes('.'))
|
||||
if (
|
||||
!isNaN(value) &&
|
||||
Math.floor(value) !== value &&
|
||||
value.toString().includes('.')
|
||||
)
|
||||
return value.toString().split('.')[1].length || 0;
|
||||
if (!isNaN(value) && Math.floor(value) !== value && value.toString().includes('e'))
|
||||
return parseInt(value.toString().split(('e-'))[1] || "0");
|
||||
if (
|
||||
!isNaN(value) &&
|
||||
Math.floor(value) !== value &&
|
||||
value.toString().includes('e')
|
||||
)
|
||||
return parseInt(value.toString().split('e-')[1] || '0');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,49 @@
|
|||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import Wallet from '@project-serum/sol-wallet-adapter';
|
||||
import { notify } from './notifications';
|
||||
import { useConnectionConfig } from './connection';
|
||||
import { useLocalStorageState } from './utils';
|
||||
import { WalletContextValues } from './types';
|
||||
import { Button, Modal } from 'antd';
|
||||
import {
|
||||
WalletAdapter,
|
||||
LedgerWalletAdapter,
|
||||
SolongWalletAdapter,
|
||||
PhantomWalletAdapter,
|
||||
} from '../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: 'Ledger',
|
||||
url: 'https://www.ledger.com',
|
||||
icon: `${ASSET_URL}/ledger.svg`,
|
||||
adapter: LedgerWalletAdapter,
|
||||
},
|
||||
{
|
||||
name: 'Solong',
|
||||
url: 'https://www.solong.com',
|
||||
icon: `${ASSET_URL}/solong.png`,
|
||||
adapter: SolongWalletAdapter,
|
||||
},
|
||||
{
|
||||
name: 'Phantom',
|
||||
url: 'https://www.phantom.app',
|
||||
icon: `https://www.phantom.app/img/logo.png`,
|
||||
adapter: PhantomWalletAdapter,
|
||||
},
|
||||
];
|
||||
|
||||
const WalletContext = React.createContext<null | WalletContextValues>(null);
|
||||
|
@ -14,62 +51,93 @@ const WalletContext = React.createContext<null | WalletContextValues>(null);
|
|||
export function WalletProvider({ children }) {
|
||||
const { endpoint } = useConnectionConfig();
|
||||
|
||||
const [savedProviderUrl, setProviderUrl] = useLocalStorageState(
|
||||
'walletProvider',
|
||||
'https://www.sollet.io',
|
||||
);
|
||||
let providerUrl;
|
||||
if (!savedProviderUrl) {
|
||||
providerUrl = 'https://www.sollet.io';
|
||||
} else {
|
||||
providerUrl = savedProviderUrl;
|
||||
}
|
||||
const [autoConnect, setAutoConnect] = useState(false);
|
||||
const [providerUrl, setProviderUrl] = useLocalStorageState('walletProvider');
|
||||
|
||||
const wallet = useMemo(() => new Wallet(providerUrl, endpoint), [
|
||||
providerUrl,
|
||||
endpoint,
|
||||
]);
|
||||
const provider = useMemo(
|
||||
() => WALLET_PROVIDERS.find(({ url }) => url === providerUrl),
|
||||
[providerUrl],
|
||||
);
|
||||
|
||||
const wallet = useMemo(
|
||||
function () {
|
||||
if (provider) {
|
||||
return new (provider.adapter || Wallet)(
|
||||
providerUrl,
|
||||
endpoint,
|
||||
) as WalletAdapter;
|
||||
}
|
||||
},
|
||||
[provider, providerUrl, endpoint],
|
||||
);
|
||||
|
||||
const [connected, setConnected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('trying to connect');
|
||||
wallet.on('connect', () => {
|
||||
console.log('connected');
|
||||
localStorage.removeItem('feeDiscountKey')
|
||||
setConnected(true);
|
||||
let walletPublicKey = wallet.publicKey.toBase58();
|
||||
let keyToDisplay =
|
||||
walletPublicKey.length > 20
|
||||
? `${walletPublicKey.substring(0, 7)}.....${walletPublicKey.substring(
|
||||
walletPublicKey.length - 7,
|
||||
walletPublicKey.length,
|
||||
)}`
|
||||
: walletPublicKey;
|
||||
notify({
|
||||
message: 'Wallet update',
|
||||
description: 'Connected to wallet ' + keyToDisplay,
|
||||
if (wallet) {
|
||||
wallet.on('connect', () => {
|
||||
if (wallet.publicKey) {
|
||||
console.log('connected');
|
||||
localStorage.removeItem('feeDiscountKey');
|
||||
setConnected(true);
|
||||
const walletPublicKey = wallet.publicKey.toBase58();
|
||||
const keyToDisplay =
|
||||
walletPublicKey.length > 20
|
||||
? `${walletPublicKey.substring(
|
||||
0,
|
||||
7,
|
||||
)}.....${walletPublicKey.substring(
|
||||
walletPublicKey.length - 7,
|
||||
walletPublicKey.length,
|
||||
)}`
|
||||
: walletPublicKey;
|
||||
|
||||
notify({
|
||||
message: 'Wallet update',
|
||||
description: 'Connected to wallet ' + keyToDisplay,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
wallet.on('disconnect', () => {
|
||||
setConnected(false);
|
||||
notify({
|
||||
message: 'Wallet update',
|
||||
description: 'Disconnected from wallet',
|
||||
|
||||
wallet.on('disconnect', () => {
|
||||
setConnected(false);
|
||||
notify({
|
||||
message: 'Wallet update',
|
||||
description: 'Disconnected from wallet',
|
||||
});
|
||||
localStorage.removeItem('feeDiscountKey');
|
||||
});
|
||||
localStorage.removeItem('feeDiscountKey')
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
wallet.disconnect();
|
||||
setConnected(false);
|
||||
if (wallet) {
|
||||
wallet.disconnect();
|
||||
setConnected(false);
|
||||
}
|
||||
};
|
||||
}, [wallet]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet && autoConnect) {
|
||||
wallet.connect();
|
||||
setAutoConnect(false);
|
||||
}
|
||||
|
||||
return () => {};
|
||||
}, [wallet, autoConnect]);
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
|
||||
const select = useCallback(() => setIsModalVisible(true), []);
|
||||
const close = useCallback(() => setIsModalVisible(false), []);
|
||||
|
||||
return (
|
||||
<WalletContext.Provider
|
||||
value={{
|
||||
wallet,
|
||||
connected,
|
||||
select,
|
||||
providerUrl,
|
||||
setProviderUrl,
|
||||
providerName:
|
||||
|
@ -78,6 +146,47 @@ export function WalletProvider({ children }) {
|
|||
}}
|
||||
>
|
||||
{children}
|
||||
<Modal
|
||||
title="Select Wallet"
|
||||
okText="Connect"
|
||||
visible={isModalVisible}
|
||||
okButtonProps={{ style: { display: 'none' } }}
|
||||
onCancel={close}
|
||||
width={400}
|
||||
>
|
||||
{WALLET_PROVIDERS.map((provider) => {
|
||||
const onClick = function () {
|
||||
setProviderUrl(provider.url);
|
||||
setAutoConnect(true);
|
||||
close();
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="large"
|
||||
type={providerUrl === provider.url ? 'primary' : 'ghost'}
|
||||
onClick={onClick}
|
||||
icon={
|
||||
<img
|
||||
alt={`${provider.name}`}
|
||||
width={20}
|
||||
height={20}
|
||||
src={provider.icon}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
textAlign: 'left',
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
{provider.name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Modal>
|
||||
</WalletContext.Provider>
|
||||
);
|
||||
}
|
||||
|
@ -87,11 +196,20 @@ export function useWallet() {
|
|||
if (!context) {
|
||||
throw new Error('Missing wallet context');
|
||||
}
|
||||
|
||||
const wallet = context.wallet;
|
||||
return {
|
||||
connected: context.connected,
|
||||
wallet: context.wallet,
|
||||
wallet: wallet,
|
||||
providerUrl: context.providerUrl,
|
||||
setProvider: context.setProviderUrl,
|
||||
providerName: context.providerName,
|
||||
select: context.select,
|
||||
connect() {
|
||||
wallet ? wallet.connect() : context.select();
|
||||
},
|
||||
disconnect() {
|
||||
wallet?.disconnect();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export * from './ledger';
|
||||
export * from './solong';
|
||||
export * from './phantom';
|
||||
export * from './types';
|
|
@ -0,0 +1,133 @@
|
|||
import type Transport from '@ledgerhq/hw-transport';
|
||||
import type { Transaction } from '@solana/web3.js';
|
||||
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
const INS_GET_PUBKEY = 0x05;
|
||||
const INS_SIGN_MESSAGE = 0x06;
|
||||
|
||||
const P1_NON_CONFIRM = 0x00;
|
||||
const P1_CONFIRM = 0x01;
|
||||
|
||||
const P2_EXTEND = 0x01;
|
||||
const P2_MORE = 0x02;
|
||||
|
||||
const MAX_PAYLOAD = 255;
|
||||
|
||||
const LEDGER_CLA = 0xe0;
|
||||
|
||||
/*
|
||||
* Helper for chunked send of large payloads
|
||||
*/
|
||||
async function ledgerSend(
|
||||
transport: Transport,
|
||||
instruction: number,
|
||||
p1: number,
|
||||
payload: Buffer,
|
||||
) {
|
||||
let p2 = 0;
|
||||
let payloadOffset = 0;
|
||||
|
||||
if (payload.length > MAX_PAYLOAD) {
|
||||
while (payload.length - payloadOffset > MAX_PAYLOAD) {
|
||||
const chunk = payload.slice(payloadOffset, payloadOffset + MAX_PAYLOAD);
|
||||
payloadOffset += MAX_PAYLOAD;
|
||||
console.log(
|
||||
'send',
|
||||
(p2 | P2_MORE).toString(16),
|
||||
chunk.length.toString(16),
|
||||
chunk,
|
||||
);
|
||||
const reply = await transport.send(
|
||||
LEDGER_CLA,
|
||||
instruction,
|
||||
p1,
|
||||
p2 | P2_MORE,
|
||||
chunk,
|
||||
);
|
||||
if (reply.length !== 2) {
|
||||
throw new Error('Received unexpected reply payload');
|
||||
}
|
||||
p2 |= P2_EXTEND;
|
||||
}
|
||||
}
|
||||
|
||||
const chunk = payload.slice(payloadOffset);
|
||||
console.log('send', p2.toString(16), chunk.length.toString(16), chunk);
|
||||
const reply = await transport.send(LEDGER_CLA, instruction, p1, p2, chunk);
|
||||
|
||||
return reply.slice(0, reply.length - 2);
|
||||
}
|
||||
|
||||
const BIP32_HARDENED_BIT = (1 << 31) >>> 0;
|
||||
function harden(n: number = 0) {
|
||||
return (n | BIP32_HARDENED_BIT) >>> 0;
|
||||
}
|
||||
|
||||
export function getSolanaDerivationPath(account?: number, change?: number) {
|
||||
var length;
|
||||
if (account !== undefined) {
|
||||
if (change !== undefined) {
|
||||
length = 4;
|
||||
} else {
|
||||
length = 3;
|
||||
}
|
||||
} else {
|
||||
length = 2;
|
||||
}
|
||||
|
||||
var derivationPath = Buffer.alloc(1 + length * 4);
|
||||
// eslint-disable-next-line
|
||||
var offset = 0;
|
||||
offset = derivationPath.writeUInt8(length, offset);
|
||||
offset = derivationPath.writeUInt32BE(harden(44), offset); // Using BIP44
|
||||
offset = derivationPath.writeUInt32BE(harden(501), offset); // Solana's BIP44 path
|
||||
|
||||
if (length > 2) {
|
||||
offset = derivationPath.writeUInt32BE(harden(account), offset);
|
||||
if (length === 4) {
|
||||
// @FIXME: https://github.com/project-serum/spl-token-wallet/issues/59
|
||||
offset = derivationPath.writeUInt32BE(harden(change), offset);
|
||||
}
|
||||
}
|
||||
|
||||
return derivationPath;
|
||||
}
|
||||
|
||||
export async function signTransaction(
|
||||
transport: Transport,
|
||||
transaction: Transaction,
|
||||
derivationPath: Buffer = getSolanaDerivationPath(),
|
||||
) {
|
||||
const messageBytes = transaction.serializeMessage();
|
||||
return signBytes(transport, messageBytes, derivationPath);
|
||||
}
|
||||
|
||||
export async function signBytes(
|
||||
transport: Transport,
|
||||
bytes: Buffer,
|
||||
derivationPath: Buffer = getSolanaDerivationPath(),
|
||||
) {
|
||||
const numPaths = Buffer.alloc(1);
|
||||
numPaths.writeUInt8(1, 0);
|
||||
|
||||
const payload = Buffer.concat([numPaths, derivationPath, bytes]);
|
||||
|
||||
// @FIXME: must enable blind signing in Solana Ledger App per https://github.com/project-serum/spl-token-wallet/issues/71
|
||||
// See also https://github.com/project-serum/spl-token-wallet/pull/23#issuecomment-712317053
|
||||
return ledgerSend(transport, INS_SIGN_MESSAGE, P1_CONFIRM, payload);
|
||||
}
|
||||
|
||||
export async function getPublicKey(
|
||||
transport: Transport,
|
||||
derivationPath: Buffer = getSolanaDerivationPath(),
|
||||
) {
|
||||
const publicKeyBytes = await ledgerSend(
|
||||
transport,
|
||||
INS_GET_PUBKEY,
|
||||
P1_NON_CONFIRM,
|
||||
derivationPath,
|
||||
);
|
||||
|
||||
return new PublicKey(publicKeyBytes);
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
import type Transport from '@ledgerhq/hw-transport';
|
||||
import type { Transaction } from '@solana/web3.js';
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import TransportWebUSB from '@ledgerhq/hw-transport-webusb';
|
||||
import { notify } from '../../utils/notifications';
|
||||
import { getPublicKey, signTransaction } from './core';
|
||||
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../types';
|
||||
|
||||
export class LedgerWalletAdapter extends EventEmitter implements WalletAdapter {
|
||||
_connecting: boolean;
|
||||
_publicKey: PublicKey | null;
|
||||
_transport: Transport | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._connecting = false;
|
||||
this._publicKey = null;
|
||||
this._transport = null;
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this._publicKey || DEFAULT_PUBLIC_KEY;
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this._publicKey !== null;
|
||||
}
|
||||
|
||||
get autoApprove() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async signAllTransactions(
|
||||
transactions: Transaction[],
|
||||
): Promise<Transaction[]> {
|
||||
const result: Transaction[] = [];
|
||||
for (let i = 0; i < transactions.length; i++) {
|
||||
const transaction = transactions[i];
|
||||
const signed = await this.signTransaction(transaction);
|
||||
result.push(signed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
if (!this._transport || !this._publicKey) {
|
||||
throw new Error('Not connected to Ledger');
|
||||
}
|
||||
|
||||
// @TODO: account selection (derivation path changes with account)
|
||||
const signature = await signTransaction(this._transport, transaction);
|
||||
|
||||
transaction.addSignature(this._publicKey, signature);
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
if (this._connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._connecting = true;
|
||||
|
||||
try {
|
||||
// @TODO: transport selection (WebUSB, WebHID, bluetooth, ...)
|
||||
this._transport = await TransportWebUSB.create();
|
||||
// @TODO: account selection
|
||||
this._publicKey = await getPublicKey(this._transport);
|
||||
this.emit('connect', this._publicKey);
|
||||
} catch (error) {
|
||||
notify({
|
||||
message: 'Ledger Error',
|
||||
description: error.message,
|
||||
});
|
||||
await this.disconnect();
|
||||
} finally {
|
||||
this._connecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
let emit = false;
|
||||
if (this._transport) {
|
||||
await this._transport.close();
|
||||
this._transport = null;
|
||||
emit = true;
|
||||
}
|
||||
|
||||
this._connecting = false;
|
||||
this._publicKey = null;
|
||||
|
||||
if (emit) {
|
||||
this.emit('disconnect');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
import EventEmitter from 'eventemitter3';
|
||||
import { PublicKey, Transaction } from '@solana/web3.js';
|
||||
import { notify } from '../../utils/notifications';
|
||||
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../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>;
|
||||
}
|
||||
|
||||
const SUPPORTED_PHANTOM_EVENTS: PhantomEvent[] = ['connect', 'disconnect'];
|
||||
|
||||
export class PhantomWalletAdapter
|
||||
extends EventEmitter
|
||||
implements WalletAdapter {
|
||||
constructor() {
|
||||
super();
|
||||
this.connect = this.connect.bind(this);
|
||||
window.onload = () => {
|
||||
for (const event of SUPPORTED_PHANTOM_EVENTS) {
|
||||
this._provider?.on(event, (...args) => this.emit(event, ...args));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private get _provider(): PhantomProvider | undefined {
|
||||
if ((window as any)?.solana?.isPhantom) {
|
||||
return (window as any).solana;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return this._provider?.connect();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._provider) {
|
||||
this._provider.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import EventEmitter from 'eventemitter3';
|
||||
import { PublicKey, Transaction } from '@solana/web3.js';
|
||||
import { notify } from '../../utils/notifications';
|
||||
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../types';
|
||||
|
||||
export class SolongWalletAdapter extends EventEmitter implements WalletAdapter {
|
||||
_publicKey?: PublicKey;
|
||||
_onProcess: boolean;
|
||||
_connected: boolean;
|
||||
constructor() {
|
||||
super();
|
||||
this._onProcess = false;
|
||||
this._connected = false;
|
||||
this.connect = this.connect.bind(this);
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this._connected;
|
||||
}
|
||||
|
||||
get autoApprove() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async signAllTransactions(
|
||||
transactions: Transaction[],
|
||||
): Promise<Transaction[]> {
|
||||
const solong = (window as any).solong;
|
||||
if (solong.signAllTransactions) {
|
||||
return solong.signAllTransactions(transactions);
|
||||
} else {
|
||||
const result: Transaction[] = [];
|
||||
for (let i = 0; i < transactions.length; i++) {
|
||||
const transaction = transactions[i];
|
||||
const signed = await solong.signTransaction(transaction);
|
||||
result.push(signed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this._publicKey || DEFAULT_PUBLIC_KEY;
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
return (window as any).solong.signTransaction(transaction);
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (this._onProcess) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((window as any).solong === undefined) {
|
||||
notify({
|
||||
message: 'Solong Error',
|
||||
description: 'Please install solong wallet from Chrome ',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._onProcess = true;
|
||||
(window as any).solong
|
||||
.selectAccount()
|
||||
.then((account: any) => {
|
||||
this._publicKey = new PublicKey(account);
|
||||
this._connected = true;
|
||||
this.emit('connect', this._publicKey);
|
||||
})
|
||||
.catch(() => {
|
||||
this.disconnect();
|
||||
})
|
||||
.finally(() => {
|
||||
this._onProcess = false;
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._publicKey) {
|
||||
this._publicKey = undefined;
|
||||
this._connected = false;
|
||||
this.emit('disconnect');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { PublicKey, Transaction } from '@solana/web3.js';
|
||||
|
||||
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;
|
||||
}
|
63
yarn.lock
63
yarn.lock
|
@ -1419,6 +1419,45 @@
|
|||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@ledgerhq/devices@^5.43.0":
|
||||
version "5.43.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.43.0.tgz#9b8ca838a7f8ece74098dc84aa6468eb7651972d"
|
||||
integrity sha512-/M5ZLUBdBK7Vl2T4yNJbES3Z4w55LbPdxD9rcOBAKH/5V3V0obQv6MUasP9b7DSkwGSSLCOGZLohoT2NxK2D2A==
|
||||
dependencies:
|
||||
"@ledgerhq/errors" "^5.43.0"
|
||||
"@ledgerhq/logs" "^5.43.0"
|
||||
rxjs "^6.6.3"
|
||||
semver "^7.3.4"
|
||||
|
||||
"@ledgerhq/errors@^5.43.0":
|
||||
version "5.43.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.43.0.tgz#6bec77ebc31c4333a7f8d13b1f3f4d739b859b75"
|
||||
integrity sha512-ZjKlUQbIn/DHXAefW3Y1VyDrlVhVqqGnXzrqbOXuDbZ2OAIfSe/A1mrlCbWt98jP/8EJQBuCzBOtnmpXIL/nYg==
|
||||
|
||||
"@ledgerhq/hw-transport-webusb@^5.41.0":
|
||||
version "5.43.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-5.43.0.tgz#02fa4a51dd93efae73e2caa1005be9782c381066"
|
||||
integrity sha512-Mf/qRn8cvK20cqqNtxFfpKVut8BvSvXkq/9HSArV7AUk+a6wga2VEvPlfk8xC551dkJlfln6+nECZ9KIEq9hFw==
|
||||
dependencies:
|
||||
"@ledgerhq/devices" "^5.43.0"
|
||||
"@ledgerhq/errors" "^5.43.0"
|
||||
"@ledgerhq/hw-transport" "^5.43.0"
|
||||
"@ledgerhq/logs" "^5.43.0"
|
||||
|
||||
"@ledgerhq/hw-transport@^5.43.0":
|
||||
version "5.43.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.43.0.tgz#dc9863706d31bae96aed66f193b8922a876cbf82"
|
||||
integrity sha512-0S+TGmiEJOqgM2MWnolZQPVKU3oRtoDj4yUFUZts9Owbgby+hmo4dIKTvv0vs8mwknQbOZByUgh3MQOQiK70MQ==
|
||||
dependencies:
|
||||
"@ledgerhq/devices" "^5.43.0"
|
||||
"@ledgerhq/errors" "^5.43.0"
|
||||
events "^3.2.0"
|
||||
|
||||
"@ledgerhq/logs@^5.43.0":
|
||||
version "5.43.0"
|
||||
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.43.0.tgz#031bad4b8a3525c5e14210afde0bc09c79564026"
|
||||
integrity sha512-QWfQjea3ekh9ZU+JeL2tJC9cTKLZ/JrcS0JGatLejpRYxQajvnHvHfh0dbHOKXEaXfCskEPTZ3f1kzuts742GA==
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
|
||||
|
@ -1820,6 +1859,21 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
||||
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
||||
|
||||
"@types/ledgerhq__hw-transport-webusb@^4.70.1":
|
||||
version "4.70.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-webusb/-/ledgerhq__hw-transport-webusb-4.70.1.tgz#ea80859607a46030f001bce462e1e7443b27ec43"
|
||||
integrity sha512-s+bt/fU5cH7etjLrNRn2LebZZqUL+YHIWciC1T6SUw2kyFpSqQQmjcM81ZrMR/tccQGfYTy3ebrJx9ZK3Mn+HA==
|
||||
dependencies:
|
||||
"@types/ledgerhq__hw-transport" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ledgerhq__hw-transport@*", "@types/ledgerhq__hw-transport@^4.21.3":
|
||||
version "4.21.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport/-/ledgerhq__hw-transport-4.21.3.tgz#1e658da6b5d01ffab92f9660cf57121aecfa7e2c"
|
||||
integrity sha512-6QveiZLsFLq9WZDk8HWAZhivoGzyz5S8WV36hpUe7KrVDaTR1fDdB+syorrNRhYbyjraAuUJrIdJR5p/7doq8g==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/lodash@^4.14.159":
|
||||
version "4.14.168"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
|
||||
|
@ -4988,7 +5042,7 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4, eventemitter3@^4.0.7:
|
|||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||
|
||||
events@^3.0.0:
|
||||
events@^3.0.0, events@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
|
||||
integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
|
||||
|
@ -10977,6 +11031,13 @@ semver@^7.3.2:
|
|||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.3.4:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||
|
|
Loading…
Reference in New Issue