mirror of https://github.com/certusone/oyster.git
Merge branch 'feature/m' into main
This commit is contained in:
commit
e316021029
|
@ -0,0 +1 @@
|
|||
REACT_APP_CLIENT_ID=BKBTX-SmaEFGddZQrwqd65YFoImRQLca_Tj2IdmKyD2UbDpzrtN2WQ-NYLuej6gP0DfF3jSpEkI13wPt1uPedm0
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="226.000000pt" height="248.000000pt" viewBox="0 0 226.000000 248.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.16, written by Peter Selinger 2001-2019
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,248.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M482 2143 c-43 -21 -79 -64 -92 -111 -13 -48 -13 -246 0 -294 29
|
||||
-103 92 -128 322 -128 l158 0 0 -588 0 -589 23 -43 c16 -30 37 -51 67 -67 39
|
||||
-21 56 -23 182 -23 94 0 149 4 171 14 43 17 95 78 102 118 3 18 5 389 3 825
|
||||
l-3 791 -31 39 c-59 74 -52 73 -484 73 -334 0 -389 -3 -418 -17z"/>
|
||||
<path d="M1743 2144 c-57 -21 -126 -84 -154 -141 -35 -73 -32 -171 6 -241 54
|
||||
-100 132 -146 245 -146 115 0 197 50 246 151 108 220 -110 459 -343 377z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 957 B |
|
@ -21,11 +21,14 @@ export const CurrentUserBadge = (props: { showBalance?: boolean, showAddress?: b
|
|||
{
|
||||
marginLeft: '0.5rem',
|
||||
display: 'flex',
|
||||
width: props.iconSize
|
||||
width: props.iconSize,
|
||||
borderRadius: 50,
|
||||
|
||||
} :{
|
||||
display: 'flex',
|
||||
width: props.iconSize,
|
||||
paddingLeft: 0,
|
||||
borderRadius: 50,
|
||||
};
|
||||
|
||||
const baseWalletKey: React.CSSProperties = { height: props.iconSize, cursor: 'pointer', userSelect: 'none' };
|
||||
|
@ -33,6 +36,21 @@ export const CurrentUserBadge = (props: { showBalance?: boolean, showAddress?: b
|
|||
baseWalletKey
|
||||
:{ ...baseWalletKey, paddingLeft: 0 };
|
||||
|
||||
let name = props.showAddress ? shortenAddress(`${wallet.publicKey}`) : '';
|
||||
const unknownWallet = wallet as any;
|
||||
if(unknownWallet.name) {
|
||||
name = unknownWallet.name;
|
||||
}
|
||||
|
||||
let image = <Identicon
|
||||
address={wallet.publicKey?.toBase58()}
|
||||
style={iconStyle}
|
||||
/>;
|
||||
|
||||
if(unknownWallet.image) {
|
||||
image = <img src={unknownWallet.image} style={iconStyle} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wallet-wrapper">
|
||||
{props.showBalance && <span>
|
||||
|
@ -46,11 +64,8 @@ export const CurrentUserBadge = (props: { showBalance?: boolean, showAddress?: b
|
|||
trigger="click"
|
||||
>
|
||||
<div className="wallet-key" style={walletKeyStyle}>
|
||||
{props.showAddress && shortenAddress(`${wallet.publicKey}`)}
|
||||
<Identicon
|
||||
address={wallet.publicKey?.toBase58()}
|
||||
style={iconStyle}
|
||||
/>
|
||||
{name && (<span style={{ marginRight: '0.5rem' }}>{name}</span>)}
|
||||
{image}
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
|
|
|
@ -30,7 +30,7 @@ export const Identicon = (props: {
|
|||
),
|
||||
);
|
||||
|
||||
}catch (err) {
|
||||
} catch (err) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,17 @@ import React from 'react';
|
|||
import { Button, Select } from 'antd';
|
||||
import { useWallet } from '../../contexts/wallet';
|
||||
import { ENDPOINTS, useConnectionConfig } from '../../contexts/connection';
|
||||
import { shortenAddress } from '../../utils';
|
||||
import {
|
||||
CopyOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
export const Settings = ({
|
||||
additionalSettings,
|
||||
}: {
|
||||
additionalSettings?: JSX.Element;
|
||||
}) => {
|
||||
const { connected, disconnect } = useWallet();
|
||||
const { connected, disconnect, select, wallet } = useWallet();
|
||||
const { endpoint, setEndpoint } = useConnectionConfig();
|
||||
|
||||
return (
|
||||
|
@ -27,9 +31,23 @@ export const Settings = ({
|
|||
))}
|
||||
</Select>
|
||||
{connected && (
|
||||
<Button type="primary" onClick={disconnect}>
|
||||
<>
|
||||
<span>Wallet:</span>
|
||||
{wallet?.publicKey && (<Button
|
||||
style={{ marginBottom: 5 }} onClick={() => navigator.clipboard.writeText(wallet.publicKey?.toBase58() || '')}>
|
||||
<CopyOutlined />
|
||||
{shortenAddress(wallet?.publicKey.toBase58())}
|
||||
</Button>)}
|
||||
|
||||
<Button onClick={select}
|
||||
style={{ marginBottom: 5 }}>
|
||||
Change
|
||||
</Button>
|
||||
<Button type="primary" onClick={disconnect}
|
||||
style={{ marginBottom: 5 }}>
|
||||
Disconnect
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{additionalSettings}
|
||||
</div>
|
||||
|
|
|
@ -416,35 +416,35 @@ export function AccountsProvider({ children = null as any }) {
|
|||
if (!connection || !publicKey) {
|
||||
setTokenAccounts([]);
|
||||
} else {
|
||||
precacheUserTokenAccounts(connection, LEND_HOST_FEE_ADDRESS);
|
||||
// precacheUserTokenAccounts(connection, LEND_HOST_FEE_ADDRESS);
|
||||
|
||||
precacheUserTokenAccounts(connection, publicKey).then(() => {
|
||||
setTokenAccounts(selectUserAccounts());
|
||||
});
|
||||
// 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);
|
||||
// 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',
|
||||
);
|
||||
// if (PRECACHED_OWNERS.has(data.owner.toBase58())) {
|
||||
// cache.add(id, info.accountInfo, TokenAccountParser);
|
||||
// setTokenAccounts(selectUserAccounts());
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// 'singleGossip',
|
||||
// );
|
||||
|
||||
return () => {
|
||||
connection.removeProgramAccountChangeListener(tokenSubID);
|
||||
// connection.removeProgramAccountChangeListener(tokenSubID);
|
||||
};
|
||||
}
|
||||
}, [connection, connected, publicKey, selectUserAccounts]);
|
||||
|
|
|
@ -9,6 +9,8 @@ import { useLocalStorageState } from "../utils/utils";
|
|||
import { LedgerProvider } from "@solana/wallet-ledger";
|
||||
import { SolongWalletAdapter } from "../wallet-adapters/solong";
|
||||
import { PhantomWalletAdapter } from "../wallet-adapters/phantom";
|
||||
import { TorusWalletAdapter } from "../wallet-adapters/torus";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
const ASSETS_URL = 'https://raw.githubusercontent.com/solana-labs/oyster/main/assets/wallets/';
|
||||
export const WALLET_PROVIDERS = [
|
||||
|
@ -37,6 +39,12 @@ export const WALLET_PROVIDERS = [
|
|||
icon: `https://www.phantom.app/img/logo.png`,
|
||||
adapter: PhantomWalletAdapter,
|
||||
},
|
||||
{
|
||||
name: 'Torus',
|
||||
url: 'https://tor.us',
|
||||
icon: `${ASSETS_URL}torus.svg`,
|
||||
adapter: TorusWalletAdapter,
|
||||
}
|
||||
];
|
||||
|
||||
const WalletContext = React.createContext<{
|
||||
|
@ -53,8 +61,8 @@ const WalletContext = React.createContext<{
|
|||
|
||||
export function WalletProvider({ children = null as any }) {
|
||||
const { endpoint } = useConnectionConfig();
|
||||
|
||||
const [autoConnect, setAutoConnect] = useState(false);
|
||||
const location = useLocation();
|
||||
const [autoConnect, setAutoConnect] = useState(location.pathname.indexOf('result=') >= 0 || false);
|
||||
const [providerUrl, setProviderUrl] = useLocalStorageState("walletProvider");
|
||||
|
||||
const provider = useMemo(() => WALLET_PROVIDERS.find(({ url }) => url === providerUrl), [providerUrl]);
|
||||
|
@ -90,6 +98,7 @@ export function WalletProvider({ children = null as any }) {
|
|||
|
||||
wallet.on("disconnect", () => {
|
||||
setConnected(false);
|
||||
// setProviderUrl(null)
|
||||
notify({
|
||||
message: "Wallet update",
|
||||
description: "Disconnected from wallet",
|
||||
|
@ -99,6 +108,7 @@ export function WalletProvider({ children = null as any }) {
|
|||
|
||||
return () => {
|
||||
setConnected(false);
|
||||
// setProviderUrl(null)
|
||||
if (wallet) {
|
||||
wallet.disconnect();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import EventEmitter from "eventemitter3"
|
||||
import { Account, PublicKey, Transaction } from "@solana/web3.js"
|
||||
import { WalletAdapter } from "@solana/wallet-base"
|
||||
import OpenLogin from "@toruslabs/openlogin"
|
||||
import { getED25519Key } from "@toruslabs/openlogin-ed25519"
|
||||
|
||||
const getSolanaPrivateKey = (openloginKey: string)=>{
|
||||
const { sk } = getED25519Key(openloginKey)
|
||||
return sk
|
||||
}
|
||||
|
||||
export class TorusWalletAdapter extends EventEmitter implements WalletAdapter {
|
||||
_provider: OpenLogin | undefined;
|
||||
endpoint: string;
|
||||
providerUrl: string;
|
||||
account: Account | undefined;
|
||||
image: string = '';
|
||||
name: string = '';
|
||||
|
||||
constructor(providerUrl: string, endpoint: string) {
|
||||
super()
|
||||
this.connect = this.connect.bind(this)
|
||||
this.endpoint = endpoint;
|
||||
this.providerUrl = providerUrl;
|
||||
}
|
||||
|
||||
async signAllTransactions(transactions: Transaction[]): Promise<Transaction[]> {
|
||||
if(this.account) {
|
||||
let account = this.account;
|
||||
transactions.forEach(t => t.partialSign(account));
|
||||
}
|
||||
|
||||
return transactions
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this.account?.publicKey || null;
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
if(this.account) {
|
||||
transaction.partialSign(this.account)
|
||||
}
|
||||
|
||||
return transaction
|
||||
}
|
||||
|
||||
connect = async () => {
|
||||
this._provider = new OpenLogin({
|
||||
clientId: process.env.REACT_APP_CLIENT_ID || 'BKBTX-SmaEFGddZQrwqd65YFoImRQLca_Tj2IdmKyD2UbDpzrtN2WQ-NYLuej6gP0DfF3jSpEkI13wPt1uPedm0',
|
||||
network: "mainnet", // mainnet, testnet, development
|
||||
});
|
||||
|
||||
try {
|
||||
await this._provider.init();
|
||||
} catch (ex) {
|
||||
console.error('init failed', ex)
|
||||
}
|
||||
|
||||
console.error(this._provider?.state.store);
|
||||
|
||||
if (this._provider.privKey) {
|
||||
const privateKey = this._provider.privKey;
|
||||
const secretKey = getSolanaPrivateKey(privateKey);
|
||||
this.account = new Account(secretKey);
|
||||
} else {
|
||||
try {
|
||||
const { privKey } = await this._provider.login();
|
||||
const secretKey = getSolanaPrivateKey(privKey);
|
||||
this.account = new Account(secretKey);
|
||||
} catch(ex) {
|
||||
console.error('login failed', ex);
|
||||
}
|
||||
}
|
||||
|
||||
this.name = this._provider?.state.store.get('name');;
|
||||
this.image = this._provider?.state.store.get('profileImage');;
|
||||
|
||||
this.emit("connect");
|
||||
}
|
||||
|
||||
disconnect = async () => {
|
||||
console.log("Disconnecting...")
|
||||
if (this._provider) {
|
||||
await this._provider.logout();
|
||||
await this._provider._cleanup();
|
||||
this._provider = undefined;
|
||||
this.emit("disconnect");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,9 @@
|
|||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"@toruslabs/openlogin": "^0.7.0",
|
||||
"@toruslabs/openlogin-ed25519": "^0.7.0",
|
||||
"@toruslabs/openlogin-utils": "^0.7.0",
|
||||
"@toruslabs/torus-embed": "^1.9.10",
|
||||
"@types/animejs": "^3.1.3",
|
||||
"@types/chart.js": "^2.9.29",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const createAuction = () => {
|
||||
// TODO:
|
||||
};
|
|
@ -1 +1,3 @@
|
|||
export const nop = () => {};
|
||||
export * from './nft';
|
||||
export * from './vault';
|
||||
export * from './auction';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const createVault = () => {
|
||||
// TODO:
|
||||
};
|
|
@ -10,7 +10,7 @@ const UserActions = () => {
|
|||
<Link to={`/art/create`}>
|
||||
<Button className="app-btn">Create</Button>
|
||||
</Link>
|
||||
<Link to={`/auction/create`}>
|
||||
<Link to={`/auction/create/0`}>
|
||||
<Button type="primary">Sell</Button>
|
||||
</Link>
|
||||
</>;
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
import React, { useLayoutEffect, useState } from 'react';
|
||||
import { Card, Avatar } from 'antd';
|
||||
import { Card, Avatar, CardProps } from 'antd';
|
||||
import { MetadataCategory } from '@oyster/common';
|
||||
import { ArtContent } from './../ArtContent';
|
||||
import './index.less';
|
||||
|
||||
const { Meta } = Card;
|
||||
|
||||
export const ArtCard = ({
|
||||
image,
|
||||
category,
|
||||
name,
|
||||
symbol,
|
||||
description,
|
||||
artist,
|
||||
preview,
|
||||
small,
|
||||
}: {
|
||||
export interface ArtCardProps extends CardProps {
|
||||
image?: string;
|
||||
category?: MetadataCategory
|
||||
name?: string;
|
||||
|
@ -23,13 +14,17 @@ export const ArtCard = ({
|
|||
description?: string;
|
||||
artist?: string;
|
||||
preview?: boolean;
|
||||
small?: boolean
|
||||
}) => {
|
||||
small?: boolean;
|
||||
}
|
||||
|
||||
export const ArtCard = (props: ArtCardProps) => {
|
||||
const { className, small, category, image, name, preview, artist, ...rest } = props;
|
||||
return (
|
||||
<Card
|
||||
hoverable={true}
|
||||
className={`art-card ${small ? 'small' : ''}`}
|
||||
cover={<ArtContent category={category} content={image} />}
|
||||
className={`art-card ${small ? 'small' : ''} ${className}`}
|
||||
cover={<ArtContent category={category} content={image} preview={preview} />}
|
||||
{...rest}
|
||||
>
|
||||
<Meta
|
||||
title={`${name}`}
|
||||
|
|
|
@ -2,11 +2,27 @@ import React, { useMemo } from 'react';
|
|||
import { Image } from 'antd';
|
||||
import { MetadataCategory } from '@oyster/common'
|
||||
|
||||
export const ArtContent = ({ content, category, className }: { category?: MetadataCategory, content?: string, className?: string }) => {
|
||||
export const ArtContent = ({
|
||||
content,
|
||||
category,
|
||||
className,
|
||||
preview
|
||||
}: {
|
||||
category?: MetadataCategory,
|
||||
content?: string,
|
||||
className?: string,
|
||||
preview?: boolean,
|
||||
}) => {
|
||||
return category === 'video' ?
|
||||
<video src={content} className={className} playsInline={true} autoPlay={true} controlsList="nodownload" loop={true} /> :
|
||||
<video src={content}
|
||||
className={className}
|
||||
playsInline={true}
|
||||
autoPlay={true}
|
||||
controlsList="nodownload"
|
||||
loop={true} /> :
|
||||
<Image
|
||||
src={content}
|
||||
preview={preview}
|
||||
wrapperClassName={className}
|
||||
/>;
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './useArt';
|
||||
export * from './useAuctions';
|
||||
export * from './useUserArts';
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { TokenAccount, useUserAccounts } from '@oyster/common';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMeta } from './../contexts';
|
||||
|
||||
export const useUserArts = () => {
|
||||
const { metadata } = useMeta();
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const accountByMint = userAccounts.reduce((prev, acc) => {
|
||||
prev.set(acc.info.mint.toBase58(), acc);
|
||||
return prev;
|
||||
}, new Map<string, TokenAccount>());
|
||||
|
||||
const ownedMetadata = metadata.filter(m => accountByMint.has(m.info.mint.toBase58()));
|
||||
|
||||
return ownedMetadata;
|
||||
}
|
|
@ -1,2 +1 @@
|
|||
export * from './dex';
|
||||
export * from './nft';
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export * from './initAuctionManager';
|
||||
export * from './redeemBid';
|
||||
export * from './redeemLimitedEditionBid';
|
||||
export * from './redeemMasterEditionBid';
|
||||
export * from './redeemOpenEditionBid';
|
||||
export * from './startAuction';
|
||||
export * from './validateSafetyDepositBox';
|
||||
|
||||
export class AuctionManager {
|
||||
key?: number;
|
||||
authority?: PublicKey;
|
||||
auction?: PublicKey;
|
||||
vault?: PublicKey;
|
||||
auctionProgram?: PublicKey;
|
||||
tokenVaultProgram?: PublicKey;
|
||||
tokenMetadataProgram?: PublicKey;
|
||||
tokenProgram?: PublicKey;
|
||||
state?: AuctionManagerState;
|
||||
settings?: AuctionManagerSettings;
|
||||
}
|
||||
|
||||
export class AuctionManagerSettings {
|
||||
openEditionWinnerConstraint?: WinningConstraint;
|
||||
openEditionNonWinningConstraint?: NonWinningConstraint;
|
||||
winningConfigs?: WinningConfig[];
|
||||
openEditionConfig?: number;
|
||||
openEditionFixedPrice?: number;
|
||||
}
|
||||
|
||||
export enum WinningConstraint {
|
||||
NoOpenEdition,
|
||||
OpenEditionGiven,
|
||||
}
|
||||
|
||||
export enum NonWinningConstraint {
|
||||
NoOpenEdition,
|
||||
GivenForFixedPrice,
|
||||
GivenForBidPrice,
|
||||
}
|
||||
|
||||
export enum EditionType {
|
||||
// Not an edition
|
||||
NA,
|
||||
/// Means you are auctioning off the master edition record
|
||||
MasterEdition,
|
||||
/// Means you are using the master edition to print off new editions during the auction (limited or open edition)
|
||||
LimitedEdition,
|
||||
}
|
||||
|
||||
export class WinningConfig {
|
||||
safetyDepositBoxIndex?: number;
|
||||
amount?: number;
|
||||
hasAuthority?: boolean;
|
||||
editionType?: EditionType;
|
||||
}
|
||||
|
||||
export class WinningConfigState {
|
||||
/// Used for cases of minting Limited Editions and keeping track of how many have been made so far.
|
||||
amountMinted?: number;
|
||||
/// Each safety deposit box needs to be validated via endpoint before auction manager will agree to let auction begin.
|
||||
validated?: boolean;
|
||||
/// Ticked to true when a prize is claimed
|
||||
claimed?: boolean;
|
||||
}
|
||||
|
||||
export class AuctionManagerState {
|
||||
status?: AuctionManagerStatus;
|
||||
/// When all configs are validated the auction is started and auction manager moves to Running
|
||||
winningConfigsValidated?: number;
|
||||
|
||||
/// Each master edition used as a template has to grant it's authority to the auction manager.
|
||||
/// This counter is incremented by one each time this is done. At the end of the auction; this is decremented
|
||||
/// each time authority is delegated back to the owner or the new owner and when it hits 0 another condition
|
||||
/// is met for going to Finished state.
|
||||
masterEditionsWithAuthoritiesRemainingToReturn?: number;
|
||||
|
||||
winningConfigStates?: WinningConfigState[];
|
||||
}
|
||||
|
||||
export enum AuctionManagerStatus {
|
||||
Initialized,
|
||||
Validated,
|
||||
Running,
|
||||
Disbursing,
|
||||
Finished,
|
||||
}
|
||||
|
||||
export class BidRedemptionTicket {
|
||||
openEditionRedeemed?: boolean;
|
||||
bidRedeemed?: boolean;
|
||||
}
|
||||
|
||||
export const SCHEMA = new Map<any, any>([
|
||||
[
|
||||
AuctionManager,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['authority', 'u32'],
|
||||
['auction', 'u32'],
|
||||
['vault', 'u32'],
|
||||
['auctionProgram', 'u32'],
|
||||
['tokenVaultProgram', 'u32'],
|
||||
['tokenMetadataProgram', 'u32'],
|
||||
['tokenProgram', 'u32'],
|
||||
['state', AuctionManagerState],
|
||||
['settings', AuctionManagerSettings],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
AuctionManagerSettings,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['openEditionWinnerConstraint', { kind: 'enum' }], // TODO:
|
||||
['openEditionNonWinningConstraint', { kind: 'enum' }], // TODO:
|
||||
['winningConfigs', [WinningConfig]], // TODO: check
|
||||
['openEditionConfig', { kind: 'option', type: 'u8' }],
|
||||
['openEditionFixedPrice', { kind: 'option', type: 'u8' }],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
WinningConfig,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['safetyDepositBoxIndex', 'u8'],
|
||||
['amount', 'u8'],
|
||||
['hasAuthority', 'u8'], // bool
|
||||
['editionType', { kind: 'enum' }], // TODO:
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
WinningConfigState,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['amountMinted', 'u8'],
|
||||
['validated', 'u8'], // bool
|
||||
['claimed', 'u8'], // bool
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
AuctionManagerState,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
// TODO: fix enum
|
||||
['status', { kind: 'enum' }],
|
||||
['winningConfigsValidated', 'u8'],
|
||||
['masterEditionsWithAuthoritiesRemainingToReturn', 'u8'],
|
||||
['winningConfigStates', [WinningConfigState]],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
BidRedemptionTicket,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['openEditionRedeemed', 'u8'], // bool
|
||||
['bidRedeemed', 'u8'], // bool
|
||||
],
|
||||
},
|
||||
],
|
||||
]);
|
|
@ -0,0 +1 @@
|
|||
export const initAuctionManager = () => {};
|
|
@ -0,0 +1 @@
|
|||
export const redeemBid = () => {};
|
|
@ -0,0 +1 @@
|
|||
export const redeemLimitedEditionBid = () => {};
|
|
@ -0,0 +1 @@
|
|||
export const redeemMasterEditionBid = () => {};
|
|
@ -0,0 +1 @@
|
|||
export const redeemOpenEditionBid = () => {};
|
|
@ -0,0 +1 @@
|
|||
export const startAuction = () => {};
|
|
@ -0,0 +1 @@
|
|||
export const validateSafetyDepositBox = () => {};
|
|
@ -25,11 +25,6 @@ export function Routes() {
|
|||
<MetaProvider>
|
||||
<AppLayout>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
component={() => <HomeView />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/art/create/:step_param?"
|
||||
|
@ -65,6 +60,10 @@ export function Routes() {
|
|||
path="/auction/:id"
|
||||
component={() => <AuctionView />}
|
||||
/>
|
||||
<Route
|
||||
path="/"
|
||||
component={() => <HomeView />}
|
||||
/>
|
||||
</Switch>
|
||||
</AppLayout>
|
||||
</MetaProvider>
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export const WORMHOLE_PROGRAM_ID = new PublicKey(
|
||||
'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC',
|
||||
export const AUCTION_PROGRAM_ID = new PublicKey(
|
||||
'aucPpK6yb5MpEHDwyp3Kg79McUoKVqTMeum7iJ9syeH',
|
||||
);
|
||||
|
||||
export const VAULT_PROGRAM_ID = new PublicKey(
|
||||
'vauLTA73sFPqA8whreUbBsbn3SLJH2vhrW9fP5dmfdC',
|
||||
);
|
||||
|
||||
export const METAPLEX_PROGRAM_ID = new PublicKey(
|
||||
'HvwC9QSAzvGXhhVrgPmauVwFWcYZhne3hVot9EbHuFTm',
|
||||
);
|
||||
|
||||
// TODO: generate key ---
|
||||
export const AR_SOL_HOLDER_ID = new PublicKey(
|
||||
'HvwC9QSAzvGXhhVrgPmauVwFWcYZhne3hVot9EbHuFTm',
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ import { ArtCard } from './../../components/ArtCard';
|
|||
import { UserSearch, UserValue } from './../../components/UserSearch';
|
||||
import { Confetti } from './../../components/Confetti';
|
||||
import './../styles.less';
|
||||
import { mintNFT } from '../../models';
|
||||
import { mintNFT } from '../../actions';
|
||||
import {
|
||||
MAX_METADATA_LEN,
|
||||
MAX_OWNER_LEN,
|
||||
|
@ -357,13 +357,13 @@ const InfoStep = (props: {
|
|||
setAttributes: (attr: IMetadataExtension) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
const [creators, setCreators] = useState<Array<UserValue>>([]);
|
||||
const [creators, setCreators] = useState<Array<UserValue>>([])
|
||||
const [royalties, setRoyalties] = useState<Array<Royalty>>([])
|
||||
|
||||
useEffect(() => {
|
||||
setRoyalties(creators.map(creator => ({
|
||||
creator_key: creator.key,
|
||||
amount: 100 / creators.length,
|
||||
amount: Math.trunc(100 / creators.length),
|
||||
})))
|
||||
}, [creators])
|
||||
|
||||
|
@ -463,6 +463,10 @@ const InfoStep = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
const shuffle = (array: Array<any>) => {
|
||||
array.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
const RoyaltiesSplitter = (props: {
|
||||
creators: Array<UserValue>,
|
||||
royalties: Array<Royalty>,
|
||||
|
@ -476,15 +480,31 @@ const RoyaltiesSplitter = (props: {
|
|||
|
||||
const amt = royalty.amount
|
||||
const handleSlide = (newAmt: number) => {
|
||||
const diff = newAmt - amt
|
||||
let n = props.royalties.length - 1
|
||||
const othersRoyalties = props.royalties.filter(_royalty => _royalty.creator_key != royalty.creator_key)
|
||||
if (othersRoyalties.length < 1) return
|
||||
shuffle(othersRoyalties)
|
||||
const others_n = props.royalties.length - 1
|
||||
const sign = Math.sign(newAmt - amt)
|
||||
let remaining = Math.abs(newAmt - amt)
|
||||
let count = 0
|
||||
while (remaining > 0 && count < 100) {
|
||||
const idx = count % others_n
|
||||
const _royalty = othersRoyalties[idx]
|
||||
if (
|
||||
(0 < _royalty.amount && _royalty.amount < 100) // Normal
|
||||
|| (_royalty.amount == 0 && sign < 0) // Low limit
|
||||
|| (_royalty.amount == 100 && sign > 0) // High limit
|
||||
) {
|
||||
_royalty.amount -= sign
|
||||
remaining -= 1
|
||||
}
|
||||
count += 1
|
||||
}
|
||||
|
||||
props.setRoyalties(props.royalties.map(_royalty => {
|
||||
let computed_amount = _royalty.amount - diff / n
|
||||
if (computed_amount <= 0) {
|
||||
computed_amount = 0
|
||||
// n -= 1
|
||||
}
|
||||
const computed_amount = othersRoyalties.find(newRoyalty =>
|
||||
newRoyalty.creator_key == _royalty.creator_key
|
||||
)?.amount
|
||||
return {
|
||||
..._royalty,
|
||||
amount: _royalty.creator_key == royalty.creator_key ? newAmt : computed_amount,
|
||||
|
@ -494,7 +514,7 @@ const RoyaltiesSplitter = (props: {
|
|||
return (
|
||||
<Row key={idx} style={{ margin: '5px auto' }}>
|
||||
<Col span={11} className="slider-elem">{creator.label}</Col>
|
||||
<Col span={8} className="slider-elem">{amt.toFixed(0)}%</Col>
|
||||
<Col span={8} className="slider-elem">{amt}%</Col>
|
||||
<Col span={4}><Slider value={amt} onChange={handleSlide} /></Col>
|
||||
</Row>
|
||||
)
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
import { ParsedAccount, TokenAccount, useUserAccounts } from '@oyster/common';
|
||||
import React from 'react';
|
||||
import { ArtCard } from '../../components/ArtCard';
|
||||
import { useMeta } from '../../contexts';
|
||||
import { Row, Col } from 'antd';
|
||||
import Masonry from 'react-masonry-css'
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useUserArts } from '../../hooks';
|
||||
|
||||
export const ArtworksView = () => {
|
||||
const { metadata } = useMeta();
|
||||
const { userAccounts } = useUserAccounts();
|
||||
const accountByMint = userAccounts.reduce((prev, acc) => {
|
||||
prev.set(acc.info.mint.toBase58(), acc);
|
||||
return prev;
|
||||
}, new Map<string, TokenAccount>());
|
||||
|
||||
const ownedMetadata = metadata.filter(m => accountByMint.has(m.info.mint.toBase58()));
|
||||
const ownedMetadata = useUserArts();
|
||||
const breakpointColumnsObj = {
|
||||
default: 4,
|
||||
1100: 3,
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Row,
|
||||
Button,
|
||||
Modal,
|
||||
ButtonProps,
|
||||
} from 'antd';
|
||||
import { ArtCard } from './../../components/ArtCard';
|
||||
import './../styles.less';
|
||||
import {
|
||||
Metadata,
|
||||
ParsedAccount,
|
||||
} from '@oyster/common';
|
||||
import { useUserArts } from '../../hooks';
|
||||
import Masonry from 'react-masonry-css';
|
||||
|
||||
export interface ArtSelectorProps extends ButtonProps {
|
||||
selected: ParsedAccount<Metadata>[];
|
||||
setSelected: (selected: ParsedAccount<Metadata>[]) => void;
|
||||
allowMultiple: boolean;
|
||||
}
|
||||
|
||||
export const ArtSelector = (props: ArtSelectorProps) => {
|
||||
const { selected, setSelected, allowMultiple, ...rest } = props;
|
||||
const items = useUserArts();
|
||||
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set(props.selected.map(item => item.pubkey.toBase58())));
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const showModal = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
props.setSelected(items.filter(item => selectedItems.has(item.pubkey.toBase58())));
|
||||
}, [selectedItems]);
|
||||
|
||||
const breakpointColumnsObj = {
|
||||
default: 4,
|
||||
1100: 3,
|
||||
700: 2,
|
||||
500: 1
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button {...rest} onClick={() => showModal} />
|
||||
<Modal visible={visible}>
|
||||
<Row className="call-to-action" style={{ marginBottom: 0 }}>
|
||||
<h2>Select the NFT you want to sell</h2>
|
||||
<p style={{ fontSize: '1.2rem' }}>
|
||||
Select the NFT that you want to sell copy/copies of.
|
||||
</p>
|
||||
</Row>
|
||||
<Row className="content-action">
|
||||
<Masonry
|
||||
breakpointCols={breakpointColumnsObj}
|
||||
className="my-masonry-grid"
|
||||
columnClassName="my-masonry-grid_column"
|
||||
>
|
||||
{items.map(m => {
|
||||
const id = m.pubkey.toBase58();
|
||||
const isSelected = selectedItems.has(id);
|
||||
|
||||
const onSelect = () => {
|
||||
let list = [...selectedItems.keys()];
|
||||
if (props.allowMultiple) {
|
||||
list = [];
|
||||
}
|
||||
|
||||
isSelected ?
|
||||
setSelectedItems(new Set(list.filter(item => item !== id))) :
|
||||
setSelectedItems(new Set([...list, id]));
|
||||
};
|
||||
|
||||
return <ArtCard key={id}
|
||||
image={m.info.extended?.image}
|
||||
category={m.info.extended?.category}
|
||||
name={m.info?.name}
|
||||
symbol={m.info.symbol}
|
||||
preview={false}
|
||||
onClick={onSelect}
|
||||
className={isSelected ? 'selected-card' : 'not-selected-card'}
|
||||
/>;
|
||||
})}
|
||||
</Masonry>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
// TODO;
|
||||
}}
|
||||
className="action-btn"
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</Row>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -61,7 +61,7 @@
|
|||
|
||||
.type-btn {
|
||||
height: 80px;
|
||||
width: 280px;
|
||||
width: 450px;
|
||||
border-radius: 8px;
|
||||
background-color: #1d1d1d;
|
||||
border-width: 0px;
|
||||
|
@ -166,6 +166,7 @@
|
|||
.action-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
|
||||
.field-title {
|
||||
text-align: left;
|
||||
|
@ -185,9 +186,34 @@
|
|||
margin: 12px 0px;
|
||||
}
|
||||
|
||||
.field-info {
|
||||
text-align: left;
|
||||
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.field-date {
|
||||
background: #282828;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
border-width: 0px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.selected-card {
|
||||
border-width: 3px;
|
||||
border-color: #5870EE !important;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.not-selected-card {
|
||||
|
||||
}
|
||||
|
||||
.royalties-input {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
|
@ -266,3 +292,15 @@
|
|||
height: 24px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.radio-field {
|
||||
display: block;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.radio-subtitle {
|
||||
color: dimgray;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
56
yarn.lock
56
yarn.lock
|
@ -3345,7 +3345,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
|
||||
|
||||
"@toruslabs/eccrypto@^1.1.5":
|
||||
"@toruslabs/eccrypto@^1.1.5", "@toruslabs/eccrypto@^1.1.6":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@toruslabs/eccrypto/-/eccrypto-1.1.6.tgz#ce877cf00d6f9cf7ab3daa6ac4d6d540110b813b"
|
||||
integrity sha512-L3TAsdEARouyzTbSKE0PqcYXmHQiFh95FB2YnsRbWERCAF0VWg3kY/YA//M/HBZqCJoRwa5WRA61lWbM7zAX5Q==
|
||||
|
@ -3372,6 +3372,44 @@
|
|||
dependencies:
|
||||
deepmerge "^4.2.2"
|
||||
|
||||
"@toruslabs/openlogin-ed25519@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin-ed25519/-/openlogin-ed25519-0.7.0.tgz#836bb22b2daf0034e73d3666ad0c98227d342f36"
|
||||
integrity sha512-+JjCadlbBI1sBie+Z/CS5EHVjjCThxq0imj0VNEcNDJUqdF91uJJghyWx46GllSHoJhWh7BsZUpVU0WZZMBPmw==
|
||||
dependencies:
|
||||
"@toruslabs/tweetnacl-js" "^1.0.3"
|
||||
|
||||
"@toruslabs/openlogin-jrpc@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin-jrpc/-/openlogin-jrpc-0.7.0.tgz#72fee3a94e9488ae133b0595bdf4d272ca01010d"
|
||||
integrity sha512-BO6QnMQoZDp6O/JPVRj216es2m+10JcvtvudNhtfeCBPEXw/uc/OBRBBM0CC002zAHDgivSlPCc+8KOvNRXl1w==
|
||||
dependencies:
|
||||
"@toruslabs/openlogin-utils" "^0.7.0"
|
||||
end-of-stream "^1.4.4"
|
||||
fast-safe-stringify "^2.0.7"
|
||||
once "^1.4.0"
|
||||
pump "^3.0.0"
|
||||
|
||||
"@toruslabs/openlogin-utils@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin-utils/-/openlogin-utils-0.7.0.tgz#d612aacd0daaaf48734c7512335777569113902b"
|
||||
integrity sha512-6caBBLGKjcDulU6oD0p7obtg0dTQlCLTAkDStkwSK9VObMOA/eR92jn3ivjE+r+epiU0LH7kFLAE8/ZFbtpNCw==
|
||||
dependencies:
|
||||
base64url "^3.0.1"
|
||||
keccak "^3.0.1"
|
||||
randombytes "^2.1.0"
|
||||
|
||||
"@toruslabs/openlogin@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin/-/openlogin-0.7.0.tgz#0169a38b0ab634f13354ded010565ff8ba179db1"
|
||||
integrity sha512-p3s1i3VaHOLBdSjgYtUhW59FdYuAp3FkTxoQHmVr2Yr8NoIhAtRBd9JWKuHm5kGtjmb6+eI7pQx1CVoIph5yWw==
|
||||
dependencies:
|
||||
"@toruslabs/eccrypto" "^1.1.6"
|
||||
"@toruslabs/openlogin-jrpc" "^0.7.0"
|
||||
"@toruslabs/openlogin-utils" "^0.7.0"
|
||||
lodash.merge "^4.6.2"
|
||||
pump "^3.0.0"
|
||||
|
||||
"@toruslabs/torus-embed@^1.8.0", "@toruslabs/torus-embed@^1.9.10":
|
||||
version "1.9.14"
|
||||
resolved "https://registry.yarnpkg.com/@toruslabs/torus-embed/-/torus-embed-1.9.14.tgz#6d487a735ec6e570d62a044069581dc372ab193e"
|
||||
|
@ -3409,6 +3447,11 @@
|
|||
memory-cache "^0.2.0"
|
||||
web3-utils "^1.3.3"
|
||||
|
||||
"@toruslabs/tweetnacl-js@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@toruslabs/tweetnacl-js/-/tweetnacl-js-1.0.3.tgz#52abbcd2a6b77959ef6a98afedce77764d87226d"
|
||||
integrity sha512-WQJYMTR/bkqvpk3DWOqRt5e24RhwJp9PXUoSj4zSthd3+fDhKYCI56YVMPNDKah1fCffOe9F3m8iZ5SgDZ+Csw==
|
||||
|
||||
"@typechain/ethers-v4@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@typechain/ethers-v4/-/ethers-v4-1.0.1.tgz#268b619c2660bf7689cda86e30798bd30753ae94"
|
||||
|
@ -5977,6 +6020,11 @@ base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1:
|
|||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
base64url@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||
|
||||
base@^0.11.1:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
||||
|
@ -8835,7 +8883,7 @@ encoding@^0.1.11:
|
|||
dependencies:
|
||||
iconv-lite "^0.6.2"
|
||||
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1:
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
|
@ -10018,7 +10066,7 @@ fast-memoize@^2.5.2:
|
|||
resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e"
|
||||
integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==
|
||||
|
||||
fast-safe-stringify@^2.0.6:
|
||||
fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
|
||||
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
|
||||
|
@ -13524,7 +13572,7 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2:
|
|||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||
|
||||
lodash.merge@^4.4.0:
|
||||
lodash.merge@^4.4.0, lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
|
Loading…
Reference in New Issue