Add support for admin-controlled pools
This commit is contained in:
parent
f7057035fc
commit
42fec272c9
|
@ -6,8 +6,9 @@
|
|||
"dependencies": {
|
||||
"@ant-design/icons": "^4.2.1",
|
||||
"@craco/craco": "^5.6.4",
|
||||
"@project-serum/associated-token": "0.1.0",
|
||||
"@project-serum/awesome-serum": "1.0.1",
|
||||
"@project-serum/pool": "^0.1.1",
|
||||
"@project-serum/pool": "^0.2.0",
|
||||
"@project-serum/serum": "^0.13.14",
|
||||
"@project-serum/sol-wallet-adapter": "^0.1.1",
|
||||
"@solana/web3.js": "0.86.1",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, Tooltip, Typography } from 'antd';
|
||||
import { Button, Form, Input, Switch, Tooltip, Typography } from 'antd';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useConnection } from '../../utils/connection';
|
||||
import FloatingElement from '../../components/layout/FloatingElement';
|
||||
|
@ -28,7 +28,7 @@ const AddRemoveTokenButtons = styled.div`
|
|||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
const DEFAULT_PROGRAM_ID = '8qZoqDMXTfLZz6BYrDfD5Cuy65JKkaNwktb54hj1yaoK';
|
||||
const DEFAULT_PROGRAM_ID = 'DL7L4cFHwmfNevZRg92rF5unbdUvFGoiuMrQ4aV7Nzsc';
|
||||
|
||||
export default function NewPoolPage() {
|
||||
const connection = useConnection();
|
||||
|
@ -40,6 +40,7 @@ export default function NewPoolPage() {
|
|||
{ valid: false },
|
||||
{ valid: false },
|
||||
]);
|
||||
const [adminControlled, setAdminControlled] = useState(false);
|
||||
const [tokenAccounts] = useTokenAccounts();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [newPoolAddress, setNewPoolAddress] = useState<PublicKey | null>(null);
|
||||
|
@ -83,6 +84,9 @@ export default function NewPoolPage() {
|
|||
}
|
||||
return found.pubkey;
|
||||
}),
|
||||
additionalAccounts: adminControlled
|
||||
? [{ pubkey: wallet.publicKey, isSigner: false, isWritable: false }]
|
||||
: [],
|
||||
});
|
||||
const signed = await Promise.all(
|
||||
transactions.map(({ transaction, signers }) =>
|
||||
|
@ -172,6 +176,19 @@ export default function NewPoolPage() {
|
|||
{initialAssets.map((asset, i) => (
|
||||
<AssetInput setInitialAssets={setInitialAssets} index={i} key={i} />
|
||||
))}
|
||||
<Form.Item
|
||||
label={
|
||||
<Tooltip title="Whether the assets in the pool can be controlled by the pool admin.">
|
||||
Admin Controlled
|
||||
</Tooltip>
|
||||
}
|
||||
name="adminControlled"
|
||||
>
|
||||
<Switch
|
||||
checked={adminControlled}
|
||||
onChange={(checked) => setAdminControlled(checked)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label=" " colon={false}>
|
||||
<Button
|
||||
type="primary"
|
||||
|
|
|
@ -0,0 +1,421 @@
|
|||
import React, { FormEvent, useState } from 'react';
|
||||
import { AdminControlledPoolInstructions, PoolInfo } from '@project-serum/pool';
|
||||
import { TokenInstructions } from '@project-serum/serum';
|
||||
import FloatingElement from '../../../components/layout/FloatingElement';
|
||||
import { useConnection } from '../../../utils/connection';
|
||||
import { useWallet } from '../../../utils/wallet';
|
||||
import {
|
||||
getSelectedTokenAccountForMint,
|
||||
useTokenAccounts,
|
||||
} from '../../../utils/markets';
|
||||
import { sendTransaction } from '../../../utils/send';
|
||||
import { notify } from '../../../utils/notifications';
|
||||
import { PublicKey, Transaction } from '@solana/web3.js';
|
||||
import { Button, Input, Tabs } from 'antd';
|
||||
import {
|
||||
createAssociatedTokenAccount,
|
||||
getAssociatedTokenAddress,
|
||||
} from '@project-serum/associated-token';
|
||||
import { parseTokenMintData } from '../../../utils/tokens';
|
||||
import BN from 'bn.js';
|
||||
import { refreshAllCaches } from '../../../utils/fetch-loop';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
export function PoolAdminPanel({ poolInfo }: { poolInfo: PoolInfo }) {
|
||||
return (
|
||||
<FloatingElement>
|
||||
<Tabs>
|
||||
<TabPane tab="Pause/Unpause" key="pause">
|
||||
<PauseUnpauseTab poolInfo={poolInfo} />
|
||||
</TabPane>
|
||||
<TabPane tab="Add Token" key="addAsset">
|
||||
<AddAssetTab poolInfo={poolInfo} />
|
||||
</TabPane>
|
||||
<TabPane tab="Remove Token" key="removeAsset">
|
||||
<RemoveAssetTab poolInfo={poolInfo} />
|
||||
</TabPane>
|
||||
<TabPane tab="Deposit" key="deposit">
|
||||
<DepositTab poolInfo={poolInfo} />
|
||||
</TabPane>
|
||||
<TabPane tab="Withdraw" key="withdraw">
|
||||
<WithdrawTab poolInfo={poolInfo} />
|
||||
</TabPane>
|
||||
<TabPane tab="Change Fee" key="updateFee">
|
||||
<UpdateFeeTab poolInfo={poolInfo} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</FloatingElement>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabParams {
|
||||
poolInfo: PoolInfo;
|
||||
}
|
||||
|
||||
function PauseUnpauseTab({ poolInfo }: TabParams) {
|
||||
const connection = useConnection();
|
||||
const { wallet, connected } = useWallet();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
async function sendPause() {
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const transaction = new Transaction();
|
||||
transaction.add(AdminControlledPoolInstructions.pause(poolInfo));
|
||||
await sendTransaction({ connection, wallet, transaction });
|
||||
} catch (e) {
|
||||
notify({
|
||||
message: 'Error pausing pool: ' + e.message,
|
||||
type: 'error',
|
||||
});
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendUnpause() {
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const transaction = new Transaction();
|
||||
transaction.add(AdminControlledPoolInstructions.unpause(poolInfo));
|
||||
await sendTransaction({ connection, wallet, transaction });
|
||||
} catch (e) {
|
||||
notify({
|
||||
message: 'Error unpausing pool: ' + e.message,
|
||||
type: 'error',
|
||||
});
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={sendPause} disabled={submitting}>
|
||||
Pause
|
||||
</Button>{' '}
|
||||
<Button onClick={sendUnpause} disabled={submitting}>
|
||||
Unpause
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AddAssetTab({ poolInfo }: TabParams) {
|
||||
const connection = useConnection();
|
||||
const [address, setAddress] = useState('');
|
||||
const { wallet, connected } = useWallet();
|
||||
const canSubmit = connected && address;
|
||||
const [onSubmit, submitting] = useOnSubmitHandler(
|
||||
'adding asset to pool',
|
||||
async () => {
|
||||
const mintAddress = new PublicKey(address);
|
||||
const vaultAddress = await getAssociatedTokenAddress(
|
||||
poolInfo.state.vaultSigner,
|
||||
mintAddress,
|
||||
);
|
||||
const transaction = new Transaction();
|
||||
if (!(await connection.getAccountInfo(vaultAddress))) {
|
||||
transaction.add(
|
||||
await createAssociatedTokenAccount(
|
||||
wallet.publicKey,
|
||||
poolInfo.state.vaultSigner,
|
||||
mintAddress,
|
||||
),
|
||||
);
|
||||
}
|
||||
transaction.add(
|
||||
AdminControlledPoolInstructions.addAsset(poolInfo, vaultAddress),
|
||||
);
|
||||
return transaction;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<Input
|
||||
addonBefore={<>Token Mint Address</>}
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value.trim())}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<SubmitButton canSubmit={canSubmit} submitting={submitting} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function RemoveAssetTab({ poolInfo }: TabParams) {
|
||||
const [address, setAddress] = useState('');
|
||||
const { connected } = useWallet();
|
||||
const canSubmit = connected && address;
|
||||
const [onSubmit, submitting] = useOnSubmitHandler(
|
||||
'removing asset from pool',
|
||||
async () => {
|
||||
const mintAddress = new PublicKey(address);
|
||||
const vaultAddress = poolInfo.state.assets.find((asset) =>
|
||||
asset.mint.equals(mintAddress),
|
||||
)?.vaultAddress;
|
||||
if (!vaultAddress) {
|
||||
throw new Error('Asset not in pool');
|
||||
}
|
||||
const transaction = new Transaction();
|
||||
transaction.add(
|
||||
AdminControlledPoolInstructions.removeAsset(poolInfo, vaultAddress),
|
||||
);
|
||||
return transaction;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<Input
|
||||
addonBefore={<>Token Mint Address</>}
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value.trim())}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<SubmitButton canSubmit={canSubmit} submitting={submitting} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function DepositTab({ poolInfo }: TabParams) {
|
||||
const [address, setAddress] = useState('');
|
||||
const [quantity, setQuantity] = useState('');
|
||||
|
||||
const connection = useConnection();
|
||||
const { wallet, connected } = useWallet();
|
||||
const [tokenAccounts] = useTokenAccounts();
|
||||
const canSubmit =
|
||||
connected && address && tokenAccounts && parseFloat(quantity);
|
||||
|
||||
const [onSubmit, submitting] = useOnSubmitHandler(
|
||||
'depositing to pool',
|
||||
async () => {
|
||||
const mintAddress = new PublicKey(address);
|
||||
const vaultAddress = poolInfo.state.assets.find((asset) =>
|
||||
asset.mint.equals(mintAddress),
|
||||
)?.vaultAddress;
|
||||
if (!vaultAddress) {
|
||||
throw new Error('Asset not in pool');
|
||||
}
|
||||
|
||||
const walletTokenAccount = getSelectedTokenAccountForMint(
|
||||
tokenAccounts,
|
||||
mintAddress,
|
||||
);
|
||||
if (!walletTokenAccount) {
|
||||
throw new Error('Asset not in wallet');
|
||||
}
|
||||
|
||||
const mintAccountInfo = await connection.getAccountInfo(mintAddress);
|
||||
if (!mintAccountInfo) {
|
||||
throw new Error('Mint not found');
|
||||
}
|
||||
const mintDecimals = parseTokenMintData(mintAccountInfo.data).decimals;
|
||||
const parsedQuantity = Math.round(
|
||||
parseFloat(quantity) * 10 ** mintDecimals,
|
||||
);
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.add(
|
||||
TokenInstructions.transfer({
|
||||
source: walletTokenAccount.pubkey,
|
||||
destination: vaultAddress,
|
||||
amount: parsedQuantity,
|
||||
owner: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
return transaction;
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<Input
|
||||
addonBefore={<>Token Mint Address</>}
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value.trim())}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<Input
|
||||
addonBefore={<>Quantity</>}
|
||||
value={quantity}
|
||||
onChange={(e) => setQuantity(e.target.value.trim())}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<SubmitButton canSubmit={canSubmit} submitting={submitting} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function WithdrawTab({ poolInfo }: TabParams) {
|
||||
const [address, setAddress] = useState('');
|
||||
const [quantity, setQuantity] = useState('');
|
||||
|
||||
const connection = useConnection();
|
||||
const { wallet, connected } = useWallet();
|
||||
const [tokenAccounts] = useTokenAccounts();
|
||||
const canSubmit =
|
||||
connected && address && tokenAccounts && parseFloat(quantity);
|
||||
|
||||
const [onSubmit, submitting] = useOnSubmitHandler(
|
||||
'withdrawing from pool',
|
||||
async () => {
|
||||
const mintAddress = new PublicKey(address);
|
||||
const vaultAddress = poolInfo.state.assets.find((asset) =>
|
||||
asset.mint.equals(mintAddress),
|
||||
)?.vaultAddress;
|
||||
if (!vaultAddress) {
|
||||
throw new Error('Asset not in pool');
|
||||
}
|
||||
|
||||
const walletTokenAccount = getSelectedTokenAccountForMint(
|
||||
tokenAccounts,
|
||||
mintAddress,
|
||||
);
|
||||
if (!walletTokenAccount) {
|
||||
throw new Error('Asset not in wallet');
|
||||
}
|
||||
|
||||
const mintAccountInfo = await connection.getAccountInfo(mintAddress);
|
||||
if (!mintAccountInfo) {
|
||||
throw new Error('Mint not found');
|
||||
}
|
||||
const mintDecimals = parseTokenMintData(mintAccountInfo.data).decimals;
|
||||
const parsedQuantity = Math.round(
|
||||
parseFloat(quantity) * 10 ** mintDecimals,
|
||||
);
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.add(
|
||||
AdminControlledPoolInstructions.approveDelegate(
|
||||
poolInfo,
|
||||
vaultAddress,
|
||||
wallet.publicKey,
|
||||
new BN(parsedQuantity),
|
||||
),
|
||||
TokenInstructions.transfer({
|
||||
source: vaultAddress,
|
||||
destination: walletTokenAccount.pubkey,
|
||||
amount: parsedQuantity,
|
||||
owner: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
return transaction;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<Input
|
||||
addonBefore={<>Token Mint Address</>}
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value.trim())}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<Input
|
||||
addonBefore={<>Quantity</>}
|
||||
value={quantity}
|
||||
onChange={(e) => setQuantity(e.target.value.trim())}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<SubmitButton canSubmit={canSubmit} submitting={submitting} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdateFeeTab({ poolInfo }: TabParams) {
|
||||
const [feeRate, setFeeRate] = useState('');
|
||||
|
||||
const { connected } = useWallet();
|
||||
const [tokenAccounts] = useTokenAccounts();
|
||||
const canSubmit = connected && tokenAccounts && parseFloat(feeRate);
|
||||
|
||||
const [onSubmit, submitting] = useOnSubmitHandler(
|
||||
'changing pool fee',
|
||||
async () => {
|
||||
const transaction = new Transaction();
|
||||
transaction.add(
|
||||
AdminControlledPoolInstructions.updateFee(
|
||||
poolInfo,
|
||||
Math.round(parseFloat(feeRate) * 1_000_000),
|
||||
),
|
||||
);
|
||||
return transaction;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<Input
|
||||
addonBefore={<>Fee Rate</>}
|
||||
value={feeRate}
|
||||
onChange={(e) => setFeeRate(e.target.value.trim())}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<SubmitButton canSubmit={canSubmit} submitting={submitting} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function useOnSubmitHandler(
|
||||
description: string,
|
||||
makeTransaction: () => Promise<Transaction | null | undefined>,
|
||||
refresh = false,
|
||||
): [(FormEvent) => void, boolean] {
|
||||
const connection = useConnection();
|
||||
const { wallet, connected } = useWallet();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
async function onSubmit(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (!connected) {
|
||||
throw new Error('Wallet not connected');
|
||||
}
|
||||
const transaction = await makeTransaction();
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
await sendTransaction({ connection, wallet, transaction });
|
||||
if (refresh) {
|
||||
refreshAllCaches();
|
||||
}
|
||||
} catch (e) {
|
||||
notify({
|
||||
message: `Error ${description}: ${e.message}`,
|
||||
type: 'error',
|
||||
});
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return [onSubmit, submitting];
|
||||
}
|
||||
|
||||
function SubmitButton({ canSubmit, submitting }) {
|
||||
const { connected } = useWallet();
|
||||
return (
|
||||
<Button
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
disabled={!canSubmit || submitting}
|
||||
>
|
||||
{!connected ? 'Wallet not connected' : 'Submit'}
|
||||
</Button>
|
||||
);
|
||||
}
|
|
@ -15,18 +15,18 @@ interface PoolInfoProps {
|
|||
mintInfo: MintInfo;
|
||||
}
|
||||
|
||||
const feeFormat = new Intl.NumberFormat(undefined, {
|
||||
style: 'percent',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 6,
|
||||
});
|
||||
|
||||
export default function PoolInfoPanel({ poolInfo, mintInfo }: PoolInfoProps) {
|
||||
const connection = useConnection();
|
||||
|
||||
const [totalBasket] = useAsyncData(
|
||||
() => getPoolBasket(connection, poolInfo, { redeem: mintInfo.supply }),
|
||||
tuple(
|
||||
getPoolBasket,
|
||||
connection,
|
||||
poolInfo.address.toBase58(),
|
||||
'total',
|
||||
mintInfo.supply.toString(),
|
||||
),
|
||||
tuple(getPoolBasket, connection, poolInfo, 'total', mintInfo),
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -39,6 +39,14 @@ export default function PoolInfoPanel({ poolInfo, mintInfo }: PoolInfoProps) {
|
|||
Pool token mint address:{' '}
|
||||
<Text copyable>{poolInfo.state.poolTokenMint.toBase58()}</Text>
|
||||
</Paragraph>
|
||||
{poolInfo.state.adminKey ? (
|
||||
<Paragraph>
|
||||
Pool admin: <Text copyable>{poolInfo.state.adminKey.toBase58()}</Text>
|
||||
</Paragraph>
|
||||
) : null}
|
||||
<Paragraph>
|
||||
Fee rate: {feeFormat.format(poolInfo.state.feeRate / 1_000_000)}
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
Total supply: {mintInfo.supply.toNumber() / 10 ** mintInfo.decimals}
|
||||
</Paragraph>
|
||||
|
|
|
@ -4,12 +4,18 @@ import { Col, PageHeader, Row, Spin, Typography } from 'antd';
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { useAccountInfo } from '../../../utils/connection';
|
||||
import FloatingElement from '../../../components/layout/FloatingElement';
|
||||
import { decodePoolState, PoolInfo } from '@project-serum/pool';
|
||||
import {
|
||||
decodePoolState,
|
||||
isAdminControlledPool,
|
||||
PoolInfo,
|
||||
} from '@project-serum/pool';
|
||||
import PoolInfoPanel from './PoolInfoPanel';
|
||||
import { parseTokenMintData } from '../../../utils/tokens';
|
||||
import PoolCreateRedeemPanel from './PoolCreateRedeemPanel';
|
||||
import PoolBalancesPanel from './PoolBalancesPanel';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { PoolAdminPanel } from './PoolAdminPanel';
|
||||
import { useWallet } from '../../../utils/wallet';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
@ -41,6 +47,7 @@ export default function PoolPage() {
|
|||
() => (mintAccountInfo ? parseTokenMintData(mintAccountInfo.data) : null),
|
||||
[mintAccountInfo],
|
||||
);
|
||||
const { wallet } = useWallet();
|
||||
|
||||
if (poolInfo && mintInfo) {
|
||||
return (
|
||||
|
@ -60,6 +67,13 @@ export default function PoolPage() {
|
|||
<Col xs={24}>
|
||||
<PoolBalancesPanel poolInfo={poolInfo} />
|
||||
</Col>
|
||||
{wallet.connected &&
|
||||
poolInfo.state.adminKey?.equals(wallet.publicKey) &&
|
||||
isAdminControlledPool(poolInfo) ? (
|
||||
<Col xs={24}>
|
||||
<PoolAdminPanel poolInfo={poolInfo} />
|
||||
</Col>
|
||||
) : null}
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useLocalStorageState } from './utils';
|
||||
import { Account, AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import React, { useContext, useEffect, useMemo } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import { setCache, useAsyncData } from './fetch-loop';
|
||||
import tuple from 'immutable-tuple';
|
||||
import { ConnectionContextValues, EndpointInfo } from './types';
|
||||
|
@ -137,14 +137,15 @@ export function useAccountInfo(
|
|||
let currentItem = accountListenerCount.get(cacheKey);
|
||||
++currentItem.count;
|
||||
} else {
|
||||
let previousData: Buffer | null = null;
|
||||
const subscriptionId = connection.onAccountChange(publicKey, (e) => {
|
||||
if (e.data) {
|
||||
if (!previousData || !previousData.equals(e.data)) {
|
||||
setCache(cacheKey, e);
|
||||
} else {
|
||||
}
|
||||
previousData = e.data;
|
||||
let previousInfo: AccountInfo<Buffer> | null = null;
|
||||
const subscriptionId = connection.onAccountChange(publicKey, (info) => {
|
||||
if (
|
||||
!previousInfo ||
|
||||
!previousInfo.data.equals(info.data) ||
|
||||
previousInfo.lamports !== info.lamports
|
||||
) {
|
||||
previousInfo = info;
|
||||
setCache(cacheKey, info);
|
||||
}
|
||||
});
|
||||
accountListenerCount.set(cacheKey, { count: 1, subscriptionId });
|
||||
|
@ -161,7 +162,16 @@ export function useAccountInfo(
|
|||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [cacheKey]);
|
||||
return [accountInfo, loaded];
|
||||
const previousInfoRef = useRef<AccountInfo<Buffer> | null>(null);
|
||||
if (
|
||||
accountInfo &&
|
||||
(!previousInfoRef.current ||
|
||||
!previousInfoRef.current.data.equals(accountInfo.data) ||
|
||||
previousInfoRef.current.lamports !== accountInfo.lamports)
|
||||
) {
|
||||
previousInfoRef.current = accountInfo;
|
||||
}
|
||||
return [previousInfoRef.current, loaded];
|
||||
}
|
||||
|
||||
export function useAccountData(publicKey) {
|
||||
|
|
|
@ -177,6 +177,7 @@ class FetchLoops {
|
|||
loop.removeListener(listener);
|
||||
if (loop.stopped) {
|
||||
this.loops.delete(listener.cacheKey);
|
||||
globalCache.delete(listener.cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
23
yarn.lock
23
yarn.lock
|
@ -1530,16 +1530,31 @@
|
|||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
||||
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
|
||||
|
||||
"@project-serum/associated-token@0.1.0", "@project-serum/associated-token@^0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@project-serum/associated-token/-/associated-token-0.1.0.tgz#ee817997c24e1089c5b99e370216ecc2d48e6d8b"
|
||||
integrity sha512-sHDKQoT36TZl5aJct/pEbRtLUX6UXwnkGaA/ibHXkvDkQrtHjtkXEkkwmdhNZQsoXkSaUJdK62jh4c/BUMoZbg==
|
||||
|
||||
"@project-serum/awesome-serum@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@project-serum/awesome-serum/-/awesome-serum-1.0.1.tgz#185a348cb57eb00592aee4f8c730b65cc9ae7a22"
|
||||
integrity sha512-WbykMEX2Ja1oeY3n7cVK79XMBxFgWsWxDMxfJ7GL+/88R568vwPvhP57HrBgULZmwu/Zt5CmMWjf6oeD1E621g==
|
||||
|
||||
"@project-serum/pool@^0.1.1":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@project-serum/pool/-/pool-0.1.3.tgz#9f99e6046f4565e38896934b24d447eae1527a62"
|
||||
integrity sha512-wAO65EccmCCMmgolvVXIc3JbSapWxLWALuVuhbHiiYQjTCMolAYvGSzeJwur7Z7MjTuRJMc8z9/6rYNOQSAwaw==
|
||||
"@project-serum/borsh@^0.0.1-alpha.0":
|
||||
version "0.0.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.0.1-alpha.0.tgz#61643cc686c31c6a4f483b3cc069dee5350b463a"
|
||||
integrity sha512-11eEM7LAwU+NW2Ha7Cc9nRNiTUuvT7zHlcDvAMEVxZbpZXQd09r6xEYgTpK87p9AcoTr56F36Ww5OvPO/LoJHw==
|
||||
dependencies:
|
||||
bn.js "^5.1.2"
|
||||
buffer-layout "^1.2.0"
|
||||
|
||||
"@project-serum/pool@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@project-serum/pool/-/pool-0.2.0.tgz#ce79b63a58e2dc7f5ae1387948ee1d91cca64b01"
|
||||
integrity sha512-Gsd1G+S/7qx/2c49zIG1WF7xjElghRsVXUQewi10Rs7zY0ryS0jmIKHf6pPxn4Bpc0GKZmENrvwe2tChgY3+CA==
|
||||
dependencies:
|
||||
"@project-serum/associated-token" "^0.1.0"
|
||||
"@project-serum/borsh" "^0.0.1-alpha.0"
|
||||
"@project-serum/serum" "^0.13.8"
|
||||
bn.js "^5.1.2"
|
||||
buffer-layout "^1.2.0"
|
||||
|
|
Loading…
Reference in New Issue