From 95f6a9405a62f3abb791f89e6a71b084da7e3903 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 00:39:21 -0500 Subject: [PATCH 01/28] feat: Create new redux-toolkit reducer w/ current actions implemented --- client/package.json | 3 + .../components/Collections/Catalog/index.tsx | 7 +- .../CreateNonFungiblePage/reducer.ts | 7 +- client/src/index.tsx | 1 + client/src/lib/nfts/queries.ts | 24 +++-- client/src/reducer/async/actions.ts | 99 +++++++++++++++++ client/src/reducer/async/queries.ts | 57 ++++++++++ client/src/reducer/async/wallet.ts | 27 +++++ client/src/reducer/index.ts | 30 ++++++ client/src/reducer/slices/collections.ts | 88 +++++++++++++++ client/src/reducer/slices/createNft.ts | 100 ++++++++++++++++++ client/src/reducer/slices/status.ts | 83 +++++++++++++++ client/src/reducer/slices/system.ts | 20 ++++ client/src/reducer/validators/index.ts | 22 ++++ client/yarn.lock | 71 ++++++++++++- 15 files changed, 625 insertions(+), 14 deletions(-) create mode 100644 client/src/reducer/async/actions.ts create mode 100644 client/src/reducer/async/queries.ts create mode 100644 client/src/reducer/async/wallet.ts create mode 100644 client/src/reducer/index.ts create mode 100644 client/src/reducer/slices/collections.ts create mode 100644 client/src/reducer/slices/createNft.ts create mode 100644 client/src/reducer/slices/status.ts create mode 100644 client/src/reducer/slices/system.ts create mode 100644 client/src/reducer/validators/index.ts diff --git a/client/package.json b/client/package.json index 7dd413d9..9270f9e8 100644 --- a/client/package.json +++ b/client/package.json @@ -8,6 +8,7 @@ "@emotion/core": "10.0.28", "@emotion/react": "11.1.4", "@emotion/styled": "11.0.0", + "@reduxjs/toolkit": "1.5.0", "@taquito/beacon-wallet": "7.2.0-beta.2", "@taquito/rpc": "7.2.0-beta.2", "@taquito/signer": "7.2.0-beta.2", @@ -22,6 +23,7 @@ "@types/react": "16.9.12", "@types/react-dom": "16.9.0", "@types/react-dropzone": "5.1.0", + "@types/react-redux": "7.1.16", "axios-retry": "3.1.9", "buffer": "6.0.3", "framer-motion": "3.1.4", @@ -35,6 +37,7 @@ "react-dom": "16.13.1", "react-dropzone": "11.2.4", "react-feather": "2.0.9", + "react-redux": "7.2.2", "react-scripts": "3.4.1", "typescript": "4.1.3", "wouter": "2.5.1" diff --git a/client/src/components/Collections/Catalog/index.tsx b/client/src/components/Collections/Catalog/index.tsx index b30563fc..d2efb3de 100644 --- a/client/src/components/Collections/Catalog/index.tsx +++ b/client/src/components/Collections/Catalog/index.tsx @@ -43,7 +43,12 @@ export default function Catalog({ state, dispatch }: CatalogProps) { setLocation('/', { replace: true }); } else { getWalletNftAssetContracts(system).then(collections => { - dispatch({ type: 'update_collections', payload: { collections } }); + dispatch({ + type: 'update_collections', + payload: { + collections: collections.map(c => ({ ...c, tokens: null })) + } + }); }); } }, [system.status]); diff --git a/client/src/components/CreateNonFungiblePage/reducer.ts b/client/src/components/CreateNonFungiblePage/reducer.ts index 7679ce04..d03ddf43 100644 --- a/client/src/components/CreateNonFungiblePage/reducer.ts +++ b/client/src/components/CreateNonFungiblePage/reducer.ts @@ -21,11 +21,16 @@ export enum CreateStatus { Complete = 'complete' } +interface MetadataRow { + name: string | null; + value: string | null; +} + export interface State { step: Step; artifactUri: string | null; fields: Fields; - metadataRows: { name: string | null; value: string | null }[]; + metadataRows: MetadataRow[]; collectionAddress: string | null; createStatus: CreateStatus; } diff --git a/client/src/index.tsx b/client/src/index.tsx index a7a79c4c..3128f66b 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -5,6 +5,7 @@ import App from './components/App'; import * as serviceWorker from './serviceWorker'; import { ChakraProvider, extendTheme } from '@chakra-ui/react'; import SystemContextProvider from './context/system'; +import { Provider } from 'react-redux'; const Button = { variants: { diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index 62e04805..e9269879 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -10,13 +10,7 @@ function fromHexString(input: string) { return input; } -function foldBigMapResponseAsObject(bigMapResponse: any) { - return bigMapResponse.reduce((acc: {}, next: any) => { - return { ...acc, [next.data.key_string]: next.data.value.value }; - }, {}); -} - -interface Nft { +export interface Nft { id: number; title: string; owner: string; @@ -80,7 +74,15 @@ export async function getContractNfts( ); } -export async function getNftAssetContract(system: System, address: string) { +export interface AssetContract { + address: string; + metadata: Record; +} + +export async function getNftAssetContract( + system: System, + address: string +): Promise { const bcd = system.betterCallDev; const storage = await bcd.getContractStorage(address); @@ -96,7 +98,9 @@ export async function getNftAssetContract(system: System, address: string) { key_string: 'contents' })?.value?.value; - const metadata = JSON.parse(fromHexString(metadataContents)); + const metadata: Record = JSON.parse( + fromHexString(metadataContents) + ); return { address, metadata }; } @@ -109,7 +113,7 @@ export async function getWalletNftAssetContracts(system: SystemWithWallet) { (i: any) => i.body.hash === nftAssetHash ); - const results: any[] = []; + const results = []; for (let assetContract of assetContracts) { const result = await getNftAssetContract(system, assetContract.value); results.push(result); diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts new file mode 100644 index 00000000..fa7fa196 --- /dev/null +++ b/client/src/reducer/async/actions.ts @@ -0,0 +1,99 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { State } from '..'; +import { + createAssetContract, + mintToken, + transferToken +} from '../../lib/nfts/actions'; +import { getContractNftsQuery, getWalletAssetContractsQuery } from './queries'; +import { collectionSelectSchema } from '../validators'; + +type Opts = { state: State }; + +export const createAssetContractAction = createAsyncThunk< + { address: string }, + string, + Opts +>( + 'action/createAssetContract', + async (name, { getState, rejectWithValue, dispatch }) => { + const { system } = getState(); + if (system.status !== 'WalletConnected') { + return rejectWithValue({ error: '' }); + } + try { + const op = await createAssetContract(system, name); + await op.confirmation(); + const { address } = await op.contract(); + dispatch(getWalletAssetContractsQuery()); + return { name, address }; + } catch (e) { + return rejectWithValue({ error: '' }); + } + } +); + +function buildMetadataFromState(state: State['createNft']) { + const address = state.collectionAddress as string; + const metadata: Record = {}; + + metadata.artifactUri = state.artifactUri as string; + metadata.displayUri = state.artifactUri as string; + metadata.name = state.fields.name as string; + + if (state.fields.description) { + metadata.description = state.fields.description; + } + + for (let row of state.metadataRows) { + if (row.name !== null && row.value !== null) { + metadata[row.name] = row.value; + } + } + + return { address, metadata }; +} + +export const mintTokenAction = createAsyncThunk< + { contract: string }, + undefined, + Opts +>('actions/mintToken', async (_, { getState, rejectWithValue, dispatch }) => { + const { system, createNft: state } = getState(); + if (collectionSelectSchema.validate(state, { allowUnknown: true }).error) { + return rejectWithValue({ error: '' }); + } else if (system.status !== 'WalletConnected') { + return rejectWithValue({ error: '' }); + } + + const { address, metadata } = buildMetadataFromState(state); + try { + const op = await mintToken(system, address, metadata); + await op.confirmation(); + dispatch(getContractNftsQuery(address)); + return { contract: address }; + } catch (e) { + return rejectWithValue({ error: '' }); + } +}); + +export const transferTokenAction = createAsyncThunk< + { contract: string; tokenId: number }, + { contract: string; tokenId: number; to: string }, + Opts +>('actions/transferToken', async (args, api) => { + const { getState, rejectWithValue, dispatch } = api; + const { contract, tokenId, to } = args; + const { system } = getState(); + if (system.status !== 'WalletConnected') { + return rejectWithValue({ error: '' }); + } + try { + const op = await transferToken(system, contract, tokenId, to); + await op.confirmation(); + dispatch(getContractNftsQuery(contract)); + return { contract: '', tokenId: 0 }; + } catch (e) { + return rejectWithValue({ error: '' }); + } +}); diff --git a/client/src/reducer/async/queries.ts b/client/src/reducer/async/queries.ts new file mode 100644 index 00000000..289184af --- /dev/null +++ b/client/src/reducer/async/queries.ts @@ -0,0 +1,57 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { State } from '../index'; +import { + getNftAssetContract, + AssetContract, + getContractNfts, + Nft, + getWalletNftAssetContracts +} from '../../lib/nfts/queries'; + +type Opts = { state: State }; + +export const getNftAssetContractQuery = createAsyncThunk< + AssetContract, + string, + Opts +>('query/getNftAssetContract', async (address, api) => { + const { system } = api.getState(); + try { + return await getNftAssetContract(system, address); + } catch (e) { + return api.rejectWithValue({ error: '' }); + } +}); + +export const getContractNftsQuery = createAsyncThunk< + { address: string; tokens: Nft[] }, + string, + Opts +>('query/getContractNfts', async (address, api) => { + const { system } = api.getState(); + try { + const tokens = await getContractNfts(system, address); + return { address, tokens }; + } catch (e) { + return api.rejectWithValue({ error: '' }); + } +}); + +export const getWalletAssetContractsQuery = createAsyncThunk< + AssetContract[], + undefined, + Opts +>( + 'query/getWalletNftAssetContracts', + async (_, { getState, rejectWithValue }) => { + const { system } = getState(); + if (system.status !== 'WalletConnected') { + return rejectWithValue({ error: '' }); + } + try { + return await getWalletNftAssetContracts(system); + } catch (e) { + return rejectWithValue({ error: '' }); + } + } +); diff --git a/client/src/reducer/async/wallet.ts b/client/src/reducer/async/wallet.ts new file mode 100644 index 00000000..0c969ce3 --- /dev/null +++ b/client/src/reducer/async/wallet.ts @@ -0,0 +1,27 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { State } from '..'; +import { Minter, SystemWithToolkit, SystemWithWallet } from '../../lib/system'; + +export const connectWallet = createAsyncThunk< + SystemWithWallet, + undefined, + { state: State } +>('wallet/connect', async (_arg, { getState, rejectWithValue }) => { + const { system } = getState(); + if (system.status === 'ToolkitConnected') { + return await Minter.connectWallet(system); + } + return rejectWithValue({ error: 'Wallet already connected' }); +}); + +export const disconnectWallet = createAsyncThunk< + SystemWithToolkit, + undefined, + { state: State } +>('wallet/disconnect', async (_arg, { getState, rejectWithValue }) => { + const { system } = getState(); + if (system.status === 'WalletConnected') { + return await Minter.disconnectWallet(system); + } + return rejectWithValue({ error: 'No wallet connected' }); +}); diff --git a/client/src/reducer/index.ts b/client/src/reducer/index.ts new file mode 100644 index 00000000..3b3e89fd --- /dev/null +++ b/client/src/reducer/index.ts @@ -0,0 +1,30 @@ +import { combineReducers, configureStore } from '@reduxjs/toolkit'; +import { + useSelector as baseUseSelector, + useDispatch as baseUseDispatch +} from 'react-redux'; +import collectionsSlice from './slices/collections'; +import createNftSlice from './slices/createNft'; +import systemSlice from './slices/system'; + +export const reducer = combineReducers({ + collections: collectionsSlice.reducer, + createNft: createNftSlice.reducer, + system: systemSlice.reducer +}); + +export const store = configureStore({ reducer }); + +export type State = ReturnType; +export type Dispatch = typeof store.dispatch; + +export function useDispatch() { + return baseUseDispatch(); +} + +export function useSelector( + selector: (s: State) => TSelect, + equalityFn?: (left: TSelect, right: TSelect) => boolean +) { + return baseUseSelector(selector, equalityFn); +} diff --git a/client/src/reducer/slices/collections.ts b/client/src/reducer/slices/collections.ts new file mode 100644 index 00000000..0ed4dd9e --- /dev/null +++ b/client/src/reducer/slices/collections.ts @@ -0,0 +1,88 @@ +import { createSlice, PayloadAction, CaseReducer } from '@reduxjs/toolkit'; +import { + getContractNftsQuery, + getNftAssetContractQuery, + getWalletAssetContractsQuery +} from '../async/queries'; +import { Nft, AssetContract } from '../../lib/nfts/queries'; +import config from '../../config.json'; + +//// State + +// Types + +export interface Collection extends AssetContract { + tokens: Nft[] | null; +} + +export interface CollectionsState { + selectedCollection: string | null; + globalCollection: string; + collections: Record; +} + +type Reducer = CaseReducer>; + +// Data + +const globalCollectionAddress = config.contracts.nftFaucet; + +export const initialState: CollectionsState = { + selectedCollection: null, + globalCollection: globalCollectionAddress, + collections: { + [globalCollectionAddress]: { + address: globalCollectionAddress, + metadata: { + name: 'Minter' + }, + tokens: null + } + } +}; + +//// Reducers & Slice + +type PopulateCollection = Reducer<{ address: string; tokens: Nft[] }>; + +const populateCollection: PopulateCollection = (state, { payload }) => { + if (state.collections[payload.address]) { + state.collections[payload.address].tokens = payload.tokens; + } +}; + +const updateCollections: Reducer = (state, action) => { + for (let coll of action.payload) { + if (!state.collections[coll.address]) { + state.collections[coll.address] = { ...coll, tokens: null }; + } + } +}; + +const updateCollection: Reducer = (state, { payload }) => { + if (!state.collections[payload.address]) { + state.collections[payload.address] = { ...payload, tokens: null }; + } +}; + +const selectCollection: Reducer = (state, action) => { + state.selectedCollection = action.payload; +}; + +const slice = createSlice({ + name: 'collections', + initialState, + reducers: { + updateCollections, + updateCollection, + selectCollection, + populateCollection + }, + extraReducers: ({ addCase }) => { + addCase(getContractNftsQuery.fulfilled, populateCollection); + addCase(getNftAssetContractQuery.fulfilled, updateCollection); + addCase(getWalletAssetContractsQuery.fulfilled, updateCollections); + } +}); + +export default slice; diff --git a/client/src/reducer/slices/createNft.ts b/client/src/reducer/slices/createNft.ts new file mode 100644 index 00000000..6cf02c8a --- /dev/null +++ b/client/src/reducer/slices/createNft.ts @@ -0,0 +1,100 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +// State + +type Step = 'file_upload' | 'asset_details' | 'collection_select'; + +export const steps: Step[] = [ + 'file_upload', + 'asset_details', + 'collection_select' +]; + +interface Fields { + name: string | null; + description: string | null; +} + +export enum CreateStatus { + Ready = 'ready', + InProgress = 'inProgress', + Complete = 'complete' +} + +export interface CreateNftState { + step: Step; + artifactUri: string | null; + fields: Fields; + metadataRows: { name: string | null; value: string | null }[]; + collectionAddress: string | null; + createStatus: CreateStatus; +} + +export const initialState: CreateNftState = { + step: 'file_upload', + artifactUri: null, + fields: { + name: null, + description: null + }, + metadataRows: [], + collectionAddress: null, + createStatus: CreateStatus.Ready +}; + +// Async Thunks + +// Reducers & Slice + +type UpdateFieldAction = PayloadAction<{ name: keyof Fields; value: string }>; +type UpdateRowNameAction = PayloadAction<{ key: number; name: string }>; +type UpdateRowValueAction = PayloadAction<{ key: number; value: string }>; + +const slice = createSlice({ + name: 'createNft', + initialState, + reducers: { + incrementStep(state) { + const stepIdx = steps.indexOf(state.step); + if (stepIdx + 1 < steps.length) { + state.step = steps[stepIdx + 1]; + } + }, + decrementStep(state) { + const stepIdx = steps.indexOf(state.step); + if (stepIdx > 0) { + state.step = steps[stepIdx - 1]; + } + }, + updateField(state, action: UpdateFieldAction) { + state.fields[action.payload.name] = action.payload.value; + }, + updateArtifactUri(state, action: PayloadAction) { + state.artifactUri = action.payload; + }, + addMetadataRow(state) { + state.metadataRows.push({ name: null, value: null }); + }, + updateMetadataRowName(state, action: UpdateRowNameAction) { + if (state.metadataRows[action.payload.key]) { + state.metadataRows[action.payload.key].name = action.payload.name; + } + }, + updateMetadataRowValue(state, action: UpdateRowValueAction) { + if (state.metadataRows[action.payload.key]) { + state.metadataRows[action.payload.key].value = action.payload.value; + } + }, + deleteMetadataRow(state, action: PayloadAction<{ key: number }>) { + state.metadataRows.splice(action.payload.key, 1); + }, + selectCollection(state, action: PayloadAction) { + state.collectionAddress = action.payload; + }, + setCreateStatus(state, action: PayloadAction) { + state.createStatus = action.payload; + } + } +}); + +export default slice; diff --git a/client/src/reducer/slices/status.ts b/client/src/reducer/slices/status.ts new file mode 100644 index 00000000..791c1ef3 --- /dev/null +++ b/client/src/reducer/slices/status.ts @@ -0,0 +1,83 @@ +import { createSlice, PayloadAction, SerializedError } from '@reduxjs/toolkit'; +import { + createAssetContractAction, + mintTokenAction, + transferTokenAction +} from '../async/actions'; +import { + getContractNftsQuery, + getNftAssetContractQuery, + getWalletAssetContractsQuery +} from '../async/queries'; + +type StatusKey = 'ready' | 'in_transit' | 'complete'; + +interface Status { + status: StatusKey; + error: SerializedError | null; +} + +export interface StatusState { + createAssetContract: Status; + mintToken: Status; + transferToken: Status; + getContractNfts: Status; + getNftAssetContract: Status; + getWalletAssetContracts: Status; +} + +type Name = keyof StatusState; + +const defaultStatus: Status = { status: 'ready', error: null }; + +const initialState: StatusState = { + createAssetContract: defaultStatus, + mintToken: defaultStatus, + transferToken: defaultStatus, + getContractNfts: defaultStatus, + getNftAssetContract: defaultStatus, + getWalletAssetContracts: defaultStatus +}; + +type SetStatusAction = PayloadAction<{ method: Name; status: StatusKey }>; +type SetErrorAction = PayloadAction<{ method: Name; message: string }>; +type ClearErrorAction = PayloadAction<{ method: Name }>; + +const slice = createSlice({ + name: 'status', + initialState, + reducers: { + setStatus(state, { payload }: SetStatusAction) { + state[payload.method].status = payload.status; + }, + setError(state, { payload }: SetErrorAction) { + state[payload.method].error = { message: payload.message }; + }, + clearError(state, { payload }: ClearErrorAction) { + state[payload.method].error = null; + } + }, + extraReducers: ({ addCase }) => { + [ + { method: 'createAssetContract', action: createAssetContractAction }, + { method: 'mintToken', action: mintTokenAction }, + { method: 'transferToken', action: transferTokenAction }, + { method: 'getContracts', action: getContractNftsQuery }, + { method: 'getNftAssetContract', action: getNftAssetContractQuery }, + { name: 'getWalletAssetContracts', action: getWalletAssetContractsQuery } + ].forEach(({ method, action }) => { + const name = method as keyof StatusState; + addCase(action.pending, state => { + state[name].status = 'in_transit'; + }); + addCase(action.fulfilled, state => { + state[name].status = 'complete'; + }); + addCase(action.rejected, (state, action) => { + state[name].error = action.error; + }); + }); + } +}); + +export default slice; diff --git a/client/src/reducer/slices/system.ts b/client/src/reducer/slices/system.ts new file mode 100644 index 00000000..0d5e229e --- /dev/null +++ b/client/src/reducer/slices/system.ts @@ -0,0 +1,20 @@ +import { Minter, SystemWithToolkit, SystemWithWallet } from '../../lib/system'; +import { createSlice } from '@reduxjs/toolkit'; +import config from '../../config.json'; +import { connectWallet, disconnectWallet } from '../async/wallet'; + +const initialState = Minter.connectToolkit(Minter.configure(config)) as + | SystemWithToolkit + | SystemWithWallet; + +const slice = createSlice({ + name: 'system', + initialState, + reducers: {}, + extraReducers: ({ addCase }) => { + addCase(connectWallet.fulfilled, (_, { payload }) => payload); + addCase(disconnectWallet.fulfilled, (_, { payload }) => payload); + } +}); + +export default slice; diff --git a/client/src/reducer/validators/index.ts b/client/src/reducer/validators/index.ts new file mode 100644 index 00000000..6b61ecc4 --- /dev/null +++ b/client/src/reducer/validators/index.ts @@ -0,0 +1,22 @@ +import Joi from 'joi'; + +export const fileUploadSchema = Joi.object({ + artifactUri: Joi.string().required() +}); + +export const assetDetailsSchema = fileUploadSchema.append({ + fields: Joi.object({ + name: Joi.string().min(1).required(), + description: Joi.string().allow(null).allow('') + }), + metadataRows: Joi.array().items( + Joi.object({ + name: Joi.string().min(1).required(), + value: Joi.string().min(1).required() + }) + ) +}); + +export const collectionSelectSchema = assetDetailsSchema.append({ + collectionAddress: Joi.string().required() +}); diff --git a/client/yarn.lock b/client/yarn.lock index 6d50ad74..7435a30c 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1116,7 +1116,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== @@ -2156,6 +2156,16 @@ dependencies: tslib "^2.0.0" +"@reduxjs/toolkit@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.5.0.tgz#1025c1ccb224d1fc06d8d98a61f6717d57e6d477" + integrity sha512-E/FUraRx+8guw9Hlg/Ja8jI/hwCrmIKed8Annt9YsZw3BQp+F24t5I5b2OWR6pkEHY4hn1BgP08FrTZFRKsdaQ== + dependencies: + immer "^8.0.0" + redux "^4.0.0" + redux-thunk "^2.3.0" + reselect "^4.0.0" + "@sheerun/mutationobserver-shim@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" @@ -2492,6 +2502,14 @@ resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.5.tgz#4f6648814d0fdcb6a510e3364a9db439a753c4b1" integrity sha512-IG8AE1m2pWtPqQ7wXhFhy6Q59bwwnLwO36v5Rit2FrbXCIp8Sk8E2PfUCreyrdo17STwFSKDAkitVuVYbpEHvQ== +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -2612,6 +2630,16 @@ dependencies: react-dropzone "*" +"@types/react-redux@7.1.16": + version "7.1.16" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21" + integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react@*": version "17.0.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" @@ -6592,7 +6620,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6814,6 +6842,11 @@ immer@8.0.0: resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.0.tgz#08763549ba9dd7d5e2eb4bec504a8315bd9440c2" integrity sha512-jm87NNBAIG4fHwouilCHIecFXp5rMGkiFrAuhVO685UnMAlOneEAnOyzPt8OnP47TC11q/E7vpzZe0WvwepFTg== +immer@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" + integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -10799,6 +10832,17 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-redux@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" + integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA== + dependencies: + "@babel/runtime" "^7.12.1" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.13.1" + react-remove-scroll-bar@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.1.1.tgz#5876428dfd546f2f63a4d277aea2197925505c1e" @@ -10990,6 +11034,19 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -11167,6 +11224,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -12163,6 +12225,11 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" From 92f05cf3be14b1435e350ed8c665c34eafd9a276 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 11:43:55 -0500 Subject: [PATCH 02/28] feat: Wrap root component in react-redux Provider --- client/src/index.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/src/index.tsx b/client/src/index.tsx index 3128f66b..7c43dd87 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -6,6 +6,7 @@ import * as serviceWorker from './serviceWorker'; import { ChakraProvider, extendTheme } from '@chakra-ui/react'; import SystemContextProvider from './context/system'; import { Provider } from 'react-redux'; +import { store } from './reducer'; const Button = { variants: { @@ -220,11 +221,13 @@ const theme = extendTheme({ function Root() { return ( - - - - - + + + + + + + ); } From 8266366b2e85ca4edbcec03d41192537c7827d1b Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 12:19:10 -0500 Subject: [PATCH 03/28] fix: Ignore system immutable check --- client/src/reducer/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/src/reducer/index.ts b/client/src/reducer/index.ts index 3b3e89fd..fe4b6d10 100644 --- a/client/src/reducer/index.ts +++ b/client/src/reducer/index.ts @@ -13,7 +13,15 @@ export const reducer = combineReducers({ system: systemSlice.reducer }); -export const store = configureStore({ reducer }); +export const store = configureStore({ + reducer, + middleware: getDefaultMiddleware => + getDefaultMiddleware({ + immutableCheck: { + ignoredPaths: ['system'] + } + }) +}); export type State = ReturnType; export type Dispatch = typeof store.dispatch; From 910abcb1ca0a850d3ecb63efa20c3930d74a723a Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 14:48:09 -0500 Subject: [PATCH 04/28] feat: Integrate redux store into UI --- client/src/components/App/index.tsx | 16 ++- .../Collections/Catalog/Sidebar.tsx | 27 ++-- .../components/Collections/Catalog/index.tsx | 55 +++----- .../Collections/TokenDetail/index.tsx | 43 +++--- client/src/components/Collections/index.tsx | 26 ---- .../CollectionSelect.tsx | 72 ++++------ .../CreateNonFungiblePage/FileUpload.tsx | 23 +--- .../components/CreateNonFungiblePage/Form.tsx | 54 ++++---- .../CreateNonFungiblePage/Preview.tsx | 5 +- .../CreateNonFungiblePage/StatusModal.tsx | 12 +- .../CreateNonFungiblePage/index.tsx | 126 +++++++----------- client/src/components/SplashPage/index.tsx | 11 +- .../components/common/CreateCollection.tsx | 36 ++--- client/src/components/common/Header.tsx | 10 +- .../src/components/common/TransferToken.tsx | 46 +++---- client/src/reducer/index.ts | 12 +- client/src/reducer/slices/collections.ts | 29 ++-- client/src/reducer/slices/createNft.ts | 13 ++ client/src/reducer/slices/status.ts | 11 +- 19 files changed, 261 insertions(+), 366 deletions(-) delete mode 100644 client/src/components/Collections/index.tsx diff --git a/client/src/components/App/index.tsx b/client/src/components/App/index.tsx index 0477b846..1df97e99 100644 --- a/client/src/components/App/index.tsx +++ b/client/src/components/App/index.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { Switch, Route } from 'wouter'; import SplashPage from '../SplashPage'; import CreateNonFungiblePage from '../CreateNonFungiblePage'; -import Collections from '../Collections'; +import CollectionsCatalog from '../Collections/Catalog'; +import CollectionsTokenDetail from '../Collections/TokenDetail'; import Header from '../common/Header'; import { Flex } from '@chakra-ui/react'; @@ -11,6 +12,7 @@ export default function App() {
+ @@ -18,7 +20,17 @@ export default function App() { - + + + + + {({ contractAddress, tokenId }) => ( + + )} + diff --git a/client/src/components/Collections/Catalog/Sidebar.tsx b/client/src/components/Collections/Catalog/Sidebar.tsx index f71a088a..aac9c5ca 100644 --- a/client/src/components/Collections/Catalog/Sidebar.tsx +++ b/client/src/components/Collections/Catalog/Sidebar.tsx @@ -1,18 +1,22 @@ import React from 'react'; import { Flex, Heading, Text } from '@chakra-ui/react'; import { CreateCollectionButton } from '../../common/CreateCollection'; -import { State, Action, Collection } from '../reducer'; +import { useSelector, useDispatch } from '../../../reducer'; +import { + selectCollection, + Collection +} from '../../../reducer/slices/collections'; interface CollectionTabProps extends Collection { selected: boolean; - dispatch: React.Dispatch; + onSelect: (address: string) => void; } function CollectionTab({ address, metadata, selected, - dispatch + onSelect }: CollectionTabProps) { return ( - dispatch({ type: 'select_collection', payload: { address } }) - } + onClick={() => onSelect(address)} role="group" > ; -} - -export default function Sidebar({ state, dispatch }: SidebarProps) { +export default function Sidebar() { + const state = useSelector(s => s.collections); + const dispatch = useDispatch(); return ( <> @@ -75,7 +74,7 @@ export default function Sidebar({ state, dispatch }: SidebarProps) { dispatch(selectCollection(address))} {...state.collections[state.globalCollection]} /> dispatch(selectCollection(address))} {...state.collections[address]} /> ))} diff --git a/client/src/components/Collections/Catalog/index.tsx b/client/src/components/Collections/Catalog/index.tsx index d2efb3de..a36cccf9 100644 --- a/client/src/components/Collections/Catalog/index.tsx +++ b/client/src/components/Collections/Catalog/index.tsx @@ -1,55 +1,37 @@ -import React, { Dispatch, useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { Box, Flex, Heading, Text } from '@chakra-ui/react'; import { useLocation } from 'wouter'; import { RefreshCw } from 'react-feather'; -import { SystemContext } from '../../../context/system'; import { MinterButton } from '../../common'; import Sidebar from './Sidebar'; import TokenGrid from './TokenGrid'; -import { State, Action } from '../reducer'; -import { - getContractNfts, - getWalletNftAssetContracts -} from '../../../lib/nfts/queries'; -interface CatalogProps { - state: State; - dispatch: Dispatch; -} +import { useSelector, useDispatch } from '../../../reducer'; +import { + getContractNftsQuery, + getWalletAssetContractsQuery +} from '../../../reducer/async/queries'; +import { selectCollection } from '../../../reducer/slices/collections'; -export default function Catalog({ state, dispatch }: CatalogProps) { +export default function Catalog() { const [, setLocation] = useLocation(); - const { system } = useContext(SystemContext); + const { system, collections: state } = useSelector(s => s); + const dispatch = useDispatch(); useEffect(() => { const selectedCollection = state.selectedCollection; if (selectedCollection === null) { - dispatch({ - type: 'select_collection', - payload: { address: state.globalCollection } - }); + dispatch(selectCollection(state.globalCollection)); } else { - getContractNfts(system, selectedCollection).then(tokens => { - dispatch({ - type: 'populate_collection', - payload: { address: selectedCollection, tokens } - }); - }); + dispatch(getContractNftsQuery(selectedCollection)); } - }, [state.selectedCollection]); + }, [system.status, state.selectedCollection]); useEffect(() => { if (system.status !== 'WalletConnected') { setLocation('/', { replace: true }); } else { - getWalletNftAssetContracts(system).then(collections => { - dispatch({ - type: 'update_collections', - payload: { - collections: collections.map(c => ({ ...c, tokens: null })) - } - }); - }); + dispatch(getWalletAssetContractsQuery()); } }, [system.status]); @@ -63,7 +45,7 @@ export default function Catalog({ state, dispatch }: CatalogProps) { return ( - + { const selectedCollection = state.selectedCollection; if (selectedCollection !== null) { - getContractNfts(system, selectedCollection).then(tokens => { - dispatch({ - type: 'populate_collection', - payload: { address: selectedCollection, tokens } - }); - }); + dispatch(getContractNftsQuery(selectedCollection)); } }} > diff --git a/client/src/components/Collections/TokenDetail/index.tsx b/client/src/components/Collections/TokenDetail/index.tsx index 482f6469..5eaa2cb1 100644 --- a/client/src/components/Collections/TokenDetail/index.tsx +++ b/client/src/components/Collections/TokenDetail/index.tsx @@ -1,16 +1,15 @@ -import React, { Dispatch, useEffect, useContext, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useLocation } from 'wouter'; -import { SystemContext } from '../../../context/system'; import { AspectRatio, Box, Flex, Heading, Image, Text } from '@chakra-ui/react'; import { ChevronLeft, HelpCircle, MoreHorizontal, Star } from 'react-feather'; import { MinterButton } from '../../common'; -import { State, Action } from '../reducer'; -import { - getNftAssetContract, - getContractNfts -} from '../../../lib/nfts/queries'; import { TransferTokenButton } from '../../common/TransferToken'; import { ipfsCidFromUri } from '../../../util'; +import { useSelector, useDispatch } from '../../../reducer'; +import { + getContractNftsQuery, + getNftAssetContractQuery +} from '../../../reducer/async/queries'; function NotFound() { return ( @@ -86,39 +85,27 @@ function TokenImage(props: { src: string }) { interface TokenDetailProps { contractAddress: string; tokenId: number; - state: State; - dispatch: Dispatch; } -export default function TokenDetail(props: TokenDetailProps) { +function TokenDetail({ contractAddress, tokenId }: TokenDetailProps) { const [, setLocation] = useLocation(); - const { system } = useContext(SystemContext); - const { dispatch, contractAddress, tokenId } = props; - const collection = props.state.collections[props.contractAddress]; + const { system, collections: state } = useSelector(s => s); + const dispatch = useDispatch(); + const collection = state.collections[contractAddress]; useEffect(() => { if (!collection) { - getNftAssetContract(system, contractAddress).then(collection => { - dispatch({ - type: 'update_collection', - payload: { collection: { ...collection, tokens: null } } - }); - }); + dispatch(getNftAssetContractQuery(contractAddress)); } else { - getContractNfts(system, contractAddress).then(tokens => { - dispatch({ - type: 'populate_collection', - payload: { address: contractAddress, tokens } - }); - }); + dispatch(getContractNftsQuery(contractAddress)); } }, [contractAddress, tokenId, collection === undefined]); - if (!collection || collection.tokens === null) { + if (!collection?.tokens) { return null; } - const token = collection.tokens?.find(token => token.id === props.tokenId); + const token = collection.tokens.find(token => token.id === tokenId); if (!token) { return ; } @@ -254,3 +241,5 @@ export default function TokenDetail(props: TokenDetailProps) { ); } + +export default TokenDetail; diff --git a/client/src/components/Collections/index.tsx b/client/src/components/Collections/index.tsx deleted file mode 100644 index efa4c924..00000000 --- a/client/src/components/Collections/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useReducer } from 'react'; -import { Route } from 'wouter'; -import { reducer, initialState } from './reducer'; -import CollectionsCatalog from '../Collections/Catalog'; -import CollectionsTokenDetail from '../Collections/TokenDetail'; - -export default function Collections() { - const [state, dispatch] = useReducer(reducer, initialState); - return ( - <> - - - - - {({ contractAddress, tokenId }) => ( - - )} - - - ); -} diff --git a/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx b/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx index 6a34a26b..e18a5848 100644 --- a/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx +++ b/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx @@ -1,20 +1,22 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { Flex, Heading, Text } from '@chakra-ui/react'; import { CreateCollectionButton } from '../common/CreateCollection'; -import { State, DispatchFn } from './reducer'; -import { SystemContext } from '../../context/system'; -import { getWalletNftAssetContracts } from '../../lib/nfts/queries'; -import config from '../../config.json'; +import { useSelector, useDispatch } from '../../reducer'; +import { + CreateNftState, + selectCollection +} from '../../reducer/slices/createNft'; +import { getWalletAssetContractsQuery } from '../../reducer/async/queries'; interface CollectionRowProps { name: string; address: string; - dispatch: DispatchFn; - state: State; + state: CreateNftState; + dispatch: ReturnType; } -function CollectionRow(props: CollectionRowProps) { - const selected = props.state.collectionAddress === props.address; +function CollectionRow({ state, dispatch, name, address }: CollectionRowProps) { + const selected = state.collectionAddress === address; return ( - props.dispatch({ - type: 'select_collection', - payload: { address: props.address } - }) - } + onClick={() => dispatch(selectCollection(address))} > - {props.name[0]} + {name[0]} - {props.name} + {name} ); } -const globalCollectionAddress = config.contracts.nftFaucet; - -const globalCollection = { - address: globalCollectionAddress, - metadata: { - name: 'Minter' - } -}; - -export default function CollectionSelect(props: { - state: State; - dispatch: DispatchFn; -}) { - const { system } = useContext(SystemContext); - const [collections, setCollections] = useState([]); +export default function CollectionSelect() { + const { collections } = useSelector(s => s.collections); + const state = useSelector(s => s.createNft); + const dispatch = useDispatch(); useEffect(() => { - if (system.status !== 'WalletConnected') { - return; - } - getWalletNftAssetContracts(system).then(collections => { - setCollections([globalCollection, ...collections]); - }); - }, [system.status]); - - if (system.status !== 'WalletConnected') { - return null; - } + dispatch(getWalletAssetContractsQuery()); + }, [collections]); return ( @@ -101,14 +78,15 @@ export default function CollectionSelect(props: { Collections - {collections.map(({ address, metadata }) => { + {Object.keys(collections).map(key => { + const { address, metadata } = collections[key]; return ( ); })} diff --git a/client/src/components/CreateNonFungiblePage/FileUpload.tsx b/client/src/components/CreateNonFungiblePage/FileUpload.tsx index f5f23b7a..3ee9165f 100644 --- a/client/src/components/CreateNonFungiblePage/FileUpload.tsx +++ b/client/src/components/CreateNonFungiblePage/FileUpload.tsx @@ -2,8 +2,8 @@ import axios from 'axios'; import React, { useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; import { Box, Flex, Heading, Text, Image } from '@chakra-ui/react'; - -import { DispatchFn, State } from './reducer'; +import { useSelector, useDispatch } from '../../reducer'; +import { updateArtifactUri } from '../../reducer/slices/createNft'; type IpfsContent = { cid: string; @@ -12,26 +12,15 @@ type IpfsContent = { publicGatewayUrl: string; }; -export default function FileUpload({ - state, - dispatch -}: { - state: State; - dispatch: DispatchFn; -}) { +export default function FileUpload() { + const state = useSelector(s => s.createNft); + const dispatch = useDispatch(); const onDrop = useCallback(async (acceptedFiles: File[]) => { const formData = new FormData(); formData.append('file', acceptedFiles[0]); const response = await axios.post('/ipfs-upload', formData); - - dispatch({ - type: 'update_artifact_uri', - payload: { value: response.data.publicGatewayUrl } - }); - - console.log('Succesfully uploaded image to IPFS Server.'); - console.log(response.data); + dispatch(updateArtifactUri(response.data.publicGatewayUrl)); }, []); const { getRootProps, getInputProps } = useDropzone({ diff --git a/client/src/components/CreateNonFungiblePage/Form.tsx b/client/src/components/CreateNonFungiblePage/Form.tsx index ebf678ca..0271d607 100644 --- a/client/src/components/CreateNonFungiblePage/Form.tsx +++ b/client/src/components/CreateNonFungiblePage/Form.tsx @@ -12,19 +12,23 @@ import { } from '@chakra-ui/react'; import { Plus, X } from 'react-feather'; import { MinterButton } from '../common'; -import { DispatchFn, State } from './reducer'; + +import { useSelector, useDispatch } from '../../reducer'; +import { + addMetadataRow, + deleteMetadataRow, + updateField, + updateMetadataRowName, + updateMetadataRowValue +} from '../../reducer/slices/createNft'; import { ipfsCidFromUri } from '../../util'; const DESCRIPTION_PLACEHOLDER = 'e.g. “This is an exclusive japanese comic illustration. Once you purchase it you will be able to get the t-shirt”'; -export default function Form({ - dispatch, - state -}: { - dispatch: DispatchFn; - state: State; -}) { +export default function Form() { + const state = useSelector(s => s.createNft); + const dispatch = useDispatch(); const { name, description } = state.fields; return ( <> @@ -38,10 +42,7 @@ export default function Form({ placeholder="Input your asset name" value={name || ''} onChange={e => - dispatch({ - type: 'update_field', - payload: { name: 'name', value: e.target.value } - }) + dispatch(updateField({ name: 'name', value: e.target.value })) } /> @@ -58,10 +59,9 @@ export default function Form({ placeholder={DESCRIPTION_PLACEHOLDER} value={description || ''} onChange={e => - dispatch({ - type: 'update_field', - payload: { name: 'description', value: e.target.value } - }) + dispatch( + updateField({ name: 'description', value: e.target.value }) + ) } /> @@ -82,19 +82,16 @@ export default function Form({ Add attributes to your asset - {state.metadataRows.map(({ name, value }, i) => { + {state.metadataRows.map(({ name, value }, key) => { return ( - + Name - dispatch({ - type: 'update_metadata_row_name', - payload: { key: i, name: e.target.value } - }) + dispatch(updateMetadataRowName({ key, name: e.target.value })) } /> @@ -104,10 +101,9 @@ export default function Form({ placeholder="e.g. India" value={value || ''} onChange={e => - dispatch({ - type: 'update_metadata_row_value', - payload: { key: i, value: e.target.value } - }) + dispatch( + updateMetadataRowValue({ key, value: e.target.value }) + ) } /> @@ -116,9 +112,7 @@ export default function Form({ ml={4} mt={1} cursor="pointer" - onClick={() => - dispatch({ type: 'delete_metadata_row', payload: { key: i } }) - } + onClick={() => dispatch(deleteMetadataRow({ key }))} _hover={{ color: 'brand.red' }} @@ -130,7 +124,7 @@ export default function Form({ })} dispatch({ type: 'add_metadata_row' })} + onClick={() => dispatch(addMetadataRow())} pl={3} pr={3} pt={2} diff --git a/client/src/components/CreateNonFungiblePage/Preview.tsx b/client/src/components/CreateNonFungiblePage/Preview.tsx index 0f2e09c6..ae793078 100644 --- a/client/src/components/CreateNonFungiblePage/Preview.tsx +++ b/client/src/components/CreateNonFungiblePage/Preview.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { Divider, Heading, Flex, Image, Text } from '@chakra-ui/react'; -import { State } from './reducer'; import { ipfsCidFromUri } from '../../util'; +import { useSelector } from '../../reducer'; -export default function Preview({ state }: { state: State }) { +export default function Preview() { + const state = useSelector(s => s.createNft); const { name, description } = state.fields; return ( void; - createStatus: CreateStatus; + status: StatusKey; } export default function StatusModal(props: StatusModalProps) { - const { isOpen, onClose, createStatus } = props; + const { isOpen, onClose, status } = props; const initialRef = React.useRef(null); const close = () => { - if (createStatus === CreateStatus.Complete) { + if (status === 'complete') { onClose(); } }; @@ -41,7 +41,7 @@ export default function StatusModal(props: StatusModalProps) { > - {createStatus === CreateStatus.InProgress ? ( + {status === 'in_transit' ? ( @@ -49,7 +49,7 @@ export default function StatusModal(props: StatusModalProps) { ) : null} - {createStatus === CreateStatus.Complete ? ( + {status === 'complete' ? ( diff --git a/client/src/components/CreateNonFungiblePage/index.tsx b/client/src/components/CreateNonFungiblePage/index.tsx index 44fbcbac..1d4c6b9c 100644 --- a/client/src/components/CreateNonFungiblePage/index.tsx +++ b/client/src/components/CreateNonFungiblePage/index.tsx @@ -1,31 +1,27 @@ -import React, { Dispatch, useContext, useEffect, useReducer } from 'react'; +import React, { useEffect } from 'react'; import { useLocation } from 'wouter'; import { Box, Flex, Text, useDisclosure } from '@chakra-ui/react'; import Joi from 'joi'; -import { SystemContext } from '../../context/system'; import { MinterButton } from '../common'; -import { - reducer, - steps, - initialState, - DispatchFn, - State, - fileUploadSchema, - assetDetailsSchema, - collectionSelectSchema, - Action, - CreateStatus -} from './reducer'; import Form from './Form'; import FileUpload from './FileUpload'; import CollectionSelect from './CollectionSelect'; import Preview from './Preview'; import StatusModal from './StatusModal'; import { ChevronLeft, X } from 'react-feather'; -import { mintToken } from '../../lib/nfts/actions'; -import { SystemWithWallet } from '../../lib/system'; -function ProgressIndicator({ state }: { state: State }) { +import { useSelector, useDispatch } from '../../reducer'; +import { + CreateNftState, + decrementStep, + incrementStep, + steps +} from '../../reducer/slices/createNft'; +import { mintTokenAction } from '../../reducer/async/actions'; +import * as validators from '../../reducer/validators'; +import { setStatus } from '../../reducer/slices/status'; + +function ProgressIndicator({ state }: { state: CreateNftState }) { const stepIdx = steps.indexOf(state.step); return ( @@ -51,70 +47,54 @@ function ProgressIndicator({ state }: { state: State }) { ); } -function LeftContent(props: { state: State; dispatch: DispatchFn }) { - if (props.state.step === 'file_upload') { - return ; - } - if (props.state.step === 'asset_details') { - return
; - } - if (props.state.step === 'collection_select') { - return ; +function LeftContent() { + const step = useSelector(s => s.createNft.step); + switch (step) { + case 'file_upload': + return ; + case 'asset_details': + return ; + case 'collection_select': + return ; + default: + return null; } - // TypeScript not checking this properly? The above cases are exhaustive... - return null; } -function isValid(schema: Joi.ObjectSchema, state: State) { +function isValid(schema: Joi.ObjectSchema, state: CreateNftState) { if (schema.validate(state, { allowUnknown: true }).error) { return false; } return true; } -function stepIsValid(state: State) { - if (state.step === 'file_upload' && isValid(fileUploadSchema, state)) { +function stepIsValid(state: CreateNftState) { + if ( + state.step === 'file_upload' && + isValid(validators.fileUploadSchema, state) + ) { return true; } - if (state.step === 'asset_details' && isValid(assetDetailsSchema, state)) { + if ( + state.step === 'asset_details' && + isValid(validators.assetDetailsSchema, state) + ) { return true; } if ( state.step === 'collection_select' && - isValid(collectionSelectSchema, state) + isValid(validators.collectionSelectSchema, state) ) { return true; } } -async function handleCreate(system: SystemWithWallet, state: State) { - const metadata: Record = {}; - const artifactUri = state.artifactUri as string; - const name = state.fields.name as string; - const address = state.collectionAddress as string; - - metadata.artifactUri = artifactUri; - metadata.displayUri = artifactUri; - metadata.name = name; - if (state.fields.description) { - metadata.description = state.fields.description; - } - - for (let row of state.metadataRows) { - if (row.name !== null && row.value !== null) { - metadata[row.name] = row.value; - } - } - - const op = await mintToken(system, address, metadata); - return await op.confirmation(); -} - export default function CreateNonFungiblePage() { - const [state, dispatch] = useReducer(reducer, initialState); + const { system, createNft: state } = useSelector(s => s); + const status = useSelector(s => s.status.mintToken); + const dispatch = useDispatch(); const [, setLocation] = useLocation(); - const { isOpen, onOpen, onClose } = useDisclosure(); - const { system } = useContext(SystemContext); + const { isOpen, onClose, onOpen } = useDisclosure(); useEffect(() => { if (system.status !== 'WalletConnected') { setLocation('/'); @@ -162,7 +142,7 @@ export default function CreateNonFungiblePage() { dispatch({ type: 'decrement_step' })} + onClick={() => dispatch(decrementStep())} > @@ -177,25 +157,14 @@ export default function CreateNonFungiblePage() { } switch (state.step) { case 'file_upload': { - return dispatch({ type: 'increment_step' }); + return dispatch(incrementStep()); } case 'asset_details': { - return dispatch({ type: 'increment_step' }); + return dispatch(incrementStep()); } case 'collection_select': { - if (system.status === 'WalletConnected') { - dispatch({ - type: 'set_create_status', - payload: { status: CreateStatus.InProgress } - }); - onOpen(); - await handleCreate(system, state); - dispatch({ - type: 'set_create_status', - payload: { status: CreateStatus.Complete } - }); - return; - } + onOpen(); + return dispatch(mintTokenAction()); } } }} @@ -208,8 +177,9 @@ export default function CreateNonFungiblePage() { onClose={() => { onClose(); setLocation('/collections'); + dispatch(setStatus({ method: 'mintToken', status: 'ready' })); }} - createStatus={state.createStatus} + status={status.status} /> @@ -221,7 +191,7 @@ export default function CreateNonFungiblePage() { minHeight="0px" flex="1" > - + @@ -251,7 +221,7 @@ export default function CreateNonFungiblePage() { px={28} pt={16} > - + )} diff --git a/client/src/components/SplashPage/index.tsx b/client/src/components/SplashPage/index.tsx index 31d48e45..6987bb06 100644 --- a/client/src/components/SplashPage/index.tsx +++ b/client/src/components/SplashPage/index.tsx @@ -1,13 +1,16 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { useLocation } from 'wouter'; import { Flex, Text, Heading, Image, Link } from '@chakra-ui/react'; -import { SystemContext } from '../../context/system'; import { MinterButton /* , MinterLink */ } from '../common'; import logo from './logo.svg'; +import { useSelector, useDispatch } from '../../reducer'; +import { connectWallet } from '../../reducer/async/wallet'; export default function SplashPage() { const [, setLocation] = useLocation(); - const { system, connect } = useContext(SystemContext); + const system = useSelector(s => s.system); + const dispatch = useDispatch(); + useEffect(() => { if (system.status === 'WalletConnected') { setLocation('/collections'); @@ -43,7 +46,7 @@ export default function SplashPage() { variant="secondaryActionLined" onClick={e => { e.preventDefault(); - connect(); + dispatch(connectWallet()); }} > Connect your wallet diff --git a/client/src/components/common/CreateCollection.tsx b/client/src/components/common/CreateCollection.tsx index 7deef26c..e35c668b 100644 --- a/client/src/components/common/CreateCollection.tsx +++ b/client/src/components/common/CreateCollection.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext, MutableRefObject } from 'react'; +import React, { useState, MutableRefObject } from 'react'; import { Box, Text, @@ -19,8 +19,10 @@ import { } from '@chakra-ui/react'; import { CheckCircle, Plus } from 'react-feather'; import { MinterButton } from '../common'; -import { createAssetContract } from '../../lib/nfts/actions'; -import { SystemContext } from '../../context/system'; + +import { useSelector, useDispatch } from '../../reducer'; +import { createAssetContractAction } from '../../reducer/async/actions'; +import { setStatus } from '../../reducer/slices/status'; interface FormProps { initialRef: MutableRefObject; @@ -58,32 +60,20 @@ function Form({ initialRef, onSubmit }: FormProps) { ); } -enum Status { - Ready = 'ready', - InProgress = 'inProgress', - Complete = 'complete' -} - export function CreateCollectionButton() { - const { system } = useContext(SystemContext); - const [status, setStatus] = useState(Status.Ready); + const { status } = useSelector(s => s.status.createAssetContract); + const dispatch = useDispatch(); const { isOpen, onOpen, onClose } = useDisclosure(); const initialRef = React.useRef(null); - if (system.status !== 'WalletConnected') { - return null; - } - const onSubmit = async (form: { contractName: string }) => { - setStatus(Status.InProgress); - const op = await createAssetContract(system, form.contractName); - await op.confirmation(); - setStatus(Status.Complete); + dispatch(createAssetContractAction(form.contractName)); }; const close = () => { - if (status !== Status.InProgress) { + if (status !== 'in_transit') { onClose(); + setStatus({ method: 'createAssetContract', status: 'ready' }); } }; @@ -107,10 +97,10 @@ export function CreateCollectionButton() { > - {status === Status.Ready ? ( + {status === 'ready' ? ( ) : null} - {status === Status.InProgress ? ( + {status === 'in_transit' ? ( @@ -118,7 +108,7 @@ export function CreateCollectionButton() { ) : null} - {status === Status.Complete ? ( + {status === 'complete' ? ( diff --git a/client/src/components/common/Header.tsx b/client/src/components/common/Header.tsx index ced3375f..080fe05a 100644 --- a/client/src/components/common/Header.tsx +++ b/client/src/components/common/Header.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React from 'react'; import { useLocation } from 'wouter'; import { Box, @@ -13,7 +13,8 @@ import { } from '@chakra-ui/react'; import { ChevronDown, Package, Plus } from 'react-feather'; import headerLogo from './assets/header-logo.svg'; -import { SystemContext } from '../../context/system'; +import { useSelector, useDispatch } from '../../reducer'; +import { disconnectWallet } from '../../reducer/async/wallet'; interface HeaderLinkProps { to: string; @@ -68,8 +69,9 @@ function WalletInfo(props: { tzPublicKey: string }) { } function WalletDisplay() { - const { system, disconnect } = useContext(SystemContext); const [, setLocation] = useLocation(); + const system = useSelector(s => s.system); + const dispatch = useDispatch(); if (system.status !== 'WalletConnected') { return null; } @@ -83,7 +85,7 @@ function WalletDisplay() { { - await disconnect(); + await dispatch(disconnectWallet()); setLocation('/'); }} > diff --git a/client/src/components/common/TransferToken.tsx b/client/src/components/common/TransferToken.tsx index 2b2183fa..6d1d4ab6 100644 --- a/client/src/components/common/TransferToken.tsx +++ b/client/src/components/common/TransferToken.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext, MutableRefObject } from 'react'; +import React, { useState, MutableRefObject } from 'react'; import { Box, Flex, @@ -19,9 +19,9 @@ import { } from '@chakra-ui/react'; import { Plus, CheckCircle } from 'react-feather'; import { MinterButton } from '../common'; -import { transferToken } from '../../lib/nfts/actions'; -import { SystemContext } from '../../context/system'; -import { SystemWithWallet } from '../../lib/system'; +import { useSelector, useDispatch } from '../../reducer'; +import { transferTokenAction } from '../../reducer/async/actions'; +import { setStatus } from '../../reducer/slices/status'; interface FormProps { initialRef: MutableRefObject; @@ -64,36 +64,26 @@ interface TransferTokenButtonProps { tokenId: number; } -enum Status { - Ready = 'ready', - InProgress = 'inProgress', - Complete = 'complete' -} - export function TransferTokenButton(props: TransferTokenButtonProps) { - const { system } = useContext(SystemContext); - const [status, setStatus] = useState(Status.Ready); + const { status } = useSelector(s => s.status.transferToken); + const dispatch = useDispatch(); + const { isOpen, onOpen, onClose } = useDisclosure(); const initialRef = React.useRef(null); - if (system.status !== 'WalletConnected') { - return null; - } - const onSubmit = async (form: { toAddress: string }) => { - setStatus(Status.InProgress); - const op = await transferToken( - system, - props.contractAddress, - props.tokenId, - form.toAddress + dispatch( + transferTokenAction({ + contract: props.contractAddress, + tokenId: props.tokenId, + to: form.toAddress + }) ); - await op.confirmation(); - setStatus(Status.Complete); }; const close = () => { - if (status !== Status.InProgress) { + if (status !== 'in_transit') { + dispatch(setStatus({ method: 'transferToken', status: 'ready' })); onClose(); } }; @@ -118,10 +108,10 @@ export function TransferTokenButton(props: TransferTokenButtonProps) { > - {status === Status.Ready ? ( + {status === 'ready' ? ( ) : null} - {status === Status.InProgress ? ( + {status === 'in_transit' ? ( @@ -129,7 +119,7 @@ export function TransferTokenButton(props: TransferTokenButtonProps) { ) : null} - {status === Status.Complete ? ( + {status === 'complete' ? ( diff --git a/client/src/reducer/index.ts b/client/src/reducer/index.ts index fe4b6d10..9075d3de 100644 --- a/client/src/reducer/index.ts +++ b/client/src/reducer/index.ts @@ -6,11 +6,13 @@ import { import collectionsSlice from './slices/collections'; import createNftSlice from './slices/createNft'; import systemSlice from './slices/system'; +import statusSlice from './slices/status'; export const reducer = combineReducers({ collections: collectionsSlice.reducer, createNft: createNftSlice.reducer, - system: systemSlice.reducer + system: systemSlice.reducer, + status: statusSlice.reducer }); export const store = configureStore({ @@ -19,16 +21,16 @@ export const store = configureStore({ getDefaultMiddleware({ immutableCheck: { ignoredPaths: ['system'] + }, + serializableCheck: { + ignoredPaths: ['system'] } }) }); export type State = ReturnType; export type Dispatch = typeof store.dispatch; - -export function useDispatch() { - return baseUseDispatch(); -} +export const useDispatch = () => baseUseDispatch(); export function useSelector( selector: (s: State) => TSelect, diff --git a/client/src/reducer/slices/collections.ts b/client/src/reducer/slices/collections.ts index 0ed4dd9e..1f9d0393 100644 --- a/client/src/reducer/slices/collections.ts +++ b/client/src/reducer/slices/collections.ts @@ -45,13 +45,13 @@ export const initialState: CollectionsState = { type PopulateCollection = Reducer<{ address: string; tokens: Nft[] }>; -const populateCollection: PopulateCollection = (state, { payload }) => { +const populateCollectionR: PopulateCollection = (state, { payload }) => { if (state.collections[payload.address]) { state.collections[payload.address].tokens = payload.tokens; } }; -const updateCollections: Reducer = (state, action) => { +const updateCollectionsR: Reducer = (state, action) => { for (let coll of action.payload) { if (!state.collections[coll.address]) { state.collections[coll.address] = { ...coll, tokens: null }; @@ -59,13 +59,13 @@ const updateCollections: Reducer = (state, action) => { } }; -const updateCollection: Reducer = (state, { payload }) => { +const updateCollectionR: Reducer = (state, { payload }) => { if (!state.collections[payload.address]) { state.collections[payload.address] = { ...payload, tokens: null }; } }; -const selectCollection: Reducer = (state, action) => { +const selectCollectionR: Reducer = (state, action) => { state.selectedCollection = action.payload; }; @@ -73,16 +73,23 @@ const slice = createSlice({ name: 'collections', initialState, reducers: { - updateCollections, - updateCollection, - selectCollection, - populateCollection + updateCollections: updateCollectionsR, + updateCollection: updateCollectionR, + selectCollection: selectCollectionR, + populateCollection: populateCollectionR }, extraReducers: ({ addCase }) => { - addCase(getContractNftsQuery.fulfilled, populateCollection); - addCase(getNftAssetContractQuery.fulfilled, updateCollection); - addCase(getWalletAssetContractsQuery.fulfilled, updateCollections); + addCase(getContractNftsQuery.fulfilled, populateCollectionR); + addCase(getNftAssetContractQuery.fulfilled, updateCollectionR); + addCase(getWalletAssetContractsQuery.fulfilled, updateCollectionsR); } }); +export const { + updateCollections, + updateCollection, + selectCollection, + populateCollection +} = slice.actions; + export default slice; diff --git a/client/src/reducer/slices/createNft.ts b/client/src/reducer/slices/createNft.ts index 6cf02c8a..5da7fb07 100644 --- a/client/src/reducer/slices/createNft.ts +++ b/client/src/reducer/slices/createNft.ts @@ -97,4 +97,17 @@ const slice = createSlice({ } }); +export const { + incrementStep, + decrementStep, + updateField, + updateArtifactUri, + addMetadataRow, + updateMetadataRowName, + updateMetadataRowValue, + deleteMetadataRow, + selectCollection, + setCreateStatus +} = slice.actions; + export default slice; diff --git a/client/src/reducer/slices/status.ts b/client/src/reducer/slices/status.ts index 791c1ef3..6477901b 100644 --- a/client/src/reducer/slices/status.ts +++ b/client/src/reducer/slices/status.ts @@ -10,7 +10,7 @@ import { getWalletAssetContractsQuery } from '../async/queries'; -type StatusKey = 'ready' | 'in_transit' | 'complete'; +export type StatusKey = 'ready' | 'in_transit' | 'complete'; interface Status { status: StatusKey; @@ -62,9 +62,12 @@ const slice = createSlice({ { method: 'createAssetContract', action: createAssetContractAction }, { method: 'mintToken', action: mintTokenAction }, { method: 'transferToken', action: transferTokenAction }, - { method: 'getContracts', action: getContractNftsQuery }, + { method: 'getContractNfts', action: getContractNftsQuery }, { method: 'getNftAssetContract', action: getNftAssetContractQuery }, - { name: 'getWalletAssetContracts', action: getWalletAssetContractsQuery } + { + method: 'getWalletAssetContracts', + action: getWalletAssetContractsQuery + } ].forEach(({ method, action }) => { const name = method as keyof StatusState; addCase(action.pending, state => { @@ -80,4 +83,6 @@ const slice = createSlice({ } }); +export const { setStatus, setError, clearError } = slice.actions; + export default slice; From 01f516fdba9e6d77c6f84dc31a9b1ba0646e49cb Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 14:51:44 -0500 Subject: [PATCH 05/28] feat: Remove old reducers --- .../Collections/Catalog/TokenGrid.tsx | 4 +- client/src/components/Collections/reducer.ts | 99 ----------- .../CreateNonFungiblePage/reducer.ts | 166 ------------------ client/src/reducer/slices/collections.ts | 6 +- 4 files changed, 6 insertions(+), 269 deletions(-) delete mode 100644 client/src/components/Collections/reducer.ts delete mode 100644 client/src/components/CreateNonFungiblePage/reducer.ts diff --git a/client/src/components/Collections/Catalog/TokenGrid.tsx b/client/src/components/Collections/Catalog/TokenGrid.tsx index 07c9a843..9515f72d 100644 --- a/client/src/components/Collections/Catalog/TokenGrid.tsx +++ b/client/src/components/Collections/Catalog/TokenGrid.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { useLocation } from 'wouter'; import { AspectRatio, Box, Flex, Grid, Image, Text } from '@chakra-ui/react'; -import { Token, State } from '../reducer'; +import { Token, CollectionsState } from '../../../reducer/slices/collections'; import { Wind, HelpCircle } from 'react-feather'; interface TokenTileProps extends Token { @@ -78,7 +78,7 @@ function TokenTile(props: TokenTileProps) { } interface TokenGridProps { - state: State; + state: CollectionsState; walletAddress: string; } diff --git a/client/src/components/Collections/reducer.ts b/client/src/components/Collections/reducer.ts deleted file mode 100644 index 2868d79c..00000000 --- a/client/src/components/Collections/reducer.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { produce } from 'immer'; -import config from '../../config.json'; - -export interface Token { - id: number; - title: string; - owner: string; - description: string; - artifactUri: string; - metadata: Record; -} - -export interface Collection { - address: string; - metadata: Record; - tokens: Token[] | null; -} - -export interface State { - selectedCollection: string | null; - globalCollection: string; - collections: Record; -} - -const globalCollectionAddress = config.contracts.nftFaucet; - -export const initialState: State = { - selectedCollection: null, - globalCollection: globalCollectionAddress, - collections: { - [globalCollectionAddress]: { - address: globalCollectionAddress, - metadata: { - name: 'Minter' - }, - tokens: null - } - } -}; - -export type Action = - | { - type: 'update_collections'; - payload: { collections: Collection[] }; - } - | { - type: 'update_collection'; - payload: { collection: Collection }; - } - | { - type: 'select_collection'; - payload: { address: string }; - } - | { - type: 'populate_collection'; - payload: { address: string; tokens: Token[] }; - }; - -export function reducer(state: State, action: Action) { - switch (action.type) { - case 'update_collections': { - return produce(state, draftState => { - for (let collection of action.payload.collections) { - if (!state.collections[collection.address]) { - draftState.collections[collection.address] = { - ...collection, - tokens: null - }; - } - } - }); - } - case 'update_collection': { - return produce(state, draftState => { - const collection = action.payload.collection; - if (!state.collections[collection.address]) { - draftState.collections[collection.address] = { - ...collection, - tokens: null - }; - } - }); - } - case 'select_collection': { - return { ...state, selectedCollection: action.payload.address }; - } - case 'populate_collection': { - const { address, tokens } = action.payload; - return produce(state, draftState => { - if (state.collections[address]) { - draftState.collections[address].tokens = tokens; - } - }); - } - default: { - return state; - } - } -} diff --git a/client/src/components/CreateNonFungiblePage/reducer.ts b/client/src/components/CreateNonFungiblePage/reducer.ts deleted file mode 100644 index d03ddf43..00000000 --- a/client/src/components/CreateNonFungiblePage/reducer.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { Dispatch } from 'react'; -import produce from 'immer'; -import Joi from 'joi'; - -type Step = 'file_upload' | 'asset_details' | 'collection_select'; - -export const steps: Step[] = [ - 'file_upload', - 'asset_details', - 'collection_select' -]; - -interface Fields { - name: string | null; - description: string | null; -} - -export enum CreateStatus { - Ready = 'ready', - InProgress = 'inProgress', - Complete = 'complete' -} - -interface MetadataRow { - name: string | null; - value: string | null; -} - -export interface State { - step: Step; - artifactUri: string | null; - fields: Fields; - metadataRows: MetadataRow[]; - collectionAddress: string | null; - createStatus: CreateStatus; -} - -export const fileUploadSchema = Joi.object({ - artifactUri: Joi.string().required() -}); - -export const assetDetailsSchema = fileUploadSchema.append({ - fields: Joi.object({ - name: Joi.string().min(1).required(), - description: Joi.string().allow(null).allow('') - }), - metadataRows: Joi.array().items( - Joi.object({ - name: Joi.string().min(1).required(), - value: Joi.string().min(1).required() - }) - ) -}); - -export const collectionSelectSchema = assetDetailsSchema.append({ - collectionAddress: Joi.string().required() -}); - -export type Action = - | { type: 'increment_step' } - | { type: 'decrement_step' } - | { type: 'update_field'; payload: { name: keyof Fields; value: string } } - | { type: 'update_artifact_uri'; payload: { value: string } } - | { type: 'add_metadata_row' } - | { - type: 'update_metadata_row_name'; - payload: { key: number; name: string }; - } - | { - type: 'update_metadata_row_value'; - payload: { key: number; value: string }; - } - | { type: 'delete_metadata_row'; payload: { key: number } } - | { type: 'select_collection'; payload: { address: string } } - | { type: 'set_create_status'; payload: { status: CreateStatus } }; - -export type DispatchFn = Dispatch; - -export const initialState: State = { - step: 'file_upload', - artifactUri: null, - fields: { - name: null, - description: null - }, - metadataRows: [], - collectionAddress: null, - createStatus: CreateStatus.Ready -}; - -export function reducer(state: State, action: Action) { - switch (action.type) { - case 'increment_step': { - const stepIdx = steps.indexOf(state.step); - if (stepIdx + 1 < steps.length) { - return produce(state, draftState => { - draftState.step = steps[stepIdx + 1]; - }); - } - return state; - } - case 'decrement_step': { - const stepIdx = steps.indexOf(state.step); - if (stepIdx > 0) { - return produce(state, draftState => { - draftState.step = steps[stepIdx - 1]; - }); - } - return state; - } - case 'update_field': { - const { name, value } = action.payload; - return produce(state, draftState => { - draftState.fields[name] = value; - }); - } - case 'update_artifact_uri': { - const { value } = action.payload; - return produce(state, draftState => { - draftState.artifactUri = value; - }); - } - case 'add_metadata_row': { - return produce(state, draftState => { - draftState.metadataRows.push({ name: null, value: null }); - }); - } - case 'update_metadata_row_name': { - const { key, name } = action.payload; - if (!state.metadataRows[key]) { - return state; - } - return produce(state, draftState => { - draftState.metadataRows[key].name = name; - }); - } - case 'update_metadata_row_value': { - const { key, value } = action.payload; - if (!state.metadataRows[key]) { - return state; - } - return produce(state, draftState => { - draftState.metadataRows[key].value = value; - }); - } - case 'delete_metadata_row': { - return produce(state, draftState => { - draftState.metadataRows.splice(action.payload.key, 1); - }); - } - case 'select_collection': { - const { address } = action.payload; - return produce(state, draftState => { - draftState.collectionAddress = address; - }); - } - case 'set_create_status': { - const { status } = action.payload; - return produce(state, draftState => { - draftState.createStatus = status; - }); - } - default: - return state; - } -} diff --git a/client/src/reducer/slices/collections.ts b/client/src/reducer/slices/collections.ts index 1f9d0393..860b2274 100644 --- a/client/src/reducer/slices/collections.ts +++ b/client/src/reducer/slices/collections.ts @@ -11,8 +11,10 @@ import config from '../../config.json'; // Types +export type Token = Nft; + export interface Collection extends AssetContract { - tokens: Nft[] | null; + tokens: Token[] | null; } export interface CollectionsState { @@ -43,7 +45,7 @@ export const initialState: CollectionsState = { //// Reducers & Slice -type PopulateCollection = Reducer<{ address: string; tokens: Nft[] }>; +type PopulateCollection = Reducer<{ address: string; tokens: Token[] }>; const populateCollectionR: PopulateCollection = (state, { payload }) => { if (state.collections[payload.address]) { From eec6541ddf03d00faaf028214b4459891f558005 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 15:02:51 -0500 Subject: [PATCH 06/28] fix: Ignore wallet actions in serializable check --- client/src/reducer/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/reducer/index.ts b/client/src/reducer/index.ts index 9075d3de..1cc0a716 100644 --- a/client/src/reducer/index.ts +++ b/client/src/reducer/index.ts @@ -23,7 +23,11 @@ export const store = configureStore({ ignoredPaths: ['system'] }, serializableCheck: { - ignoredPaths: ['system'] + ignoredPaths: ['system'], + ignoredActions: [ + 'wallet/connect/fulfilled', + 'wallet/disconnect/fulfilled' + ] } }) }); From 40b1df1773546d9acf07c18833cb3337337b2a9a Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 15:45:59 -0500 Subject: [PATCH 07/28] fix: Resolve compiler warnings --- client/package.json | 11 +++++------ .../src/components/Collections/Catalog/index.tsx | 9 +++++++-- .../components/Collections/TokenDetail/index.tsx | 12 +++++++++--- .../CreateNonFungiblePage/CollectionSelect.tsx | 2 +- .../CreateNonFungiblePage/FileUpload.tsx | 15 +++++++++------ client/src/components/SplashPage/index.tsx | 2 +- 6 files changed, 32 insertions(+), 19 deletions(-) diff --git a/client/package.json b/client/package.json index 9270f9e8..8de30a1a 100644 --- a/client/package.json +++ b/client/package.json @@ -3,20 +3,19 @@ "version": "0.1.0", "private": true, "dependencies": { - "@airgap/beacon-sdk": "2.1.0", + "@airgap/beacon-sdk": "2.2.0-beta.2", "@chakra-ui/react": "1.1.2", "@emotion/core": "10.0.28", "@emotion/react": "11.1.4", "@emotion/styled": "11.0.0", "@reduxjs/toolkit": "1.5.0", - "@taquito/beacon-wallet": "7.2.0-beta.2", - "@taquito/rpc": "7.2.0-beta.2", - "@taquito/signer": "7.2.0-beta.2", - "@taquito/taquito": "7.2.0-beta.2", + "@taquito/beacon-wallet": "8.0.0-beta.5", + "@taquito/rpc": "8.0.0-beta.5", + "@taquito/signer": "8.0.0-beta.5", + "@taquito/taquito": "8.0.0-beta.5", "@testing-library/jest-dom": "4.2.4", "@testing-library/react": "9.3.2", "@testing-library/user-event": "7.1.2", - "@thanos-wallet/dapp": "0.7.0", "@types/jest": "24.0.0", "@types/lodash": "4.14.165", "@types/node": "12.0.0", diff --git a/client/src/components/Collections/Catalog/index.tsx b/client/src/components/Collections/Catalog/index.tsx index a36cccf9..a7163233 100644 --- a/client/src/components/Collections/Catalog/index.tsx +++ b/client/src/components/Collections/Catalog/index.tsx @@ -25,7 +25,12 @@ export default function Catalog() { } else { dispatch(getContractNftsQuery(selectedCollection)); } - }, [system.status, state.selectedCollection]); + }, [ + system.status, + state.selectedCollection, + state.globalCollection, + dispatch + ]); useEffect(() => { if (system.status !== 'WalletConnected') { @@ -33,7 +38,7 @@ export default function Catalog() { } else { dispatch(getWalletAssetContractsQuery()); } - }, [system.status]); + }, [system.status, setLocation, dispatch]); const selectedCollection = state.selectedCollection; if (system.status !== 'WalletConnected' || !selectedCollection) { diff --git a/client/src/components/Collections/TokenDetail/index.tsx b/client/src/components/Collections/TokenDetail/index.tsx index 5eaa2cb1..48b8a837 100644 --- a/client/src/components/Collections/TokenDetail/index.tsx +++ b/client/src/components/Collections/TokenDetail/index.tsx @@ -1,7 +1,11 @@ import React, { useEffect, useState } from 'react'; import { useLocation } from 'wouter'; import { AspectRatio, Box, Flex, Heading, Image, Text } from '@chakra-ui/react'; -import { ChevronLeft, HelpCircle, MoreHorizontal, Star } from 'react-feather'; +import { + ChevronLeft, + HelpCircle, + /* MoreHorizontal, */ Star +} from 'react-feather'; import { MinterButton } from '../../common'; import { TransferTokenButton } from '../../common/TransferToken'; import { ipfsCidFromUri } from '../../../util'; @@ -93,13 +97,15 @@ function TokenDetail({ contractAddress, tokenId }: TokenDetailProps) { const dispatch = useDispatch(); const collection = state.collections[contractAddress]; + const collectionUndefined = collection === undefined; + useEffect(() => { - if (!collection) { + if (collectionUndefined) { dispatch(getNftAssetContractQuery(contractAddress)); } else { dispatch(getContractNftsQuery(contractAddress)); } - }, [contractAddress, tokenId, collection === undefined]); + }, [contractAddress, tokenId, collectionUndefined, dispatch]); if (!collection?.tokens) { return null; diff --git a/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx b/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx index e18a5848..ef128688 100644 --- a/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx +++ b/client/src/components/CreateNonFungiblePage/CollectionSelect.tsx @@ -63,7 +63,7 @@ export default function CollectionSelect() { useEffect(() => { dispatch(getWalletAssetContractsQuery()); - }, [collections]); + }, [collections, dispatch]); return ( diff --git a/client/src/components/CreateNonFungiblePage/FileUpload.tsx b/client/src/components/CreateNonFungiblePage/FileUpload.tsx index 3ee9165f..88d605ba 100644 --- a/client/src/components/CreateNonFungiblePage/FileUpload.tsx +++ b/client/src/components/CreateNonFungiblePage/FileUpload.tsx @@ -15,13 +15,16 @@ type IpfsContent = { export default function FileUpload() { const state = useSelector(s => s.createNft); const dispatch = useDispatch(); - const onDrop = useCallback(async (acceptedFiles: File[]) => { - const formData = new FormData(); - formData.append('file', acceptedFiles[0]); + const onDrop = useCallback( + async (acceptedFiles: File[]) => { + const formData = new FormData(); + formData.append('file', acceptedFiles[0]); - const response = await axios.post('/ipfs-upload', formData); - dispatch(updateArtifactUri(response.data.publicGatewayUrl)); - }, []); + const response = await axios.post('/ipfs-upload', formData); + dispatch(updateArtifactUri(response.data.publicGatewayUrl)); + }, + [dispatch] + ); const { getRootProps, getInputProps } = useDropzone({ onDrop, diff --git a/client/src/components/SplashPage/index.tsx b/client/src/components/SplashPage/index.tsx index 6987bb06..1cdf0ba8 100644 --- a/client/src/components/SplashPage/index.tsx +++ b/client/src/components/SplashPage/index.tsx @@ -15,7 +15,7 @@ export default function SplashPage() { if (system.status === 'WalletConnected') { setLocation('/collections'); } - }, [system.status]); + }, [system.status, setLocation]); return ( Date: Tue, 2 Feb 2021 15:46:35 -0500 Subject: [PATCH 08/28] chore: Remove deprecated system context --- client/src/context/system.tsx | 41 ----------------------------------- client/src/index.tsx | 5 +---- 2 files changed, 1 insertion(+), 45 deletions(-) delete mode 100644 client/src/context/system.tsx diff --git a/client/src/context/system.tsx b/client/src/context/system.tsx deleted file mode 100644 index 962bbb71..00000000 --- a/client/src/context/system.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { createContext, useState } from 'react'; -import { Minter, SystemWithToolkit, SystemWithWallet } from '../lib/system'; -import config from '../config.json'; - -type System = SystemWithToolkit | SystemWithWallet; - -interface ISystemContext { - system: System; - connect: () => Promise; - disconnect: () => Promise; -} - -export const SystemContext = createContext( - {} as ISystemContext -); - -function SystemContextProvider(props: { children: React.ReactNode }) { - const configured = Minter.configure(config); - const withToolkit = Minter.connectToolkit(configured); - const [system, setSystem] = useState(withToolkit); - - const connect = async () => { - if (system.status === 'ToolkitConnected') { - setSystem(await Minter.connectWallet(system)); - } - }; - - const disconnect = async () => { - if (system.status === 'WalletConnected') { - setSystem(await Minter.disconnectWallet(system)); - } - }; - - return ( - - {props.children} - - ); -} - -export default SystemContextProvider; diff --git a/client/src/index.tsx b/client/src/index.tsx index 7c43dd87..9d1ba20e 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -4,7 +4,6 @@ import './index.css'; import App from './components/App'; import * as serviceWorker from './serviceWorker'; import { ChakraProvider, extendTheme } from '@chakra-ui/react'; -import SystemContextProvider from './context/system'; import { Provider } from 'react-redux'; import { store } from './reducer'; @@ -223,9 +222,7 @@ function Root() { return ( - - - + ); From 435a5941930d319103fdf7eaa58f34177735b6e6 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Tue, 2 Feb 2021 15:46:53 -0500 Subject: [PATCH 09/28] chore: Remove unused files and outdated comments --- client/src/components/SplashPage/index.tsx | 3 - codegen.yml | 26 --- package-lock.json | 199 --------------------- 3 files changed, 228 deletions(-) delete mode 100644 codegen.yml delete mode 100644 package-lock.json diff --git a/client/src/components/SplashPage/index.tsx b/client/src/components/SplashPage/index.tsx index 1cdf0ba8..63f0bfb2 100644 --- a/client/src/components/SplashPage/index.tsx +++ b/client/src/components/SplashPage/index.tsx @@ -64,9 +64,6 @@ export default function SplashPage() { {/* Create */} {/* */} - {/* */} - {/* Learn more about TZIP-12 */} - {/* */} Date: Tue, 2 Feb 2021 18:40:28 -0500 Subject: [PATCH 10/28] feat: Add more robust validation and async error handling --- .../CreateNonFungiblePage/index.tsx | 39 ++----------- client/src/reducer/async/actions.ts | 51 ++++++++++++----- client/src/reducer/async/errors.ts | 17 ++++++ client/src/reducer/async/queries.ts | 31 +++++++--- client/src/reducer/slices/status.ts | 56 +++++++++++-------- client/src/reducer/validators/createNft.ts | 44 +++++++++++++++ client/src/reducer/validators/index.ts | 22 -------- 7 files changed, 160 insertions(+), 100 deletions(-) create mode 100644 client/src/reducer/async/errors.ts create mode 100644 client/src/reducer/validators/createNft.ts delete mode 100644 client/src/reducer/validators/index.ts diff --git a/client/src/components/CreateNonFungiblePage/index.tsx b/client/src/components/CreateNonFungiblePage/index.tsx index 1d4c6b9c..d89ed133 100644 --- a/client/src/components/CreateNonFungiblePage/index.tsx +++ b/client/src/components/CreateNonFungiblePage/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect } from 'react'; import { useLocation } from 'wouter'; import { Box, Flex, Text, useDisclosure } from '@chakra-ui/react'; -import Joi from 'joi'; import { MinterButton } from '../common'; import Form from './Form'; import FileUpload from './FileUpload'; @@ -18,7 +17,7 @@ import { steps } from '../../reducer/slices/createNft'; import { mintTokenAction } from '../../reducer/async/actions'; -import * as validators from '../../reducer/validators'; +import { validateCreateNftStep } from '../../reducer/validators/createNft'; import { setStatus } from '../../reducer/slices/status'; function ProgressIndicator({ state }: { state: CreateNftState }) { @@ -61,34 +60,6 @@ function LeftContent() { } } -function isValid(schema: Joi.ObjectSchema, state: CreateNftState) { - if (schema.validate(state, { allowUnknown: true }).error) { - return false; - } - return true; -} - -function stepIsValid(state: CreateNftState) { - if ( - state.step === 'file_upload' && - isValid(validators.fileUploadSchema, state) - ) { - return true; - } - if ( - state.step === 'asset_details' && - isValid(validators.assetDetailsSchema, state) - ) { - return true; - } - if ( - state.step === 'collection_select' && - isValid(validators.collectionSelectSchema, state) - ) { - return true; - } -} - export default function CreateNonFungiblePage() { const { system, createNft: state } = useSelector(s => s); const status = useSelector(s => s.status.mintToken); @@ -101,7 +72,7 @@ export default function CreateNonFungiblePage() { } }); - const valid = stepIsValid(state); + const stepIsValid = validateCreateNftStep(state); return ( @@ -150,11 +121,9 @@ export default function CreateNonFungiblePage() { Back { - if (!valid) { - return; - } + if (!stepIsValid) return; switch (state.step) { case 'file_upload': { return dispatch(incrementStep()); diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts index fa7fa196..d15cc7b0 100644 --- a/client/src/reducer/async/actions.ts +++ b/client/src/reducer/async/actions.ts @@ -5,21 +5,28 @@ import { mintToken, transferToken } from '../../lib/nfts/actions'; +import { ErrorKind, RejectValue } from './errors'; import { getContractNftsQuery, getWalletAssetContractsQuery } from './queries'; -import { collectionSelectSchema } from '../validators'; +import { validateCreateNftForm } from '../validators/createNft'; -type Opts = { state: State }; +type Options = { + state: State; + rejectValue: RejectValue; +}; export const createAssetContractAction = createAsyncThunk< { address: string }, string, - Opts + Options >( 'action/createAssetContract', async (name, { getState, rejectWithValue, dispatch }) => { const { system } = getState(); if (system.status !== 'WalletConnected') { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.WalletNotConnected, + message: 'Cannot create collection: Wallet not connected' + }); } try { const op = await createAssetContract(system, name); @@ -28,7 +35,10 @@ export const createAssetContractAction = createAsyncThunk< dispatch(getWalletAssetContractsQuery()); return { name, address }; } catch (e) { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.CreateAssetContractFailed, + message: 'Collection creation failed' + }); } } ); @@ -57,13 +67,19 @@ function buildMetadataFromState(state: State['createNft']) { export const mintTokenAction = createAsyncThunk< { contract: string }, undefined, - Opts + Options >('actions/mintToken', async (_, { getState, rejectWithValue, dispatch }) => { const { system, createNft: state } = getState(); - if (collectionSelectSchema.validate(state, { allowUnknown: true }).error) { - return rejectWithValue({ error: '' }); + if (!validateCreateNftForm(state)) { + return rejectWithValue({ + kind: ErrorKind.CreateNftFormInvalid, + message: 'Could not mint token: Form validation failed' + }); } else if (system.status !== 'WalletConnected') { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.WalletNotConnected, + message: 'Could not mint token: no wallet connected' + }); } const { address, metadata } = buildMetadataFromState(state); @@ -73,20 +89,26 @@ export const mintTokenAction = createAsyncThunk< dispatch(getContractNftsQuery(address)); return { contract: address }; } catch (e) { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.MintTokenFailed, + message: 'Mint token failed' + }); } }); export const transferTokenAction = createAsyncThunk< { contract: string; tokenId: number }, { contract: string; tokenId: number; to: string }, - Opts + Options >('actions/transferToken', async (args, api) => { const { getState, rejectWithValue, dispatch } = api; const { contract, tokenId, to } = args; const { system } = getState(); if (system.status !== 'WalletConnected') { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.WalletNotConnected, + message: 'Could not transfer token: no wallet connected' + }); } try { const op = await transferToken(system, contract, tokenId, to); @@ -94,6 +116,9 @@ export const transferTokenAction = createAsyncThunk< dispatch(getContractNftsQuery(contract)); return { contract: '', tokenId: 0 }; } catch (e) { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.TransferTokenFailed, + message: 'Transfer token failed' + }); } }); diff --git a/client/src/reducer/async/errors.ts b/client/src/reducer/async/errors.ts new file mode 100644 index 00000000..cac55b47 --- /dev/null +++ b/client/src/reducer/async/errors.ts @@ -0,0 +1,17 @@ +export enum ErrorKind { + UknownError, + WalletNotConnected, + CreateAssetContractFailed, + CreateNftFormInvalid, + MintTokenFailed, + TransferTokenFailed, + GetNftAssetContractFailed, + GetContractNftsFailed, + GetWalletNftAssetContractsFailed +} + +export interface RejectValue { + kind: ErrorKind; + message: string; + errorObj?: any; +} diff --git a/client/src/reducer/async/queries.ts b/client/src/reducer/async/queries.ts index 289184af..2cd5b46d 100644 --- a/client/src/reducer/async/queries.ts +++ b/client/src/reducer/async/queries.ts @@ -7,19 +7,24 @@ import { Nft, getWalletNftAssetContracts } from '../../lib/nfts/queries'; +import { ErrorKind, RejectValue } from './errors'; -type Opts = { state: State }; +type Opts = { state: State; rejectValue: RejectValue }; export const getNftAssetContractQuery = createAsyncThunk< AssetContract, string, Opts >('query/getNftAssetContract', async (address, api) => { - const { system } = api.getState(); + const { getState, rejectWithValue } = api; + const { system } = getState(); try { return await getNftAssetContract(system, address); } catch (e) { - return api.rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.GetNftAssetContractFailed, + message: `Failed to retrieve asset contract: ${address}` + }); } }); @@ -27,13 +32,16 @@ export const getContractNftsQuery = createAsyncThunk< { address: string; tokens: Nft[] }, string, Opts ->('query/getContractNfts', async (address, api) => { - const { system } = api.getState(); +>('query/getContractNfts', async (address, { getState, rejectWithValue }) => { + const { system } = getState(); try { const tokens = await getContractNfts(system, address); return { address, tokens }; } catch (e) { - return api.rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.GetContractNftsFailed, + message: `Failed to retrieve contract nfts from: ${address}` + }); } }); @@ -46,12 +54,19 @@ export const getWalletAssetContractsQuery = createAsyncThunk< async (_, { getState, rejectWithValue }) => { const { system } = getState(); if (system.status !== 'WalletConnected') { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.WalletNotConnected, + message: + "Could not retrieve wallet's asset contracts: no wallet connected" + }); } try { return await getWalletNftAssetContracts(system); } catch (e) { - return rejectWithValue({ error: '' }); + return rejectWithValue({ + kind: ErrorKind.GetWalletNftAssetContractsFailed, + message: "Failed to retrieve wallet's asset contracts" + }); } } ); diff --git a/client/src/reducer/slices/status.ts b/client/src/reducer/slices/status.ts index 6477901b..3e9c9207 100644 --- a/client/src/reducer/slices/status.ts +++ b/client/src/reducer/slices/status.ts @@ -9,12 +9,16 @@ import { getNftAssetContractQuery, getWalletAssetContractsQuery } from '../async/queries'; +import { ErrorKind, RejectValue } from '../async/errors'; export type StatusKey = 'ready' | 'in_transit' | 'complete'; interface Status { status: StatusKey; - error: SerializedError | null; + error: { + rejectValue: RejectValue; + serialized: SerializedError; + } | null; } export interface StatusState { @@ -26,7 +30,7 @@ export interface StatusState { getWalletAssetContracts: Status; } -type Name = keyof StatusState; +type Method = keyof StatusState; const defaultStatus: Status = { status: 'ready', error: null }; @@ -39,9 +43,12 @@ const initialState: StatusState = { getWalletAssetContracts: defaultStatus }; -type SetStatusAction = PayloadAction<{ method: Name; status: StatusKey }>; -type SetErrorAction = PayloadAction<{ method: Name; message: string }>; -type ClearErrorAction = PayloadAction<{ method: Name }>; +type SetStatusAction = PayloadAction<{ method: Method; status: StatusKey }>; +type ClearErrorAction = PayloadAction<{ method: Method }>; + +function methodMap(method: keyof StatusState, action: A) { + return { method, action }; +} const slice = createSlice({ name: 'status', @@ -50,39 +57,44 @@ const slice = createSlice({ setStatus(state, { payload }: SetStatusAction) { state[payload.method].status = payload.status; }, - setError(state, { payload }: SetErrorAction) { - state[payload.method].error = { message: payload.message }; - }, clearError(state, { payload }: ClearErrorAction) { state[payload.method].error = null; } }, extraReducers: ({ addCase }) => { [ - { method: 'createAssetContract', action: createAssetContractAction }, - { method: 'mintToken', action: mintTokenAction }, - { method: 'transferToken', action: transferTokenAction }, - { method: 'getContractNfts', action: getContractNftsQuery }, - { method: 'getNftAssetContract', action: getNftAssetContractQuery }, - { - method: 'getWalletAssetContracts', - action: getWalletAssetContractsQuery - } + methodMap('createAssetContract', createAssetContractAction), + methodMap('mintToken', mintTokenAction), + methodMap('transferToken', transferTokenAction), + methodMap('getContractNfts', getContractNftsQuery), + methodMap('getNftAssetContract', getNftAssetContractQuery), + methodMap('getWalletAssetContracts', getWalletAssetContractsQuery) ].forEach(({ method, action }) => { - const name = method as keyof StatusState; addCase(action.pending, state => { - state[name].status = 'in_transit'; + state[method].status = 'in_transit'; }); addCase(action.fulfilled, state => { - state[name].status = 'complete'; + state[method].status = 'complete'; }); addCase(action.rejected, (state, action) => { - state[name].error = action.error; + if (action.payload) { + state[method].error = { + rejectValue: action.payload, + serialized: action.error + }; + } + state[method].error = { + rejectValue: { + kind: ErrorKind.UknownError, + message: 'Unknown error' + }, + serialized: action.error + }; }); }); } }); -export const { setStatus, setError, clearError } = slice.actions; +export const { setStatus, clearError } = slice.actions; export default slice; diff --git a/client/src/reducer/validators/createNft.ts b/client/src/reducer/validators/createNft.ts new file mode 100644 index 00000000..26c7991d --- /dev/null +++ b/client/src/reducer/validators/createNft.ts @@ -0,0 +1,44 @@ +import Joi from 'joi'; +import { State } from '..'; + +export const fileUploadSchema = Joi.object({ + artifactUri: Joi.string().required() +}); + +export const assetDetailsSchema = fileUploadSchema.append({ + fields: Joi.object({ + name: Joi.string().min(1).required(), + description: Joi.string().allow(null).allow('') + }), + metadataRows: Joi.array().items( + Joi.object({ + name: Joi.string().min(1).required(), + value: Joi.string().min(1).required() + }) + ) +}); + +export const collectionSelectSchema = assetDetailsSchema.append({ + collectionAddress: Joi.string().required() +}); + +function isValid(schema: Joi.ObjectSchema, object: any) { + return !!schema.validate(object, { allowUnknown: true }).error; +} + +export function validateCreateNftStep(state: State['createNft']) { + switch (state.step) { + case 'file_upload': + return isValid(fileUploadSchema, state); + case 'asset_details': + return isValid(assetDetailsSchema, state); + case 'collection_select': + return isValid(collectionSelectSchema, state); + default: + return false; + } +} + +export function validateCreateNftForm(state: State['createNft']) { + return isValid(collectionSelectSchema, state); +} diff --git a/client/src/reducer/validators/index.ts b/client/src/reducer/validators/index.ts deleted file mode 100644 index 6b61ecc4..00000000 --- a/client/src/reducer/validators/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import Joi from 'joi'; - -export const fileUploadSchema = Joi.object({ - artifactUri: Joi.string().required() -}); - -export const assetDetailsSchema = fileUploadSchema.append({ - fields: Joi.object({ - name: Joi.string().min(1).required(), - description: Joi.string().allow(null).allow('') - }), - metadataRows: Joi.array().items( - Joi.object({ - name: Joi.string().min(1).required(), - value: Joi.string().min(1).required() - }) - ) -}); - -export const collectionSelectSchema = assetDetailsSchema.append({ - collectionAddress: Joi.string().required() -}); From c372280f54ac7e7e725ecc35d0963583b53d533b Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Wed, 3 Feb 2021 14:50:00 -0500 Subject: [PATCH 11/28] chore: Remove unsed client deps, use devDependencies --- client/package.json | 20 +- client/yarn.lock | 767 ++++---------------------------------------- 2 files changed, 74 insertions(+), 713 deletions(-) diff --git a/client/package.json b/client/package.json index 8de30a1a..afaa5c60 100644 --- a/client/package.json +++ b/client/package.json @@ -3,22 +3,14 @@ "version": "0.1.0", "private": true, "dependencies": { - "@airgap/beacon-sdk": "2.2.0-beta.2", "@chakra-ui/react": "1.1.2", "@emotion/core": "10.0.28", "@emotion/react": "11.1.4", "@emotion/styled": "11.0.0", "@reduxjs/toolkit": "1.5.0", "@taquito/beacon-wallet": "8.0.0-beta.5", - "@taquito/rpc": "8.0.0-beta.5", - "@taquito/signer": "8.0.0-beta.5", "@taquito/taquito": "8.0.0-beta.5", - "@testing-library/jest-dom": "4.2.4", - "@testing-library/react": "9.3.2", - "@testing-library/user-event": "7.1.2", - "@types/jest": "24.0.0", "@types/lodash": "4.14.165", - "@types/node": "12.0.0", "@types/react": "16.9.12", "@types/react-dom": "16.9.0", "@types/react-dropzone": "5.1.0", @@ -27,21 +19,23 @@ "buffer": "6.0.3", "framer-motion": "3.1.4", "immer": "8.0.0", - "ipfs-http-client": "46.0.1", "joi": "17.3.0", - "lodash": "4.17.20", "prettier": "2.1.1", "react": "16.13.1", - "react-awesome-reveal": "3.0.0", "react-dom": "16.13.1", "react-dropzone": "11.2.4", "react-feather": "2.0.9", "react-redux": "7.2.2", - "react-scripts": "3.4.1", "typescript": "4.1.3", "wouter": "2.5.1" }, - "devDependencies": {}, + "devDependencies": { + "@types/jest": "24.0.0", + "@testing-library/jest-dom": "4.2.4", + "@testing-library/react": "9.3.2", + "@testing-library/user-event": "7.1.2", + "react-scripts": "3.4.1" + }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", diff --git a/client/yarn.lock b/client/yarn.lock index 7435a30c..fa1f924d 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@airgap/beacon-sdk@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@airgap/beacon-sdk/-/beacon-sdk-2.1.0.tgz#214e07eb9dee6ad942884e005d23cfecbd0d6b41" - integrity sha512-0aAQnzC9xs1ftiCkVRwcVOHKtfo5IxhdEAoob4j32jzVmKMA3OeQkMvC7DudPLYnvUZ54qfMPgrtDCGyO5Q6Pw== +"@airgap/beacon-sdk@^2.2.0-beta.2": + version "2.2.0-beta.3" + resolved "https://registry.yarnpkg.com/@airgap/beacon-sdk/-/beacon-sdk-2.2.0-beta.3.tgz#84d9f574d86b7ffd4d90e4922612f1493c32c44d" + integrity sha512-mmoQ51XKqoS1OVn4H16lBPmZ8g0YogA2Zm32yKHWYOdxhlgbmKg4rzdVq94o3xEE3ItjKK5HGbkh7BHm/3b6mQ== dependencies: "@types/chrome" "0.0.115" "@types/libsodium-wrappers" "0.7.7" @@ -15,19 +15,6 @@ libsodium-wrappers "0.7.8" qrcode-generator "1.4.4" -"@airgap/beacon-sdk@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@airgap/beacon-sdk/-/beacon-sdk-2.0.0.tgz#b968fffeb1766a4fb6813988d815d122e7cfafd7" - integrity sha512-RvcuG7WDssKF3Qqks+pNpFDBGHv8Bk2376qMEr2jIQw4nWHtYkt+qYxRjBNj4PkwY9SzBc7JjIRykSX33cUfIA== - dependencies: - "@types/chrome" "0.0.115" - "@types/libsodium-wrappers" "0.7.7" - axios "0.19.2" - bignumber.js "9.0.0" - bs58check "2.1.2" - libsodium-wrappers "0.7.8" - qrcode-generator "1.4.4" - "@babel/code-frame@7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -1721,18 +1708,6 @@ "@emotion/sheet" "0.9.4" "@emotion/utils" "0.11.3" -"@emotion/core@^10.0.28": - version "10.1.1" - resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3" - integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/cache" "^10.0.27" - "@emotion/css" "^10.0.27" - "@emotion/serialize" "^0.11.15" - "@emotion/sheet" "0.9.4" - "@emotion/utils" "0.11.3" - "@emotion/css@^10.0.27": version "10.0.27" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" @@ -1747,7 +1722,7 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.2": +"@emotion/is-prop-valid@^0.8.2": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== @@ -1816,16 +1791,6 @@ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698" integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g== -"@emotion/styled-base@^10.0.27": - version "10.0.31" - resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" - integrity sha512-wTOE1NcXmqMWlyrtwdkqg87Mu6Rj1MaukEoEmEkHirO5IoHDJ8LgCQL4MjJODgxWxXibGR3opGp1p7YvkNEdXQ== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/is-prop-valid" "0.8.8" - "@emotion/serialize" "^0.11.15" - "@emotion/utils" "0.11.3" - "@emotion/styled@11.0.0": version "11.0.0" resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.0.0.tgz#698196c2822746360a8644a73a5d842b2d1a78a5" @@ -1837,14 +1802,6 @@ "@emotion/serialize" "^1.0.0" "@emotion/utils" "^1.0.0" -"@emotion/styled@^10.0.27": - version "10.0.27" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-10.0.27.tgz#12cb67e91f7ad7431e1875b1d83a94b814133eaf" - integrity sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q== - dependencies: - "@emotion/styled-base" "^10.0.27" - babel-plugin-emotion "^10.0.27" - "@emotion/stylis@0.8.5": version "0.8.5" resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" @@ -2291,78 +2248,64 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" -"@taquito/beacon-wallet@7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/beacon-wallet/-/beacon-wallet-7.2.0-beta.2.tgz#3eb3d443aae3fd807cf7aa4fa5ba1b43ae69554c" - integrity sha512-aMutuIL2mWQYr/LGdfJbrR4c6oUAKRwFh54fIItW1jMxSk83emJA5wP3jglY8cFVw4gjTo1OH4VYUpQytIayag== +"@taquito/beacon-wallet@8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/beacon-wallet/-/beacon-wallet-8.0.0-beta.5.tgz#ce21cd5ace358acaeceb24bc9aff89c5b9c7afaa" + integrity sha512-ARxrhmOQCWypKRuyzqNVwPgJq/hzXOfVYewsyF268mS01BJXKRF9jtyvdVXeeSVkY0Uc3xNl5IMz1EY3EhdC+Q== dependencies: - "@airgap/beacon-sdk" "^2.0.0" - "@taquito/taquito" "^7.2.0-beta.2" - "@taquito/utils" "^7.2.0-beta.2" + "@airgap/beacon-sdk" "^2.2.0-beta.2" + "@taquito/taquito" "^8.0.0-beta.5" + "@taquito/utils" "^8.0.0-beta.5" -"@taquito/http-utils@^7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/http-utils/-/http-utils-7.2.0-beta.2.tgz#19ac99df11083383102032e54f38835d1103f80f" - integrity sha512-COElCghypZt3QQhcsaMnC6oeTA0tJ5IvBnWT5kxZ6f1d6fWS8NZqcW2PPFbFFEsz1j9UaeAq9SAIvaUjsiTXNg== +"@taquito/http-utils@^8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/http-utils/-/http-utils-8.0.0-beta.5.tgz#9a106b4f8ddeb9c42b42f2fc324887a4cf7499a5" + integrity sha512-BgeSQZw4c8CF34baeG47GoptCuE446xiTg1fLIP4Fgl0h4+WtT6RTIJ46vwPl7yylv7ToyYDjXO+GYiTKayyVw== dependencies: xhr2-cookies "^1.1.0" -"@taquito/michel-codec@^7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/michel-codec/-/michel-codec-7.2.0-beta.2.tgz#d1031c60d09abceb7acc8a1dfcc60124fdb6714e" - integrity sha512-3kENFAAJ97StRWxjiTmCIByJh/+OAwNEso7utoU103J3Qe4ZN3TkmVLqWtMYxNYSuwA4VG+b8fPJx1yL351AEQ== +"@taquito/michel-codec@^8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/michel-codec/-/michel-codec-8.0.0-beta.5.tgz#b0196a949bbb29915cc02b1b9d634645135e8d23" + integrity sha512-d4emu+WMmf8Si3rzjatqDF3YSebkst0rXqPwKq+PAlJoehq56Vk/Exg4Ih0xd4JJzC4832R6445Zgj4zoq30Vg== -"@taquito/michelson-encoder@^7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/michelson-encoder/-/michelson-encoder-7.2.0-beta.2.tgz#2bb3debaf6b62f51225afbe2123453d5f1d5dc50" - integrity sha512-bFd6+vgWtOw/6ctNpq1Bk7Io+RAt6HinTQocEcgkyd+v5UUPu7D1zTbcwwiPf2Z8Z+9oFSLMbqqgVvPCZP/t3g== +"@taquito/michelson-encoder@^8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/michelson-encoder/-/michelson-encoder-8.0.0-beta.5.tgz#75d550c67d88ed708399a83a2514ed0c2361ff5a" + integrity sha512-c7tqrNe/PMnZCv8HCOS9qTbqIb/I1W0UkbNsTIvM5zhGm2uUR1Itx1x+2aULwG+lFjbjofB4vS9YRg64AJBy4w== dependencies: - "@taquito/rpc" "^7.2.0-beta.2" - "@taquito/utils" "^7.2.0-beta.2" + "@taquito/rpc" "^8.0.0-beta.5" + "@taquito/utils" "^8.0.0-beta.5" bignumber.js "^9.0.1" fast-json-stable-stringify "^2.1.0" -"@taquito/rpc@7.2.0-beta.2", "@taquito/rpc@^7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/rpc/-/rpc-7.2.0-beta.2.tgz#637f8ca78335da4cbffce326e68ea234b1398f48" - integrity sha512-891LKsy3m+J77T5GpswTl3Nh+XAU7FiQ1Vh1/qXaU08sgBd1Bb/uE6DHXZoTcw4FaR4rTpjCWy96XgqySsPWzQ== +"@taquito/rpc@^8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/rpc/-/rpc-8.0.0-beta.5.tgz#b01b61ce69dade0211f3708320915d229e8f316b" + integrity sha512-upmNi5EU2FUh4svjSSaizhkNSWzqYDG4zQNpMFij9NYeZ6I8Oej3IayaSEIl4fIfwm1FSDcCfo2IZD4NKCLruA== dependencies: - "@taquito/http-utils" "^7.2.0-beta.2" + "@taquito/http-utils" "^8.0.0-beta.5" bignumber.js "^9.0.1" lodash "^4.17.20" -"@taquito/signer@7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/signer/-/signer-7.2.0-beta.2.tgz#3940363d16b9a4fad8fd3c1f30c456813eb4811b" - integrity sha512-abrBtY0TERzYX1ed6ow/z2Vo7uCjWbB0epvxrYaWSo4jXOl4rPzm+JN4Vvi6I4/Tt7BVBT3dJVR4z+7psJ5cpw== +"@taquito/taquito@8.0.0-beta.5", "@taquito/taquito@^8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/taquito/-/taquito-8.0.0-beta.5.tgz#8f42650473dbfe17ce3418f03b321f3cdf749808" + integrity sha512-AVNZ17BtPciKf+K53c5Alx1v2Y5f+xNJCqKcaN4uEMWjSZB03JL8mX0PXHQq0d9DNdufjcVtEB7BJbJ61h8ZXw== dependencies: - "@taquito/taquito" "^7.2.0-beta.2" - "@taquito/utils" "^7.2.0-beta.2" - bignumber.js "^9.0.1" - bip39 "^3.0.2" - elliptic "^6.5.3" - libsodium-wrappers "^0.7.8" - pbkdf2 "^3.1.1" - typedarray-to-buffer "^3.1.5" - -"@taquito/taquito@7.2.0-beta.2", "@taquito/taquito@^7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/taquito/-/taquito-7.2.0-beta.2.tgz#de7064194052ac16658f15c7bdf9cf0da3d13398" - integrity sha512-vRQJ7HRj2UY0yVzfalJn4/j1Suaf60PM5f6NEvjExOiED7g8X/Cqi8lSkHGgKnzM59M6Jbi30Q9yNmunwQayBg== - dependencies: - "@taquito/http-utils" "^7.2.0-beta.2" - "@taquito/michel-codec" "^7.2.0-beta.2" - "@taquito/michelson-encoder" "^7.2.0-beta.2" - "@taquito/rpc" "^7.2.0-beta.2" - "@taquito/utils" "^7.2.0-beta.2" + "@taquito/http-utils" "^8.0.0-beta.5" + "@taquito/michel-codec" "^8.0.0-beta.5" + "@taquito/michelson-encoder" "^8.0.0-beta.5" + "@taquito/rpc" "^8.0.0-beta.5" + "@taquito/utils" "^8.0.0-beta.5" bignumber.js "^9.0.1" rx-sandbox "^1.0.3" rxjs "^6.6.3" -"@taquito/utils@^7.2.0-beta.2": - version "7.2.0-beta.2" - resolved "https://registry.yarnpkg.com/@taquito/utils/-/utils-7.2.0-beta.2.tgz#a89b949c700575f00e15ff4091d6cade65901ec1" - integrity sha512-/xAznoXXnOX2RcZFvWbBB+f2sYox+t+PKPwKdWZd1w3kzK02Fui1TaRHTn9CNOdb36TyVKCRbUsdYkBMfd9NQg== +"@taquito/utils@^8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/utils/-/utils-8.0.0-beta.5.tgz#602ed70cb613507939b0f6ca0a0a3421c5437fad" + integrity sha512-s6asR9zloDWl2kcwp+me5Qcwtx+Wz0NSv/YhSanvMUcPvoddJcN0SlgnxJifNaPSOjaNlj1uxaDsrRq5eOvnrA== dependencies: blakejs "^1.1.0" bs58check "^2.1.2" @@ -2424,13 +2367,6 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-7.1.2.tgz#3a71bb8a45a1e08b71a54c9efcee9927f3895e80" integrity sha512-lDyCVxxgX5lrgCa75ELCfWcdEDyfisjqoDIM3YsghQ+lyViIac/qT67qabQ/HmoVxyikFKovjKwWdn3b/oKhZA== -"@thanos-wallet/dapp@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@thanos-wallet/dapp/-/dapp-0.7.0.tgz#bbe49b879431e6c79e8271364c1943532c7bb9e4" - integrity sha512-eXa8NwHYmlXo1ZWbLfW0tVRAjame92NKJo1I+EsS0ZQyoz9dJTIQSsqwA1cfzIoXV/wFSXmfhTnHniraCtc2fQ== - dependencies: - nanoid "^3.1.10" - "@types/aria-query@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" @@ -2579,16 +2515,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae" integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ== -"@types/node@11.11.6": - version "11.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" - integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== - -"@types/node@12.0.0": - version "12.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.0.tgz#d11813b9c0ff8aaca29f04cbc12817f4c7d656e5" - integrity sha512-Jrb/x3HT4PTJp6a4avhmJCDEVrPdqLfl3e8GGMbpkGGdwAV5UGlIs4vVEfsHHfylZVOKZWpOqmqFH8CbfOZ6kg== - "@types/object-assign@4.0.30": version "4.0.30" resolved "https://registry.yarnpkg.com/@types/object-assign/-/object-assign-4.0.30.tgz#8949371d5a99f4381ee0f1df0a9b7a187e07e652" @@ -2950,13 +2876,6 @@ abab@^2.0.0: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -3108,13 +3027,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -any-signal@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/any-signal/-/any-signal-1.2.0.tgz#d755f690896f3e75c4a07480f429a1ee7f8db3b4" - integrity sha512-Cl08k4xItix3jvu4cxO/dt2rQ6iUAjO66pTyRMub+WL1VXeAyZydCpD8GqWTPKfdL28U0R0UucmQVsUsBnvCmQ== - dependencies: - abort-controller "^3.0.0" - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -3327,11 +3239,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -3372,13 +3279,6 @@ axios-retry@3.1.9: dependencies: is-retry-allowed "^1.1.0" -axios@0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== - dependencies: - follow-redirects "1.5.10" - axios@0.21.1: version "0.21.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" @@ -3567,7 +3467,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base-x@^3.0.2, base-x@^3.0.8: +base-x@^3.0.2: version "3.0.8" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== @@ -3614,7 +3514,7 @@ bignumber.js@9.0.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== -bignumber.js@^9.0.0, bignumber.js@^9.0.1: +bignumber.js@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== @@ -3636,37 +3536,11 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bip39@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.3.tgz#4a8b79067d6ed2e74f9199ac994a2ab61b176760" - integrity sha512-P0dKrz4g0V0BjXfx7d9QNkJ/Txcz/k+hM9TnjqjUaXtuOfAvxXSw2rJw8DX0e3ZPwnK/IgDxoRqf0bvoVCqbMg== - dependencies: - "@types/node" "11.11.6" - create-hash "^1.1.0" - pbkdf2 "^3.0.9" - randombytes "^2.0.1" - -bl@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" - integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - blakejs@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5" integrity sha1-ad+S75U6qIylGjLfarHFShVfx6U= -blob-to-it@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/blob-to-it/-/blob-to-it-0.0.1.tgz#27067ec8f8c4ed008d42eb2a7227fc9df3b7fa25" - integrity sha512-gvOVIs0YUpKHAwvhoJcRs81LJrOb+kwOol0/NnF/JgD0a5i9SJ/Es/njJo3NgFzb+x/FDPh4cD4D1KnrBeUWuw== - dependencies: - browser-readablestream-to-it "^0.0.1" - bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -3715,19 +3589,6 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -borc@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/borc/-/borc-2.1.2.tgz#6ce75e7da5ce711b963755117dd1b187f6f8cf19" - integrity sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w== - dependencies: - bignumber.js "^9.0.0" - buffer "^5.5.0" - commander "^2.15.0" - ieee754 "^1.1.13" - iso-url "~0.4.7" - json-text-sequence "~0.1.0" - readable-stream "^3.6.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3769,11 +3630,6 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browser-readablestream-to-it@0.0.1, browser-readablestream-to-it@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/browser-readablestream-to-it/-/browser-readablestream-to-it-0.0.1.tgz#90e5f37838115241686e219e040f155c087c1ca4" - integrity sha512-leRiI4bLRr7K8znNmQZ3frgL8A7aX4LI4g7444YEtT3alaxqem+XPGsJmOlFRRdRqjFpvf2wW4dXKcgBLxypVg== - browser-resolve@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" @@ -3918,7 +3774,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: +buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -4180,17 +4036,6 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -cids@^0.8.3, cids@~0.8.0, cids@~0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/cids/-/cids-0.8.3.tgz#aaf48ac8ed857c3d37dad94d8db1d8c9407b92db" - integrity sha512-yoXTbV3llpm+EBGWKeL9xKtksPE/s6DPoDSY4fn8I8TEW1zehWXPSB0pwAXVDlLaOlrw+sNynj995uD9abmPhA== - dependencies: - buffer "^5.6.0" - class-is "^1.1.0" - multibase "^1.0.0" - multicodec "^1.0.1" - multihashes "^1.0.1" - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -4199,11 +4044,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -class-is@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" - integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -4353,14 +4193,14 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.15.0, commander@^2.20.0: +commander@^2.11.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4926,13 +4766,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debug@^3.1.1, debug@^3.2.5: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -5029,11 +4862,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delimit-stream@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/delimit-stream/-/delimit-stream-0.1.0.tgz#9b8319477c0e5f8aeb3ce357ae305fc25ea1cd2b" - integrity sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -5348,11 +5176,6 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== -err-code@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" - integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== - errno@^0.1.3, errno@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -5702,11 +5525,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -5893,11 +5711,6 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-fifo@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.0.0.tgz#9bc72e6860347bb045a876d1c5c0af11e9b984e7" - integrity sha512-4VEXmjxLj7sbs8J//cn2qhRap50dGzF5n8fjay8mau+Jn4hxSeR3xPFwxMaQq/pDaq7+KQk0PAbC2+nWDkJrmQ== - fast-glob@^2.0.2: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -6128,13 +5941,6 @@ focus-lock@^0.7.0: resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a" integrity sha512-LI7v2mH02R55SekHYdv9pRHR9RajVNyIJ2N5IEkWbg7FT5ZmJ9Hw4mWxHeEUcd+dJo0QmzztHvDvWcc7prVFsw== -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - follow-redirects@^1.0.0, follow-redirects@^1.10.0: version "1.13.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" @@ -6176,15 +5982,6 @@ fork-ts-checker-webpack-plugin@3.1.1: tapable "^1.0.0" worker-rpc "^0.1.0" -form-data@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" - integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -6264,16 +6061,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -6348,11 +6135,6 @@ get-intrinsic@^1.0.0, get-intrinsic@^1.0.1: has "^1.0.3" has-symbols "^1.0.1" -get-iterator@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-iterator/-/get-iterator-1.0.2.tgz#cd747c02b4c084461fac14f48f6b45a80ed25c82" - integrity sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg== - get-nonce@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" @@ -7005,11 +6787,6 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -ip-regex@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.2.0.tgz#a03f5eb661d9a154e3973a03de8b23dd0ad6892e" - integrity sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A== - ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -7020,116 +6797,6 @@ ipaddr.js@1.9.1, ipaddr.js@^1.9.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipfs-core-utils@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ipfs-core-utils/-/ipfs-core-utils-0.3.2.tgz#24112ff687f79bd8d536c9b6aff4b7964161b8b9" - integrity sha512-4kn6qbhYsyn48HeH7qAKPG07CxwEr1EsgRccGQOUy/5OjcfqIjw4HnBwYmsRU6QuWsNR7nOAhwrVc6Y3glVvnQ== - dependencies: - blob-to-it "0.0.1" - browser-readablestream-to-it "0.0.1" - buffer "^5.6.0" - cids "^0.8.3" - err-code "^2.0.0" - ipfs-utils "^3.0.0" - it-all "^1.0.1" - it-map "^1.0.0" - it-peekable "0.0.1" - -ipfs-http-client@46.0.1: - version "46.0.1" - resolved "https://registry.yarnpkg.com/ipfs-http-client/-/ipfs-http-client-46.0.1.tgz#72c69fb8ab8f3c312573d15f28b93a130a033618" - integrity sha512-/AK3lpeoxzyagmuAix1BNmDH/R5PpB+VdFtjV6mC57XE6/OoYl5FCmgxOtz/gusfnTLLjm87i9I5+4IbuzVhFg== - dependencies: - abort-controller "^3.0.0" - any-signal "^1.1.0" - bignumber.js "^9.0.0" - buffer "^5.6.0" - cids "^0.8.3" - debug "^4.1.0" - form-data "^3.0.0" - ipfs-core-utils "^0.3.2" - ipfs-utils "^3.0.0" - ipld-block "^0.9.2" - ipld-dag-cbor "^0.16.0" - ipld-dag-pb "^0.19.0" - ipld-raw "^5.0.0" - iso-url "^0.4.7" - it-last "^1.0.1" - it-tar "^1.2.2" - it-to-buffer "^1.0.0" - it-to-stream "^0.1.1" - merge-options "^2.0.0" - multiaddr "^7.4.3" - multiaddr-to-uri "^5.1.0" - multibase "^1.0.1" - multicodec "^1.0.0" - multihashes "^1.0.1" - nanoid "^3.0.2" - node-fetch "^2.6.0" - parse-duration "^0.4.4" - stream-to-it "^0.2.1" - -ipfs-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ipfs-utils/-/ipfs-utils-3.0.0.tgz#58f8345ff26c4ae6b4a8e3a2366bd25de3e1460e" - integrity sha512-qahDc+fghrM57sbySr2TeWjaVR/RH/YEB/hvdAjiTbjESeD87qZawrXwj+19Q2LtGmFGusKNLo5wExeuI5ZfDQ== - dependencies: - abort-controller "^3.0.0" - any-signal "^1.1.0" - buffer "^5.6.0" - err-code "^2.0.0" - fs-extra "^9.0.1" - is-electron "^2.2.0" - iso-url "^0.4.7" - it-glob "0.0.8" - merge-options "^2.0.0" - nanoid "^3.1.3" - node-fetch "^2.6.0" - stream-to-it "^0.2.0" - -ipld-block@^0.9.2: - version "0.9.2" - resolved "https://registry.yarnpkg.com/ipld-block/-/ipld-block-0.9.2.tgz#d6c702e3c4171ff44e0a7b76c21d337676599196" - integrity sha512-/i99foB+QI8WhyZWu6ZVPFw2sP6kzZSnnjPNlxxrgaJeFX22w2z00nYWafY2YYYP4mZ9xkLZDSS/msli7XXyvw== - dependencies: - buffer "^5.5.0" - cids "~0.8.0" - class-is "^1.1.0" - -ipld-dag-cbor@^0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/ipld-dag-cbor/-/ipld-dag-cbor-0.16.0.tgz#2f2b54ba46dc64a7cfce107cee7b9b2114034b98" - integrity sha512-dnmR8Pgt1gGmEXWSf/V3dKDPveGnHsovvAAN7m/WHW5mXsBqYYOStt98K1RhCifbB7vY+IHmpdRhVka0g9DWFQ== - dependencies: - borc "^2.1.2" - buffer "^5.6.0" - cids "~0.8.3" - is-circular "^1.0.2" - multicodec "^1.0.3" - multihashing-async "^1.0.0" - -ipld-dag-pb@^0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/ipld-dag-pb/-/ipld-dag-pb-0.19.0.tgz#9029e28e7843ca224e2e5377b5761a931f948047" - integrity sha512-qwuJM2Ev74HLKxgfmH7Qw/ob/Iwo4Te6ADZas8OqV2FCY+I4H+KJujLvaBs+By2g3h0aagv0ei3aUgqE8XzDfw== - dependencies: - buffer "^5.6.0" - cids "~0.8.3" - class-is "^1.1.0" - multicodec "^1.0.3" - multihashing-async "^1.0.0" - protons "^1.2.1" - stable "^0.1.8" - -ipld-raw@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ipld-raw/-/ipld-raw-5.0.0.tgz#06624a9de7a4f5e0cdb3a4e05de3c5ab5bfbb0a8" - integrity sha512-z1Fie224lTtQZbFg+wC5WDY692G3SIpO8vT86yCU83vqpIvasVuV3SzDSv7G36kRxP03PPZOkvKAOFrcjb7gpw== - dependencies: - cids "~0.8.0" - multicodec "^1.0.1" - multihashing-async "~0.8.1" - is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" @@ -7202,11 +6869,6 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-circular@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-circular/-/is-circular-1.0.2.tgz#2e0ab4e9835f4c6b0ea2b9855a84acd501b8366c" - integrity sha512-YttjnrswnUYRVJvxCvu8z+PGMUSzC2JttP0OEXezlAEdp3EXzhf7IZ3j0gRAybJBQupedIZFhY61Tga6E0qASA== - is-color-stop@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" @@ -7273,11 +6935,6 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== -is-electron@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.0.tgz#8943084f09e8b731b3a7a0298a7b5d56f6b7eef0" - integrity sha512-SpMppC2XR3YdxSzczXReBjqs2zGscWQpBIKqwXYBFic0ERaxNVgwLCHwOLZeESfdJQjX0RDvrJ1lBXX2ij+G1Q== - is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -7331,13 +6988,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-ip@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8" - integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q== - dependencies: - ip-regex "^4.0.0" - is-negative-zero@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" @@ -7389,11 +7039,6 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -7452,7 +7097,7 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -7484,16 +7129,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -iso-constants@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/iso-constants/-/iso-constants-0.1.2.tgz#3d2456ed5aeaa55d18564f285ba02a47a0d885b4" - integrity sha512-OTCM5ZCQsHBCI4Wdu4tSxvDIkmDHd5EwJDps5mKqnQnWJSKlnwMs3EDZ4n3Fh1tmkWkDlyd2vCDbEYuPbyrUNQ== - -iso-url@^0.4.7, iso-url@~0.4.7: - version "0.4.7" - resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.7.tgz#de7e48120dae46921079fe78f325ac9e9217a385" - integrity sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog== - isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -7556,79 +7191,6 @@ istanbul-reports@^2.2.6: dependencies: html-escaper "^2.0.0" -it-all@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/it-all/-/it-all-1.0.4.tgz#5a1aac996e2516c0d030911a631190b330afdb6d" - integrity sha512-7K+gjHHzZ7t+bCkrtulYiow35k3UgqH7miC+iUa9RGiyDRXJ6hVDeFsDrnWrlscjrkLFOJRKHxNOke4FNoQnhw== - -it-concat@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/it-concat/-/it-concat-1.0.2.tgz#7229fedb935bcf7b2fcac23e040e7588b34143e6" - integrity sha512-YZtXOe10qBcTDOsz59AscfmsKRoVPYX5AFxCans2L/QL20Jah1H1/+wzWDaJj8zu0KiA9gys3vBoZIZwhsUeeg== - dependencies: - bl "^4.0.0" - -it-glob@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/it-glob/-/it-glob-0.0.8.tgz#b63d24945c18b35de8bb593a8c872fd0257c0cac" - integrity sha512-PmIAgb64aJPM6wwT1UTlNDAJnNgdGrvr0vRr3AYCngcUuq1KaAovuz0dQAmUkaXudDG3EQzc7OttuLW9DaL3YQ== - dependencies: - fs-extra "^8.1.0" - minimatch "^3.0.4" - -it-last@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/it-last/-/it-last-1.0.4.tgz#4009aac79ee76e3417443c6c1dfb64cd380e9e5b" - integrity sha512-h0aV43BaD+1nubAKwStWcda6vlbejPSTQKfOrQvyNrrceluWfoq8DrBXnL0PSz6RkyHSiVSHtAEaqUijYMPo8Q== - -it-map@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/it-map/-/it-map-1.0.4.tgz#d413d2b0c3d8d9703df9e8a915ad96cb74a837ac" - integrity sha512-LZgYdb89XMo8cFUp6jF0cn5j3gF7wcZnKRVFS3qHHn0bPB2rpToh2vIkTBKduZLZxRRjWx1VW/udd98x+j2ulg== - -it-peekable@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/it-peekable/-/it-peekable-0.0.1.tgz#e3f91583d172444b9cd894ed2df6e26f0c176617" - integrity sha512-fd0JzbNldseeq+FFWthbqYB991UpKNyjPG6LqFhIOmJviCxSompMyoopKIXvLPLY+fBhhv2CT5PT31O/lEnTHw== - -it-reader@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/it-reader/-/it-reader-2.1.0.tgz#b1164be343f8538d8775e10fb0339f61ccf71b0f" - integrity sha512-hSysqWTO9Tlwc5EGjVf8JYZzw0D2FsxD/g+eNNWrez9zODxWt6QlN6JAMmycK72Mv4jHEKEXoyzUN4FYGmJaZw== - dependencies: - bl "^4.0.0" - -it-tar@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/it-tar/-/it-tar-1.2.2.tgz#8d79863dad27726c781a4bcc491f53c20f2866cf" - integrity sha512-M8V4a9I+x/vwXTjqvixcEZbQZHjwDIb8iUQ+D4M2QbhAdNs3WKVSl+45u5/F2XFx6jYMFOGzMVlKNK/uONgNIA== - dependencies: - bl "^4.0.0" - buffer "^5.4.3" - iso-constants "^0.1.2" - it-concat "^1.0.0" - it-reader "^2.0.0" - p-defer "^3.0.0" - -it-to-buffer@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/it-to-buffer/-/it-to-buffer-1.0.4.tgz#4fcbd34c9c503e607744c0fdbeaff30008429703" - integrity sha512-wycpGeAdQ8WH8eSBkMHN/HMNiQ0Y88XEXo6s6LGJbQZjf9K7ppVzUfCXn7OnxFfUPN0HTWZr+uhthwtrwMTTfw== - dependencies: - buffer "^5.5.0" - -it-to-stream@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/it-to-stream/-/it-to-stream-0.1.2.tgz#7163151f75b60445e86b8ab1a968666acaacfe7b" - integrity sha512-DTB5TJRZG3untmZehcaFN0kGWl2bNv7tnJRgQHAO9QEt8jfvVRrebZtnD5NZd4SCj4WVPjl0LSrugNWE/UaZRQ== - dependencies: - buffer "^5.6.0" - fast-fifo "^1.0.0" - get-iterator "^1.0.2" - p-defer "^3.0.0" - p-fifo "^1.0.0" - readable-stream "^3.6.0" - jest-changed-files@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" @@ -8072,11 +7634,6 @@ joi@17.3.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -js-sha3@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -8211,13 +7768,6 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json-text-sequence@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/json-text-sequence/-/json-text-sequence-0.1.1.tgz#a72f217dc4afc4629fff5feb304dc1bd51a2f3d2" - integrity sha1-py8hfcSvxGKf/1/rME3BvVGi89I= - dependencies: - delimit-stream "0.1.0" - json3@^3.3.2: version "3.3.3" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" @@ -8244,15 +7794,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -8367,7 +7908,7 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -libsodium-wrappers@0.7.8, libsodium-wrappers@^0.7.8: +libsodium-wrappers@0.7.8: version "0.7.8" resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.8.tgz#d95cdf3e7236c2aef76844bf8e1929ba9eef3e9e" integrity sha512-PDhPWXBqd/SaqAFUBgH2Ux7b3VEEJgyD6BQB+VdNFJb9PbExGr/T/myc/MBoSvl8qLzfm0W0IVByOQS5L1MrCg== @@ -8498,7 +8039,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -8650,13 +8191,6 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -merge-options@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-2.0.0.tgz#36ca5038badfc3974dbde5e58ba89d3df80882c3" - integrity sha512-S7xYIeWHl2ZUKF7SDeBhGg6rfv5bKxVBdk95s/I7wVF8d+hjLSztJ/B271cnUiF6CAFduEQ5Zn3HYwAjT16DlQ== - dependencies: - is-plain-obj "^2.0.0" - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -8880,41 +8414,6 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multiaddr-to-uri@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/multiaddr-to-uri/-/multiaddr-to-uri-5.1.0.tgz#879b55e4170db37cf05e1bce5831de70084933b9" - integrity sha512-rIlMLkw3yk3RJmf2hxYYzeqPXz4Vx7C4M/hg7BVWhmksDW0rDVNMEyoVb0H1A+sh3deHOh5EAFK87XcW+mFimA== - dependencies: - multiaddr "^7.2.1" - -multiaddr@^7.2.1, multiaddr@^7.4.3: - version "7.5.0" - resolved "https://registry.yarnpkg.com/multiaddr/-/multiaddr-7.5.0.tgz#976c88e256e512263445ab03b3b68c003d5f485e" - integrity sha512-GvhHsIGDULh06jyb6ev+VfREH9evJCFIRnh3jUt9iEZ6XDbyoisZRFEI9bMvK/AiR6y66y6P+eoBw9mBYMhMvw== - dependencies: - buffer "^5.5.0" - cids "~0.8.0" - class-is "^1.1.0" - is-ip "^3.1.0" - multibase "^0.7.0" - varint "^5.0.0" - -multibase@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b" - integrity sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg== - dependencies: - base-x "^3.0.8" - buffer "^5.5.0" - -multibase@^1.0.0, multibase@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/multibase/-/multibase-1.0.1.tgz#4adbe1de0be8a1ab0274328b653c3f1903476724" - integrity sha512-KcCxpBVY8fdVKu4dJMAahq4F/2Z/9xqEjIiR7PiMe7LRGeorFn2NLmicN6nLBCqQvft6MG2Lc9X5P0IdyvnxEw== - dependencies: - base-x "^3.0.8" - buffer "^5.5.0" - multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -8928,52 +8427,6 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multicodec@^1.0.0, multicodec@^1.0.1, multicodec@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.4.tgz#46ac064657c40380c28367c90304d8ed175a714f" - integrity sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg== - dependencies: - buffer "^5.6.0" - varint "^5.0.0" - -multihashes@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-1.0.1.tgz#a89415d68283cf6287c6e219e304e75ce7fb73fe" - integrity sha512-S27Tepg4i8atNiFaU5ZOm3+gl3KQlUanLs/jWcBxQHFttgq+5x1OgbQmf2d8axJ/48zYGBd/wT9d723USMFduw== - dependencies: - buffer "^5.6.0" - multibase "^1.0.1" - varint "^5.0.0" - -multihashing-async@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/multihashing-async/-/multihashing-async-1.0.0.tgz#578a5dffc0d47caac9b255406eae24f02bff4e55" - integrity sha512-gRtHjJuULvo2dd9ybIsF+aUEamraAwet/ib3YapWdaP7QWkI8JtN/6EZBhdoqlzSVU7POrC3/rp13Or7zY7x1A== - dependencies: - blakejs "^1.1.0" - buffer "^5.4.3" - err-code "^2.0.0" - js-sha3 "^0.8.0" - multihashes "^1.0.1" - murmurhash3js-revisited "^3.0.0" - -multihashing-async@~0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/multihashing-async/-/multihashing-async-0.8.2.tgz#3d5da05df27d83be923f6d04143a0954ff87f27f" - integrity sha512-2lKa1autuCy8x7KIEj9aVNbAb3aIMRFYIwN7mq/zD4pxgNIVgGlm+f6GKY4880EOF2Y3GktHYssRy7TAJQ2DyQ== - dependencies: - blakejs "^1.1.0" - buffer "^5.4.3" - err-code "^2.0.0" - js-sha3 "^0.8.0" - multihashes "^1.0.1" - murmurhash3js-revisited "^3.0.0" - -murmurhash3js-revisited@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz#6bd36e25de8f73394222adc6e41fa3fac08a5869" - integrity sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g== - mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -8984,11 +8437,6 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@^3.0.2, nanoid@^3.1.10, nanoid@^3.1.3: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -9039,11 +8487,6 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-fetch@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -9382,11 +8825,6 @@ p-defer@^1.0.0: resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= -p-defer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83" - integrity sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw== - p-each-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" @@ -9394,14 +8832,6 @@ p-each-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-fifo@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-fifo/-/p-fifo-1.0.0.tgz#e29d5cf17c239ba87f51dde98c1d26a9cfe20a63" - integrity sha512-IjoCxXW48tqdtDFz6fqo5q1UfFVjjVZe8TC1QRflvNUJtNfCUhxOUw6MOVZhDPjqhSzc26xKdugsO17gmzd5+A== - dependencies: - fast-fifo "^1.0.0" - p-defer "^3.0.0" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -9521,11 +8951,6 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-duration@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-0.4.4.tgz#11c0f51a689e97d06c57bd772f7fda7dc013243c" - integrity sha512-KbAJuYGUhZkB9gotDiKLnZ7Z3VTacK3fgwmDdB6ZVDtJbMBT6MfLga0WJaYpPDu0mzqT0NgHtHDt5PY4l0nidg== - parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -9655,7 +9080,7 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.3, pbkdf2@^3.0.9, pbkdf2@^3.1.1: +pbkdf2@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== @@ -9758,13 +9183,13 @@ pnp-webpack-plugin@1.6.4: ts-pnp "^1.1.6" popmotion@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.2.tgz#477650c3b4af97161011809223d9ca6860f3a2b5" - integrity sha512-WfSg8IfoUwYIP9uqeqbgncIsMHLAKWqebT2IP1aGAI6gdSJqTPy/H8NvP4ZyDtDCUCx5Yh3Pth/7iUJjIwR7LA== + version "9.1.0" + resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.1.0.tgz#4360d06bd18ce8baa8f9284ecec7d55344af6325" + integrity sha512-+J7pzzBy5kk2qsP8ilowKs/CH+HoZa3kOGEBNCleCvsPXEF3nKHdfAR3SboMyPvdpIrofaT7ZIy/xWgz446Azw== dependencies: framesync "5.0.0" hey-listen "^1.0.8" - style-value-types "3.2.0" + style-value-types "^4.0.1" tslib "^1.10.0" portfinder@^1.0.25: @@ -10545,21 +9970,6 @@ prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -protocol-buffers-schema@^3.3.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.4.0.tgz#2f0ea31ca96627d680bf2fefae7ebfa2b6453eae" - integrity sha512-G/2kcamPF2S49W5yaMGdIpkG6+5wZF0fzBteLKgEHjbNzqjZQ85aAs1iJGto31EJaSTkNvHs5IXuHSaTLWBAiA== - -protons@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/protons/-/protons-1.2.1.tgz#5f1e0db8b2139469cd1c3b4e332a4c2d95d0a218" - integrity sha512-2oqDyc/SN+tNcJf8XxrXhYL7sQn2/OMl8mSdD7NVGsWjMEmAbks4eDVnCyf0vAoRbBWyWTEXWk4D8XfuKVl3zg== - dependencies: - buffer "^5.5.0" - protocol-buffers-schema "^3.3.1" - signed-varint "^2.0.1" - varint "^5.0.0" - proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -10722,16 +10132,6 @@ react-app-polyfill@^1.0.6: regenerator-runtime "^0.13.3" whatwg-fetch "^3.0.0" -react-awesome-reveal@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/react-awesome-reveal/-/react-awesome-reveal-3.0.0.tgz#e66102a893fcc8aaa73b50620bac674780b54a7c" - integrity sha512-3bxfiCdlJYzjiVm0hWPk3u3NT340WpQpOqVjY14x4gfbI138EaFyq1zQJVFDBWm/JWG7pSsW8bfbLsNmAlzQKA== - dependencies: - "@emotion/core" "^10.0.28" - "@emotion/styled" "^10.0.27" - react-intersection-observer "^8.26.2" - react-is "^16.13.1" - react-clientside-effect@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.3.tgz#95c95f520addfb71743608b990bfe01eb002012b" @@ -10817,11 +10217,6 @@ react-focus-lock@2.4.1: use-callback-ref "^1.2.1" use-sidecar "^1.0.1" -react-intersection-observer@^8.26.2: - version "8.31.0" - resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.31.0.tgz#0ed21aaf93c4c0475b22b0ccaba6169076d01605" - integrity sha512-XraIC/tkrD9JtrmVA7ypEN1QIpKc52mXBH1u/bz/aicRLo8QQEJQAMUTb8mz4B6dqpPwyzgjrr7Ljv/2ACDtqw== - react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -10987,7 +10382,7 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -11673,13 +11068,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -signed-varint@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/signed-varint/-/signed-varint-2.0.1.tgz#50a9989da7c98c2c61dad119bc97470ef8528129" - integrity sha1-UKmYnafJjCxh2tEZvJdHDvhSgSk= - dependencies: - varint "~5.0.0" - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -11972,13 +11360,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -stream-to-it@^0.2.0, stream-to-it@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stream-to-it/-/stream-to-it-0.2.2.tgz#fb3de7917424c354a987c7bc2aab2d0facbd7d94" - integrity sha512-waULBmQpVdr6TkDzci6t1P7dIaSZ0bHC1TaPXDUeJC5PpSK7U3T0H0Zeo/LWUnd6mnhXOmGGDKAkjUCHw5IOng== - dependencies: - get-iterator "^1.0.2" - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -12153,7 +11534,7 @@ style-loader@0.23.1: loader-utils "^1.1.0" schema-utils "^1.0.0" -style-value-types@3.2.0, style-value-types@^3.2.0: +style-value-types@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.2.0.tgz#eb89cab1340823fa7876f3e289d29d99c92111bb" integrity sha512-ih0mGsrYYmVvdDi++/66O6BaQPRPRMQHoZevNNdMMcPlP/cH28Rnfsqf1UEba/Bwfuw9T8BmIMwbGdzsPwQKrQ== @@ -12161,6 +11542,14 @@ style-value-types@3.2.0, style-value-types@^3.2.0: hey-listen "^1.0.8" tslib "^1.10.0" +style-value-types@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.0.1.tgz#23f05dd03e8a850654defc22cf03ebac572aaa00" + integrity sha512-aOV/HHyynIyTmU27qfs0oAHhFde6BFIvV4+nMerE2MAPZMwYOeQk1/F3S6djxF2u4HdbiieCPs3ZzWsbNUoc9A== + dependencies: + hey-listen "^1.0.8" + tslib "^1.10.0" + stylehacks@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" @@ -12512,13 +11901,6 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -12591,16 +11973,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" - integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -12744,11 +12116,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -varint@^5.0.0, varint@~5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4" - integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow== - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" From f8ee6782e9c7bde302f207fcf2ceec5db1a275b9 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Wed, 3 Feb 2021 14:52:39 -0500 Subject: [PATCH 12/28] feat: Cache dev docker images, add yarn.lock to build --- bin/build-dev-images | 6 +++--- docker/api-dev.dockerfile | 4 ++-- docker/ui-dev.dockerfile | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/build-dev-images b/bin/build-dev-images index ddb1d906..b67bd196 100755 --- a/bin/build-dev-images +++ b/bin/build-dev-images @@ -4,6 +4,6 @@ set -e source ${BASH_SOURCE%/*}/setup-env -docker build --no-cache -t bashcurl -f $PROJECT_ROOT_DIR/docker/bashcurl.dockerfile $PROJECT_ROOT_DIR/docker/ -docker build --no-cache -t minter-api-dev -f $PROJECT_ROOT_DIR/docker/api-dev.dockerfile $PROJECT_ROOT_DIR -docker build --no-cache -t minter-ui-dev -f $PROJECT_ROOT_DIR/docker/ui-dev.dockerfile $PROJECT_ROOT_DIR +docker build -t bashcurl -f $PROJECT_ROOT_DIR/docker/bashcurl.dockerfile $PROJECT_ROOT_DIR/docker/ +docker build -t minter-api-dev -f $PROJECT_ROOT_DIR/docker/api-dev.dockerfile $PROJECT_ROOT_DIR +docker build -t minter-ui-dev -f $PROJECT_ROOT_DIR/docker/ui-dev.dockerfile $PROJECT_ROOT_DIR diff --git a/docker/api-dev.dockerfile b/docker/api-dev.dockerfile index d0f120d9..808fbd9d 100644 --- a/docker/api-dev.dockerfile +++ b/docker/api-dev.dockerfile @@ -2,9 +2,9 @@ FROM node:12 WORKDIR /usr/src/app/server -COPY server/package.json . +COPY server/package.json server/yarn.lock ./ -RUN yarn install +RUN yarn install --frozen-lockfile COPY server . diff --git a/docker/ui-dev.dockerfile b/docker/ui-dev.dockerfile index 0cfebe00..54789eb2 100644 --- a/docker/ui-dev.dockerfile +++ b/docker/ui-dev.dockerfile @@ -2,9 +2,9 @@ FROM node:14 WORKDIR /usr/src/app/client -COPY client/package.json . +COPY client/package.json client/yarn.lock ./ -RUN yarn install +RUN yarn install --frozen-lockfile COPY client . From d12b5e0e85be02df3bb3c3521b1cf7010f63973f Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Wed, 3 Feb 2021 14:53:11 -0500 Subject: [PATCH 13/28] fix: Handle Beacon issue, cache Beacon client --- client/public/index.html | 59 ++++++++++++++++++++++++++-------------- client/src/lib/system.ts | 22 +++++++++------ 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/client/public/index.html b/client/public/index.html index 23d7914d..2462d30d 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -1,21 +1,26 @@ - - - - - - - - - - - - - - Minter - + OpenMinter + + - - -
- - - + diff --git a/client/src/lib/system.ts b/client/src/lib/system.ts index 13e1dd21..9ebeb715 100644 --- a/client/src/lib/system.ts +++ b/client/src/lib/system.ts @@ -2,7 +2,7 @@ import { TezosToolkit } from '@taquito/taquito'; import { BeaconWallet } from '@taquito/beacon-wallet'; import { BetterCallDev } from './service/bcd'; import * as tzUtils from './util/tezosToolkit'; -import { NetworkType } from '@airgap/beacon-sdk'; +import { DAppClientOptions, NetworkType } from '@airgap/beacon-sdk'; export interface Config { rpc: string; @@ -81,15 +81,21 @@ function networkType(config: Config) { return NetworkType.CUSTOM; } +let wallet: BeaconWallet | null = null; + export async function connectWallet( - system: SystemWithToolkit + system: SystemWithToolkit, + eventHandlers?: DAppClientOptions['eventHandlers'] ): Promise { - const network = networkType(system.config) as any; - - const wallet = new BeaconWallet({ - name: 'OpenSystem dApp', - preferredNetwork: network - }); + const network = networkType(system.config); + + if (wallet === null) { + wallet = new BeaconWallet({ + name: 'OpenSystem dApp', + preferredNetwork: network, + eventHandlers + }); + } await wallet.requestPermissions({ network: { type: network, rpcUrl: system.config.rpc } From b8bd17ce0e179b3f84d0eec824b3839d8f442157 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Wed, 3 Feb 2021 14:54:02 -0500 Subject: [PATCH 14/28] feat: Scaffold custom Beacon event handling --- client/src/reducer/async/wallet.ts | 43 ++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/client/src/reducer/async/wallet.ts b/client/src/reducer/async/wallet.ts index 0c969ce3..b72232e3 100644 --- a/client/src/reducer/async/wallet.ts +++ b/client/src/reducer/async/wallet.ts @@ -6,10 +6,49 @@ export const connectWallet = createAsyncThunk< SystemWithWallet, undefined, { state: State } ->('wallet/connect', async (_arg, { getState, rejectWithValue }) => { +>('wallet/connect', async (_arg, { getState, rejectWithValue, dispatch }) => { const { system } = getState(); if (system.status === 'ToolkitConnected') { - return await Minter.connectWallet(system); + // NOTE: These event handlers will be passed to the Beacon DAppClient *once* + // as the client is cached after its first instantiation + const eventHandlers: Parameters[1] = { + PERMISSION_REQUEST_SENT: { + handler(data) { + console.log(data); + } + }, + PERMISSION_REQUEST_SUCCESS: { + handler(data) { + console.log(data); + } + }, + PERMISSION_REQUEST_ERROR: { + handler(data) { + console.log(data); + } + }, + OPERATION_REQUEST_SENT: { + handler(data) { + console.log(data); + } + }, + OPERATION_REQUEST_SUCCESS: { + handler(data) { + console.log(data); + } + }, + OPERATION_REQUEST_ERROR: { + handler(data) { + console.log(data); + } + }, + ACKNOWLEDGE_RECEIVED: { + handler(data) { + console.log(data); + } + } + }; + return await Minter.connectWallet(system /*eventHandlers*/); } return rejectWithValue({ error: 'Wallet already connected' }); }); From 2171040d2871a4e95020afc3b483c88271e3a136 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 13:06:10 -0500 Subject: [PATCH 15/28] fix: Flip boolean in validation helper function --- client/src/reducer/validators/createNft.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/reducer/validators/createNft.ts b/client/src/reducer/validators/createNft.ts index 26c7991d..2b28f962 100644 --- a/client/src/reducer/validators/createNft.ts +++ b/client/src/reducer/validators/createNft.ts @@ -23,7 +23,7 @@ export const collectionSelectSchema = assetDetailsSchema.append({ }); function isValid(schema: Joi.ObjectSchema, object: any) { - return !!schema.validate(object, { allowUnknown: true }).error; + return !schema.validate(object, { allowUnknown: true }).error; } export function validateCreateNftStep(state: State['createNft']) { From f2995c8eee8b48e83f04c45bcb3e5f8e7bad4f35 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 13:10:42 -0500 Subject: [PATCH 16/28] feat: Use ipfs:// URIs in artifactUri & displayUri metadata --- .../Collections/Catalog/TokenGrid.tsx | 5 ++-- .../Collections/TokenDetail/index.tsx | 6 ++--- .../CreateNonFungiblePage/FileUpload.tsx | 6 +++-- .../components/CreateNonFungiblePage/Form.tsx | 6 ++--- .../CreateNonFungiblePage/Preview.tsx | 10 +++---- client/src/util/index.ts | 27 ++++++++++++++++--- server/src/index.ts | 3 ++- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/client/src/components/Collections/Catalog/TokenGrid.tsx b/client/src/components/Collections/Catalog/TokenGrid.tsx index 9515f72d..43e14ba4 100644 --- a/client/src/components/Collections/Catalog/TokenGrid.tsx +++ b/client/src/components/Collections/Catalog/TokenGrid.tsx @@ -1,8 +1,9 @@ import React, { useState } from 'react'; import { useLocation } from 'wouter'; import { AspectRatio, Box, Flex, Grid, Image, Text } from '@chakra-ui/react'; -import { Token, CollectionsState } from '../../../reducer/slices/collections'; import { Wind, HelpCircle } from 'react-feather'; +import { Token, CollectionsState } from '../../../reducer/slices/collections'; +import { ipfsUriToGatewayUrl } from '../../../util'; interface TokenTileProps extends Token { selectedCollection: string; @@ -60,7 +61,7 @@ function TokenTile(props: TokenTileProps) { > - + - + @@ -223,7 +223,7 @@ function TokenDetail({ contractAddress, tokenId }: TokenDetailProps) { > IPFS Hash - {ipfsCidFromUri(token.artifactUri) || 'No IPFS Hash'} + {uriToCid(token.artifactUri) || 'No IPFS Hash'} {system.status === 'WalletConnected' ? ( diff --git a/client/src/components/CreateNonFungiblePage/FileUpload.tsx b/client/src/components/CreateNonFungiblePage/FileUpload.tsx index 88d605ba..32fb7456 100644 --- a/client/src/components/CreateNonFungiblePage/FileUpload.tsx +++ b/client/src/components/CreateNonFungiblePage/FileUpload.tsx @@ -4,10 +4,12 @@ import { useDropzone } from 'react-dropzone'; import { Box, Flex, Heading, Text, Image } from '@chakra-ui/react'; import { useSelector, useDispatch } from '../../reducer'; import { updateArtifactUri } from '../../reducer/slices/createNft'; +import { ipfsUriToGatewayUrl } from '../../util'; type IpfsContent = { cid: string; size: number; + ipfsUri: string; url: string; publicGatewayUrl: string; }; @@ -21,7 +23,7 @@ export default function FileUpload() { formData.append('file', acceptedFiles[0]); const response = await axios.post('/ipfs-upload', formData); - dispatch(updateArtifactUri(response.data.publicGatewayUrl)); + dispatch(updateArtifactUri(response.data.ipfsUri)); }, [dispatch] ); @@ -63,7 +65,7 @@ export default function FileUpload() { p={4} maxWidth="400px" maxHeight="400px" - src={state.artifactUri} + src={ipfsUriToGatewayUrl(state.artifactUri)} /> ) : ( IPFS Hash - - {(state.artifactUri && ipfsCidFromUri(state.artifactUri)) || ''} - + {(state.artifactUri && uriToCid(state.artifactUri)) || ''} diff --git a/client/src/components/CreateNonFungiblePage/Preview.tsx b/client/src/components/CreateNonFungiblePage/Preview.tsx index ae793078..401ea6d5 100644 --- a/client/src/components/CreateNonFungiblePage/Preview.tsx +++ b/client/src/components/CreateNonFungiblePage/Preview.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Divider, Heading, Flex, Image, Text } from '@chakra-ui/react'; -import { ipfsCidFromUri } from '../../util'; +import { ipfsUriToGatewayUrl, uriToCid } from '../../util'; import { useSelector } from '../../reducer'; export default function Preview() { @@ -24,7 +24,9 @@ export default function Preview() { overflow="hidden" > IPFS Hash - - {(state.artifactUri && ipfsCidFromUri(state.artifactUri)) || ''} - + {(state.artifactUri && uriToCid(state.artifactUri)) || ''} ); diff --git a/client/src/util/index.ts b/client/src/util/index.ts index ee38eb29..9238c6f3 100644 --- a/client/src/util/index.ts +++ b/client/src/util/index.ts @@ -1,7 +1,26 @@ -export function ipfsCidFromUri(uri: string) { - const reBaseStr = /.*\/ipfs\//; - if (reBaseStr.test(uri)) { - return uri.replace(reBaseStr, ''); +export function ipfsUriToCid(uri: string) { + const baseRegex = /^ipfs:\/\//; + const ipfsRegex = new RegExp(baseRegex.source + '.+'); + if (ipfsRegex.test(uri)) { + return uri.replace(baseRegex, ''); + } + return null; +} + +export function ipfsUriToGatewayUrl(uri: string) { + const cid = ipfsUriToCid(uri); + return cid ? `https://cloudflare-ipfs.com/ipfs/${cid}` : uri; +} + +export function uriToCid(uri: string) { + const ipfsUriCid = ipfsUriToCid(uri); + if (ipfsUriCid) { + return ipfsUriCid; + } + const baseRegex = /^https:\/\/.*\/ipfs\//; + const httpRegex = new RegExp(baseRegex.source + '.+'); + if (httpRegex.test(uri)) { + return uri.replace(baseRegex, ''); } return uri; } diff --git a/server/src/index.ts b/server/src/index.ts index f9cb145d..e6059277 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -8,7 +8,6 @@ import axios from 'axios'; import fs from 'fs'; import FormData from 'form-data'; import { promisify } from 'util'; -import { Readable } from 'stream'; const readFileAsync = promisify(fs.readFile); @@ -52,6 +51,7 @@ async function uploadToPinata( return res.status(200).send({ cid, size: pinataData.PinSize, + ipfsUri: `ipfs://${cid}`, url: url.resolve(ipfsConfig.pinataGatewayUrl, `ipfs/${cid}`), publicGatewayUrl: url.resolve(ipfsConfig.publicGatewayUrl, `ipfs/${cid}`) }); @@ -68,6 +68,7 @@ async function uploadToIpfs(file: UploadedFile, res: Response) { return res.status(200).send({ cid, size: ipfsFile.size, + ipfsUri: `ipfs://${cid}`, url: url.resolve(ipfsConfig.gatewayUrl, `ipfs/${cid}`), publicGatewayUrl: url.resolve(ipfsConfig.gatewayUrl, `ipfs/${cid}`) }); From f6bf0a0bc3597b6044c2c4a2f11323f39fb444ba Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 13:11:26 -0500 Subject: [PATCH 17/28] doc: Add TODO comment to custom Beacon event handlers map --- client/src/reducer/async/wallet.ts | 93 ++++++++++++++++-------------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/client/src/reducer/async/wallet.ts b/client/src/reducer/async/wallet.ts index b72232e3..378ad992 100644 --- a/client/src/reducer/async/wallet.ts +++ b/client/src/reducer/async/wallet.ts @@ -6,52 +6,57 @@ export const connectWallet = createAsyncThunk< SystemWithWallet, undefined, { state: State } ->('wallet/connect', async (_arg, { getState, rejectWithValue, dispatch }) => { - const { system } = getState(); - if (system.status === 'ToolkitConnected') { - // NOTE: These event handlers will be passed to the Beacon DAppClient *once* - // as the client is cached after its first instantiation - const eventHandlers: Parameters[1] = { - PERMISSION_REQUEST_SENT: { - handler(data) { - console.log(data); - } - }, - PERMISSION_REQUEST_SUCCESS: { - handler(data) { - console.log(data); - } - }, - PERMISSION_REQUEST_ERROR: { - handler(data) { - console.log(data); - } - }, - OPERATION_REQUEST_SENT: { - handler(data) { - console.log(data); +>( + 'wallet/connect', + async (_arg, { getState, rejectWithValue /* , dispatch */ }) => { + const { system } = getState(); + if (system.status === 'ToolkitConnected') { + // TODO: Implement custom UI behavior by overriding Beacon events + // NOTE: These event handlers will be passed to the Beacon DAppClient *once* + // as the client is cached after its first instantiation + // eslint-disable-next-line + const eventHandlers: Parameters[1] = { + PERMISSION_REQUEST_SENT: { + handler(data) { + console.log(data); + } + }, + PERMISSION_REQUEST_SUCCESS: { + handler(data) { + console.log(data); + } + }, + PERMISSION_REQUEST_ERROR: { + handler(data) { + console.log(data); + } + }, + OPERATION_REQUEST_SENT: { + handler(data) { + console.log(data); + } + }, + OPERATION_REQUEST_SUCCESS: { + handler(data) { + console.log(data); + } + }, + OPERATION_REQUEST_ERROR: { + handler(data) { + console.log(data); + } + }, + ACKNOWLEDGE_RECEIVED: { + handler(data) { + console.log(data); + } } - }, - OPERATION_REQUEST_SUCCESS: { - handler(data) { - console.log(data); - } - }, - OPERATION_REQUEST_ERROR: { - handler(data) { - console.log(data); - } - }, - ACKNOWLEDGE_RECEIVED: { - handler(data) { - console.log(data); - } - } - }; - return await Minter.connectWallet(system /*eventHandlers*/); + }; + return await Minter.connectWallet(system /*eventHandlers*/); + } + return rejectWithValue({ error: 'Wallet already connected' }); } - return rejectWithValue({ error: 'Wallet already connected' }); -}); +); export const disconnectWallet = createAsyncThunk< SystemWithToolkit, From b9564d0664156e4302c314331c6150144de962d8 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 13:12:53 -0500 Subject: [PATCH 18/28] feat: Create resolveMetadata function from @taquito/tzip16 --- client/package.json | 1 + client/src/lib/nfts/queries.ts | 12 ++++++++--- client/src/lib/system.ts | 39 ++++++++++++++++++++++++++++++++-- client/yarn.lock | 18 ++++++++++++++++ 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/client/package.json b/client/package.json index afaa5c60..a31da607 100644 --- a/client/package.json +++ b/client/package.json @@ -9,6 +9,7 @@ "@emotion/styled": "11.0.0", "@reduxjs/toolkit": "1.5.0", "@taquito/beacon-wallet": "8.0.0-beta.5", + "@taquito/tzip16": "8.0.0-beta.5", "@taquito/taquito": "8.0.0-beta.5", "@types/lodash": "4.14.165", "@types/react": "16.9.12", diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index e9269879..94ffbbc5 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -1,5 +1,5 @@ import { Buffer } from 'buffer'; -import { System, SystemWithWallet } from '../system'; +import { SystemWithToolkit, SystemWithWallet } from '../system'; import { hash as nftAssetHash } from './code/fa2_tzip16_compat_multi_nft_asset'; import select from '../util/selectObjectByKeys'; @@ -20,9 +20,15 @@ export interface Nft { } export async function getContractNfts( - system: System, + system: SystemWithToolkit | SystemWithWallet, address: string ): Promise { + // TODO: Resolve IPFS metadata as seen in below example + // console.log('Resolving metadata...'); + // const resolvedMetadata = await system.resolveMetadata( + // 'ipfs://QmRjVUAuS7V2c8bKbXKN9eXzp2dMXW8jwYLCAFo9nHBSeb' + // ); + // console.log(resolvedMetadata); const storage = await system.betterCallDev.getContractStorage(address); const ledgerBigMapId = select(storage, { @@ -80,7 +86,7 @@ export interface AssetContract { } export async function getNftAssetContract( - system: System, + system: SystemWithToolkit | SystemWithWallet, address: string ): Promise { const bcd = system.betterCallDev; diff --git a/client/src/lib/system.ts b/client/src/lib/system.ts index 9ebeb715..6d40c173 100644 --- a/client/src/lib/system.ts +++ b/client/src/lib/system.ts @@ -1,5 +1,10 @@ -import { TezosToolkit } from '@taquito/taquito'; +import { TezosToolkit, Context } from '@taquito/taquito'; import { BeaconWallet } from '@taquito/beacon-wallet'; +import { + MetadataProvider, + DEFAULT_HANDLERS, + IpfsHttpHandler +} from '@taquito/tzip16'; import { BetterCallDev } from './service/bcd'; import * as tzUtils from './util/tezosToolkit'; import { DAppClientOptions, NetworkType } from '@airgap/beacon-sdk'; @@ -31,11 +36,16 @@ export interface SystemConfigured { tzPublicKey: null; } +type ResolveMetadata = ( + uri: string +) => ReturnType; + export interface SystemWithToolkit { status: Status.ToolkitConnected; config: Config; betterCallDev: BetterCallDev; toolkit: TezosToolkit; + resolveMetadata: ResolveMetadata; wallet: null; tzPublicKey: null; } @@ -45,6 +55,7 @@ export interface SystemWithWallet { config: Config; betterCallDev: BetterCallDev; toolkit: TezosToolkit; + resolveMetadata: ResolveMetadata; wallet: BeaconWallet; tzPublicKey: string; } @@ -62,12 +73,36 @@ export function configure(config: Config): SystemConfigured { }; } +function createMetadataResolver( + toolkit: TezosToolkit, + contractAddress: string +): ResolveMetadata { + DEFAULT_HANDLERS.set('ipfs', new IpfsHttpHandler('cloudflare-ipfs.com')); + const provider = new MetadataProvider(DEFAULT_HANDLERS); + const context = new Context(toolkit.rpc); + // This is a performance optimization: We're only resolving off-chain + // metadata, however the storage handler requires a ContractAbstraction + // instance present - if we fetch a contract on each invokation, the time + // to resolution can take several hundred milliseconds. + // + // TODO: Is it possible to only fetch contracts at the storage resolver level + // and make an "off-chain" metadata resolver that excludes the need for a + // ContractAbstraction instance? + const defaultContract = toolkit.contract.at(contractAddress); + return async uri => { + const contract = await defaultContract; + return provider.provideMetadata(contract, uri, context); + }; +} + export function connectToolkit(system: SystemConfigured): SystemWithToolkit { const toolkit = new TezosToolkit(system.config.rpc); + const faucetAddress = system.config.contracts.nftFaucet; return { ...system, status: Status.ToolkitConnected, - toolkit: toolkit + toolkit: toolkit, + resolveMetadata: createMetadataResolver(toolkit, faucetAddress) }; } diff --git a/client/yarn.lock b/client/yarn.lock index fa1f924d..eb600072 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2302,6 +2302,19 @@ rx-sandbox "^1.0.3" rxjs "^6.6.3" +"@taquito/tzip16@8.0.0-beta.5": + version "8.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@taquito/tzip16/-/tzip16-8.0.0-beta.5.tgz#4633e330354a68de9d33a52b672919d47eb15b9b" + integrity sha512-J4OzL0DMCPffpN3Ncp4Hg2B6B8oXa29QhRRELKkGXG0CToU9znObar0Vmmu7aT00bxB0la1EhtI13ONe2ebmkg== + dependencies: + "@taquito/http-utils" "^8.0.0-beta.5" + "@taquito/michelson-encoder" "^8.0.0-beta.5" + "@taquito/rpc" "^8.0.0-beta.5" + "@taquito/taquito" "^8.0.0-beta.5" + "@taquito/utils" "^8.0.0-beta.5" + bignumber.js "^9.0.1" + crypto-js "^4.0.0" + "@taquito/utils@^8.0.0-beta.5": version "8.0.0-beta.5" resolved "https://registry.yarnpkg.com/@taquito/utils/-/utils-8.0.0-beta.5.tgz#602ed70cb613507939b0f6ca0a0a3421c5437fad" @@ -4477,6 +4490,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc" + integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg== + css-blank-pseudo@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" From 0e8074ab532e2b62a6924bdeb2a6d8b9c1d5cd36 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 15:27:23 -0500 Subject: [PATCH 19/28] feat: Upload and display token metadata to IPFS --- .../Collections/Catalog/Sidebar.tsx | 2 +- .../components/Collections/Catalog/index.tsx | 2 +- .../CreateNonFungiblePage/FileUpload.tsx | 6 +- client/src/lib/nfts/queries.ts | 54 ++++++++------- client/src/reducer/async/actions.ts | 19 +++++- client/src/util/index.ts | 2 +- docker-stack/nginx.conf | 4 +- server/src/index.ts | 67 ++++++++++++++++--- 8 files changed, 117 insertions(+), 39 deletions(-) diff --git a/client/src/components/Collections/Catalog/Sidebar.tsx b/client/src/components/Collections/Catalog/Sidebar.tsx index aac9c5ca..c1418c6d 100644 --- a/client/src/components/Collections/Catalog/Sidebar.tsx +++ b/client/src/components/Collections/Catalog/Sidebar.tsx @@ -97,7 +97,7 @@ export default function Sidebar() { {...state.collections[address]} /> ))} - + diff --git a/client/src/components/Collections/Catalog/index.tsx b/client/src/components/Collections/Catalog/index.tsx index a7163233..b0e1b4b7 100644 --- a/client/src/components/Collections/Catalog/index.tsx +++ b/client/src/components/Collections/Catalog/index.tsx @@ -49,7 +49,7 @@ export default function Catalog() { return ( - + s.createNft); const dispatch = useDispatch(); + const onDrop = useCallback( async (acceptedFiles: File[]) => { const formData = new FormData(); formData.append('file', acceptedFiles[0]); - const response = await axios.post('/ipfs-upload', formData); + const response = await axios.post( + '/ipfs-file-upload', + formData + ); dispatch(updateArtifactUri(response.data.ipfsUri)); }, [dispatch] diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index 94ffbbc5..ba60083c 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -2,6 +2,7 @@ import { Buffer } from 'buffer'; import { SystemWithToolkit, SystemWithWallet } from '../system'; import { hash as nftAssetHash } from './code/fa2_tzip16_compat_multi_nft_asset'; import select from '../util/selectObjectByKeys'; +import { ipfsUriToCid, uriToCid } from '../../util'; function fromHexString(input: string) { if (/^([A-Fa-f0-9]{2})*$/.test(input)) { @@ -53,30 +54,37 @@ export async function getContractNfts( if (!tokens) return []; - return tokens.map( - (token: any): Nft => { - const tokenId = select(token, { name: 'token_id' })?.value; - const metadataMap = select(token, { name: 'token_info' })?.children; - const metadata = metadataMap.reduce((acc: any, next: any) => { - return { ...acc, [next.name]: fromHexString(next.value) }; - }, {}); - - const owner = select( - ledger.filter((v: any) => v.data.key.value === tokenId), - { - type: 'address' + return Promise.all( + tokens.map( + async (token: any): Promise => { + const tokenId = select(token, { name: 'token_id' })?.value; + const metadataMap = select(token, { name: 'token_info' })?.children; + let metadata = metadataMap.reduce((acc: any, next: any) => { + return { ...acc, [next.name]: fromHexString(next.value) }; + }, {}); + + if (ipfsUriToCid(metadata[''])) { + const resolvedMetadata = await system.resolveMetadata(metadata['']); + metadata = { ...metadata, ...resolvedMetadata.metadata }; } - )?.value; - - return { - id: parseInt(tokenId, 10), - title: metadata.name, - owner, - description: metadata.description, - artifactUri: metadata.artifactUri, - metadata: metadata - }; - } + + const owner = select( + ledger.filter((v: any) => v.data.key.value === tokenId), + { + type: 'address' + } + )?.value; + + return { + id: parseInt(tokenId, 10), + title: metadata.name, + owner, + description: metadata.description, + artifactUri: metadata.artifactUri, + metadata: metadata + }; + } + ) ); } diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts index d15cc7b0..6ea16b47 100644 --- a/client/src/reducer/async/actions.ts +++ b/client/src/reducer/async/actions.ts @@ -1,3 +1,4 @@ +import axios from 'axios'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { State } from '..'; import { @@ -64,6 +65,14 @@ function buildMetadataFromState(state: State['createNft']) { return { address, metadata }; } +type IpfsContent = { + cid: string; + size: number; + ipfsUri: string; + url: string; + publicGatewayUrl: string; +}; + export const mintTokenAction = createAsyncThunk< { contract: string }, undefined, @@ -83,8 +92,16 @@ export const mintTokenAction = createAsyncThunk< } const { address, metadata } = buildMetadataFromState(state); + try { - const op = await mintToken(system, address, metadata); + const response = await axios.post( + '/ipfs-json-upload', + metadata + ); + + const op = await mintToken(system, address, { + '': response.data.ipfsUri + }); await op.confirmation(); dispatch(getContractNftsQuery(address)); return { contract: address }; diff --git a/client/src/util/index.ts b/client/src/util/index.ts index 9238c6f3..45077e3c 100644 --- a/client/src/util/index.ts +++ b/client/src/util/index.ts @@ -22,5 +22,5 @@ export function uriToCid(uri: string) { if (httpRegex.test(uri)) { return uri.replace(baseRegex, ''); } - return uri; + return null; } diff --git a/docker-stack/nginx.conf b/docker-stack/nginx.conf index 75e2a64a..4932fcd2 100644 --- a/docker-stack/nginx.conf +++ b/docker-stack/nginx.conf @@ -58,7 +58,7 @@ http { proxy_set_header Host $host; } - location /graphql { + location /ipfs-file-upload { proxy_pass http://$api_server_upstream; proxy_set_header X-Original-URI $request_uri; proxy_http_version 1.1; @@ -66,7 +66,7 @@ http { proxy_set_header Connection $connection_upgrade; } - location /ipfs-upload { + location /ipfs-json-upload { proxy_pass http://$api_server_upstream; proxy_set_header X-Original-URI $request_uri; proxy_http_version 1.1; diff --git a/server/src/index.ts b/server/src/index.ts index e6059277..86bfd058 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -24,7 +24,7 @@ interface PinataConfig { apiSecret: string; } -async function uploadToPinata( +async function uploadFileToPinata( pinataConfig: PinataConfig, file: UploadedFile, res: Response @@ -60,9 +60,38 @@ async function uploadToPinata( } } -async function uploadToIpfs(file: UploadedFile, res: Response) { +async function uploadJSONToPinata( + pinataConfig: PinataConfig, + req: Request, + res: Response +) { + try { + const pinataUrl = `https://api.pinata.cloud/pinning/pinJSONToIPFS`; + const pinataRes = await axios.post(pinataUrl, req.body, { + headers: { + pinata_api_key: pinataConfig.apiKey, + pinata_secret_api_key: pinataConfig.apiSecret + } + }); + + const pinataData = pinataRes.data; + const cid = pinataData.IpfsHash; + + return res.status(200).send({ + cid, + size: pinataData.PinSize, + ipfsUri: `ipfs://${cid}`, + url: url.resolve(ipfsConfig.pinataGatewayUrl, `ipfs/${cid}`), + publicGatewayUrl: url.resolve(ipfsConfig.publicGatewayUrl, `ipfs/${cid}`) + }); + } catch (e) { + console.log(e); + } +} + +async function uploadToIpfs(data: any, res: Response) { const ipfsClient = IpfsClient(ipfsConfig.apiUrl); - const ipfsFile = await ipfsClient.add(fs.readFileSync(file.tempFilePath)); + const ipfsFile = await ipfsClient.add(data); const cid = ipfsFile.cid.toString(); return res.status(200).send({ @@ -94,7 +123,7 @@ async function getPinataConfig(): Promise { } } -async function handleIpfsUpload( +async function handleIpfsFileUpload( pinataConfig: PinataConfig | null, req: Request, res: Response @@ -105,10 +134,26 @@ async function handleIpfsUpload( } if (pinataConfig) { - return await uploadToPinata(pinataConfig, file, res); + return await uploadFileToPinata(pinataConfig, file, res); + } + const data = fs.readFileSync(file.tempFilePath); + return await uploadToIpfs(data, res); +} + +async function handleIpfsJSONUpload( + pinataConfig: PinataConfig | null, + req: Request, + res: Response +) { + if (!req.body) { + throw Error('No file data found'); + } + + if (pinataConfig) { + return await uploadJSONToPinata(pinataConfig, req, res); } - return await uploadToIpfs(file, res); + return await uploadToIpfs(req.body, res); } async function createHttpServer(app: Express) { @@ -123,9 +168,13 @@ async function createHttpServer(app: Express) { const pinataConfig = await getPinataConfig(); - app.post('/ipfs-upload', (req, res) => - handleIpfsUpload(pinataConfig, req, res) - ); + app.post('/ipfs-file-upload', (req, res) => { + return handleIpfsFileUpload(pinataConfig, req, res); + }); + + app.post('/ipfs-json-upload', (req, res) => { + return handleIpfsJSONUpload(pinataConfig, req, res); + }); const httpServer = http.createServer(app); return httpServer; From cf02525e321f40f43516129e93df73bbc40cbc7e Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 15:40:01 -0500 Subject: [PATCH 20/28] chore: Remove TODO from nft query --- client/src/lib/nfts/queries.ts | 6 ------ client/src/reducer/async/actions.ts | 10 ++-------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index ba60083c..4f880b66 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -24,12 +24,6 @@ export async function getContractNfts( system: SystemWithToolkit | SystemWithWallet, address: string ): Promise { - // TODO: Resolve IPFS metadata as seen in below example - // console.log('Resolving metadata...'); - // const resolvedMetadata = await system.resolveMetadata( - // 'ipfs://QmRjVUAuS7V2c8bKbXKN9eXzp2dMXW8jwYLCAFo9nHBSeb' - // ); - // console.log(resolvedMetadata); const storage = await system.betterCallDev.getContractStorage(address); const ledgerBigMapId = select(storage, { diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts index 6ea16b47..e0c01dad 100644 --- a/client/src/reducer/async/actions.ts +++ b/client/src/reducer/async/actions.ts @@ -94,14 +94,8 @@ export const mintTokenAction = createAsyncThunk< const { address, metadata } = buildMetadataFromState(state); try { - const response = await axios.post( - '/ipfs-json-upload', - metadata - ); - - const op = await mintToken(system, address, { - '': response.data.ipfsUri - }); + const resp = await axios.post('/ipfs-json-upload', metadata); + const op = await mintToken(system, address, { '': resp.data.ipfsUri }); await op.confirmation(); dispatch(getContractNftsQuery(address)); return { contract: address }; From 5e51ba6eedf9d97019d4770a3dcade4094e30fb9 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 15:40:49 -0500 Subject: [PATCH 21/28] chore: Remove unused import --- client/src/lib/nfts/queries.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index 4f880b66..90908fd9 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -2,7 +2,7 @@ import { Buffer } from 'buffer'; import { SystemWithToolkit, SystemWithWallet } from '../system'; import { hash as nftAssetHash } from './code/fa2_tzip16_compat_multi_nft_asset'; import select from '../util/selectObjectByKeys'; -import { ipfsUriToCid, uriToCid } from '../../util'; +import { ipfsUriToCid } from '../../util'; function fromHexString(input: string) { if (/^([A-Fa-f0-9]{2})*$/.test(input)) { From 69b473c4159e49b4efe4056032648e9b26b486d4 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 15:51:56 -0500 Subject: [PATCH 22/28] refactor: Move common IPFS functions into lib/util/ipfs.ts --- .../Collections/Catalog/TokenGrid.tsx | 2 +- .../Collections/TokenDetail/index.tsx | 2 +- .../CreateNonFungiblePage/FileUpload.tsx | 20 +++-------------- .../components/CreateNonFungiblePage/Form.tsx | 2 +- .../CreateNonFungiblePage/Preview.tsx | 2 +- client/src/lib/nfts/queries.ts | 2 +- .../src/{util/index.ts => lib/util/ipfs.ts} | 22 +++++++++++++++++++ client/src/reducer/async/actions.ts | 12 ++-------- 8 files changed, 32 insertions(+), 32 deletions(-) rename client/src/{util/index.ts => lib/util/ipfs.ts} (59%) diff --git a/client/src/components/Collections/Catalog/TokenGrid.tsx b/client/src/components/Collections/Catalog/TokenGrid.tsx index 43e14ba4..596366bf 100644 --- a/client/src/components/Collections/Catalog/TokenGrid.tsx +++ b/client/src/components/Collections/Catalog/TokenGrid.tsx @@ -3,7 +3,7 @@ import { useLocation } from 'wouter'; import { AspectRatio, Box, Flex, Grid, Image, Text } from '@chakra-ui/react'; import { Wind, HelpCircle } from 'react-feather'; import { Token, CollectionsState } from '../../../reducer/slices/collections'; -import { ipfsUriToGatewayUrl } from '../../../util'; +import { ipfsUriToGatewayUrl } from '../../../lib/util/ipfs'; interface TokenTileProps extends Token { selectedCollection: string; diff --git a/client/src/components/Collections/TokenDetail/index.tsx b/client/src/components/Collections/TokenDetail/index.tsx index 9e22a92a..9eed6845 100644 --- a/client/src/components/Collections/TokenDetail/index.tsx +++ b/client/src/components/Collections/TokenDetail/index.tsx @@ -8,7 +8,7 @@ import { } from 'react-feather'; import { MinterButton } from '../../common'; import { TransferTokenButton } from '../../common/TransferToken'; -import { ipfsUriToGatewayUrl, uriToCid } from '../../../util'; +import { ipfsUriToGatewayUrl, uriToCid } from '../../../lib/util/ipfs'; import { useSelector, useDispatch } from '../../../reducer'; import { getContractNftsQuery, diff --git a/client/src/components/CreateNonFungiblePage/FileUpload.tsx b/client/src/components/CreateNonFungiblePage/FileUpload.tsx index bc2adefc..d4208d52 100644 --- a/client/src/components/CreateNonFungiblePage/FileUpload.tsx +++ b/client/src/components/CreateNonFungiblePage/FileUpload.tsx @@ -1,18 +1,10 @@ -import axios from 'axios'; import React, { useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; import { Box, Flex, Heading, Text, Image } from '@chakra-ui/react'; import { useSelector, useDispatch } from '../../reducer'; import { updateArtifactUri } from '../../reducer/slices/createNft'; -import { ipfsUriToGatewayUrl } from '../../util'; - -type IpfsContent = { - cid: string; - size: number; - ipfsUri: string; - url: string; - publicGatewayUrl: string; -}; +import { ipfsUriToGatewayUrl } from '../../lib/util/ipfs'; +import { uploadFiletoIpfs } from '../../lib/util/ipfs'; export default function FileUpload() { const state = useSelector(s => s.createNft); @@ -20,13 +12,7 @@ export default function FileUpload() { const onDrop = useCallback( async (acceptedFiles: File[]) => { - const formData = new FormData(); - formData.append('file', acceptedFiles[0]); - - const response = await axios.post( - '/ipfs-file-upload', - formData - ); + const response = await uploadFiletoIpfs(acceptedFiles[0]); dispatch(updateArtifactUri(response.data.ipfsUri)); }, [dispatch] diff --git a/client/src/components/CreateNonFungiblePage/Form.tsx b/client/src/components/CreateNonFungiblePage/Form.tsx index a411b915..d5fbf88e 100644 --- a/client/src/components/CreateNonFungiblePage/Form.tsx +++ b/client/src/components/CreateNonFungiblePage/Form.tsx @@ -21,7 +21,7 @@ import { updateMetadataRowName, updateMetadataRowValue } from '../../reducer/slices/createNft'; -import { uriToCid } from '../../util'; +import { uriToCid } from '../../lib/util/ipfs'; const DESCRIPTION_PLACEHOLDER = 'e.g. “This is an exclusive japanese comic illustration. Once you purchase it you will be able to get the t-shirt”'; diff --git a/client/src/components/CreateNonFungiblePage/Preview.tsx b/client/src/components/CreateNonFungiblePage/Preview.tsx index 401ea6d5..b59d89a2 100644 --- a/client/src/components/CreateNonFungiblePage/Preview.tsx +++ b/client/src/components/CreateNonFungiblePage/Preview.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Divider, Heading, Flex, Image, Text } from '@chakra-ui/react'; -import { ipfsUriToGatewayUrl, uriToCid } from '../../util'; +import { ipfsUriToGatewayUrl, uriToCid } from '../../lib/util/ipfs'; import { useSelector } from '../../reducer'; export default function Preview() { diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index 90908fd9..65d8e9c9 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -2,7 +2,7 @@ import { Buffer } from 'buffer'; import { SystemWithToolkit, SystemWithWallet } from '../system'; import { hash as nftAssetHash } from './code/fa2_tzip16_compat_multi_nft_asset'; import select from '../util/selectObjectByKeys'; -import { ipfsUriToCid } from '../../util'; +import { ipfsUriToCid } from '../util/ipfs'; function fromHexString(input: string) { if (/^([A-Fa-f0-9]{2})*$/.test(input)) { diff --git a/client/src/util/index.ts b/client/src/lib/util/ipfs.ts similarity index 59% rename from client/src/util/index.ts rename to client/src/lib/util/ipfs.ts index 45077e3c..1986a6d7 100644 --- a/client/src/util/index.ts +++ b/client/src/lib/util/ipfs.ts @@ -1,3 +1,25 @@ +import axios from 'axios'; + +export type IpfsContent = { + cid: string; + size: number; + ipfsUri: string; + url: string; + publicGatewayUrl: string; +}; + +export async function uploadJSONToIpfs(data: any) { + return axios.post('/ipfs-json-upload', data); +} + +export async function uploadFiletoIpfs(file: File) { + const formData = new FormData(); + formData.append('file', file); + return axios.post('/ipfs-file-upload', formData); +} + +// URI Utils + export function ipfsUriToCid(uri: string) { const baseRegex = /^ipfs:\/\//; const ipfsRegex = new RegExp(baseRegex.source + '.+'); diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts index e0c01dad..5435d804 100644 --- a/client/src/reducer/async/actions.ts +++ b/client/src/reducer/async/actions.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { State } from '..'; import { @@ -9,6 +8,7 @@ import { import { ErrorKind, RejectValue } from './errors'; import { getContractNftsQuery, getWalletAssetContractsQuery } from './queries'; import { validateCreateNftForm } from '../validators/createNft'; +import { uploadJSONToIpfs } from '../../lib/util/ipfs'; type Options = { state: State; @@ -65,14 +65,6 @@ function buildMetadataFromState(state: State['createNft']) { return { address, metadata }; } -type IpfsContent = { - cid: string; - size: number; - ipfsUri: string; - url: string; - publicGatewayUrl: string; -}; - export const mintTokenAction = createAsyncThunk< { contract: string }, undefined, @@ -94,7 +86,7 @@ export const mintTokenAction = createAsyncThunk< const { address, metadata } = buildMetadataFromState(state); try { - const resp = await axios.post('/ipfs-json-upload', metadata); + const resp = await uploadJSONToIpfs(metadata); const op = await mintToken(system, address, { '': resp.data.ipfsUri }); await op.confirmation(); dispatch(getContractNftsQuery(address)); From d764f5900f99b6e986c0ae4c593ead8f2d523501 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 16:13:01 -0500 Subject: [PATCH 23/28] fix: Clear create form on cancel/completion --- client/src/components/CreateNonFungiblePage/index.tsx | 6 +++++- client/src/reducer/slices/createNft.ts | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/src/components/CreateNonFungiblePage/index.tsx b/client/src/components/CreateNonFungiblePage/index.tsx index d89ed133..c9dd9a73 100644 --- a/client/src/components/CreateNonFungiblePage/index.tsx +++ b/client/src/components/CreateNonFungiblePage/index.tsx @@ -11,6 +11,7 @@ import { ChevronLeft, X } from 'react-feather'; import { useSelector, useDispatch } from '../../reducer'; import { + clearForm, CreateNftState, decrementStep, incrementStep, @@ -89,7 +90,10 @@ export default function CreateNonFungiblePage() { setLocation('/collections')} + onClick={() => { + dispatch(clearForm()); + setLocation('/collections'); + }} display="flex" alignItems="center" color="brand.red" diff --git a/client/src/reducer/slices/createNft.ts b/client/src/reducer/slices/createNft.ts index 5da7fb07..d68badec 100644 --- a/client/src/reducer/slices/createNft.ts +++ b/client/src/reducer/slices/createNft.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { mintTokenAction } from '../async/actions'; // State @@ -93,7 +94,13 @@ const slice = createSlice({ }, setCreateStatus(state, action: PayloadAction) { state.createStatus = action.payload; + }, + clearForm() { + return initialState; } + }, + extraReducers: builder => { + builder.addCase(mintTokenAction.fulfilled, () => initialState); } }); @@ -107,7 +114,8 @@ export const { updateMetadataRowValue, deleteMetadataRow, selectCollection, - setCreateStatus + setCreateStatus, + clearForm } = slice.actions; export default slice; From 5ffef024970f47f077790549649f242a06b4c705 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 18:02:05 -0500 Subject: [PATCH 24/28] fix: Store decimals and booleanAmount in metadata --- client/src/lib/nfts/actions.ts | 3 --- client/src/reducer/async/actions.ts | 8 +++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/client/src/lib/nfts/actions.ts b/client/src/lib/nfts/actions.ts index 45764a4f..d7d08a83 100644 --- a/client/src/lib/nfts/actions.ts +++ b/client/src/lib/nfts/actions.ts @@ -82,9 +82,6 @@ export async function mintToken( const token_id = storage.assets.next_token_id; const token_info = new MichelsonMap(); - token_info.set('decimals', toHexString('0')); - token_info.set('booleanAmount', toHexString('true')); - for (let key in metadata) { const value = toHexString(metadata[key]); token_info.set(key, value); diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts index 5435d804..02641b4a 100644 --- a/client/src/reducer/async/actions.ts +++ b/client/src/reducer/async/actions.ts @@ -86,7 +86,13 @@ export const mintTokenAction = createAsyncThunk< const { address, metadata } = buildMetadataFromState(state); try { - const resp = await uploadJSONToIpfs(metadata); + // TODO: Move this into the `mintTokens` library action, with a configured + // IPFS endpoint from the toplevel `config/` + const resp = await uploadJSONToIpfs({ + ...metadata, + decimals: 0, + booleanAmount: true + }); const op = await mintToken(system, address, { '': resp.data.ipfsUri }); await op.confirmation(); dispatch(getContractNftsQuery(address)); From f3227b8885a19ee96eafab662847166541ca199f Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 18:35:04 -0500 Subject: [PATCH 25/28] feat: Upload thumbnails to IPFS and store in token metadata --- .../CreateNonFungiblePage/FileUpload.tsx | 6 +- client/src/lib/util/ipfs.ts | 12 +- client/src/reducer/async/actions.ts | 1 + client/src/reducer/slices/createNft.ts | 6 + client/src/reducer/validators/createNft.ts | 3 +- server/package.json | 2 + server/src/handlers.ts | 64 +++ server/src/helpers/ipfs.ts | 24 + server/src/helpers/pinata.ts | 109 +++++ server/src/index.ts | 158 +------ server/yarn.lock | 433 +++++++++++++++++- 11 files changed, 649 insertions(+), 169 deletions(-) create mode 100644 server/src/handlers.ts create mode 100644 server/src/helpers/ipfs.ts create mode 100644 server/src/helpers/pinata.ts diff --git a/client/src/components/CreateNonFungiblePage/FileUpload.tsx b/client/src/components/CreateNonFungiblePage/FileUpload.tsx index d4208d52..913231a3 100644 --- a/client/src/components/CreateNonFungiblePage/FileUpload.tsx +++ b/client/src/components/CreateNonFungiblePage/FileUpload.tsx @@ -2,7 +2,10 @@ import React, { useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; import { Box, Flex, Heading, Text, Image } from '@chakra-ui/react'; import { useSelector, useDispatch } from '../../reducer'; -import { updateArtifactUri } from '../../reducer/slices/createNft'; +import { + updateArtifactUri, + updateThumbnailUri +} from '../../reducer/slices/createNft'; import { ipfsUriToGatewayUrl } from '../../lib/util/ipfs'; import { uploadFiletoIpfs } from '../../lib/util/ipfs'; @@ -14,6 +17,7 @@ export default function FileUpload() { async (acceptedFiles: File[]) => { const response = await uploadFiletoIpfs(acceptedFiles[0]); dispatch(updateArtifactUri(response.data.ipfsUri)); + dispatch(updateThumbnailUri(response.data.thumbnail.ipfsUri)); }, [dispatch] ); diff --git a/client/src/lib/util/ipfs.ts b/client/src/lib/util/ipfs.ts index 1986a6d7..f04cf606 100644 --- a/client/src/lib/util/ipfs.ts +++ b/client/src/lib/util/ipfs.ts @@ -1,21 +1,25 @@ import axios from 'axios'; -export type IpfsContent = { +export interface IpfsContent { cid: string; size: number; ipfsUri: string; url: string; publicGatewayUrl: string; -}; +} + +export interface IpfsResponse extends IpfsContent { + thumbnail: IpfsContent; +} export async function uploadJSONToIpfs(data: any) { - return axios.post('/ipfs-json-upload', data); + return axios.post('/ipfs-json-upload', data); } export async function uploadFiletoIpfs(file: File) { const formData = new FormData(); formData.append('file', file); - return axios.post('/ipfs-file-upload', formData); + return axios.post('/ipfs-file-upload', formData); } // URI Utils diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts index 02641b4a..5dca7e8a 100644 --- a/client/src/reducer/async/actions.ts +++ b/client/src/reducer/async/actions.ts @@ -50,6 +50,7 @@ function buildMetadataFromState(state: State['createNft']) { metadata.artifactUri = state.artifactUri as string; metadata.displayUri = state.artifactUri as string; + metadata.thumbnailUri = state.thumbnailUri as string; metadata.name = state.fields.name as string; if (state.fields.description) { diff --git a/client/src/reducer/slices/createNft.ts b/client/src/reducer/slices/createNft.ts index d68badec..2e03cc18 100644 --- a/client/src/reducer/slices/createNft.ts +++ b/client/src/reducer/slices/createNft.ts @@ -25,6 +25,7 @@ export enum CreateStatus { export interface CreateNftState { step: Step; artifactUri: string | null; + thumbnailUri: string | null; fields: Fields; metadataRows: { name: string | null; value: string | null }[]; collectionAddress: string | null; @@ -34,6 +35,7 @@ export interface CreateNftState { export const initialState: CreateNftState = { step: 'file_upload', artifactUri: null, + thumbnailUri: null, fields: { name: null, description: null @@ -73,6 +75,9 @@ const slice = createSlice({ updateArtifactUri(state, action: PayloadAction) { state.artifactUri = action.payload; }, + updateThumbnailUri(state, action: PayloadAction) { + state.thumbnailUri = action.payload; + }, addMetadataRow(state) { state.metadataRows.push({ name: null, value: null }); }, @@ -109,6 +114,7 @@ export const { decrementStep, updateField, updateArtifactUri, + updateThumbnailUri, addMetadataRow, updateMetadataRowName, updateMetadataRowValue, diff --git a/client/src/reducer/validators/createNft.ts b/client/src/reducer/validators/createNft.ts index 2b28f962..a605c2eb 100644 --- a/client/src/reducer/validators/createNft.ts +++ b/client/src/reducer/validators/createNft.ts @@ -2,7 +2,8 @@ import Joi from 'joi'; import { State } from '..'; export const fileUploadSchema = Joi.object({ - artifactUri: Joi.string().required() + artifactUri: Joi.string().required(), + thumbnailUri: Joi.string().required() }); export const assetDetailsSchema = fileUploadSchema.append({ diff --git a/server/package.json b/server/package.json index a43b1c01..6e646923 100644 --- a/server/package.json +++ b/server/package.json @@ -7,6 +7,7 @@ "@types/express": "4.17.2", "@types/express-fileupload": "1.1.5", "@types/node": "12.12.9", + "@types/sharp": "0.27.1", "axios": "0.21.1", "cids": "1.0.0", "express": "4.17.1", @@ -14,6 +15,7 @@ "form-data": "3.0.0", "ipfs-http-client": "47.0.1", "lru-cache": "5.1.1", + "sharp": "0.27.1", "ts-node": "8.5.0", "typescript": "3.7.2" }, diff --git a/server/src/handlers.ts b/server/src/handlers.ts new file mode 100644 index 00000000..e6f0dd1d --- /dev/null +++ b/server/src/handlers.ts @@ -0,0 +1,64 @@ +import { Request, Response } from 'express'; +import { + PinataConfig, + uploadImageWithThumbnailToPinata, + uploadJSONToPinata +} from './helpers/pinata'; +import { uploadDataToIpfs } from './helpers/ipfs'; +import fs from 'fs'; + +export async function handleIpfsFileUpload( + pinataConfig: PinataConfig | null, + req: Request, + res: Response +) { + const file = req.files?.file; + if (!file?.data) { + return res.status(500).json({ + error: 'No file data found' + }); + } + + try { + if (pinataConfig) { + const content = await uploadImageWithThumbnailToPinata( + pinataConfig, + file.tempFilePath + ); + return res.status(200).json(content); + } + const data = fs.readFileSync(file.tempFilePath); + const content = await uploadDataToIpfs(data); + return res.status(200).json(content); + } catch (e) { + return res.status(500).json({ + error: 'File upload failed' + }); + } +} + +export async function handleIpfsJSONUpload( + pinataConfig: PinataConfig | null, + req: Request, + res: Response +) { + if (req.body === undefined) { + return res.status(500).json({ + error: 'Could not retrieve JSON request body' + }); + } + + try { + if (pinataConfig) { + const content = await uploadJSONToPinata(pinataConfig, req.body); + return res.status(200).json(content); + } + + const content = await uploadDataToIpfs(req.body); + return res.status(200).json(content); + } catch (e) { + return res.status(500).json({ + error: 'JSON upload failed' + }); + } +} diff --git a/server/src/helpers/ipfs.ts b/server/src/helpers/ipfs.ts new file mode 100644 index 00000000..01360a1b --- /dev/null +++ b/server/src/helpers/ipfs.ts @@ -0,0 +1,24 @@ +import url from 'url'; +import IpfsClient from 'ipfs-http-client'; + +// TODO: Move this configuration to a JSON definition +export const ipfsConfig = { + apiUrl: 'http://ipfs:5001', + gatewayUrl: 'http://127.0.0.1:8080/', + pinataGatewayUrl: 'https://gateway.pinata.cloud/', + publicGatewayUrl: 'https://cloudflare-ipfs.com/' +}; + +export async function uploadDataToIpfs(data: any) { + const ipfsClient = IpfsClient(ipfsConfig.apiUrl); + const ipfsFile = await ipfsClient.add(data); + const cid = ipfsFile.cid.toString(); + + return { + cid, + size: ipfsFile.size, + ipfsUri: `ipfs://${cid}`, + url: url.resolve(ipfsConfig.gatewayUrl, `ipfs/${cid}`), + publicGatewayUrl: url.resolve(ipfsConfig.gatewayUrl, `ipfs/${cid}`) + }; +} diff --git a/server/src/helpers/pinata.ts b/server/src/helpers/pinata.ts new file mode 100644 index 00000000..4dd318cf --- /dev/null +++ b/server/src/helpers/pinata.ts @@ -0,0 +1,109 @@ +import axios from 'axios'; +import fs from 'fs'; +import url from 'url'; +import sharp from 'sharp'; +import FormData from 'form-data'; +import { promisify } from 'util'; +import { ipfsConfig } from './ipfs'; + +const readFileAsync = promisify(fs.readFile); + +// Configuration + +export interface PinataConfig { + apiKey: string; + apiSecret: string; +} + +export async function getPinataConfig(): Promise { + try { + const path = url.resolve(__dirname, './config.json'); + const config = JSON.parse(await readFileAsync(path, { encoding: 'utf8' })); + const apiKey = config?.pinata?.apiKey; + const apiSecret = config?.pinata?.apiSecret; + + if (!(typeof apiKey === 'string' && typeof apiSecret === 'string')) { + return null; + } + + return { + apiKey, + apiSecret + }; + } catch (e) { + return null; + } +} + +// Helper Functions + +export async function uploadFileToPinata( + pinataConfig: PinataConfig, + path: string +) { + const pinataUrl = `https://api.pinata.cloud/pinning/pinFileToIPFS`; + const formData = new FormData(); + formData.append('file', fs.createReadStream(path)); + + const pinataRes = await axios.post(pinataUrl, formData, { + maxContentLength: Infinity, + headers: { + 'Content-Type': `multipart/form-data; boundary=${ + (formData as any)._boundary + }`, + pinata_api_key: pinataConfig.apiKey, + pinata_secret_api_key: pinataConfig.apiSecret + } + }); + + const pinataData = pinataRes.data; + const cid = pinataData.IpfsHash; + + return { + cid, + size: pinataData.PinSize, + ipfsUri: `ipfs://${cid}`, + url: url.resolve(ipfsConfig.pinataGatewayUrl, `ipfs/${cid}`), + publicGatewayUrl: url.resolve(ipfsConfig.publicGatewayUrl, `ipfs/${cid}`) + }; +} + +export async function uploadImageWithThumbnailToPinata( + pinataConfig: PinataConfig, + path: string +) { + const thumbnailPath = `${path}-thumbnail`; + await sharp(path).resize(200, 200).toFile(thumbnailPath); + + const origFile = await uploadFileToPinata(pinataConfig, path); + const thumbnailFile = await uploadFileToPinata(pinataConfig, thumbnailPath); + fs.unlink(thumbnailPath, () => null); + return { + ...origFile, + thumbnail: thumbnailFile + }; +} + +export async function uploadJSONToPinata( + pinataConfig: PinataConfig, + json: any +) { + const pinataUrl = `https://api.pinata.cloud/pinning/pinJSONToIPFS`; + const pinataRes = await axios.post(pinataUrl, json, { + headers: { + pinata_api_key: pinataConfig.apiKey, + pinata_secret_api_key: pinataConfig.apiSecret + } + }); + + const pinataData = pinataRes.data; + const cid = pinataData.IpfsHash; + + return { + cid, + size: pinataData.PinSize, + ipfsUri: `ipfs://${cid}`, + url: url.resolve(ipfsConfig.pinataGatewayUrl, `ipfs/${cid}`), + publicGatewayUrl: url.resolve(ipfsConfig.publicGatewayUrl, `ipfs/${cid}`) + }; +} diff --git a/server/src/index.ts b/server/src/index.ts index 86bfd058..1989a21e 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,159 +1,13 @@ -import express, { Express, Request, Response } from 'express'; -import IpfsClient from 'ipfs-http-client'; -import url from 'url'; +import express, { Express } from 'express'; import bodyParser from 'body-parser'; -import fileUpload, { UploadedFile } from 'express-fileupload'; +import fileUpload from 'express-fileupload'; import http from 'http'; -import axios from 'axios'; import fs from 'fs'; -import FormData from 'form-data'; -import { promisify } from 'util'; +import { getPinataConfig } from './helpers/pinata'; +import { handleIpfsFileUpload, handleIpfsJSONUpload } from './handlers'; -const readFileAsync = promisify(fs.readFile); - -// TODO: Move this configuration to a JSON definition -const ipfsConfig = { - apiUrl: 'http://ipfs:5001', - gatewayUrl: 'http://127.0.0.1:8080/', - pinataGatewayUrl: 'https://gateway.pinata.cloud/', - publicGatewayUrl: 'https://cloudflare-ipfs.com/' -}; - -interface PinataConfig { - apiKey: string; - apiSecret: string; -} - -async function uploadFileToPinata( - pinataConfig: PinataConfig, - file: UploadedFile, - res: Response -) { - try { - const pinataUrl = `https://api.pinata.cloud/pinning/pinFileToIPFS`; - const formData = new FormData(); - formData.append('file', fs.createReadStream(file.tempFilePath)); - - const pinataRes = await axios.post(pinataUrl, formData, { - maxContentLength: Infinity, - headers: { - 'Content-Type': `multipart/form-data; boundary=${ - (formData as any)._boundary - }`, - pinata_api_key: pinataConfig.apiKey, - pinata_secret_api_key: pinataConfig.apiSecret - } - }); - - const pinataData = pinataRes.data; - const cid = pinataData.IpfsHash; - - return res.status(200).send({ - cid, - size: pinataData.PinSize, - ipfsUri: `ipfs://${cid}`, - url: url.resolve(ipfsConfig.pinataGatewayUrl, `ipfs/${cid}`), - publicGatewayUrl: url.resolve(ipfsConfig.publicGatewayUrl, `ipfs/${cid}`) - }); - } catch (e) { - console.log(e); - } -} - -async function uploadJSONToPinata( - pinataConfig: PinataConfig, - req: Request, - res: Response -) { - try { - const pinataUrl = `https://api.pinata.cloud/pinning/pinJSONToIPFS`; - const pinataRes = await axios.post(pinataUrl, req.body, { - headers: { - pinata_api_key: pinataConfig.apiKey, - pinata_secret_api_key: pinataConfig.apiSecret - } - }); - - const pinataData = pinataRes.data; - const cid = pinataData.IpfsHash; - - return res.status(200).send({ - cid, - size: pinataData.PinSize, - ipfsUri: `ipfs://${cid}`, - url: url.resolve(ipfsConfig.pinataGatewayUrl, `ipfs/${cid}`), - publicGatewayUrl: url.resolve(ipfsConfig.publicGatewayUrl, `ipfs/${cid}`) - }); - } catch (e) { - console.log(e); - } -} - -async function uploadToIpfs(data: any, res: Response) { - const ipfsClient = IpfsClient(ipfsConfig.apiUrl); - const ipfsFile = await ipfsClient.add(data); - const cid = ipfsFile.cid.toString(); - - return res.status(200).send({ - cid, - size: ipfsFile.size, - ipfsUri: `ipfs://${cid}`, - url: url.resolve(ipfsConfig.gatewayUrl, `ipfs/${cid}`), - publicGatewayUrl: url.resolve(ipfsConfig.gatewayUrl, `ipfs/${cid}`) - }); -} - -async function getPinataConfig(): Promise { - try { - const path = `${__dirname}/config.json`; - const config = JSON.parse(await readFileAsync(path, { encoding: 'utf8' })); - const apiKey = config?.pinata?.apiKey; - const apiSecret = config?.pinata?.apiSecret; - - if (!(typeof apiKey === 'string' && typeof apiSecret === 'string')) { - return null; - } - - return { - apiKey, - apiSecret - }; - } catch (e) { - return null; - } -} - -async function handleIpfsFileUpload( - pinataConfig: PinataConfig | null, - req: Request, - res: Response -) { - const file = req.files?.file; - if (!file?.data) { - throw Error('No file data found'); - } - - if (pinataConfig) { - return await uploadFileToPinata(pinataConfig, file, res); - } - const data = fs.readFileSync(file.tempFilePath); - return await uploadToIpfs(data, res); -} - -async function handleIpfsJSONUpload( - pinataConfig: PinataConfig | null, - req: Request, - res: Response -) { - if (!req.body) { - throw Error('No file data found'); - } - - if (pinataConfig) { - return await uploadJSONToPinata(pinataConfig, req, res); - } - - return await uploadToIpfs(req.body, res); +if (!fs.existsSync('./tmp')) { + fs.mkdirSync('./tmp'); } async function createHttpServer(app: Express) { diff --git a/server/yarn.lock b/server/yarn.lock index 2210920f..6b09702e 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -107,6 +107,13 @@ "@types/mime" "*" "@types/node" "*" +"@types/sharp@0.27.1": + version "0.27.1" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.27.1.tgz#26212ceb191b3de654a898a06577869afc200c57" + integrity sha512-RbYmyPjDUzi3lI9Qm68I+82I+DNOe/jW5w+EC1FvpT/f2TYXDG6mmPZQQohy98ufq+u6OB6pQkqpcNMDxzVclg== + dependencies: + "@types/node" "*" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -134,6 +141,16 @@ ansi-align@^3.0.0: dependencies: string-width "^3.0.0" +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -166,6 +183,19 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -176,6 +206,11 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +array-flatten@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541" + integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -222,6 +257,15 @@ bl@^4.0.0: inherits "^2.0.4" readable-stream "^3.4.0" +bl@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.4.tgz#f4fda39f81a811d0df6368c1ed91dae499d1c900" + integrity sha512-7tdr4EpSd7jJ6tuQ21vu2ke8w7pNEstzj1O8wwq6sNNzO3UDi5MA8Gny/gquCj7r2C6fHudg8tKRGyjRgmvNxQ== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blakejs@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5" @@ -363,6 +407,11 @@ chokidar@^3.2.2: optionalDependencies: fsevents "~2.1.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -406,6 +455,18 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -413,11 +474,32 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" + integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.4" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -447,6 +529,11 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -469,6 +556,11 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -502,6 +594,20 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -524,6 +630,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + delimit-stream@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/delimit-stream/-/delimit-stream-0.1.0.tgz#9b8319477c0e5f8aeb3ce357ae305fc25ea1cd2b" @@ -539,6 +650,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + dicer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" @@ -592,7 +708,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.1.0: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: 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== @@ -624,6 +740,11 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + express-fileupload@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.2.0.tgz#356c4dfd645be71ab9fb2f4e6d84eeb00d247979" @@ -716,6 +837,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -740,6 +866,20 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + get-iterator@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-iterator/-/get-iterator-1.0.2.tgz#cd747c02b4c084461fac14f48f6b45a80ed25c82" @@ -759,6 +899,11 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" @@ -812,6 +957,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + has-yarn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" @@ -876,7 +1026,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: +inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1013,6 +1163,11 @@ ipld-raw@^6.0.0: multicodec "^2.0.0" multihashing-async "^2.0.0" +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -1042,6 +1197,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -1109,6 +1271,11 @@ is-yarn-global@^0.3.0: resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + iso-constants@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/iso-constants/-/iso-constants-0.1.2.tgz#3d2456ed5aeaa55d18564f285ba02a47a0d885b4" @@ -1256,6 +1423,13 @@ lru-cache@5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -1312,6 +1486,16 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimatch@*, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -1319,11 +1503,16 @@ minimatch@*, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.3: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1412,6 +1601,11 @@ nanoid@^3.0.2, nanoid@^3.1.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + native-fetch@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/native-fetch/-/native-fetch-2.0.1.tgz#319d53741a7040def92d5dc8ea5fe9416b1fad89" @@ -1424,6 +1618,18 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +node-abi@^2.7.0: + version "2.19.3" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.3.tgz#252f5dcab12dad1b5503b2d27eddd4733930282d" + integrity sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg== + dependencies: + semver "^5.4.1" + +node-addon-api@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" + integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw== + node-fetch@^2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -1445,6 +1651,11 @@ nodemon@2.0.4: undefsafe "^2.0.2" update-notifier "^4.0.0" +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -1462,6 +1673,26 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +npmlog@^4.0.1, npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + object-keys@^1.0.12: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -1529,11 +1760,37 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +prebuild-install@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.0.0.tgz#669022bcde57c710a869e39c5ca6bf9cd207f316" + integrity sha512-h2ZJ1PXHKWZpp1caLw0oX9sagVpL2YTk+ZwInQbQ3QqNd4J03O6MpFNmMTJlkfgPENWqe5kP0WjQLqz5OjLfsw== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + protocol-buffers-schema@^3.3.1: version "3.4.0" resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.4.0.tgz#2f0ea31ca96627d680bf2fefae7ebfa2b6453eae" @@ -1597,7 +1854,7 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.8: +rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -1607,7 +1864,20 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^2.0.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -1663,12 +1933,12 @@ run@^1.4.0: dependencies: minimatch "*" -safe-buffer@5.1.2: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -1685,7 +1955,7 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver@^5.7.1: +semver@^5.4.1, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -1695,6 +1965,13 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -1724,12 +2001,33 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -signal-exit@^3.0.2: +sharp@0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.27.1.tgz#cd04926406a697b58dfc5fb62e3c7a3a2d68389a" + integrity sha512-IQNXWdspb4nZcJemXa6cfgz+JvKONsuqP8Mwi1Oti23Uo7+J+UF2jihJDf6I1BQbrmhcZ0lagH/1WYG+ReAzyQ== + dependencies: + array-flatten "^3.0.0" + color "^3.1.3" + detect-libc "^1.0.3" + node-addon-api "^3.1.0" + npmlog "^4.1.2" + prebuild-install "^6.0.0" + semver "^7.3.4" + simple-get "^4.0.0" + tar-fs "^2.1.1" + tunnel-agent "^0.6.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -1741,6 +2039,36 @@ signed-varint@^2.0.1: dependencies: varint "~5.0.0" +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + source-map-support@^0.5.6: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -1776,6 +2104,23 @@ streamsearch@0.1.2: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -1801,6 +2146,27 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -1834,6 +2200,27 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +tar-fs@^2.0.0, tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" @@ -1874,6 +2261,13 @@ ts-node@8.5.0: source-map-support "^0.5.6" yn "^3.0.0" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -1967,7 +2361,7 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -1997,6 +2391,18 @@ web-encoding@^1.0.2, web-encoding@^1.0.4: resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.0.6.tgz#ec631356ee523b4474ecbcae680440bd1e79416a" integrity sha512-26wEnRPEFAc5d5lmH1Q/DuvWEYsRF1D2alX2jlKpdmqv7cj+BbANL7Xlcl9r4s72Eg9kItZa9RWVbBMC9dMv4w== +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -2029,6 +2435,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yn@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 88ff48038fc43fca2c68957f67323dcdb6b3b77e Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 21:25:56 -0500 Subject: [PATCH 26/28] feat: Create asset collections with IPFS metadata --- .../Collections/Catalog/Sidebar.tsx | 2 +- .../CreateNonFungiblePage/index.tsx | 1 + client/src/lib/nfts/actions.ts | 27 +++++------ client/src/lib/nfts/queries.ts | 46 +++++++++---------- client/src/lib/service/bcd.ts | 10 ++++ client/src/reducer/async/actions.ts | 12 +---- client/src/reducer/slices/createNft.ts | 4 -- 7 files changed, 49 insertions(+), 53 deletions(-) diff --git a/client/src/components/Collections/Catalog/Sidebar.tsx b/client/src/components/Collections/Catalog/Sidebar.tsx index c1418c6d..b0fe3a9e 100644 --- a/client/src/components/Collections/Catalog/Sidebar.tsx +++ b/client/src/components/Collections/Catalog/Sidebar.tsx @@ -97,7 +97,7 @@ export default function Sidebar() { {...state.collections[address]} /> ))} - + diff --git a/client/src/components/CreateNonFungiblePage/index.tsx b/client/src/components/CreateNonFungiblePage/index.tsx index c9dd9a73..52d0c49b 100644 --- a/client/src/components/CreateNonFungiblePage/index.tsx +++ b/client/src/components/CreateNonFungiblePage/index.tsx @@ -151,6 +151,7 @@ export default function CreateNonFungiblePage() { onClose(); setLocation('/collections'); dispatch(setStatus({ method: 'mintToken', status: 'ready' })); + dispatch(clearForm()); }} status={status.status} /> diff --git a/client/src/lib/nfts/actions.ts b/client/src/lib/nfts/actions.ts index d7d08a83..bc9619db 100644 --- a/client/src/lib/nfts/actions.ts +++ b/client/src/lib/nfts/actions.ts @@ -3,6 +3,7 @@ import { Buffer } from 'buffer'; import { SystemWithWallet } from '../system'; import faucetCode from './code/fa2_tzip16_compat_multi_nft_faucet'; import assetCode from './code/fa2_tzip16_compat_multi_nft_asset'; +import { uploadJSONToIpfs } from '../util/ipfs'; function toHexString(input: string) { return Buffer.from(input).toString('hex'); @@ -39,17 +40,16 @@ export async function createFaucetContract( export async function createAssetContract( system: SystemWithWallet, - name: string + metadata: Record ) { - const metadata = new MichelsonMap(); - metadata.set('', toHexString('tezos-storage:contents')); - const contents = { - name, + const metadataMap = new MichelsonMap(); + const resp = await uploadJSONToIpfs({ + ...metadata, description: 'An OpenMinter assets contract.', interfaces: ['TZIP-012', 'TZIP-016', 'TZIP-020'], tokenCategory: 'collectibles' - }; - metadata.set('contents', toHexString(JSON.stringify(contents))); + }); + metadataMap.set('', toHexString(resp.data.ipfsUri)); return await system.toolkit.wallet .originate({ code: assetCode, @@ -65,7 +65,7 @@ export async function createAssetContract( pending_admin: null, paused: false }, - metadata: metadata + metadata: metadataMap } }) .send(); @@ -81,11 +81,12 @@ export async function mintToken( const token_id = storage.assets.next_token_id; const token_info = new MichelsonMap(); - - for (let key in metadata) { - const value = toHexString(metadata[key]); - token_info.set(key, value); - } + const resp = await uploadJSONToIpfs({ + ...metadata, + decimals: 0, + booleanAmount: true + }); + token_info.set('', toHexString(resp.data.ipfsUri)); return contract.methods .mint([ diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index 65d8e9c9..5f0883ea 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -48,8 +48,18 @@ export async function getContractNfts( if (!tokens) return []; + const ownedTokens = tokens.filter((token: any) => { + if (system.tzPublicKey === null) { + return true; + } + const tokenId = select(token, { name: 'token_id' })?.value; + const ledgerEntry = ledger.filter((v: any) => v.data.key.value === tokenId); + const owner = select(ledgerEntry, { type: 'address' })?.value; + return owner === system.tzPublicKey; + }); + return Promise.all( - tokens.map( + ownedTokens.map( async (token: any): Promise => { const tokenId = select(token, { name: 'token_id' })?.value; const metadataMap = select(token, { name: 'token_info' })?.children; @@ -62,12 +72,15 @@ export async function getContractNfts( metadata = { ...metadata, ...resolvedMetadata.metadata }; } - const owner = select( - ledger.filter((v: any) => v.data.key.value === tokenId), - { - type: 'address' - } - )?.value; + const owner = + system.tzPublicKey === null + ? select( + ledger.filter((v: any) => v.data.key.value === tokenId), + { + type: 'address' + } + )?.value + : system.tzPublicKey; return { id: parseInt(tokenId, 10), @@ -92,24 +105,7 @@ export async function getNftAssetContract( address: string ): Promise { const bcd = system.betterCallDev; - const storage = await bcd.getContractStorage(address); - - const metadataBigMapId = select(storage, { - type: 'big_map', - name: 'metadata' - })?.value; - - const metadataResponse = await bcd.getBigMapKeys(metadataBigMapId); - - // TODO: Resolve and validate metadata to token standard. - const metadataContents = select(metadataResponse, { - key_string: 'contents' - })?.value?.value; - - const metadata: Record = JSON.parse( - fromHexString(metadataContents) - ); - + const metadata = await bcd.getAccountMetadata(address); return { address, metadata }; } diff --git a/client/src/lib/service/bcd.ts b/client/src/lib/service/bcd.ts index 77c15938..ad7325ff 100644 --- a/client/src/lib/service/bcd.ts +++ b/client/src/lib/service/bcd.ts @@ -36,6 +36,12 @@ export async function getWalletContracts(config: Config, address: string) { return response.data; } +export async function getAccountMetadata(config: Config, address: string) { + const uri = `${config.bcd.api}/v1/account/${config.network}/${address}/metadata`; + const response = await axios.get(uri); + return response.data; +} + export class BetterCallDev { config: Config; @@ -62,4 +68,8 @@ export class BetterCallDev { getWalletContracts(address: string) { return getWalletContracts(this.config, address); } + + getAccountMetadata(address: string) { + return getAccountMetadata(this.config, address); + } } diff --git a/client/src/reducer/async/actions.ts b/client/src/reducer/async/actions.ts index 5dca7e8a..686196a0 100644 --- a/client/src/reducer/async/actions.ts +++ b/client/src/reducer/async/actions.ts @@ -8,7 +8,6 @@ import { import { ErrorKind, RejectValue } from './errors'; import { getContractNftsQuery, getWalletAssetContractsQuery } from './queries'; import { validateCreateNftForm } from '../validators/createNft'; -import { uploadJSONToIpfs } from '../../lib/util/ipfs'; type Options = { state: State; @@ -30,7 +29,7 @@ export const createAssetContractAction = createAsyncThunk< }); } try { - const op = await createAssetContract(system, name); + const op = await createAssetContract(system, { name }); await op.confirmation(); const { address } = await op.contract(); dispatch(getWalletAssetContractsQuery()); @@ -87,14 +86,7 @@ export const mintTokenAction = createAsyncThunk< const { address, metadata } = buildMetadataFromState(state); try { - // TODO: Move this into the `mintTokens` library action, with a configured - // IPFS endpoint from the toplevel `config/` - const resp = await uploadJSONToIpfs({ - ...metadata, - decimals: 0, - booleanAmount: true - }); - const op = await mintToken(system, address, { '': resp.data.ipfsUri }); + const op = await mintToken(system, address, metadata); await op.confirmation(); dispatch(getContractNftsQuery(address)); return { contract: address }; diff --git a/client/src/reducer/slices/createNft.ts b/client/src/reducer/slices/createNft.ts index 2e03cc18..ea1f3542 100644 --- a/client/src/reducer/slices/createNft.ts +++ b/client/src/reducer/slices/createNft.ts @@ -1,5 +1,4 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { mintTokenAction } from '../async/actions'; // State @@ -103,9 +102,6 @@ const slice = createSlice({ clearForm() { return initialState; } - }, - extraReducers: builder => { - builder.addCase(mintTokenAction.fulfilled, () => initialState); } }); From 44775e5156a6ffd12441cb7137cb1aefe1ad9ca6 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Thu, 4 Feb 2021 21:36:29 -0500 Subject: [PATCH 27/28] fix: Revert owned token query optimization --- client/src/lib/nfts/queries.ts | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/client/src/lib/nfts/queries.ts b/client/src/lib/nfts/queries.ts index 5f0883ea..fc1bb3a1 100644 --- a/client/src/lib/nfts/queries.ts +++ b/client/src/lib/nfts/queries.ts @@ -48,18 +48,8 @@ export async function getContractNfts( if (!tokens) return []; - const ownedTokens = tokens.filter((token: any) => { - if (system.tzPublicKey === null) { - return true; - } - const tokenId = select(token, { name: 'token_id' })?.value; - const ledgerEntry = ledger.filter((v: any) => v.data.key.value === tokenId); - const owner = select(ledgerEntry, { type: 'address' })?.value; - return owner === system.tzPublicKey; - }); - return Promise.all( - ownedTokens.map( + tokens.map( async (token: any): Promise => { const tokenId = select(token, { name: 'token_id' })?.value; const metadataMap = select(token, { name: 'token_info' })?.children; @@ -72,15 +62,8 @@ export async function getContractNfts( metadata = { ...metadata, ...resolvedMetadata.metadata }; } - const owner = - system.tzPublicKey === null - ? select( - ledger.filter((v: any) => v.data.key.value === tokenId), - { - type: 'address' - } - )?.value - : system.tzPublicKey; + const entry = ledger.filter((v: any) => v.data.key.value === tokenId); + const owner = select(entry, { type: 'address' })?.value; return { id: parseInt(tokenId, 10), From 1a205228929e9f216b4044e5c3f50ed54c868ca3 Mon Sep 17 00:00:00 2001 From: Philip Diaz Date: Fri, 5 Feb 2021 12:47:40 -0500 Subject: [PATCH 28/28] doc: Fix README typos, clarify Pinata usage --- README.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c3acad1f..eaecb777 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,7 @@ $ yarn install ``` The installation process will fetch toplevel NPM dependences and build -the `minter-ui-dev` and `minter-api-dev` Docker images. Subsequent runs of -`yarn install` will rebuild these images without checking for cached versions. +the `minter-ui-dev` and `minter-api-dev` Docker images. ### Configuration @@ -89,16 +88,16 @@ administrator during contract origination: ``` > **Note:** Since sandbox keys don't represent sensitive accounts, the `config/` -> folder includes default configurations with `admin` wallets. To configure Minter -> for the `testnet` or `mainnet` networks, update the definitions in +> folder includes default configurations with `admin` wallets. To configure +> OpenMinter for the `testnet` or `mainnet` networks, update the definitions in > `config/minter..example.json` and copy it to the proper path for the -> application to read it. For: +> application to read it. For example: > > `cp config/minter.mainnet.example.json config/minter.mainnet.json` -If the `contracts` key or its children `nftFaucet` or `nftFactory` keys are not -specified, these contracts will be originated and their addresses saved in the -configuration file when starting the Minter devleopment environment. +If the `contracts` key or its child `nftFaucet` keys is not specified, a new +contract will be originated and its addresses saved in the configuration file +when starting the OpenMinter development environment. #### Pinata @@ -107,21 +106,27 @@ keys in order to direct all file uploads through their service. This allows for ease of use while working with IPFS as running OpenMinter without Pinata will rely on using and maintaining a local IPFS node. -> **Note:** The example `testnet` and `mainnet` configurations in the `config/` -> folder have placeholder Pinata API keys. If you want to use OpenMinter on -> these networks without Pinata, remove the `pinata` key from the configuration. +> ⚠️ **Note:** The example `testnet` and `mainnet` configurations in the +`config/` folder have placeholder Pinata API keys as it's the most robust way +> to easily persist data on IPFS. Using OpenMinter on these networks without +> Pinata may cause data loss as the NFT metadata and artifacts must be resolved +> over IPFS. If you want to use OpenMinter on these networks without Pinata, +> remove the `pinata` key from the configuration, but be aware that this entails +> running and maintaining your own IPFS gateway in order for your NFT data token +> remain accessible. + [pinata]: https://pinata.cloud ### Starting and Stopping -During its start process, Minter will create or update Docker services for its -specified environment and also bootstrap the required contracts if their +During its start process, OpenMinter will create or update Docker services for +its specified environment and also bootstrap the required contracts if their addresses are not defined in the environment's configuration file. #### Sandbox -To start Minter on a `sandbox` network, run: +To start Minter in a `sandbox` network, run: ```sh $ yarn start:sandbox @@ -142,7 +147,7 @@ $ yarn stop:sandbox #### Testnet -To start Minter on a `testnet` network, run: +To start Minter on the `testnet` network, run: ```sh $ yarn start:testnet @@ -161,7 +166,7 @@ $ yarn stop:testnet #### Mainnet -To start Minter on a `mainnet` network, run: +To start Minter on the `mainnet` network, run: ```sh $ yarn start:mainnet @@ -229,7 +234,7 @@ $ yarn log:api --since 5m ### Editor Environments -Docker development images are set up to reload server and web ui on source code +Docker development images are set up to reload server and web UI on source code changes. To setup this project for an IDE, you will want to install NPM dependencies @@ -263,7 +268,7 @@ $ svc-restart(){docker service scale minter-dev-sandbox_$1=0 && docker service s ## Release Builds (WIP) -Development ui and api server builds can be swapped out for release builds: +Development UI and API server builds can be swapped out for release builds: ```sh $ bin/build-release-images