Merge pull request #313 from exromany/feat/api-server-2

API-2: preparing to api
This commit is contained in:
exromany 2021-09-15 12:19:45 +03:00 committed by GitHub
commit f3366f3ab0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 1335 additions and 1300 deletions

View File

@ -13,7 +13,8 @@ import {
} from '../utils/ids'; } from '../utils/ids';
import { programIds } from '../utils/programIds'; import { programIds } from '../utils/programIds';
import { TokenAccount } from '../models/account'; import { TokenAccount } from '../models/account';
import { cache, TokenAccountParser } from '../contexts/accounts'; import { cache } from '../contexts/accounts/cache';
import { TokenAccountParser } from '../contexts/accounts/parsesrs';
export function ensureSplAccount( export function ensureSplAccount(
instructions: TransactionInstruction[], instructions: TransactionInstruction[],

View File

@ -8,7 +8,7 @@ import {
import { programIds } from '../utils/programIds'; import { programIds } from '../utils/programIds';
import { deserializeUnchecked, serialize } from 'borsh'; import { deserializeUnchecked, serialize } from 'borsh';
import BN from 'bn.js'; import BN from 'bn.js';
import { AccountParser } from '../contexts'; import { AccountParser } from '../contexts/accounts/types';
import moment from 'moment'; import moment from 'moment';
import { findProgramAddress, StringPublicKey, toPublicKey } from '../utils'; import { findProgramAddress, StringPublicKey, toPublicKey } from '../utils';
export const AUCTION_PREFIX = 'auction'; export const AUCTION_PREFIX = 'auction';
@ -672,7 +672,7 @@ export async function createAuction(
); );
} }
export async function startAuction( export async function startAuctionWithResource(
resource: StringPublicKey, resource: StringPublicKey,
creator: StringPublicKey, creator: StringPublicKey,
instructions: TransactionInstruction[], instructions: TransactionInstruction[],

View File

@ -1,664 +0,0 @@
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { useConnection } from '../contexts/connection';
import { useWallet } from '@solana/wallet-adapter-react';
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import { AccountLayout, MintInfo, MintLayout, u64 } from '@solana/spl-token';
import { TokenAccount } from '../models';
import { chunks } from '../utils/utils';
import { EventEmitter } from '../utils/eventEmitter';
import { StringPublicKey, WRAPPED_SOL_MINT } from '../utils/ids';
import { programIds } from '../utils/programIds';
const AccountsContext = React.createContext<any>(null);
const pendingCalls = new Map<string, Promise<ParsedAccountBase>>();
const genericCache = new Map<string, ParsedAccountBase>();
const pendingMintCalls = new Map<string, Promise<MintInfo>>();
const mintCache = new Map<string, MintInfo>();
export interface ParsedAccountBase {
pubkey: StringPublicKey;
account: AccountInfo<Buffer>;
info: any; // TODO: change to unknown
}
export type AccountParser = (
pubkey: StringPublicKey,
data: AccountInfo<Buffer>,
) => ParsedAccountBase | undefined;
export interface ParsedAccount<T> extends ParsedAccountBase {
info: T;
}
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
const info = await connection.getAccountInfo(pubKey);
if (info === null) {
throw new Error('Failed to find mint account');
}
const data = Buffer.from(info.data);
return deserializeMint(data);
};
export const MintParser = (pubKey: string, info: AccountInfo<Buffer>) => {
const buffer = Buffer.from(info.data);
const data = deserializeMint(buffer);
const details = {
pubkey: pubKey,
account: {
...info,
},
info: data,
} as ParsedAccountBase;
return details;
};
export const TokenAccountParser = (
pubKey: string,
info: AccountInfo<Buffer>,
) => {
// Sometimes a wrapped sol account gets closed, goes to 0 length,
// triggers an update over wss which triggers this guy to get called
// since your UI already logged that pubkey as a token account. Check for length.
if (info.data.length > 0) {
const buffer = Buffer.from(info.data);
const data = deserializeAccount(buffer);
const details = {
pubkey: pubKey,
account: {
...info,
},
info: data,
} as TokenAccount;
return details;
}
};
export const GenericAccountParser = (
pubKey: string,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data);
const details = {
pubkey: pubKey,
account: {
...info,
},
info: buffer,
} as ParsedAccountBase;
return details;
};
export const keyToAccountParser = new Map<string, AccountParser>();
export const cache = {
emitter: new EventEmitter(),
query: async (
connection: Connection,
pubKey: string | PublicKey,
parser?: AccountParser,
) => {
let id: PublicKey;
if (typeof pubKey === 'string') {
id = new PublicKey(pubKey);
} else {
id = pubKey;
}
const address = id.toBase58();
let account = genericCache.get(address);
if (account) {
return account;
}
let query = pendingCalls.get(address);
if (query) {
return query;
}
// TODO: refactor to use multiple accounts query with flush like behavior
query = connection.getAccountInfo(id).then(data => {
if (!data) {
throw new Error('Account not found');
}
return cache.add(id, data, parser);
}) as Promise<TokenAccount>;
pendingCalls.set(address, query as any);
return query;
},
add: (
id: PublicKey | string,
obj: AccountInfo<Buffer>,
parser?: AccountParser,
isActive?: boolean | undefined | ((parsed: any) => boolean),
) => {
const address = typeof id === 'string' ? id : id?.toBase58();
const deserialize = parser ? parser : keyToAccountParser.get(address);
if (!deserialize) {
throw new Error(
'Deserializer needs to be registered or passed as a parameter',
);
}
cache.registerParser(id, deserialize);
pendingCalls.delete(address);
const account = deserialize(address, obj);
if (!account) {
return;
}
if (isActive === undefined) isActive = true;
else if (isActive instanceof Function) isActive = isActive(account);
const isNew = !genericCache.has(address);
genericCache.set(address, account);
cache.emitter.raiseCacheUpdated(address, isNew, deserialize, isActive);
return account;
},
get: (pubKey: string | PublicKey) => {
let key: string;
if (typeof pubKey !== 'string') {
key = pubKey.toBase58();
} else {
key = pubKey;
}
return genericCache.get(key);
},
delete: (pubKey: string | PublicKey) => {
let key: string;
if (typeof pubKey !== 'string') {
key = pubKey.toBase58();
} else {
key = pubKey;
}
if (genericCache.get(key)) {
genericCache.delete(key);
cache.emitter.raiseCacheDeleted(key);
return true;
}
return false;
},
byParser: (parser: AccountParser) => {
const result: string[] = [];
for (const id of keyToAccountParser.keys()) {
if (keyToAccountParser.get(id) === parser) {
result.push(id);
}
}
return result;
},
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
if (pubkey) {
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
keyToAccountParser.set(address, parser);
}
return pubkey;
},
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
let id: PublicKey;
if (typeof pubKey === 'string') {
id = new PublicKey(pubKey);
} else {
id = pubKey;
}
const address = id.toBase58();
let mint = mintCache.get(address);
if (mint) {
return mint;
}
let query = pendingMintCalls.get(address);
if (query) {
return query;
}
query = getMintInfo(connection, id).then(data => {
pendingMintCalls.delete(address);
mintCache.set(address, data);
return data;
}) as Promise<MintInfo>;
pendingMintCalls.set(address, query as any);
return query;
},
getMint: (pubKey: string | PublicKey) => {
let key: string;
if (typeof pubKey !== 'string') {
key = pubKey.toBase58();
} else {
key = pubKey;
}
return mintCache.get(key);
},
addMint: (pubKey: PublicKey, obj: AccountInfo<Buffer>) => {
const mint = deserializeMint(obj.data);
const id = pubKey.toBase58();
mintCache.set(id, mint);
return mint;
},
};
export const useAccountsContext = () => {
const context = useContext(AccountsContext);
return context;
};
function wrapNativeAccount(
pubkey: string,
account?: AccountInfo<Buffer>,
): TokenAccount | undefined {
if (!account) {
return undefined;
}
const key = new PublicKey(pubkey);
return {
pubkey: pubkey,
account,
info: {
address: key,
mint: WRAPPED_SOL_MINT,
owner: key,
amount: new u64(account.lamports),
delegate: null,
delegatedAmount: new u64(0),
isInitialized: true,
isFrozen: false,
isNative: true,
rentExemptReserve: null,
closeAuthority: null,
},
};
}
export const getCachedAccount = (
predicate: (account: TokenAccount) => boolean,
) => {
for (const account of genericCache.values()) {
if (predicate(account)) {
return account as TokenAccount;
}
}
};
const UseNativeAccount = () => {
const connection = useConnection();
const { publicKey } = useWallet();
const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>();
const updateCache = useCallback(
account => {
if (publicKey) {
const wrapped = wrapNativeAccount(publicKey.toBase58(), account);
if (wrapped !== undefined) {
const id = publicKey.toBase58();
cache.registerParser(id, TokenAccountParser);
genericCache.set(id, wrapped as TokenAccount);
cache.emitter.raiseCacheUpdated(id, false, TokenAccountParser, true);
}
}
},
[publicKey],
);
useEffect(() => {
let subId = 0;
const updateAccount = (account: AccountInfo<Buffer> | null) => {
if (account) {
updateCache(account);
setNativeAccount(account);
}
};
(async () => {
if (!connection || !publicKey) {
return;
}
const account = await connection.getAccountInfo(publicKey);
updateAccount(account);
subId = connection.onAccountChange(publicKey, updateAccount);
})();
return () => {
if (subId) {
connection.removeAccountChangeListener(subId);
}
};
}, [setNativeAccount, publicKey, connection, updateCache]);
return { nativeAccount };
};
const PRECACHED_OWNERS = new Set<string>();
const precacheUserTokenAccounts = async (
connection: Connection,
owner?: PublicKey,
) => {
if (!owner) {
return;
}
// used for filtering account updates over websocket
PRECACHED_OWNERS.add(owner.toBase58());
// user accounts are updated via ws subscription
const accounts = await connection.getTokenAccountsByOwner(owner, {
programId: programIds().token,
});
accounts.value.forEach(info => {
cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser);
});
};
export function AccountsProvider({ children = null as any }) {
const connection = useConnection();
const { publicKey } = useWallet();
const [tokenAccounts, setTokenAccounts] = useState<TokenAccount[]>([]);
const [userAccounts, setUserAccounts] = useState<TokenAccount[]>([]);
const { nativeAccount } = UseNativeAccount();
const walletKey = publicKey?.toBase58();
const selectUserAccounts = useCallback(() => {
return cache
.byParser(TokenAccountParser)
.map(id => cache.get(id))
.filter(a => a && a.info.owner.toBase58() === walletKey)
.map(a => a as TokenAccount);
}, [walletKey]);
useEffect(() => {
const accounts = selectUserAccounts().filter(
a => a !== undefined,
) as TokenAccount[];
setUserAccounts(accounts);
}, [nativeAccount, tokenAccounts, selectUserAccounts]);
useEffect(() => {
const subs: number[] = [];
cache.emitter.onCache(args => {
if (args.isNew && args.isActive) {
let id = args.id;
let deserialize = args.parser;
connection.onAccountChange(new PublicKey(id), info => {
cache.add(id, info, deserialize);
});
}
});
return () => {
subs.forEach(id => connection.removeAccountChangeListener(id));
};
}, [connection]);
useEffect(() => {
if (!connection || !publicKey) {
setTokenAccounts([]);
} else {
precacheUserTokenAccounts(connection, publicKey).then(() => {
setTokenAccounts(selectUserAccounts());
});
// This can return different types of accounts: token-account, mint, multisig
// TODO: web3.js expose ability to filter.
// this should use only filter syntax to only get accounts that are owned by user
const tokenSubID = connection.onProgramAccountChange(
programIds().token,
info => {
// TODO: fix type in web3.js
const id = info.accountId as unknown as string;
// TODO: do we need a better way to identify layout (maybe a enum identifing type?)
if (info.accountInfo.data.length === AccountLayout.span) {
const data = deserializeAccount(info.accountInfo.data);
if (PRECACHED_OWNERS.has(data.owner.toBase58())) {
cache.add(id, info.accountInfo, TokenAccountParser);
setTokenAccounts(selectUserAccounts());
}
}
},
'singleGossip',
);
return () => {
connection.removeProgramAccountChangeListener(tokenSubID);
};
}
}, [connection, publicKey, selectUserAccounts]);
return (
<AccountsContext.Provider
value={{
userAccounts,
nativeAccount,
}}
>
{children}
</AccountsContext.Provider>
);
}
export function useNativeAccount() {
const context = useContext(AccountsContext);
return {
account: context.nativeAccount as AccountInfo<Buffer>,
};
}
export const getMultipleAccounts = async (
connection: any,
keys: string[],
commitment: string,
) => {
const result = await Promise.all(
chunks(keys, 99).map(chunk =>
getMultipleAccountsCore(connection, chunk, commitment),
),
);
const array = result
.map(
a =>
a.array.map(acc => {
if (!acc) {
return undefined;
}
const { data, ...rest } = acc;
const obj = {
...rest,
data: Buffer.from(data[0], 'base64'),
} as AccountInfo<Buffer>;
return obj;
}) as AccountInfo<Buffer>[],
)
.flat();
return { keys, array };
};
const getMultipleAccountsCore = async (
connection: any,
keys: string[],
commitment: string,
) => {
const args = connection._buildArgs([keys], commitment, 'base64');
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
if (unsafeRes.error) {
throw new Error(
'failed to get info about account ' + unsafeRes.error.message,
);
}
if (unsafeRes.result.value) {
const array = unsafeRes.result.value as AccountInfo<string[]>[];
return { keys, array };
}
// TODO: fix
throw new Error();
};
export function useMint(key?: string | PublicKey) {
const connection = useConnection();
const [mint, setMint] = useState<MintInfo>();
const id = typeof key === 'string' ? key : key?.toBase58();
useEffect(() => {
if (!id) {
return;
}
cache
.query(connection, id, MintParser)
.then(acc => setMint(acc.info as any))
.catch(err => console.log(err));
const dispose = cache.emitter.onCache(e => {
const event = e;
if (event.id === id) {
cache
.query(connection, id, MintParser)
.then(mint => setMint(mint.info as any));
}
});
return () => {
dispose();
};
}, [connection, id]);
return mint;
}
export function useAccount(pubKey?: PublicKey) {
const connection = useConnection();
const [account, setAccount] = useState<TokenAccount>();
const key = pubKey?.toBase58();
useEffect(() => {
const query = async () => {
try {
if (!key) {
return;
}
const acc = await cache
.query(connection, key, TokenAccountParser)
.catch(err => console.log(err));
if (acc) {
setAccount(acc);
}
} catch (err) {
console.error(err);
}
};
query();
const dispose = cache.emitter.onCache(e => {
const event = e;
if (event.id === key) {
query();
}
});
return () => {
dispose();
};
}, [connection, key]);
return account;
}
// TODO: expose in spl package
export const deserializeAccount = (data: Buffer) => {
const accountInfo = AccountLayout.decode(data);
accountInfo.mint = new PublicKey(accountInfo.mint);
accountInfo.owner = new PublicKey(accountInfo.owner);
accountInfo.amount = u64.fromBuffer(accountInfo.amount);
if (accountInfo.delegateOption === 0) {
accountInfo.delegate = null;
accountInfo.delegatedAmount = new u64(0);
} else {
accountInfo.delegate = new PublicKey(accountInfo.delegate);
accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
}
accountInfo.isInitialized = accountInfo.state !== 0;
accountInfo.isFrozen = accountInfo.state === 2;
if (accountInfo.isNativeOption === 1) {
accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
accountInfo.isNative = true;
} else {
accountInfo.rentExemptReserve = null;
accountInfo.isNative = false;
}
if (accountInfo.closeAuthorityOption === 0) {
accountInfo.closeAuthority = null;
} else {
accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
}
return accountInfo;
};
// TODO: expose in spl package
export const deserializeMint = (data: Buffer) => {
if (data.length !== MintLayout.span) {
throw new Error('Not a valid Mint');
}
const mintInfo = MintLayout.decode(data);
if (mintInfo.mintAuthorityOption === 0) {
mintInfo.mintAuthority = null;
} else {
mintInfo.mintAuthority = new PublicKey(mintInfo.mintAuthority);
}
mintInfo.supply = u64.fromBuffer(mintInfo.supply);
mintInfo.isInitialized = mintInfo.isInitialized !== 0;
if (mintInfo.freezeAuthorityOption === 0) {
mintInfo.freezeAuthority = null;
} else {
mintInfo.freezeAuthority = new PublicKey(mintInfo.freezeAuthority);
}
return mintInfo as MintInfo;
};

