From b1ac01fb3c866ddc7442059d0f566fdfe92482c8 Mon Sep 17 00:00:00 2001 From: zoz Date: Tue, 31 May 2022 14:01:24 -0700 Subject: [PATCH] Add integration --- .DS_Store | Bin 0 -> 8196 bytes package.json | 5 +- src/.DS_Store | Bin 0 -> 6148 bytes src/components/AddressBook.tsx | 43 +- src/components/CeramicConnect.tsx | 58 ++ src/components/SearchDialog.tsx | 11 +- src/components/SfAppBar.tsx | 2 + src/hooks/useAddressBook.ts | 128 ++++ src/hooks/useSearchAddressBook.ts | 2 +- src/pages/_app.tsx | 68 +- src/redux/slices/addressBook.slice.ts | 4 +- src/utils/ceramicModel.json | 11 + yarn.lock | 912 +++++++++++++++++++++++++- 13 files changed, 1181 insertions(+), 63 deletions(-) create mode 100644 .DS_Store create mode 100644 src/.DS_Store create mode 100644 src/components/CeramicConnect.tsx create mode 100644 src/hooks/useAddressBook.ts create mode 100644 src/utils/ceramicModel.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..92a74a540a2c74dc3214c504c5f4c5d50744537c GIT binary patch literal 8196 zcmeHM%Wl&^6ur}y)=}E1JSw_Dvc$H8Ry>NtCQaK#C1BAYSO5xk98y!q6C9_Zs*19P zf8ZC`@+Eu*zX0dXxRJ+61v{uZBh8$Neb3Cj=g!y~hloUd5Nr^w5|M+-xLigvrSN^O z6J;j5vH}#~6E#WFJG3pJEmi@mfK|XMU=^?m{ErIYoz2BL=e@7*+SV#y6}XfN@bke% zWvpd5(x@ICXw(q^IETxsppSEa;sG8}1CRE()l4^p=JlqRpumW8f|M8unJ5nz-RYs>bxFM+c>|+6wm;7QmRsyg1tz#{7Cw+ z=PkB{TK%O8dPEDbJfDK>fHR)JThC$vY~v`f#Zfwl_o zK2lm>eJQY(D27+CqdUNuj=&2Px9BHF^)(k&Wz4K9jbeP=2&tp}HD@J`G0$BDS7IJk zwX1pqidY?!r(!gDJ@PQyA=(b?&(nsZ%=6}<{(yYU5mL*XgR>TF z9&DVhxhI>O=-8mzR)Gttz^wLqwG7-n|NH-ijLkZ26|f5YodRNRr?FFq?b*w^NaM4%i~1Io3)_t} rDhe8P90#i7IB@)jA^I*%nN!Pfq!Cw8{=tU;KEK%azf#{emsNq^G`4=8 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 7ccb0110..0c50939c 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,13 @@ "@emotion/react": "^11.7.1", "@emotion/server": "^11.4.0", "@emotion/styled": "^11.6.0", + "@glazed/types": "^0.2.0", "@mui/icons-material": "^5.3.1", "@mui/lab": "^5.0.0-alpha.67", "@mui/material": "^5.4.0", "@mui/x-data-grid": "^5.4.0", "@reduxjs/toolkit": "^1.7.1", + "@self.id/framework": "^0.3.2", "@sentry/nextjs": "^6.17.4", "@superfluid-finance/sdk-core": "0.4.2", "@superfluid-finance/sdk-redux": "0.3.0", @@ -28,6 +30,7 @@ "graphiql-explorer": "^0.9.0", "graphql": "^16.3.0", "graphql-ws": "^5.8.2", + "key-did-provider-ed25519": "^2.0.0", "lodash": "^4.17.21", "next": "^12.1.7-canary.6", "next-redux-cookie-wrapper": "^2.1.2", @@ -39,7 +42,7 @@ "redux": "^4.1.2", "redux-persist": "^6.0.0" }, - "devDependencies": { + "devDependencies": { "@types/lodash": "^4.14.178", "@types/node": "^17.0.14", "@types/react": "^18.0.0", diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..fbd3319b1cf6ba4c0faa82b58c7d34d6d85e3792 GIT binary patch literal 6148 zcmeHK&u`N(6n@^?E!~9F14z3dMdCU^D}EG-OX$XdD?xAoRFWpzqIq%Eq;ykNDQEZ} z_!qeHm+QVeAOXs8R?Gu%kQI@Au9grm{DDU59d7|b+HOrGi z$A+fE>v)|}_h_*gJnbF({m1>~vA-A$P=ECB$#U88?%aFu?A7Q(UgqjEG=T~1>Dq2t zTmgTFwRis%PIIa9GqkQER!oQ6tbqMm-r+VU>2h-lI0gQR0=z%?aK^x5Wl(P&DD)Ko z*g~^5)cJRTV|??p>%%|7*_c-u m)FCMBajYwR6d%F0q0i?EFtAt|L<`LQ2xu8x;S~6*3j6{n@2?O5 literal 0 HcmV?d00001 diff --git a/src/components/AddressBook.tsx b/src/components/AddressBook.tsx index eb9bc048..bfb68956 100644 --- a/src/components/AddressBook.tsx +++ b/src/components/AddressBook.tsx @@ -1,6 +1,7 @@ import { Box, Button, + Card, Dialog, DialogActions, DialogContent, @@ -11,19 +12,22 @@ import { SvgIconProps, TextField, Tooltip, + Typography } from "@mui/material"; import { FC, useEffect, useState } from "react"; import StarIcon from "@mui/icons-material/Star"; import StarBorderIcon from "@mui/icons-material/StarBorder"; -import { useAppDispatch, useAppSelector } from "../redux/hooks"; +import { useViewerConnection, useViewerRecord } from '@self.id/framework' +import { useAppSelector } from "../redux/hooks"; import { addressBookSelectors, - addressBookSlice, createEntryId, getEntryId, } from "../redux/slices/addressBook.slice"; import { Network } from "../redux/networks"; import { ethers } from "ethers"; +import useAddressBook from "../hooks/useAddressBook"; + export const AddressBookButton: FC<{ network: Network; @@ -64,7 +68,6 @@ export const AddressBookDialog: FC<{ open: boolean; handleClose: () => void; }> = ({ network, address, open, handleClose }) => { - const dispatch = useAppDispatch(); const existingEntry = useAppSelector((state) => addressBookSelectors.selectById(state, createEntryId(network, address)) ); @@ -72,6 +75,10 @@ export const AddressBookDialog: FC<{ const getInitialNameTag = () => existingEntry?.nameTag ?? ""; const [nameTag, setNameTag] = useState(getInitialNameTag()); + const [connection, connect, disconnect] = useViewerConnection() + const { addAddressBookEntry, removeAddressBookEntry } = useAddressBook(); + const record = useViewerRecord('myAddressBook'); + // Fixes: https://github.com/superfluid-finance/superfluid-console/issues/21 useEffect(() => { setNameTag(getInitialNameTag()); @@ -80,24 +87,21 @@ export const AddressBookDialog: FC<{ const handleRemove = () => { if (existingEntry) { - dispatch( - addressBookSlice.actions.entryRemoved(getEntryId(existingEntry)) - ); + removeAddressBookEntry(existingEntry, getEntryId(existingEntry)) } handleClose(); }; - const handleSave = () => { + const handleSave = async () => { const nameTagTrimmed = nameTag.trim(); // Only save non-empty names if (nameTagTrimmed) { - dispatch( - addressBookSlice.actions.entryUpserted({ - chainId: network.chainId, - address: ethers.utils.getAddress(address), - nameTag: nameTagTrimmed, - }) - ); + const entry = { + chainId: network.chainId, + address: ethers.utils.getAddress(address), + nameTag: nameTagTrimmed, + } + addAddressBookEntry( entry ) } handleClose(); }; @@ -105,7 +109,14 @@ export const AddressBookDialog: FC<{ return ( - + {connection.status !== 'connected' ? + ( + + Please Connect(top right) DID to Access Your Ceramic Address Book. + ) + + :(
+ {existingEntry ? "Edit entry" : "Add entry"} @@ -137,6 +148,8 @@ export const AddressBookDialog: FC<{ Save +
) + }
); diff --git a/src/components/CeramicConnect.tsx b/src/components/CeramicConnect.tsx new file mode 100644 index 00000000..e5ef3896 --- /dev/null +++ b/src/components/CeramicConnect.tsx @@ -0,0 +1,58 @@ +import { useAppDispatch } from "../redux/hooks"; +import useAddressBook from "../hooks/useAddressBook"; +import { EthereumAuthProvider, useViewerConnection } from '@self.id/framework' +import { addressBookSlice } from "../redux/slices/addressBook.slice"; +import { Button, styled, Tooltip } from "@mui/material"; + + + +export function CeramicConnectButton() { + + const [connection, connect, disconnect] = useViewerConnection() + const dispatch = useAppDispatch(); + const { populateAddressBook } = useAddressBook(); + + const connectToCeramic = async () => { + const accounts = await window.ethereum.request({ + method: 'eth_requestAccounts', + }) + await connect(new EthereumAuthProvider(window.ethereum, accounts[0])) + populateAddressBook() + } + + const disconnectFromCeramic = async () => { + console.log('disconnect') + disconnect(); + dispatch(addressBookSlice.actions.entryRemoveAll()); + + } + + return connection.status === 'connected' ? ( + + ) : typeof window !== undefined ? ( + + ) : ( +

+ An injected Ethereum provider such as{' '} + MetaMask is needed to authenticate. +

+ ) +} + + + diff --git a/src/components/SearchDialog.tsx b/src/components/SearchDialog.tsx index af02c565..2bbd813d 100644 --- a/src/components/SearchDialog.tsx +++ b/src/components/SearchDialog.tsx @@ -26,7 +26,9 @@ import { useAppSelector } from "../redux/hooks"; import { addressBookSelectors } from "../redux/slices/addressBook.slice"; import { searchBarPlaceholderText } from "./SearchBar"; import { useSearch } from "../hooks/useSearch"; +import { useViewerConnection } from "@self.id/framework"; +//Change search dialog const SearchDialog: FC<{ open: boolean; close: () => void }> = ({ open, close, @@ -62,7 +64,8 @@ const SearchDialog: FC<{ open: boolean; close: () => void }> = ({ }; const networkSearchResults = useSearch(searchTermDebounced); - + console.log('network: ' + JSON.stringify(networkSearchResults)) + const [ connection ] = useViewerConnection(); useEffect(() => { const handleRouteChange = () => { handleClose(); @@ -115,7 +118,11 @@ const SearchDialog: FC<{ open: boolean; close: () => void }> = ({ }} onChange={(e) => setSearchTerm(e.currentTarget.value)} /> - + { connection.status !== 'connected' && + ( + Please Connect(top right) DID to Access Your Ceramic Address Book. + ) + } {networkSearchResults .filter((x) => x.tokens.length || x.accounts.length) .map((x) => ( diff --git a/src/components/SfAppBar.tsx b/src/components/SfAppBar.tsx index 0d2e0af9..b9b1716b 100644 --- a/src/components/SfAppBar.tsx +++ b/src/components/SfAppBar.tsx @@ -17,6 +17,7 @@ import { FC, useState } from "react"; import SubgraphIcon from "./SubgraphIcon"; import SettingsIcon from "@mui/icons-material/SettingsOutlined"; import SettingsDrawer from "./SettingsDrawer"; +import { CeramicConnectButton } from "./CeramicConnect"; export const SfAppBar = () => { const [searchOpen, setSearchOpen] = useState(false); @@ -112,6 +113,7 @@ export const SfAppBar = () => { Subgraph + { + + const emptyAddressBook: AddressBook = {"total_cnt": 0,"contacts": []} + const dispatch = useAppDispatch(); + const [connection] = useViewerConnection(); + const addressBook = useViewerRecord('myAddressBook'); + let localAddressBook = useAppSelector((state) => + addressBookSelectors.selectAll(state) + ); + + + const addAddressBookEntry = (entry: Contact) => { + let contacts: Contact[] = transformToCeramicData(localAddressBook.concat(entry)); + dispatch(addressBookSlice.actions.entryUpserted(entry)) + updateAddressBook(addressBook, contacts); + } + + + const removeAddressBookEntry = async ( entry: Contact,id) => { + let contacts = transformToCeramicData(localAddressBook.filter( + (c) => c.chainId !== entry.chainId && c.address !== entry.address + )) + await updateAddressBook(addressBook, contacts); + dispatch(addressBookSlice.actions.entryRemoved(id)); + } + + + const populateAddressBook = useCallback(async () => { + if('content' in addressBook){ + await initializeAddressBook(addressBook); + const contacts = transformFromCeramicData(addressBook.content.contacts) + dispatch(addressBookSlice.actions.entryUpsertMany(contacts)); + } + }, [addressBook]) + + + async function initializeAddressBook(addressBook: AddressBook){ + if(addressBook.content == null){ + await addressBook.set(emptyAddressBook) + } + } + + + async function updateAddressBook(addressBook: AddressBook, contacts: Contact[]){ + await addressBook + .set({ total_cnt: contacts.length, contacts }) + .then(() => console.log("Update Address Book")); + + populateAddressBook(); + + } + + return { + addAddressBookEntry, + removeAddressBookEntry, + populateAddressBook + } +} + + + function transformToCeramicData(entries: AddressBookEntry[]) { + return Object.values( + entries.reduce((contacts: any, c) => { + const name = c.nameTag; + const network = c.chainId.toString(); + const walletAddress = c.address; + + // Each stored name can have multiple wallets - one for each network + address + const id = `${network}_${walletAddress}`; + + contacts[name] = contacts[name] || {}; + contacts[name].name = name; + contacts[name].wallets = contacts[name].wallets || {}; + contacts[name].wallets[id] = { network, walletAddress }; + + return contacts; + }, {}) as { name: string; wallets: [] }[] + ).map(({ name, wallets }) => ({ name, wallets: Object.values(wallets) })); + } + + function transformFromCeramicData(contacts: Contact[]) { + const contactsMap: { [key: string]: any } = {}; + contacts.forEach((contact: Contact) => { + contact.wallets.forEach((wallet) => { + const network = wallet.network; + const address = wallet.walletAddress; + const id = `${network}_${address}`; + + contactsMap[id] = { + chainId: Number(network), + address, + nameTag: contact.name, + }; + }); + }); + + return Object.values(contactsMap); + } + + export default useAddressBook; \ No newline at end of file diff --git a/src/hooks/useSearchAddressBook.ts b/src/hooks/useSearchAddressBook.ts index 848881ca..899bdb29 100644 --- a/src/hooks/useSearchAddressBook.ts +++ b/src/hooks/useSearchAddressBook.ts @@ -9,7 +9,7 @@ export const useSearchAddressBook = (searchTerm: string) => { () => ethers.utils.isAddress(searchTerm), [searchTerm] ); - + //add ceramic Address search return networks.map((network) => { const addressBookEntries = useAppSelector((state) => searchTerm !== "" && !isSearchTermAddress diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index fcf37c84..9c992200 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,6 +2,7 @@ import Head from "next/head"; import { AppProps } from "next/app"; import CssBaseline from "@mui/material/CssBaseline"; import { CacheProvider, EmotionCache } from "@emotion/react"; +import { Provider as SelfIDProvider} from '@self.id/framework'; import createEmotionCache from "../utils/createEmotionCache"; import { wrapper } from "../redux/store"; import SfAppBar from "../components/SfAppBar"; @@ -19,6 +20,13 @@ import { isDynamicRoute } from "../utils/isDynamicRoute"; import Error from "next/error"; import NetworkContext from "../contexts/NetworkContext"; import IdContext from "../contexts/IdContext"; +import type { ModelTypesToAliases, ModelTypeAliases } from '@glazed/types' +import addressBookAliases from "../utils/ceramicModel.json" + +type AddressBook = any; +type DIDToAddressBook = any; +const aliases: ModelTypeAliases<{AddressBook: AddressBook, DIDToAddressBook: DIDToAddressBook }, + {myAddressBook: AddressBook, DIDToAddressBook: DIDToAddressBook }> = addressBookAliases; // Client-side cache, shared for the whole session of the user in the browser. const clientSideEmotionCache = createEmotionCache(); @@ -55,38 +63,40 @@ function MyApp(props: MyAppProps) { }, []); return ( - - - Superfluid Console - {/* "theme-mode" is required to be in the head element by `useSfTheme`. */} - - - - - - {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} - - - + + + + Superfluid Console + {/* "theme-mode" is required to be in the head element by `useSfTheme`. */} + + + + + + {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} + - - - -