View File

@ -0,0 +1,286 @@
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import { AccountLayout, MintInfo, u64 } from '@solana/spl-token';
import { useConnection } from '../../contexts/connection';
import { TokenAccount } from '../../models';
import { StringPublicKey, WRAPPED_SOL_MINT } from '../../utils/ids';
import { programIds } from '../../utils/programIds';
import { genericCache, cache } from './cache';
import { deserializeAccount } from './deserialize';
import { TokenAccountParser, MintParser } from './parsesrs';
const AccountsContext = React.createContext<any>(null);
export const useAccountsContext = () => {
const context = useContext(AccountsContext);
return context;
};
function wrapNativeAccount(
pubkey: StringPublicKey,
account?: AccountInfo<Buffer>,
): TokenAccount | undefined {
if (!account) {
return undefined;
}
const key = new PublicKey(pubkey);
return {
pubkey: pubkey,
account,
info: {
address: key,
mint: WRAPPED_SOL_MINT,
owner: key,
amount: new u64(account.lamports),
delegate: null,
delegatedAmount: new u64(0),
isInitialized: true,
isFrozen: false,
isNative: true,
rentExemptReserve: null,
closeAuthority: null,
},
};
}
const UseNativeAccount = () => {
const connection = useConnection();
const { publicKey } = useWallet();
const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>();
const updateCache = useCallback(
account => {
if (publicKey) {
const wrapped = wrapNativeAccount(publicKey.toBase58(), account);
if (wrapped !== undefined) {
const id = publicKey.toBase58();
cache.registerParser(id, TokenAccountParser);
genericCache.set(id, wrapped as TokenAccount);
cache.emitter.raiseCacheUpdated(id, false, TokenAccountParser, true);
}
}
},
[publicKey],
);
useEffect(() => {
let subId = 0;
const updateAccount = (account: AccountInfo<Buffer> | null) => {
if (account) {
updateCache(account);
setNativeAccount(account);
}
};
(async () => {
if (!connection || !publicKey) {
return;
}
const account = await connection.getAccountInfo(publicKey);
updateAccount(account);
subId = connection.onAccountChange(publicKey, updateAccount);
})();
return () => {
if (subId) {
connection.removeAccountChangeListener(subId);
}
};
}, [setNativeAccount, publicKey, connection, updateCache]);
return { nativeAccount };
};
const PRECACHED_OWNERS = new Set<string>();
const precacheUserTokenAccounts = async (
connection: Connection,
owner?: PublicKey,
) => {
if (!owner) {
return;
}
// used for filtering account updates over websocket
PRECACHED_OWNERS.add(owner.toBase58());
// user accounts are updated via ws subscription
const accounts = await connection.getTokenAccountsByOwner(owner, {
programId: programIds().token,
});
accounts.value.forEach(info => {
cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser);
});
};
export function AccountsProvider({ children = null as any }) {
const connection = useConnection();
const { publicKey } = useWallet();
const [tokenAccounts, setTokenAccounts] = useState<TokenAccount[]>([]);
const [userAccounts, setUserAccounts] = useState<TokenAccount[]>([]);
const { nativeAccount } = UseNativeAccount();
const walletKey = publicKey?.toBase58();
const selectUserAccounts = useCallback(() => {
return cache
.byParser(TokenAccountParser)
.map(id => cache.get(id))
.filter(a => a && a.info.owner.toBase58() === walletKey)
.map(a => a as TokenAccount);
}, [walletKey]);
useEffect(() => {
const accounts = selectUserAccounts().filter(
a => a !== undefined,
) as TokenAccount[];
setUserAccounts(accounts);
}, [nativeAccount, tokenAccounts, selectUserAccounts]);
useEffect(() => {
const subs: number[] = [];
cache.emitter.onCache(args => {
if (args.isNew && args.isActive) {
let id = args.id;
let deserialize = args.parser;
connection.onAccountChange(new PublicKey(id), info => {
cache.add(id, info, deserialize);
});
}
});
return () => {
subs.forEach(id => connection.removeAccountChangeListener(id));
};
}, [connection]);
useEffect(() => {
if (!connection || !publicKey) {
setTokenAccounts([]);
} else {
precacheUserTokenAccounts(connection, publicKey).then(() => {
setTokenAccounts(selectUserAccounts());
});
// This can return different types of accounts: token-account, mint, multisig
// TODO: web3.js expose ability to filter.
// this should use only filter syntax to only get accounts that are owned by user
const tokenSubID = connection.onProgramAccountChange(
programIds().token,
info => {
// TODO: fix type in web3.js
const id = info.accountId as unknown as string;
// TODO: do we need a better way to identify layout (maybe a enum identifing type?)
if (info.accountInfo.data.length === AccountLayout.span) {
const data = deserializeAccount(info.accountInfo.data);
if (PRECACHED_OWNERS.has(data.owner.toBase58())) {
cache.add(id, info.accountInfo, TokenAccountParser);
setTokenAccounts(selectUserAccounts());
}
}
},
'singleGossip',
);
return () => {
connection.removeProgramAccountChangeListener(tokenSubID);
};
}
}, [connection, publicKey, selectUserAccounts]);
return (
<AccountsContext.Provider
value={{
userAccounts,
nativeAccount,
}}
>
{children}
</AccountsContext.Provider>
);
}
export function useNativeAccount() {
const context = useContext(AccountsContext);
return {
account: context.nativeAccount as AccountInfo<Buffer>,
};
}
export function useMint(key?: string | PublicKey) {
const connection = useConnection();
const [mint, setMint] = useState<MintInfo>();
const id = typeof key === 'string' ? key : key?.toBase58();
useEffect(() => {
if (!id) {
return;
}
cache
.query(connection, id, MintParser)
.then(acc => setMint(acc.info as any))
.catch(err => console.log(err));
const dispose = cache.emitter.onCache(e => {
const event = e;
if (event.id === id) {
cache
.query(connection, id, MintParser)
.then(mint => setMint(mint.info as any));
}
});
return () => {
dispose();
};
}, [connection, id]);
return mint;
}
export function useAccount(pubKey?: PublicKey) {
const connection = useConnection();
const [account, setAccount] = useState<TokenAccount>();
const key = pubKey?.toBase58();
useEffect(() => {
const query = async () => {
try {
if (!key) {
return;
}
const acc = await cache
.query(connection, key, TokenAccountParser)
.catch(err => console.log(err));
if (acc) {
setAccount(acc);
}
} catch (err) {
console.error(err);
}
};
query();
const dispose = cache.emitter.onCache(e => {
const event = e;
if (event.id === key) {
query();
}
});
return () => {
dispose();
};
}, [connection, key]);
return account;
}

View File

@ -0,0 +1,193 @@
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import { MintInfo } from '@solana/spl-token';
import { TokenAccount } from '../../models';
import { EventEmitter } from '../../utils/eventEmitter';
import { ParsedAccountBase, AccountParser } from './types';
import { deserializeMint } from './deserialize';
export const genericCache = new Map<string, ParsedAccountBase>();
const mintCache = new Map<string, MintInfo>();
const pendingCalls = new Map<string, Promise<ParsedAccountBase>>();
const pendingMintCalls = new Map<string, Promise<MintInfo>>();
const keyToAccountParser = new Map<string, AccountParser>();
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
const info = await connection.getAccountInfo(pubKey);
if (info === null) {
throw new Error('Failed to find mint account');
}
const data = Buffer.from(info.data);
return deserializeMint(data);
};
export const cache = {
emitter: new EventEmitter(),
query: async (
connection: Connection,
pubKey: string | PublicKey,
parser?: AccountParser,
) => {
let id: PublicKey;
if (typeof pubKey === 'string') {
id = new PublicKey(pubKey);
} else {
id = pubKey;
}
const address = id.toBase58();
const account = genericCache.get(address);
if (account) {
return account;
}
let query = pendingCalls.get(address);
if (query) {
return query;
}
// TODO: refactor to use multiple accounts query with flush like behavior
query = connection.getAccountInfo(id).then(data => {
if (!data) {
throw new Error('Account not found');
}
return cache.add(id, data, parser);
}) as Promise<TokenAccount>;
pendingCalls.set(address, query as any);
return query;
},
add: (
id: PublicKey | string,
obj: AccountInfo<Buffer>,
parser?: AccountParser,
isActive?: boolean | undefined | ((parsed: any) => boolean),
) => {
const address = typeof id === 'string' ? id : id?.toBase58();
const deserialize = parser ? parser : keyToAccountParser.get(address);
if (!deserialize) {
throw new Error(
'Deserializer needs to be registered or passed as a parameter',
);
}
cache.registerParser(id, deserialize);
pendingCalls.delete(address);
const account = deserialize(address, obj);
if (!account) {
return;
}
if (isActive === undefined) isActive = true;
else if (isActive instanceof Function) isActive = isActive(account);
const isNew = !genericCache.has(address);
genericCache.set(address, account);
cache.emitter.raiseCacheUpdated(address, isNew, deserialize, isActive);
return account;
},
get: (pubKey: string | PublicKey) => {
let key: string;
if (typeof pubKey !== 'string') {
key = pubKey.toBase58();
} else {
key = pubKey;
}
return genericCache.get(key);
},
delete: (pubKey: string | PublicKey) => {
let key: string;
if (typeof pubKey !== 'string') {
key = pubKey.toBase58();
} else {
key = pubKey;
}
if (genericCache.get(key)) {
genericCache.delete(key);
cache.emitter.raiseCacheDeleted(key);
return true;
}
return false;
},
byParser: (parser: AccountParser) => {
const result: string[] = [];
for (const id of keyToAccountParser.keys()) {
if (keyToAccountParser.get(id) === parser) {
result.push(id);
}
}
return result;
},
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
if (pubkey) {
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
keyToAccountParser.set(address, parser);
}
return pubkey;
},
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
let id: PublicKey;
if (typeof pubKey === 'string') {
id = new PublicKey(pubKey);
} else {
id = pubKey;
}
const address = id.toBase58();
const mint = mintCache.get(address);
if (mint) {
return mint;
}
let query = pendingMintCalls.get(address);
if (query) {
return query;
}
query = getMintInfo(connection, id).then(data => {
pendingMintCalls.delete(address);
mintCache.set(address, data);
return data;
}) as Promise<MintInfo>;
pendingMintCalls.set(address, query as any);
return query;
},
getMint: (pubKey: string | PublicKey) => {
let key: string;
if (typeof pubKey !== 'string') {
key = pubKey.toBase58();
} else {
key = pubKey;
}
return mintCache.get(key);
},
addMint: (pubKey: PublicKey, obj: AccountInfo<Buffer>) => {
const mint = deserializeMint(obj.data);
const id = pubKey.toBase58();
mintCache.set(id, mint);
return mint;
},
};
export const getCachedAccount = (
predicate: (account: TokenAccount) => boolean,
) => {
for (const account of genericCache.values()) {
if (predicate(account)) {
return account as TokenAccount;
}
}
};

View File

@ -0,0 +1,63 @@
import { PublicKey } from '@solana/web3.js';
import { AccountLayout, MintInfo, MintLayout, u64 } from '@solana/spl-token';
// TODO: expose in spl package
export const deserializeAccount = (data: Buffer) => {
const accountInfo = AccountLayout.decode(data);
accountInfo.mint = new PublicKey(accountInfo.mint);
accountInfo.owner = new PublicKey(accountInfo.owner);
accountInfo.amount = u64.fromBuffer(accountInfo.amount);
if (accountInfo.delegateOption === 0) {
accountInfo.delegate = null;
accountInfo.delegatedAmount = new u64(0);
} else {
accountInfo.delegate = new PublicKey(accountInfo.delegate);
accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
}
accountInfo.isInitialized = accountInfo.state !== 0;
accountInfo.isFrozen = accountInfo.state === 2;
if (accountInfo.isNativeOption === 1) {
accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
accountInfo.isNative = true;
} else {
accountInfo.rentExemptReserve = null;
accountInfo.isNative = false;
}
if (accountInfo.closeAuthorityOption === 0) {
accountInfo.closeAuthority = null;
} else {
accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
}
return accountInfo;
};
// TODO: expose in spl package
export const deserializeMint = (data: Buffer) => {
if (data.length !== MintLayout.span) {
throw new Error('Not a valid Mint');
}
const mintInfo = MintLayout.decode(data);
if (mintInfo.mintAuthorityOption === 0) {
mintInfo.mintAuthority = null;
} else {
mintInfo.mintAuthority = new PublicKey(mintInfo.mintAuthority);
}
mintInfo.supply = u64.fromBuffer(mintInfo.supply);
mintInfo.isInitialized = mintInfo.isInitialized !== 0;
if (mintInfo.freezeAuthorityOption === 0) {
mintInfo.freezeAuthority = null;
} else {
mintInfo.freezeAuthority = new PublicKey(mintInfo.freezeAuthority);
}
return mintInfo as MintInfo;
};

View File

@ -0,0 +1,56 @@
import { AccountInfo } from '@solana/web3.js';
import { chunks } from '../../utils/utils';
export const getMultipleAccounts = async (
connection: any,
keys: string[],
commitment: string,
) => {
const result = await Promise.all(
chunks(keys, 99).map(chunk =>
getMultipleAccountsCore(connection, chunk, commitment),
),
);
const array = result
.map(
a =>
a.array.map(acc => {
if (!acc) {
return undefined;
}
const { data, ...rest } = acc;
const obj = {
...rest,
data: Buffer.from(data[0], 'base64'),
} as AccountInfo<Buffer>;
return obj;
}) as AccountInfo<Buffer>[],
)
.flat();
return { keys, array };
};
const getMultipleAccountsCore = async (
connection: any,
keys: string[],
commitment: string,
) => {
const args = connection._buildArgs([keys], commitment, 'base64');
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
if (unsafeRes.error) {
throw new Error(
'failed to get info about account ' + unsafeRes.error.message,
);
}
if (unsafeRes.result.value) {
const array = unsafeRes.result.value as AccountInfo<string[]>[];
return { keys, array };
}
// TODO: fix
throw new Error();
};

View File

@ -0,0 +1,6 @@
export * from './accounts';
export * from './cache';
export * from './getMultipleAccounts';
export * from './parsesrs';
export * from './deserialize';
export * from './types';

View File

@ -0,0 +1,64 @@
import { AccountInfo } from '@solana/web3.js';
import { TokenAccount } from '../../models';
import { ParsedAccountBase } from './types';
import { deserializeMint, deserializeAccount } from './deserialize';
import { StringPublicKey } from '../../utils';
export const MintParser = (
pubKey: StringPublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data);
const data = deserializeMint(buffer);
const details = {
pubkey: pubKey,
account: {
...info,
},
info: data,
} as ParsedAccountBase;
return details;
};
export const TokenAccountParser = (
pubKey: StringPublicKey,
info: AccountInfo<Buffer>,
) => {
// Sometimes a wrapped sol account gets closed, goes to 0 length,
// triggers an update over wss which triggers this guy to get called
// since your UI already logged that pubkey as a token account. Check for length.
if (info.data.length > 0) {
const buffer = Buffer.from(info.data);
const data = deserializeAccount(buffer);
const details = {
pubkey: pubKey,
account: {
...info,
},
info: data,
} as TokenAccount;
return details;
}
};
export const GenericAccountParser = (
pubKey: StringPublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data);
const details = {
pubkey: pubKey,
account: {
...info,
},
info: buffer,
} as ParsedAccountBase;
return details;
};

View File

@ -0,0 +1,17 @@
import { AccountInfo } from '@solana/web3.js';
import { StringPublicKey } from '../../utils';
export interface ParsedAccountBase {
pubkey: StringPublicKey;
account: AccountInfo<Buffer>;
info: any; // TODO: change to unknown
}
export type AccountParser = (
pubkey: StringPublicKey,
data: AccountInfo<Buffer>,
) => ParsedAccountBase | undefined;
export interface ParsedAccount<T> extends ParsedAccountBase {
info: T;
}

View File

@ -4,5 +4,5 @@ export * as Connection from './connection';
export * from './connection'; export * from './connection';
export * as Wallet from './wallet'; export * as Wallet from './wallet';
export * from './wallet'; export * from './wallet';
export * as Store from './store';
export * from './store'; export * from './store';
export * from './meta';

View File

@ -0,0 +1,27 @@
import { MetaState } from './types';
export const getEmptyMetaState = (): MetaState => ({
metadata: [],
metadataByMint: {},
masterEditions: {},
masterEditionsByPrintingMint: {},
masterEditionsByOneTimeAuthMint: {},
metadataByMasterEdition: {},
editions: {},
auctionManagersByAuction: {},
bidRedemptions: {},
auctions: {},
auctionDataExtended: {},
vaults: {},
payoutTickets: {},
store: null,
whitelistedCreatorsByCreator: {},
bidderMetadataByAuctionAndBidder: {},
bidderPotsByAuctionAndBidder: {},
safetyDepositBoxesByVaultAndIndex: {},
prizeTrackingTickets: {},
safetyDepositConfigsByAuctionManagerAndIndex: {},
bidRedemptionV2sByAuctionManagerAndWinningIndex: {},
stores: {},
creators: {},
});

View File

@ -0,0 +1,11 @@
export * from './meta';
export * from './isMetadataPartOfStore';
export * from './loadAccounts';
export * from './onChangeAccount';
export * from './subscribeAccountsChange';
export * from './processAuctions';
export * from './processMetaData';
export * from './processMetaplexAccounts';
export * from './processVaultData';
export * from './queryExtendedMetadata';
export * from './types';

View File

@ -1,5 +1,6 @@
import { Metadata, ParsedAccount } from '@oyster/common'; import { Metadata } from '../../actions';
import { Store, WhitelistedCreator } from '../../models/metaplex'; import { Store, WhitelistedCreator } from '../../models/metaplex';
import { ParsedAccount } from '../accounts/types';
export const isMetadataPartOfStore = ( export const isMetadataPartOfStore = (
m: ParsedAccount<Metadata>, m: ParsedAccount<Metadata>,
@ -8,11 +9,7 @@ export const isMetadataPartOfStore = (
string, string,
ParsedAccount<WhitelistedCreator> ParsedAccount<WhitelistedCreator>
>, >,
useAll: boolean,
) => { ) => {
if (useAll) {
return true;
}
if (!m?.info?.data?.creators || !store?.info) { if (!m?.info?.data?.creators || !store?.info) {
return false; return false;
} }

View File

@ -5,7 +5,18 @@ import {
StringPublicKey, StringPublicKey,
toPublicKey, toPublicKey,
VAULT_ID, VAULT_ID,
} from '@oyster/common/dist/lib/utils/ids'; } from '../../utils/ids';
import { MAX_WHITELISTED_CREATOR_SIZE } from '../../models';
import {
getEdition,
Metadata,
MAX_CREATOR_LEN,
MAX_CREATOR_LIMIT,
MAX_NAME_LENGTH,
MAX_SYMBOL_LENGTH,
MAX_URI_LENGTH,
METADATA_PREFIX,
} from '../../actions';
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'; import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import { AccountAndPubkey, MetaState, ProcessAccountsFunc } from './types'; import { AccountAndPubkey, MetaState, ProcessAccountsFunc } from './types';
import { isMetadataPartOfStore } from './isMetadataPartOfStore'; import { isMetadataPartOfStore } from './isMetadataPartOfStore';
@ -13,19 +24,9 @@ import { processAuctions } from './processAuctions';
import { processMetaplexAccounts } from './processMetaplexAccounts'; import { processMetaplexAccounts } from './processMetaplexAccounts';
import { processMetaData } from './processMetaData'; import { processMetaData } from './processMetaData';
import { processVaultData } from './processVaultData'; import { processVaultData } from './processVaultData';
import { import { ParsedAccount } from '../accounts/types';
getEdition, import { getEmptyMetaState } from './getEmptyMetaState';
getMultipleAccounts, import { getMultipleAccounts } from '../accounts/getMultipleAccounts';
MAX_CREATOR_LEN,
MAX_CREATOR_LIMIT,
MAX_NAME_LENGTH,
MAX_SYMBOL_LENGTH,
MAX_URI_LENGTH,
Metadata,
METADATA_PREFIX,
ParsedAccount,
} from '@oyster/common';
import { MAX_WHITELISTED_CREATOR_SIZE } from '../../models/metaplex';
async function getProgramAccounts( async function getProgramAccounts(
connection: Connection, connection: Connection,
@ -82,30 +83,7 @@ async function getProgramAccounts(
} }
export const loadAccounts = async (connection: Connection, all: boolean) => { export const loadAccounts = async (connection: Connection, all: boolean) => {
const tempCache: MetaState = { const tempCache: MetaState = getEmptyMetaState();
metadata: [],
metadataByMint: {},
masterEditions: {},
masterEditionsByPrintingMint: {},
masterEditionsByOneTimeAuthMint: {},
metadataByMasterEdition: {},
editions: {},
auctionManagersByAuction: {},
bidRedemptions: {},
auctions: {},
auctionDataExtended: {},
vaults: {},
payoutTickets: {},
store: null,
whitelistedCreatorsByCreator: {},
bidderMetadataByAuctionAndBidder: {},
bidderPotsByAuctionAndBidder: {},
safetyDepositBoxesByVaultAndIndex: {},
prizeTrackingTickets: {},
safetyDepositConfigsByAuctionManagerAndIndex: {},
bidRedemptionV2sByAuctionManagerAndWinningIndex: {},
stores: {},
};
const updateTemp = makeSetter(tempCache); const updateTemp = makeSetter(tempCache);
const forEach = const forEach =
@ -115,89 +93,58 @@ export const loadAccounts = async (connection: Connection, all: boolean) => {
} }
}; };
const additionalPromises: Promise<void>[] = []; let isSelectivePullMetadata = false;
const pullMetadata = async (creators: AccountAndPubkey[]) => {
await forEach(processMetaplexAccounts)(creators);
const IS_BIG_STORE = const whitelistedCreators = Object.values(
process.env.NEXT_PUBLIC_BIG_STORE?.toLowerCase() === 'true'; tempCache.whitelistedCreatorsByCreator,
console.log(`Is big store: ${IS_BIG_STORE}`); );
const promises = [ if (whitelistedCreators.length > 3) {
getProgramAccounts(connection, VAULT_ID).then(forEach(processVaultData)), console.log(' too many creators, pulling all nfts in one go');
getProgramAccounts(connection, AUCTION_ID).then(forEach(processAuctions)), additionalPromises.push(
getProgramAccounts(connection, METAPLEX_ID).then( getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
forEach(processMetaplexAccounts),
),
IS_BIG_STORE
? getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
forEach(processMetaData), forEach(processMetaData),
) ),
: undefined,
getProgramAccounts(connection, METAPLEX_ID, {
filters: [
{
dataSize: MAX_WHITELISTED_CREATOR_SIZE,
},
],
}).then(async creators => {
const result = await forEach(processMetaplexAccounts)(creators);
if (IS_BIG_STORE) {
return result;
}
const whitelistedCreators = Object.values(
tempCache.whitelistedCreatorsByCreator,
); );
} else {
console.log('pulling optimized nfts');
isSelectivePullMetadata = true;
if (whitelistedCreators.length > 3) { for (let i = 0; i < MAX_CREATOR_LIMIT; i++) {
console.log(' too many creators, pulling all nfts in one go'); for (let j = 0; j < whitelistedCreators.length; j++) {
additionalPromises.push( additionalPromises.push(
getProgramAccounts(connection, METADATA_PROGRAM_ID).then( getProgramAccounts(connection, METADATA_PROGRAM_ID, {
forEach(processMetaData), filters: [
), {
); memcmp: {
} else { offset:
console.log('pulling optimized nfts'); 1 + // key
32 + // update auth
for (let i = 0; i < MAX_CREATOR_LIMIT; i++) { 32 + // mint
for (let j = 0; j < whitelistedCreators.length; j++) { 4 + // name string length
additionalPromises.push( MAX_NAME_LENGTH + // name
getProgramAccounts(connection, METADATA_PROGRAM_ID, { 4 + // uri string length
filters: [ MAX_URI_LENGTH + // uri
{ 4 + // symbol string length
memcmp: { MAX_SYMBOL_LENGTH + // symbol
offset: 2 + // seller fee basis points
1 + // key 1 + // whether or not there is a creators vec
32 + // update auth 4 + // creators vec length
32 + // mint i * MAX_CREATOR_LEN,
4 + // name string length bytes: whitelistedCreators[j].info.address,
MAX_NAME_LENGTH + // name
4 + // uri string length
MAX_URI_LENGTH + // uri
4 + // symbol string length
MAX_SYMBOL_LENGTH + // symbol
2 + // seller fee basis points
1 + // whether or not there is a creators vec
4 + // creators vec length
i * MAX_CREATOR_LEN,
bytes: whitelistedCreators[j].info.address,
},
}, },
], },
}).then(forEach(processMetaData)), ],
); }).then(forEach(processMetaData)),
} );
} }
} }
}), }
]; };
await Promise.all(promises);
await Promise.all(additionalPromises);
await postProcessMetadata(tempCache, all); const pullEditions = async () => {
console.log('Metadata size', tempCache.metadata.length);
if (additionalPromises.length > 0) {
console.log('Pulling editions for optimized metadata'); console.log('Pulling editions for optimized metadata');
let setOf100MetadataEditionKeys: string[] = []; let setOf100MetadataEditionKeys: string[] = [];
const editionPromises: Promise<{ const editionPromises: Promise<{
@ -263,6 +210,39 @@ export const loadAccounts = async (connection: Connection, all: boolean) => {
Object.keys(tempCache.editions).length, Object.keys(tempCache.editions).length,
Object.keys(tempCache.masterEditions).length, Object.keys(tempCache.masterEditions).length,
); );
};
const IS_BIG_STORE =
all || process.env.NEXT_PUBLIC_BIG_STORE?.toLowerCase() === 'true';
console.log(`Is big store: ${IS_BIG_STORE}`);
const additionalPromises: Promise<void>[] = [];
const basePromises = [
getProgramAccounts(connection, VAULT_ID).then(forEach(processVaultData)),
getProgramAccounts(connection, AUCTION_ID).then(forEach(processAuctions)),
getProgramAccounts(connection, METAPLEX_ID).then(
forEach(processMetaplexAccounts),
),
IS_BIG_STORE
? getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
forEach(processMetaData),
)
: getProgramAccounts(connection, METAPLEX_ID, {
filters: [
{
dataSize: MAX_WHITELISTED_CREATOR_SIZE,
},
],
}).then(pullMetadata),
];
await Promise.all(basePromises);
await Promise.all(additionalPromises);
await postProcessMetadata(tempCache, all);
console.log('Metadata size', tempCache.metadata.length);
if (isSelectivePullMetadata) {
await pullEditions();
} }
return tempCache; return tempCache;
@ -294,11 +274,11 @@ export const metadataByMintUpdater = async (
) => { ) => {
const key = metadata.info.mint; const key = metadata.info.mint;
if ( if (
all ||
isMetadataPartOfStore( isMetadataPartOfStore(
metadata, metadata,
state.store, state.store,
state.whitelistedCreatorsByCreator, state.whitelistedCreatorsByCreator,
all,
) )
) { ) {
await metadata.info.init(); await metadata.info.init();

View File

@ -0,0 +1,124 @@
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { queryExtendedMetadata } from './queryExtendedMetadata';
import { subscribeAccountsChange } from './subscribeAccountsChange';
import { getEmptyMetaState } from './getEmptyMetaState';
import { loadAccounts } from './loadAccounts';
import { MetaContextState, MetaState } from './types';
import { useConnection } from '../connection';
import { useStore } from '../store';
import { useQuerySearch } from '../../hooks';
const MetaContext = React.createContext<MetaContextState>({
...getEmptyMetaState(),
isLoading: false,
});
export function MetaProvider({ children = null as any }) {
const connection = useConnection();
const { isReady, storeAddress } = useStore();
const searchParams = useQuerySearch();
const all = searchParams.get('all') == 'true';
const [state, setState] = useState<MetaState>(getEmptyMetaState());
const [isLoading, setIsLoading] = useState(true);
const updateMints = useCallback(
async metadataByMint => {
try {
if (!all) {
const { metadata, mintToMetadata } = await queryExtendedMetadata(
connection,
metadataByMint,
);
setState(current => ({
...current,
metadata,
metadataByMint: mintToMetadata,
}));
}
} catch (er) {
console.error(er);
}
},
[setState],
);
useEffect(() => {
(async () => {
if (!storeAddress) {
if (isReady) {
setIsLoading(false);
}
return;
} else if (!state.store) {
setIsLoading(true);
}
console.log('-----> Query started');
const nextState = await loadAccounts(connection, all);
console.log('------->Query finished');
setState(nextState);
setIsLoading(false);
console.log('------->set finished');
updateMints(nextState.metadataByMint);
})();
}, [connection, setState, updateMints, storeAddress, isReady]);
useEffect(() => {
if (isLoading) {
return;
}
return subscribeAccountsChange(connection, all, () => state, setState);
}, [connection, setState, isLoading]);
// TODO: fetch names dynamically
// TODO: get names for creators
// useEffect(() => {
// (async () => {
// const twitterHandles = await connection.getProgramAccounts(NAME_PROGRAM_ID, {
// filters: [
// {
// dataSize: TWITTER_ACCOUNT_LENGTH,
// },
// {
// memcmp: {
// offset: VERIFICATION_AUTHORITY_OFFSET,
// bytes: TWITTER_VERIFICATION_AUTHORITY.toBase58()
// }
// }
// ]
// });
// const handles = twitterHandles.map(t => {
// const owner = new PublicKey(t.account.data.slice(32, 64));
// const name = t.account.data.slice(96, 114).toString();
// });
// console.log(handles);
// })();
// }, [whitelistedCreatorsByCreator]);
return (
<MetaContext.Provider
value={{
...state,
isLoading,
}}
>
{children}
</MetaContext.Provider>
);
}
export const useMeta = () => {
const context = useContext(MetaContext);
return context;
};

View File

@ -1,7 +1,5 @@
import { import { ProgramAccountChangeCallback } from '@solana/web3.js';
KeyedAccountInfo, import { pubkeyToString } from '../../utils';
ProgramAccountChangeCallback,
} from '@solana/web3.js';
import { ProcessAccountsFunc, UpdateStateValueFunc } from './types'; import { ProcessAccountsFunc, UpdateStateValueFunc } from './types';
export const onChangeAccount = export const onChangeAccount =
@ -11,7 +9,7 @@ export const onChangeAccount =
all: boolean, all: boolean,
): ProgramAccountChangeCallback => ): ProgramAccountChangeCallback =>
async info => { async info => {
const pubkey = pubkeyByAccountInfo(info); const pubkey = pubkeyToString(info.accountId);
await process( await process(
{ {
pubkey, pubkey,
@ -21,9 +19,3 @@ export const onChangeAccount =
all, all,
); );
}; };
const pubkeyByAccountInfo = (info: KeyedAccountInfo) => {
return typeof info.accountId === 'string'
? info.accountId
: info.accountId.toBase58();
};

View File

@ -1,19 +1,19 @@
import { import {
AuctionParser,
cache,
ParsedAccount,
AuctionData, AuctionData,
AuctionDataExtended,
AuctionDataExtendedParser,
AuctionParser,
BidderMetadata, BidderMetadata,
BidderMetadataParser, BidderMetadataParser,
BidderPot, BidderPot,
BidderPotParser, BidderPotParser,
BIDDER_METADATA_LEN, BIDDER_METADATA_LEN,
BIDDER_POT_LEN, BIDDER_POT_LEN,
AuctionDataExtended,
MAX_AUCTION_DATA_EXTENDED_SIZE, MAX_AUCTION_DATA_EXTENDED_SIZE,
AuctionDataExtendedParser, } from '../../actions';
AUCTION_ID, import { AUCTION_ID } from '../../utils';
} from '@oyster/common'; import { ParsedAccount } from '../accounts/types';
import { cache } from '../accounts/cache';
import { CheckAccountFunc, ProcessAccountsFunc } from './types'; import { CheckAccountFunc, ProcessAccountsFunc } from './types';
export const processAuctions: ProcessAccountsFunc = ( export const processAuctions: ProcessAccountsFunc = (

View File

@ -1,18 +1,18 @@
import {
decodeMetadata,
decodeEdition,
decodeMasterEdition,
Metadata,
ParsedAccount,
Edition,
MasterEditionV1,
MasterEditionV2,
MetadataKey,
METADATA_PROGRAM_ID,
} from '@oyster/common';
import { AccountInfo } from '@solana/web3.js'; import { AccountInfo } from '@solana/web3.js';
import { ProcessAccountsFunc } from './types'; import { ProcessAccountsFunc } from './types';
import { isValidHttpUrl } from '../../utils/isValidHttpUrl'; import { isValidHttpUrl } from '../../utils/isValidHttpUrl';
import {
decodeEdition,
decodeMasterEdition,
decodeMetadata,
Edition,
MasterEditionV1,
MasterEditionV2,
Metadata,
MetadataKey,
} from '../../actions';
import { ParsedAccount } from '../accounts/types';
import { METADATA_PROGRAM_ID } from '../../utils';
export const processMetaData: ProcessAccountsFunc = ( export const processMetaData: ProcessAccountsFunc = (
{ account, pubkey }, { account, pubkey },

View File

@ -1,13 +1,12 @@
import { programIds, cache, ParsedAccount, METAPLEX_ID } from '@oyster/common';
import { AccountInfo, PublicKey } from '@solana/web3.js'; import { AccountInfo, PublicKey } from '@solana/web3.js';
import { import {
AuctionManagerV1,
AuctionManagerV2, AuctionManagerV2,
BidRedemptionTicket, BidRedemptionTicket,
decodeAuctionManager, decodeAuctionManager,
decodeBidRedemptionTicket, decodeBidRedemptionTicket,
decodeStore, decodeStore,
decodeWhitelistedCreator, isCreatorPartOfTheStore,
getWhitelistedCreator,
MetaplexKey, MetaplexKey,
Store, Store,
WhitelistedCreator, WhitelistedCreator,
@ -20,9 +19,10 @@ import {
decodeSafetyDepositConfig, decodeSafetyDepositConfig,
SafetyDepositConfig, SafetyDepositConfig,
} from '../../models/metaplex'; } from '../../models/metaplex';
import { AuctionManagerV1 } from '../../models/metaplex/deprecatedStates';
import names from '../../config/userNames.json';
import { ProcessAccountsFunc } from './types'; import { ProcessAccountsFunc } from './types';
import { METAPLEX_ID, programIds } from '../../utils';
import { ParsedAccount } from '../accounts/types';
import { cache } from '../accounts/cache';
export const processMetaplexAccounts: ProcessAccountsFunc = async ( export const processMetaplexAccounts: ProcessAccountsFunc = async (
{ account, pubkey }, { account, pubkey },
@ -130,30 +130,33 @@ export const processMetaplexAccounts: ProcessAccountsFunc = async (
} }
if (isWhitelistedCreatorV1Account(account)) { if (isWhitelistedCreatorV1Account(account)) {
const whitelistedCreator = decodeWhitelistedCreator(account.data); const parsedAccount = cache.add(
pubkey,
account,
WhitelistedCreatorParser,
false,
) as ParsedAccount<WhitelistedCreator>;
// TODO: figure out a way to avoid generating creator addresses during parsing // TODO: figure out a way to avoid generating creator addresses during parsing
// should we store store id inside creator? // should we store store id inside creator?
const creatorKeyIfCreatorWasPartOfThisStore = await getWhitelistedCreator( if (STORE_ID) {
whitelistedCreator.address, const isWhitelistedCreator = await isCreatorPartOfTheStore(
); parsedAccount.info.address,
if (creatorKeyIfCreatorWasPartOfThisStore === pubkey) {
const parsedAccount = cache.add(
pubkey, pubkey,
account, );
WhitelistedCreatorParser, if (isWhitelistedCreator) {
false, setter(
) as ParsedAccount<WhitelistedCreator>; 'whitelistedCreatorsByCreator',
parsedAccount.info.address,
const nameInfo = (names as any)[parsedAccount.info.address]; parsedAccount,
);
if (nameInfo) {
parsedAccount.info = { ...parsedAccount.info, ...nameInfo };
} }
}
if (useAll) {
setter( setter(
'whitelistedCreatorsByCreator', 'creators',
whitelistedCreator.address, parsedAccount.info.address + '-' + pubkey,
parsedAccount, parsedAccount,
); );
} }

View File

@ -1,13 +1,13 @@
import { AccountInfo } from '@solana/web3.js';
import { import {
ParsedAccount,
SafetyDepositBox,
VaultKey,
decodeSafetyDeposit, decodeSafetyDeposit,
decodeVault, decodeVault,
SafetyDepositBox,
Vault, Vault,
} from '@oyster/common'; VaultKey,
import { VAULT_ID } from '@oyster/common/dist/lib/utils/ids'; } from '../../actions';
import { AccountInfo } from '@solana/web3.js'; import { VAULT_ID } from '../../utils';
import { ParsedAccount } from '../accounts/types';
import { ProcessAccountsFunc } from './types'; import { ProcessAccountsFunc } from './types';
export const processVaultData: ProcessAccountsFunc = ( export const processVaultData: ProcessAccountsFunc = (

View File

@ -1,12 +1,10 @@
import {
Metadata,
getMultipleAccounts,
cache,
MintParser,
ParsedAccount,
} from '@oyster/common';
import { MintInfo } from '@solana/spl-token'; import { MintInfo } from '@solana/spl-token';
import { Connection } from '@solana/web3.js'; import { Connection } from '@solana/web3.js';
import { Metadata } from '../../actions';
import { ParsedAccount } from '../accounts/types';
import { cache } from '../accounts/cache';
import { getMultipleAccounts } from '../accounts/getMultipleAccounts';
import { MintParser } from '../accounts/parsesrs';
export const queryExtendedMetadata = async ( export const queryExtendedMetadata = async (
connection: Connection, connection: Connection,

View File

@ -0,0 +1,76 @@
import { Connection } from '@solana/web3.js';
import {
AUCTION_ID,
METADATA_PROGRAM_ID,
METAPLEX_ID,
toPublicKey,
VAULT_ID,
} from '../../utils';
import { makeSetter, metadataByMintUpdater } from './loadAccounts';
import { onChangeAccount } from './onChangeAccount';
import { processAuctions } from './processAuctions';
import { processMetaData } from './processMetaData';
import { processMetaplexAccounts } from './processMetaplexAccounts';
import { processVaultData } from './processVaultData';
import { MetaState, UpdateStateValueFunc } from './types';
export const subscribeAccountsChange = (
connection: Connection,
all: boolean,
getState: () => MetaState,
setState: (v: MetaState) => void,
) => {
const subscriptions: number[] = [];
const updateStateValue: UpdateStateValueFunc = (prop, key, value) => {
const state = getState();
const nextState = makeSetter({ ...state })(prop, key, value);
setState(nextState);
};
subscriptions.push(
connection.onProgramAccountChange(
toPublicKey(VAULT_ID),
onChangeAccount(processVaultData, updateStateValue, all),
),
);
subscriptions.push(
connection.onProgramAccountChange(
toPublicKey(AUCTION_ID),
onChangeAccount(processAuctions, updateStateValue, all),
),
);
subscriptions.push(
connection.onProgramAccountChange(
toPublicKey(METAPLEX_ID),
onChangeAccount(processMetaplexAccounts, updateStateValue, all),
),
);
subscriptions.push(
connection.onProgramAccountChange(
toPublicKey(METADATA_PROGRAM_ID),
onChangeAccount(
processMetaData,
async (prop, key, value) => {
if (prop === 'metadataByMint') {
const state = getState();
const nextState = await metadataByMintUpdater(value, state, all);
setState(nextState);
} else {
updateStateValue(prop, key, value);
}
},
all,
),
),
);
return () => {
subscriptions.forEach(subscriptionId => {
connection.removeProgramAccountChangeListener(subscriptionId);
});
};
};

View File

@ -1,29 +1,29 @@
import {
Metadata,
ParsedAccount,
Edition,
AuctionData,
SafetyDepositBox,
BidderMetadata,
BidderPot,
Vault,
AuctionDataExtended,
MasterEditionV1,
MasterEditionV2,
PublicKeyStringAndAccount,
} from '@oyster/common';
import { AccountInfo } from '@solana/web3.js'; import { AccountInfo } from '@solana/web3.js';
import { import {
AuctionData,
AuctionDataExtended,
BidderMetadata,
BidderPot,
Edition,
MasterEditionV1,
MasterEditionV2,
Metadata,
SafetyDepositBox,
Vault,
} from '../../actions';
import {
AuctionManagerV1,
AuctionManagerV2,
BidRedemptionTicket, BidRedemptionTicket,
Store, BidRedemptionTicketV2,
WhitelistedCreator,
PayoutTicket, PayoutTicket,
PrizeTrackingTicket, PrizeTrackingTicket,
AuctionManagerV2,
SafetyDepositConfig, SafetyDepositConfig,
BidRedemptionTicketV2, Store,
WhitelistedCreator,
} from '../../models/metaplex'; } from '../../models/metaplex';
import { AuctionManagerV1 } from '../../models/metaplex/deprecatedStates'; import { PublicKeyStringAndAccount } from '../../utils';
import { ParsedAccount } from '../accounts/types';
export interface MetaState { export interface MetaState {
metadata: ParsedAccount<Metadata>[]; metadata: ParsedAccount<Metadata>[];
@ -72,6 +72,7 @@ export interface MetaState {
>; >;
payoutTickets: Record<string, ParsedAccount<PayoutTicket>>; payoutTickets: Record<string, ParsedAccount<PayoutTicket>>;
stores: Record<string, ParsedAccount<Store>>; stores: Record<string, ParsedAccount<Store>>;
creators: Record<string, ParsedAccount<WhitelistedCreator>>;
} }
export interface MetaContextState extends MetaState { export interface MetaContextState extends MetaState {

View File

@ -1 +1,2 @@
export * from './account'; export * from './account';
export * from './metaplex';

View File

@ -1,13 +1,9 @@
import {
getBidderPotKey,
programIds,
StringPublicKey,
toPublicKey,
} from '@oyster/common';
import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js'; import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { getAuctionKeys, ClaimBidArgs, SCHEMA } from '.'; import { getAuctionKeys, ClaimBidArgs, SCHEMA } from '.';
import { getBidderPotKey } from '../../actions';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function claimBid( export async function claimBid(
acceptPayment: StringPublicKey, acceptPayment: StringPublicKey,

View File

@ -1,8 +1,8 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js'; import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { DecommissionAuctionManagerArgs, SCHEMA } from '.'; import { DecommissionAuctionManagerArgs, SCHEMA } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function decommissionAuctionManager( export async function decommissionAuctionManager(
auctionManager: StringPublicKey, auctionManager: StringPublicKey,

View File

@ -1,4 +1,3 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -7,6 +6,7 @@ import {
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { getAuctionKeys, SCHEMA } from '.'; import { getAuctionKeys, SCHEMA } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
import { import {
AuctionManagerSettingsV1, AuctionManagerSettingsV1,
DeprecatedInitAuctionManagerV1Args, DeprecatedInitAuctionManagerV1Args,

View File

@ -1,15 +1,13 @@
import {
programIds,
VAULT_PREFIX,
getAuctionExtended,
findProgramAddress,
StringPublicKey,
toPublicKey,
} from '@oyster/common';
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js'; import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { SCHEMA } from '.'; import { SCHEMA } from '.';
import { getAuctionExtended, VAULT_PREFIX } from '../../actions';
import {
findProgramAddress,
programIds,
StringPublicKey,
toPublicKey,
} from '../../utils';
import { DeprecatedPopulateParticipationPrintingAccountArgs } from './deprecatedStates'; import { DeprecatedPopulateParticipationPrintingAccountArgs } from './deprecatedStates';
export async function deprecatedPopulateParticipationPrintingAccount( export async function deprecatedPopulateParticipationPrintingAccount(

View File

@ -1,4 +1,3 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -12,6 +11,7 @@ import {
SCHEMA, SCHEMA,
getSafetyDepositConfig, getSafetyDepositConfig,
} from '.'; } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
import { DeprecatedRedeemParticipationBidArgs } from './deprecatedStates'; import { DeprecatedRedeemParticipationBidArgs } from './deprecatedStates';
export async function deprecatedRedeemParticipationBid( export async function deprecatedRedeemParticipationBid(

View File

@ -1,4 +1,3 @@
import { programIds, findProgramAddress, toPublicKey } from '@oyster/common';
import BN from 'bn.js'; import BN from 'bn.js';
import { import {
AuctionManagerStatus, AuctionManagerStatus,
@ -9,6 +8,7 @@ import {
WinningConfigType, WinningConfigType,
WinningConstraint, WinningConstraint,
} from '.'; } from '.';
import { findProgramAddress, programIds, toPublicKey } from '../../utils';
export const MAX_BID_REDEMPTION_TICKET_V1_SIZE = 3; export const MAX_BID_REDEMPTION_TICKET_V1_SIZE = 3;

View File

@ -1,4 +1,3 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -7,6 +6,7 @@ import {
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { SCHEMA } from '.'; import { SCHEMA } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
import { DeprecatedValidateParticipationArgs } from './deprecatedStates'; import { DeprecatedValidateParticipationArgs } from './deprecatedStates';
export async function deprecatedValidateParticipation( export async function deprecatedValidateParticipation(

View File

@ -1,4 +1,3 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -7,6 +6,7 @@ import {
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { getAuctionKeys, getOriginalAuthority, SCHEMA } from '.'; import { getAuctionKeys, getOriginalAuthority, SCHEMA } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
import { import {
getSafetyDepositBoxValidationTicket, getSafetyDepositBoxValidationTicket,

View File

@ -1,4 +1,3 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -13,6 +12,7 @@ import {
getSafetyDepositConfig, getSafetyDepositConfig,
SCHEMA, SCHEMA,
} from '.'; } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function emptyPaymentAccount( export async function emptyPaymentAccount(
acceptPayment: StringPublicKey, acceptPayment: StringPublicKey,

View File

@ -1,24 +1,24 @@
import {
AUCTION_PREFIX,
programIds,
METADATA,
AccountParser,
findProgramAddress,
AuctionData,
ParsedAccount,
Vault,
Metadata,
MasterEditionV1,
SafetyDepositBox,
MasterEditionV2,
toPublicKey,
StringPublicKey,
} from '@oyster/common';
import { AccountInfo, SystemProgram } from '@solana/web3.js'; import { AccountInfo, SystemProgram } from '@solana/web3.js';
import BN from 'bn.js'; import BN from 'bn.js';
import { deserializeUnchecked } from 'borsh';
import bs58 from 'bs58'; import bs58 from 'bs58';
import { AuctionViewItem } from '../../hooks'; import { deserializeUnchecked } from 'borsh';
import {
AuctionData,
AUCTION_PREFIX,
MasterEditionV1,
MasterEditionV2,
METADATA,
Metadata,
SafetyDepositBox,
Vault,
} from '../../actions';
import { AccountParser, ParsedAccount } from '../../contexts';
import {
findProgramAddress,
programIds,
toPublicKey,
StringPublicKey,
} from '../../utils';
import { import {
AuctionManagerV1, AuctionManagerV1,
BidRedemptionTicketV1, BidRedemptionTicketV1,
@ -35,6 +35,7 @@ export * from './deprecatedValidateSafetyDepositBoxV1';
export * from './redeemParticipationBidV3'; export * from './redeemParticipationBidV3';
export * from './redeemPrintingV2Bid'; export * from './redeemPrintingV2Bid';
export * from './withdrawMasterEdition'; export * from './withdrawMasterEdition';
export * from './deprecatedStates';
export const METAPLEX_PREFIX = 'metaplex'; export const METAPLEX_PREFIX = 'metaplex';
export const TOTALS = 'totals'; export const TOTALS = 'totals';
@ -537,6 +538,14 @@ export interface BidRedemptionTicket {
getBidRedeemed(order: number): boolean; getBidRedeemed(order: number): boolean;
} }
export interface AuctionViewItem {
winningConfigType: WinningConfigType;
amount: BN;
metadata: ParsedAccount<Metadata>;
safetyDeposit: ParsedAccount<SafetyDepositBox>;
masterEdition?: ParsedAccount<MasterEditionV1 | MasterEditionV2>;
}
export class BidRedemptionTicketV2 implements BidRedemptionTicket { export class BidRedemptionTicketV2 implements BidRedemptionTicket {
key: MetaplexKey = MetaplexKey.BidRedemptionTicketV2; key: MetaplexKey = MetaplexKey.BidRedemptionTicketV2;
winnerIndex: BN | null; winnerIndex: BN | null;
@ -1087,9 +1096,22 @@ export async function getOriginalAuthority(
)[0]; )[0];
} }
export async function getWhitelistedCreator(creator: string) { export const isCreatorPartOfTheStore = async (
creatorAddress: StringPublicKey,
pubkey: StringPublicKey,
store?: StringPublicKey,
) => {
const creatorKeyInStore = await getWhitelistedCreator(creatorAddress, store);
return creatorKeyInStore === pubkey;
};
export async function getWhitelistedCreator(
creator: StringPublicKey,
storeId?: StringPublicKey,
) {
const PROGRAM_IDS = programIds(); const PROGRAM_IDS = programIds();
const store = PROGRAM_IDS.store; const store = storeId || PROGRAM_IDS.store;
if (!store) { if (!store) {
throw new Error('Store not initialized'); throw new Error('Store not initialized');
} }

View File

@ -1,4 +1,3 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -14,6 +13,7 @@ import {
SCHEMA, SCHEMA,
TupleNumericType, TupleNumericType,
} from '.'; } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function initAuctionManagerV2( export async function initAuctionManagerV2(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -1,10 +1,3 @@
import {
findProgramAddress,
programIds,
StringPublicKey,
toPublicKey,
VAULT_PREFIX,
} from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -21,6 +14,13 @@ import {
RedeemUnusedWinningConfigItemsAsAuctioneerArgs, RedeemUnusedWinningConfigItemsAsAuctioneerArgs,
SCHEMA, SCHEMA,
} from '.'; } from '.';
import { VAULT_PREFIX } from '../../actions';
import {
findProgramAddress,
programIds,
StringPublicKey,
toPublicKey,
} from '../../utils';
export async function redeemBid( export async function redeemBid(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -1,10 +1,3 @@
import {
programIds,
VAULT_PREFIX,
findProgramAddress,
StringPublicKey,
toPublicKey,
} from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -21,6 +14,13 @@ import {
RedeemUnusedWinningConfigItemsAsAuctioneerArgs, RedeemUnusedWinningConfigItemsAsAuctioneerArgs,
SCHEMA, SCHEMA,
} from '.'; } from '.';
import { VAULT_PREFIX } from '../../actions';
import {
findProgramAddress,
programIds,
StringPublicKey,
toPublicKey,
} from '../../utils';
export async function redeemFullRightsTransferBid( export async function redeemFullRightsTransferBid(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -1,12 +1,3 @@
import {
getEdition,
programIds,
getMetadata,
getEditionMarkPda,
getAuctionExtended,
StringPublicKey,
toPublicKey,
} from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -23,6 +14,13 @@ import {
getPrizeTrackingTicket, getPrizeTrackingTicket,
getSafetyDepositConfig, getSafetyDepositConfig,
} from '.'; } from '.';
import {
getAuctionExtended,
getEdition,
getEditionMarkPda,
getMetadata,
} from '../../actions';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function redeemParticipationBidV3( export async function redeemParticipationBidV3(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -1,11 +1,3 @@
import {
getEdition,
getEditionMarkPda,
getMetadata,
programIds,
StringPublicKey,
toPublicKey,
} from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -22,6 +14,8 @@ import {
SCHEMA, SCHEMA,
getSafetyDepositConfig, getSafetyDepositConfig,
} from '.'; } from '.';
import { getEdition, getEditionMarkPda, getMetadata } from '../../actions';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function redeemPrintingV2Bid( export async function redeemPrintingV2Bid(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -1,8 +1,8 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js'; import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { SCHEMA, SetStoreArgs } from '.'; import { SCHEMA, SetStoreArgs } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function setStore( export async function setStore(
isPublic: boolean, isPublic: boolean,

View File

@ -1,8 +1,8 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js'; import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { getWhitelistedCreator, SCHEMA, SetWhitelistedCreatorArgs } from '.'; import { getWhitelistedCreator, SCHEMA, SetWhitelistedCreatorArgs } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function setWhitelistedCreator( export async function setWhitelistedCreator(
creator: StringPublicKey, creator: StringPublicKey,

View File

@ -1,8 +1,8 @@
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js'; import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { serialize } from 'borsh'; import { serialize } from 'borsh';
import { getAuctionKeys, SCHEMA, StartAuctionArgs } from '.'; import { getAuctionKeys, SCHEMA, StartAuctionArgs } from '.';
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
export async function startAuction( export async function startAuction(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -1,4 +1,3 @@
import { programIds, toPublicKey, StringPublicKey } from '@oyster/common';
import { import {
SystemProgram, SystemProgram,
SYSVAR_RENT_PUBKEY, SYSVAR_RENT_PUBKEY,
@ -15,6 +14,7 @@ import {
SCHEMA, SCHEMA,
ValidateSafetyDepositBoxV2Args, ValidateSafetyDepositBoxV2Args,
} from '.'; } from '.';
import { programIds, toPublicKey, StringPublicKey } from '../../utils';
export async function validateSafetyDepositBoxV2( export async function validateSafetyDepositBoxV2(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -1,12 +1,3 @@
import {
AUCTION_PREFIX,
EXTENDED,
findProgramAddress,
programIds,
StringPublicKey,
toPublicKey,
VAULT_PREFIX,
} from '@oyster/common';
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js'; import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { serialize } from 'borsh'; import { serialize } from 'borsh';
@ -17,6 +8,13 @@ import {
getPrizeTrackingTicket, getPrizeTrackingTicket,
getSafetyDepositConfig, getSafetyDepositConfig,
} from '.'; } from '.';
import { AUCTION_PREFIX, EXTENDED, VAULT_PREFIX } from '../../actions';
import {
findProgramAddress,
programIds,
toPublicKey,
StringPublicKey,
} from '../../utils';
export async function withdrawMasterEdition( export async function withdrawMasterEdition(
vault: StringPublicKey, vault: StringPublicKey,

View File

@ -36,6 +36,10 @@ export const toPublicKey = (key: string | PublicKey) => {
return result; return result;
}; };
export const pubkeyToString = (key: PublicKey | null | string = '') => {
return typeof key === 'string' ? key : key?.toBase58() || '';
};
export interface PublicKeyStringAndAccount<T> { export interface PublicKeyStringAndAccount<T> {
pubkey: string; pubkey: string;
account: AccountInfo<T>; account: AccountInfo<T>;

View File

@ -4,6 +4,8 @@ export * from './programIds';
export * as Layout from './layout'; export * as Layout from './layout';
export * from './notifications'; export * from './notifications';
export * from './utils'; export * from './utils';
export * from './useLocalStorage';
export * from './strings'; export * from './strings';
export * as shortvec from './shortvec'; export * as shortvec from './shortvec';
export * from './isValidHttpUrl';
export * from './borsh'; export * from './borsh';

View File

@ -0,0 +1,32 @@
type UseStorageReturnValue = {
getItem: (key: string) => string;
setItem: (key: string, value: string) => boolean;
removeItem: (key: string) => void;
};
export const useLocalStorage = (): UseStorageReturnValue => {
const isBrowser: boolean = ((): boolean => typeof window !== 'undefined')();
const getItem = (key: string): string => {
return isBrowser ? window.localStorage[key] : '';
};
const setItem = (key: string, value: string): boolean => {
if (isBrowser) {
window.localStorage.setItem(key, value);
return true;
}
return false;
};
const removeItem = (key: string): void => {
window.localStorage.removeItem(key);
};
return {
getItem,
setItem,
removeItem,
};
};

View File

@ -6,6 +6,7 @@ import { PublicKey } from '@solana/web3.js';
import BN from 'bn.js'; import BN from 'bn.js';
import { WAD, ZERO } from '../constants'; import { WAD, ZERO } from '../constants';
import { TokenInfo } from '@solana/spl-token-registry'; import { TokenInfo } from '@solana/spl-token-registry';
import { useLocalStorage } from './useLocalStorage';
export type KnownTokenMap = Map<string, TokenInfo>; export type KnownTokenMap = Map<string, TokenInfo>;
@ -16,6 +17,7 @@ export const formatPriceNumber = new Intl.NumberFormat('en-US', {
}); });
export function useLocalStorageState(key: string, defaultState?: string) { export function useLocalStorageState(key: string, defaultState?: string) {
const localStorage = useLocalStorage();
const [state, setState] = useState(() => { const [state, setState] = useState(() => {
// NOTE: Not sure if this is ok // NOTE: Not sure if this is ok
const storedState = localStorage.getItem(key); const storedState = localStorage.getItem(key);
@ -52,6 +54,7 @@ export const findProgramAddress = async (
seeds: (Buffer | Uint8Array)[], seeds: (Buffer | Uint8Array)[],
programId: PublicKey, programId: PublicKey,
) => { ) => {
const localStorage = useLocalStorage();
const key = const key =
'pda-' + 'pda-' +
seeds.reduce((agg, item) => agg + item.toString('hex'), '') + seeds.reduce((agg, item) => agg + item.toString('hex'), '') +

View File

@ -1,22 +1,24 @@
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js'; import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
import { import {
utils, utils,
actions,
models,
findProgramAddress, findProgramAddress,
MetadataKey, MetadataKey,
StringPublicKey, StringPublicKey,
toPublicKey, toPublicKey,
WalletSigner, WalletSigner,
} from '@oyster/common'; } from '@oyster/common';
import { SafetyDepositConfig } from '@oyster/common/dist/lib/models/metaplex/index';
import { approve } from '@oyster/common/dist/lib/models/account';
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
import {
addTokenToInactiveVault,
VAULT_PREFIX,
} from '@oyster/common/dist/lib/actions/vault';
import { AccountLayout } from '@solana/spl-token'; import { AccountLayout } from '@solana/spl-token';
import BN from 'bn.js'; import BN from 'bn.js';
import { SafetyDepositDraft } from './createAuctionManager'; import { SafetyDepositDraft } from './createAuctionManager';
import { SafetyDepositConfig } from '../models/metaplex';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
const { createTokenAccount, addTokenToInactiveVault, VAULT_PREFIX } = actions;
const { approve } = models;
export interface SafetyDepositInstructionTemplate { export interface SafetyDepositInstructionTemplate {
box: { box: {

View File

@ -16,7 +16,10 @@ import {
import { AccountLayout } from '@solana/spl-token'; import { AccountLayout } from '@solana/spl-token';
import { TransactionInstruction, Keypair, Connection } from '@solana/web3.js'; import { TransactionInstruction, Keypair, Connection } from '@solana/web3.js';
import { AuctionView } from '../hooks'; import { AuctionView } from '../hooks';
import { BidRedemptionTicket, PrizeTrackingTicket } from '../models/metaplex'; import {
BidRedemptionTicket,
PrizeTrackingTicket,
} from '@oyster/common/dist/lib/models/metaplex/index';
import { claimUnusedPrizes } from './claimUnusedPrizes'; import { claimUnusedPrizes } from './claimUnusedPrizes';
import { setupPlaceBid } from './sendPlaceBid'; import { setupPlaceBid } from './sendPlaceBid';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';

View File

@ -1,6 +1,5 @@
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js'; import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
import { import {
actions,
ParsedAccount, ParsedAccount,
TokenAccount, TokenAccount,
SafetyDepositBox, SafetyDepositBox,
@ -16,7 +15,7 @@ import {
} from '@oyster/common'; } from '@oyster/common';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { AccountLayout, MintLayout } from '@solana/spl-token'; import { AccountLayout, MintLayout } from '@solana/spl-token';
import { AuctionView, AuctionViewItem } from '../hooks'; import { AuctionView } from '../hooks';
import { import {
WinningConfigType, WinningConfigType,
redeemBid, redeemBid,
@ -25,13 +24,14 @@ import {
BidRedemptionTicket, BidRedemptionTicket,
getBidRedemption, getBidRedemption,
PrizeTrackingTicket, PrizeTrackingTicket,
} from '../models/metaplex'; AuctionViewItem,
} from '@oyster/common/dist/lib/models/metaplex/index';
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
import { import {
eligibleForParticipationPrizeGivenWinningIndex, eligibleForParticipationPrizeGivenWinningIndex,
setupRedeemParticipationInstructions, setupRedeemParticipationInstructions,
setupRedeemPrintingV2Instructions, setupRedeemPrintingV2Instructions,
} from './sendRedeemBid'; } from './sendRedeemBid';
const { createTokenAccount } = actions;
export async function findEligibleParticipationBidsForRedemption( export async function findEligibleParticipationBidsForRedemption(
auctionView: AuctionView, auctionView: AuctionView,

View File

@ -1,16 +1,15 @@
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js'; import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
import {
actions,
models,
StringPublicKey,
toPublicKey,
WalletSigner,
} from '@oyster/common';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { StringPublicKey, toPublicKey, WalletSigner } from '@oyster/common';
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
import {
activateVault,
combineVault,
} from '@oyster/common/dist/lib/actions/vault';
import { approve } from '@oyster/common/dist/lib/models/account';
import { AccountLayout } from '@solana/spl-token'; import { AccountLayout } from '@solana/spl-token';
import BN from 'bn.js'; import BN from 'bn.js';
const { createTokenAccount, activateVault, combineVault } = actions;
const { approve } = models;
// This command "closes" the vault, by activating & combining it in one go, handing it over to the auction manager // This command "closes" the vault, by activating & combining it in one go, handing it over to the auction manager
// authority (that may or may not exist yet.) // authority (that may or may not exist yet.)

View File

@ -5,7 +5,6 @@ import {
SystemProgram, SystemProgram,
} from '@solana/web3.js'; } from '@solana/web3.js';
import { import {
actions,
Metadata, Metadata,
ParsedAccount, ParsedAccount,
MasterEditionV1, MasterEditionV1,
@ -41,7 +40,8 @@ import {
TupleNumericType, TupleNumericType,
SafetyDepositConfig, SafetyDepositConfig,
ParticipationStateV2, ParticipationStateV2,
} from '../models/metaplex'; } from '@oyster/common/dist/lib/models/metaplex/index';
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
import { createVault } from './createVault'; import { createVault } from './createVault';
import { closeVault } from './closeVault'; import { closeVault } from './closeVault';
import { import {
@ -50,15 +50,13 @@ import {
} from './addTokensToVault'; } from './addTokensToVault';
import { makeAuction } from './makeAuction'; import { makeAuction } from './makeAuction';
import { createExternalPriceAccount } from './createExternalPriceAccount'; import { createExternalPriceAccount } from './createExternalPriceAccount';
import { deprecatedValidateParticipation } from '../models/metaplex/deprecatedValidateParticipation'; import { deprecatedValidateParticipation } from '@oyster/common/dist/lib/models/metaplex/deprecatedValidateParticipation';
import { deprecatedCreateReservationListForTokens } from './deprecatedCreateReservationListsForTokens'; import { deprecatedCreateReservationListForTokens } from './deprecatedCreateReservationListsForTokens';
import { deprecatedPopulatePrintingTokens } from './deprecatedPopulatePrintingTokens'; import { deprecatedPopulatePrintingTokens } from './deprecatedPopulatePrintingTokens';
import { setVaultAndAuctionAuthorities } from './setVaultAndAuctionAuthorities'; import { setVaultAndAuctionAuthorities } from './setVaultAndAuctionAuthorities';
import { markItemsThatArentMineAsSold } from './markItemsThatArentMineAsSold'; import { markItemsThatArentMineAsSold } from './markItemsThatArentMineAsSold';
import { validateSafetyDepositBoxV2 } from '../models/metaplex/validateSafetyDepositBoxV2'; import { validateSafetyDepositBoxV2 } from '@oyster/common/dist/lib/models/metaplex/validateSafetyDepositBoxV2';
import { initAuctionManagerV2 } from '../models/metaplex/initAuctionManagerV2'; import { initAuctionManagerV2 } from '@oyster/common/dist/lib/models/metaplex/initAuctionManagerV2';
const { createTokenAccount } = actions;
interface normalPattern { interface normalPattern {
instructions: TransactionInstruction[]; instructions: TransactionInstruction[];

View File

@ -4,22 +4,22 @@ import {
SystemProgram, SystemProgram,
TransactionInstruction, TransactionInstruction,
} from '@solana/web3.js'; } from '@solana/web3.js';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { import {
utils, utils,
actions,
StringPublicKey, StringPublicKey,
toPublicKey, toPublicKey,
WalletSigner, WalletSigner,
} from '@oyster/common'; } from '@oyster/common';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import {
import BN from 'bn.js';
import { QUOTE_MINT } from '../constants';
const {
updateExternalPriceAccount, updateExternalPriceAccount,
ExternalPriceAccount, ExternalPriceAccount,
MAX_EXTERNAL_ACCOUNT_SIZE, MAX_EXTERNAL_ACCOUNT_SIZE,
} = actions; } from '@oyster/common/dist/lib/actions/vault';
import BN from 'bn.js';
import { QUOTE_MINT } from '../constants';
// This command creates the external pricing oracle // This command creates the external pricing oracle
export async function createExternalPriceAccount( export async function createExternalPriceAccount(

View File

@ -6,19 +6,22 @@ import {
} from '@solana/web3.js'; } from '@solana/web3.js';
import { import {
utils, utils,
actions,
createMint, createMint,
findProgramAddress, findProgramAddress,
StringPublicKey, StringPublicKey,
toPublicKey, toPublicKey,
WalletSigner, WalletSigner,
} from '@oyster/common'; } from '@oyster/common';
import {
initVault,
MAX_VAULT_SIZE,
VAULT_PREFIX,
} from '@oyster/common/dist/lib/actions/vault';
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
import { AccountLayout, MintLayout } from '@solana/spl-token'; import { AccountLayout, MintLayout } from '@solana/spl-token';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
const { createTokenAccount, initVault, MAX_VAULT_SIZE, VAULT_PREFIX } = actions;
// This command creates the external pricing oracle a vault // This command creates the external pricing oracle a vault
// This gets the vault ready for adding the tokens. // This gets the vault ready for adding the tokens.
export async function createVault( export async function createVault(

View File

@ -9,8 +9,8 @@ import {
} from '@oyster/common'; } from '@oyster/common';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { AuctionView } from '../hooks'; import { AuctionView } from '../hooks';
import { AuctionManagerStatus } from '../models/metaplex'; import { AuctionManagerStatus } from '@oyster/common/dist/lib/models/metaplex/index';
import { decommissionAuctionManager } from '../models/metaplex/decommissionAuctionManager'; import { decommissionAuctionManager } from '@oyster/common/dist/lib/models/metaplex/decommissionAuctionManager';
import { unwindVault } from './unwindVault'; import { unwindVault } from './unwindVault';
export async function decommAuctionManagerAndReturnPrizes( export async function decommAuctionManagerAndReturnPrizes(

View File

@ -6,7 +6,7 @@ import {
} from '@oyster/common'; } from '@oyster/common';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { SafetyDepositInstructionTemplate } from './addTokensToVault'; import { SafetyDepositInstructionTemplate } from './addTokensToVault';
import { WinningConfigType } from '../models/metaplex'; import { WinningConfigType } from '@oyster/common/dist/lib/models/metaplex/index';
const BATCH_SIZE = 10; const BATCH_SIZE = 10;
// This command batches out creating reservation lists for those tokens who are being sold in PrintingV1 mode. // This command batches out creating reservation lists for those tokens who are being sold in PrintingV1 mode.

View File

@ -1,7 +1,6 @@
import { Keypair, TransactionInstruction } from '@solana/web3.js'; import { Keypair, TransactionInstruction } from '@solana/web3.js';
import { import {
utils, utils,
actions,
findProgramAddress, findProgramAddress,
IPartialCreateAuctionArgs, IPartialCreateAuctionArgs,
CreateAuctionArgs, CreateAuctionArgs,
@ -9,10 +8,12 @@ import {
toPublicKey, toPublicKey,
WalletSigner, WalletSigner,
} from '@oyster/common'; } from '@oyster/common';
import {
AUCTION_PREFIX,
createAuction,
} from '@oyster/common/dist/lib/actions/auction';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
const { AUCTION_PREFIX, createAuction } = actions;
// This command makes an auction // This command makes an auction
export async function makeAuction( export async function makeAuction(
wallet: WalletSigner, wallet: WalletSigner,

View File

@ -5,10 +5,10 @@ import {
sendTransactionWithRetry, sendTransactionWithRetry,
WalletSigner, WalletSigner,
} from '@oyster/common'; } from '@oyster/common';
import { WhitelistedCreator } from '@oyster/common/dist/lib/models/metaplex/index';
import { setStore } from '@oyster/common/dist/lib/models/metaplex/setStore';
import { setWhitelistedCreator } from '@oyster/common/dist/lib/models/metaplex/setWhitelistedCreator';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { WhitelistedCreator } from '../models/metaplex';
import { setStore } from '../models/metaplex/setStore';
import { setWhitelistedCreator } from '../models/metaplex/setWhitelistedCreator';
// TODO if this becomes very slow move to batching txns like we do with settle.ts // TODO if this becomes very slow move to batching txns like we do with settle.ts
// but given how little this should be used keep it simple // but given how little this should be used keep it simple

View File

@ -1,11 +1,8 @@
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js'; import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
import { import {
actions,
sendTransactionWithRetry, sendTransactionWithRetry,
placeBid, placeBid,
models,
cache, cache,
TokenAccount,
ensureWrappedAccount, ensureWrappedAccount,
toLamports, toLamports,
ParsedAccount, ParsedAccount,
@ -13,15 +10,16 @@ import {
WalletSigner, WalletSigner,
} from '@oyster/common'; } from '@oyster/common';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { approve } from '@oyster/common/dist/lib/models/account';
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
import { TokenAccount } from '@oyster/common/dist/lib/models/account';
import { AccountLayout, MintInfo } from '@solana/spl-token'; import { AccountLayout, MintInfo } from '@solana/spl-token';
import { AuctionView } from '../hooks'; import { AuctionView } from '../hooks';
import BN from 'bn.js'; import BN from 'bn.js';
import { setupCancelBid } from './cancelBid'; import { setupCancelBid } from './cancelBid';
import { QUOTE_MINT } from '../constants'; import { QUOTE_MINT } from '../constants';
const { createTokenAccount } = actions;
const { approve } = models;
export async function sendPlaceBid( export async function sendPlaceBid(
connection: Connection, connection: Connection,
wallet: WalletSigner, wallet: WalletSigner,

View File

@ -1,9 +1,7 @@
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js'; import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
import { import {
actions,
ParsedAccount, ParsedAccount,
programIds, programIds,
models,
TokenAccount, TokenAccount,
createMint, createMint,
SafetyDepositBox, SafetyDepositBox,
@ -28,8 +26,10 @@ import {
} from '@oyster/common'; } from '@oyster/common';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
import { AccountLayout, MintLayout, Token } from '@solana/spl-token'; import { AccountLayout, MintLayout, Token } from '@solana/spl-token';
import { AuctionView, AuctionViewItem } from '../hooks'; import { AuctionView } from '../hooks';
import { import {
AuctionManagerV1,
ParticipationStateV1,
WinningConfigType, WinningConfigType,
NonWinningConstraint, NonWinningConstraint,
redeemBid, redeemBid,
@ -41,21 +41,18 @@ import {
PrizeTrackingTicket, PrizeTrackingTicket,
getPrizeTrackingTicket, getPrizeTrackingTicket,
BidRedemptionTicket, BidRedemptionTicket,
} from '../models/metaplex'; AuctionViewItem,
import { claimBid } from '../models/metaplex/claimBid'; } from '@oyster/common/dist/lib/models/metaplex/index';
import { claimBid } from '@oyster/common/dist/lib/models/metaplex/claimBid';
import { approve } from '@oyster/common/dist/lib/models/account';
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
import { setupCancelBid } from './cancelBid'; import { setupCancelBid } from './cancelBid';
import { deprecatedPopulateParticipationPrintingAccount } from '../models/metaplex/deprecatedPopulateParticipationPrintingAccount'; import { deprecatedPopulateParticipationPrintingAccount } from '@oyster/common/dist/lib/models/metaplex/deprecatedPopulateParticipationPrintingAccount';
import { setupPlaceBid } from './sendPlaceBid'; import { setupPlaceBid } from './sendPlaceBid';
import { claimUnusedPrizes } from './claimUnusedPrizes'; import { claimUnusedPrizes } from './claimUnusedPrizes';
import { createMintAndAccountWithOne } from './createMintAndAccountWithOne'; import { createMintAndAccountWithOne } from './createMintAndAccountWithOne';
import { BN } from 'bn.js'; import { BN } from 'bn.js';
import { QUOTE_MINT } from '../constants'; import { QUOTE_MINT } from '../constants';
import {
AuctionManagerV1,
ParticipationStateV1,
} from '../models/metaplex/deprecatedStates';
const { createTokenAccount } = actions;
const { approve } = models;
export function eligibleForParticipationPrizeGivenWinningIndex( export function eligibleForParticipationPrizeGivenWinningIndex(
winnerIndex: number | null, winnerIndex: number | null,

View File

@ -16,8 +16,8 @@ import {
import { AuctionView } from '../hooks'; import { AuctionView } from '../hooks';
import { claimBid } from '../models/metaplex/claimBid'; import { claimBid } from '@oyster/common/dist/lib/models/metaplex/claimBid';
import { emptyPaymentAccount } from '../models/metaplex/emptyPaymentAccount'; import { emptyPaymentAccount } from '@oyster/common/dist/lib/models/metaplex/emptyPaymentAccount';
import { QUOTE_MINT } from '../constants'; import { QUOTE_MINT } from '../constants';
import { setupPlaceBid } from './sendPlaceBid'; import { setupPlaceBid } from './sendPlaceBid';
import { WalletNotConnectedError } from '@solana/wallet-adapter-base'; import { WalletNotConnectedError } from '@solana/wallet-adapter-base';

View File

@ -1,5 +1,5 @@
import { Connection, Keypair, TransactionInstruction } from '@solana/web3.js'; import { Connection, Keypair, TransactionInstruction } from '@solana/web3.js';
import { startAuction } from '../models/metaplex'; import { startAuction } from '@oyster/common/dist/lib/models/metaplex/index';
import { notify, sendTransactionWithRetry, WalletSigner } from '@oyster/common'; import { notify, sendTransactionWithRetry, WalletSigner } from '@oyster/common';
import { AuctionView } from '../hooks'; import { AuctionView } from '../hooks';

View File

@ -1,13 +1,12 @@
import React, { Ref, useCallback, useEffect, useState } from 'react'; import React, { Ref, useCallback, useEffect, useState } from 'react';
import { Image } from 'antd'; import { Image } from 'antd';
import { MetadataCategory, MetadataFile } from '@oyster/common'; import { MetadataCategory, MetadataFile, pubkeyToString } from '@oyster/common';
import { MeshViewer } from '../MeshViewer'; import { MeshViewer } from '../MeshViewer';
import { ThreeDots } from '../MyLoader'; import { ThreeDots } from '../MyLoader';
import { useCachedImage, useExtendedArt } from '../../hooks'; import { useCachedImage, useExtendedArt } from '../../hooks';
import { Stream, StreamPlayerApi } from '@cloudflare/stream-react'; import { Stream, StreamPlayerApi } from '@cloudflare/stream-react';
import { PublicKey } from '@solana/web3.js'; import { PublicKey } from '@solana/web3.js';
import { getLast } from '../../utils/utils'; import { getLast } from '../../utils/utils';
import { pubkeyToString } from '../../utils/pubkeyToString';
const MeshArtContent = ({ const MeshArtContent = ({
uri, uri,

View File

@ -43,7 +43,7 @@ import { findEligibleParticipationBidsForRedemption } from '../../actions/claimU
import { import {
BidRedemptionTicket, BidRedemptionTicket,
MAX_PRIZE_TRACKING_TICKET_SIZE, MAX_PRIZE_TRACKING_TICKET_SIZE,
} from '../../models/metaplex'; } from '@oyster/common/dist/lib/models/metaplex/index';
async function calculateTotalCostOfRedeemingOtherPeoplesBids( async function calculateTotalCostOfRedeemingOtherPeoplesBids(
connection: Connection, connection: Connection,

View File

@ -1,2 +1,2 @@
export * from './meta'; export * from '@oyster/common/dist/lib/contexts/meta/meta';
export * from './coingecko'; export * from './coingecko';

View File

@ -1 +0,0 @@
export * from './meta';

View File

@ -1,241 +0,0 @@
import {
useConnection,
useStore,
AUCTION_ID,
METAPLEX_ID,
VAULT_ID,
METADATA_PROGRAM_ID,
toPublicKey,
useQuerySearch,
} from '@oyster/common';
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { MetaState, MetaContextState, UpdateStateValueFunc } from './types';
import { queryExtendedMetadata } from './queryExtendedMetadata';
import { processAuctions } from './processAuctions';
import { processMetaplexAccounts } from './processMetaplexAccounts';
import { processMetaData } from './processMetaData';
import { processVaultData } from './processVaultData';
import {
loadAccounts,
makeSetter,
metadataByMintUpdater,
} from './loadAccounts';
import { onChangeAccount } from './onChangeAccount';
const MetaContext = React.createContext<MetaContextState>({
metadata: [],
metadataByMint: {},
masterEditions: {},
masterEditionsByPrintingMint: {},
masterEditionsByOneTimeAuthMint: {},
metadataByMasterEdition: {},
editions: {},
auctionManagersByAuction: {},
auctions: {},
auctionDataExtended: {},
vaults: {},
store: null,
isLoading: false,
bidderMetadataByAuctionAndBidder: {},
safetyDepositBoxesByVaultAndIndex: {},
safetyDepositConfigsByAuctionManagerAndIndex: {},
bidRedemptionV2sByAuctionManagerAndWinningIndex: {},
bidderPotsByAuctionAndBidder: {},
bidRedemptions: {},
whitelistedCreatorsByCreator: {},
payoutTickets: {},
prizeTrackingTickets: {},
stores: {},
});
export function MetaProvider({ children = null as any }) {
const connection = useConnection();
const { isReady, storeAddress } = useStore();
const searchParams = useQuerySearch();
const all = searchParams.get('all') == 'true';
const [state, setState] = useState<MetaState>({
metadata: [],
metadataByMint: {},
masterEditions: {},
masterEditionsByPrintingMint: {},
masterEditionsByOneTimeAuthMint: {},
metadataByMasterEdition: {},
editions: {},
auctionManagersByAuction: {},
bidRedemptions: {},
auctions: {},
auctionDataExtended: {},
vaults: {},
payoutTickets: {},
store: null,
whitelistedCreatorsByCreator: {},
bidderMetadataByAuctionAndBidder: {},
bidderPotsByAuctionAndBidder: {},
safetyDepositBoxesByVaultAndIndex: {},
prizeTrackingTickets: {},
safetyDepositConfigsByAuctionManagerAndIndex: {},
bidRedemptionV2sByAuctionManagerAndWinningIndex: {},
stores: {},
});
const [isLoading, setIsLoading] = useState(true);
const updateMints = useCallback(
async metadataByMint => {
try {
if (!all) {
const { metadata, mintToMetadata } = await queryExtendedMetadata(
connection,
metadataByMint,
);
setState(current => ({
...current,
metadata,
metadataByMint: mintToMetadata,
}));
}
} catch (er) {
console.error(er);
}
},
[setState],
);
useEffect(() => {
(async () => {
if (!storeAddress) {
if (isReady) {
setIsLoading(false);
}
return;
} else if (!state.store) {
setIsLoading(true);
}
console.log('-----> Query started');
const nextState = await loadAccounts(connection, all);
console.log('------->Query finished');
setState(nextState);
setIsLoading(false);
console.log('------->set finished');
updateMints(nextState.metadataByMint);
})();
}, [connection, setState, updateMints, storeAddress, isReady]);
const updateStateValue = useMemo<UpdateStateValueFunc>(
() => (prop, key, value) => {
setState(current => makeSetter({ ...current })(prop, key, value));
},
[setState],
);
const store = state.store;
const whitelistedCreatorsByCreator = state.whitelistedCreatorsByCreator;
useEffect(() => {
if (isLoading) {
return;
}
const vaultSubId = connection.onProgramAccountChange(
toPublicKey(VAULT_ID),
onChangeAccount(processVaultData, updateStateValue, all),
);
const auctionSubId = connection.onProgramAccountChange(
toPublicKey(AUCTION_ID),
onChangeAccount(processAuctions, updateStateValue, all),
);
const metaplexSubId = connection.onProgramAccountChange(
toPublicKey(METAPLEX_ID),
onChangeAccount(processMetaplexAccounts, updateStateValue, all),
);
const metaSubId = connection.onProgramAccountChange(
toPublicKey(METADATA_PROGRAM_ID),
onChangeAccount(
processMetaData,
async (prop, key, value) => {
if (prop === 'metadataByMint') {
const nextState = await metadataByMintUpdater(value, state, all);
setState(nextState);
} else {
updateStateValue(prop, key, value);
}
},
all,
),
);
return () => {
connection.removeProgramAccountChangeListener(vaultSubId);
connection.removeProgramAccountChangeListener(metaplexSubId);
connection.removeProgramAccountChangeListener(metaSubId);
connection.removeProgramAccountChangeListener(auctionSubId);
};
}, [
connection,
updateStateValue,
setState,
store,
whitelistedCreatorsByCreator,
isLoading,
]);
// TODO: fetch names dynamically
// TODO: get names for creators
// useEffect(() => {
// (async () => {
// const twitterHandles = await connection.getProgramAccounts(NAME_PROGRAM_ID, {
// filters: [
// {
// dataSize: TWITTER_ACCOUNT_LENGTH,
// },
// {
// memcmp: {
// offset: VERIFICATION_AUTHORITY_OFFSET,
// bytes: TWITTER_VERIFICATION_AUTHORITY.toBase58()
// }
// }
// ]
// });
// const handles = twitterHandles.map(t => {
// const owner = new PublicKey(t.account.data.slice(32, 64));
// const name = t.account.data.slice(96, 114).toString();
// });
// console.log(handles);
// })();
// }, [whitelistedCreatorsByCreator]);
return (
<MetaContext.Provider
value={{
...state,
isLoading,
}}
>
{children}
</MetaContext.Provider>
);
}
export const useMeta = () => {
const context = useContext(MetaContext);
return context;
};

View File

@ -9,11 +9,12 @@ import {
Metadata, Metadata,
ParsedAccount, ParsedAccount,
StringPublicKey, StringPublicKey,
useLocalStorage,
pubkeyToString,
} from '@oyster/common'; } from '@oyster/common';
import { WhitelistedCreator } from '../models/metaplex'; import { WhitelistedCreator } from '@oyster/common/dist/lib/models/metaplex/index';
import { Cache } from 'three'; import { Cache } from 'three';
import { useInView } from 'react-intersection-observer'; import { useInView } from 'react-intersection-observer';
import { pubkeyToString } from '../utils/pubkeyToString';
const metadataToArt = ( const metadataToArt = (
info: Metadata | undefined, info: Metadata | undefined,
@ -159,6 +160,7 @@ export const useExtendedArt = (id?: StringPublicKey) => {
const [data, setData] = useState<IMetadataExtension>(); const [data, setData] = useState<IMetadataExtension>();
const { ref, inView } = useInView(); const { ref, inView } = useInView();
const localStorage = useLocalStorage();
const key = pubkeyToString(id); const key = pubkeyToString(id);

View File

@ -18,14 +18,15 @@ import { useMeta } from '../contexts';
import { import {
AuctionManager, AuctionManager,
AuctionManagerStatus, AuctionManagerStatus,
AuctionManagerV1,
AuctionManagerV2, AuctionManagerV2,
BidRedemptionTicket, BidRedemptionTicket,
BidRedemptionTicketV2, BidRedemptionTicketV2,
getBidderKeys, getBidderKeys,
SafetyDepositConfig, SafetyDepositConfig,
WinningConfigType, WinningConfigType,
} from '../models/metaplex'; AuctionViewItem,
import { AuctionManagerV1 } from '../models/metaplex/deprecatedStates'; } from '@oyster/common/dist/lib/models/metaplex/index';
export enum AuctionViewState { export enum AuctionViewState {
Live = '0', Live = '0',
@ -35,14 +36,6 @@ export enum AuctionViewState {
Defective = '-1', Defective = '-1',
} }
export interface AuctionViewItem {
winningConfigType: WinningConfigType;
amount: BN;
metadata: ParsedAccount<Metadata>;
safetyDeposit: ParsedAccount<SafetyDepositBox>;
masterEdition?: ParsedAccount<MasterEditionV1 | MasterEditionV2>;
}
// Flattened surface item for easy display // Flattened surface item for easy display
export interface AuctionView { export interface AuctionView {
// items 1:1 with winning configs FOR NOW // items 1:1 with winning configs FOR NOW

View File

@ -1,6 +1,5 @@
import { StringPublicKey } from '@oyster/common'; import { StringPublicKey, pubkeyToString } from '@oyster/common';
import { useMeta } from '../contexts'; import { useMeta } from '../contexts';
import { pubkeyToString } from '../utils/pubkeyToString';
export const useCreator = (id?: StringPublicKey) => { export const useCreator = (id?: StringPublicKey) => {
const { whitelistedCreatorsByCreator } = useMeta(); const { whitelistedCreatorsByCreator } = useMeta();

View File

@ -12,7 +12,7 @@ import {
ParticipationConfigV2, ParticipationConfigV2,
WinningConfigType, WinningConfigType,
WinningConstraint, WinningConstraint,
} from '../models/metaplex'; } from '@oyster/common/dist/lib/models/metaplex/index';
import { useMeta } from './../contexts'; import { useMeta } from './../contexts';
export const useUserArts = (): SafetyDepositDraft[] => { export const useUserArts = (): SafetyDepositDraft[] => {

View File

@ -3,11 +3,11 @@ import {
ConnectionProvider, ConnectionProvider,
StoreProvider, StoreProvider,
WalletProvider, WalletProvider,
MetaProvider,
} from '@oyster/common'; } from '@oyster/common';
import { FC } from 'react'; import { FC } from 'react';
import { ConfettiProvider } from './components/Confetti'; import { ConfettiProvider } from './components/Confetti';
import { AppLayout } from './components/Layout'; import { AppLayout } from './components/Layout';
import { MetaProvider } from './contexts/meta';
import { CoingeckoProvider } from './contexts/coingecko'; import { CoingeckoProvider } from './contexts/coingecko';
export const Providers: FC = ({ children }) => { export const Providers: FC = ({ children }) => {

View File

@ -1,3 +1,4 @@
import { useLocalStorage } from '@oyster/common';
import { TokenInfo } from '@solana/spl-token-registry'; import { TokenInfo } from '@solana/spl-token-registry';
export const LAMPORT_MULTIPLIER = 10 ** 9; export const LAMPORT_MULTIPLIER = 10 ** 9;
@ -8,6 +9,7 @@ export const filterModalSolTokens = (tokens: TokenInfo[]) => {
}; };
export async function getAssetCostToStore(files: File[]) { export async function getAssetCostToStore(files: File[]) {
const localStorage = useLocalStorage();
const totalBytes = files.reduce((sum, f) => (sum += f.size), 0); const totalBytes = files.reduce((sum, f) => (sum += f.size), 0);
console.log('Total bytes', totalBytes); console.log('Total bytes', totalBytes);
const txnFeeInWinstons = parseInt( const txnFeeInWinstons = parseInt(

View File

@ -1,5 +0,0 @@
import { PublicKey } from '@solana/web3.js';
export const pubkeyToString = (key: PublicKey | string = '') => {
return typeof key === 'string' ? key : key?.toBase58() || '';
};

View File

@ -12,7 +12,10 @@ import {
Divider, Divider,
} from 'antd'; } from 'antd';
import { useMeta } from '../../contexts'; import { useMeta } from '../../contexts';
import { Store, WhitelistedCreator } from '../../models/metaplex'; import {
Store,
WhitelistedCreator,
} from '@oyster/common/dist/lib/models/metaplex/index';
import { import {
MasterEditionV1, MasterEditionV1,
notify, notify,

View File

@ -1,7 +1,11 @@
import React, { Dispatch, SetStateAction, useState } from 'react'; import React, { Dispatch, SetStateAction, useState } from 'react';
import { Layout, Button, Col, Spin } from 'antd'; import { Layout, Button, Col, Spin } from 'antd';
import { useMeta } from '../../contexts'; import { useMeta } from '../../contexts';
import { AuctionManagerV2, WinningConfigType } from '../../models/metaplex'; import {
AuctionManagerV1,
AuctionManagerV2,
WinningConfigType,
} from '@oyster/common/dist/lib/models/metaplex/index';
import { Pie, Bar } from 'react-chartjs-2'; import { Pie, Bar } from 'react-chartjs-2';
import { import {
AuctionDataExtended, AuctionDataExtended,
@ -16,7 +20,6 @@ import {
import { AuctionView, useAuctions } from '../../hooks'; import { AuctionView, useAuctions } from '../../hooks';
import { QUOTE_MINT } from '../../constants'; import { QUOTE_MINT } from '../../constants';
import { MintInfo } from '@solana/spl-token'; import { MintInfo } from '@solana/spl-token';
import { AuctionManagerV1 } from '../../models/metaplex/deprecatedStates';
const { Content } = Layout; const { Content } = Layout;
export const AnalyticsView = () => { export const AnalyticsView = () => {

View File

@ -33,7 +33,7 @@ import {
NonWinningConstraint, NonWinningConstraint,
PayoutTicket, PayoutTicket,
WinningConstraint, WinningConstraint,
} from '../../models/metaplex'; } from '@oyster/common/dist/lib/models/metaplex/index';
import { Connection } from '@solana/web3.js'; import { Connection } from '@solana/web3.js';
import { settle } from '../../actions/settle'; import { settle } from '../../actions/settle';
import { MintInfo } from '@solana/spl-token'; import { MintInfo } from '@solana/spl-token';

View File

@ -3,9 +3,9 @@ import { useParams } from 'react-router-dom';
import { Row, Col, Button, Skeleton, Carousel, List, Card } from 'antd'; import { Row, Col, Button, Skeleton, Carousel, List, Card } from 'antd';
import { AuctionCard } from '../../components/AuctionCard'; import { AuctionCard } from '../../components/AuctionCard';
import { Connection } from '@solana/web3.js'; import { Connection } from '@solana/web3.js';
import { AuctionViewItem } from '@oyster/common/dist/lib/models/metaplex/index';
import { import {
AuctionView as Auction, AuctionView as Auction,
AuctionViewItem,
useArt, useArt,
useAuction, useAuction,
useBidsForAuction, useBidsForAuction,

View File

@ -37,7 +37,10 @@ import { useWallet } from '@solana/wallet-adapter-react';
import { MintLayout } from '@solana/spl-token'; import { MintLayout } from '@solana/spl-token';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { capitalize } from 'lodash'; import { capitalize } from 'lodash';
import { WinningConfigType, AmountRange } from '../../models/metaplex'; import {
WinningConfigType,
AmountRange,
} from '@oyster/common/dist/lib/models/metaplex/index';
import moment from 'moment'; import moment from 'moment';
import { import {
createAuctionManager, createAuctionManager,

View File

@ -2,7 +2,7 @@ import {
useConnection, useConnection,
useStore, useStore,
useWalletModal, useWalletModal,
WalletSigner, WhitelistedCreator,
} from '@oyster/common'; } from '@oyster/common';
import { useWallet } from '@solana/wallet-adapter-react'; import { useWallet } from '@solana/wallet-adapter-react';
import { Button } from 'antd'; import { Button } from 'antd';
@ -10,9 +10,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { saveAdmin } from '../../actions/saveAdmin'; import { saveAdmin } from '../../actions/saveAdmin';
import { useMeta } from '../../contexts'; import { useMeta } from '../../contexts';
import { WhitelistedCreator } from '../../models/metaplex';
import { SetupVariables } from '../../components/SetupVariables'; import { SetupVariables } from '../../components/SetupVariables';
import { WalletAdapter } from '@solana/wallet-adapter-base';
export const SetupView = () => { export const SetupView = () => {
const [isInitalizingStore, setIsInitalizingStore] = useState(false); const [isInitalizingStore, setIsInitalizingStore] = useState(false);