├── .npmrc ├── .prettierignore ├── mocks └── file.ts ├── .eslintignore ├── src ├── vite-env.d.ts ├── favicon.png ├── utils │ ├── isBlank.ts │ ├── getCommitId.ts │ ├── getExplorerURL.ts │ ├── trunc.ts │ ├── fallbackIdenticon.ts │ ├── getSigner.ts │ ├── focus.ts │ ├── isSameAddress.ts │ ├── getBeginningOfDay.ts │ ├── addNetwork.ts │ ├── getJoinPolicyRegistry.ts │ ├── getNewStreamrClient.ts │ ├── switchNetwork.ts │ ├── getDelegatedAccessRegistry.ts │ ├── chains.ts │ ├── isCorrectNetwork.ts │ ├── handleError.ts │ ├── getTransactionalClient.ts │ ├── pathnameToRoomIdPartials.ts │ ├── getDefaultWeb3Account.ts │ ├── isAuthorizedDelegatedAccount.ts │ ├── fetchPrivacy.ts │ ├── defer.ts │ ├── waitForPermissions.ts │ ├── tokenIdPreflight.ts │ ├── getDisplayUsername.ts │ ├── getWalletProvider.ts │ ├── pathnameToRoomIdPartials.test.ts │ ├── index.tsx │ ├── toLocalMessage.ts │ ├── getUserPermissions.ts │ ├── networkPreflight.ts │ ├── preflight.ts │ ├── takeEveryUnique.ts │ ├── authorizeDelegatedAccount.ts │ ├── getAccountType.ts │ ├── getConnector.ts │ ├── integrations │ │ └── index.ts │ ├── recover.ts │ └── delegationPreflight.ts ├── components │ ├── Page │ │ ├── background.png │ │ └── index.tsx │ ├── Conversation │ │ ├── DateTooltip.tsx │ │ ├── ActionTextButton.tsx │ │ └── MessageInputPlaceholder.tsx │ ├── Hint.tsx │ ├── Label.tsx │ ├── PrimaryButton.tsx │ ├── UtilityButton.tsx │ ├── Text.tsx │ ├── modals │ │ └── HowItWorksModal.tsx │ ├── Hodl.tsx │ ├── Dot.tsx │ ├── SecondaryButton.tsx │ ├── TokenLogo.tsx │ ├── Form.tsx │ ├── TokenLabel.tsx │ ├── Tag.tsx │ ├── TextField.tsx │ ├── Credits.tsx │ ├── Submit.tsx │ ├── IndexPage.tsx │ ├── Button.tsx │ ├── LoadingIndicator.tsx │ ├── Nav.tsx │ ├── MoreActionButton.tsx │ ├── Id.tsx │ ├── ActionButton.tsx │ ├── TryMetaMask.tsx │ └── Spinner.tsx ├── fonts │ ├── Karelia │ │ ├── KareliaWeb-Medium.woff │ │ ├── KareliaWeb-Medium.woff2 │ │ ├── KareliaWeb-Regular.woff │ │ └── KareliaWeb-Regular.woff2 │ └── Karelia.css ├── store.ts ├── features │ ├── ens │ │ ├── types.ts │ │ ├── index.ts │ │ └── helpers │ │ │ └── storeEnsDomains.ts │ ├── misc │ │ ├── selectors.ts │ │ ├── helpers │ │ │ ├── toast.ts │ │ │ ├── showEditMembersModal.ts │ │ │ ├── showRoomPropertiesModal.ts │ │ │ ├── goto.ts │ │ │ ├── showHowItWorksModal.ts │ │ │ ├── showAnonExplainerModal.ts │ │ │ ├── fetchKnownTokens.ts │ │ │ ├── showWalletModal.ts │ │ │ ├── retoast.ts │ │ │ └── showAccountModal.ts │ │ └── types.ts │ ├── drafts │ │ ├── types.ts │ │ ├── index.ts │ │ └── helpers │ │ │ └── storeDraft.ts │ ├── rooms │ │ ├── index.ts │ │ └── helpers │ │ │ └── fetchRooms.ts │ ├── message │ │ ├── parser.ts │ │ ├── helpers │ │ │ ├── publishMessage.ts │ │ │ └── updateMessageSeenAt.saga.ts │ │ ├── selectors.ts │ │ └── types.ts │ ├── preferences │ │ ├── index.ts │ │ ├── types.ts │ │ ├── hooks.ts │ │ └── helpers │ │ │ └── setPreferences.ts │ ├── identicons │ │ ├── types.ts │ │ ├── selectors.ts │ │ ├── index.ts │ │ ├── hooks.ts │ │ └── helpers │ │ │ └── retrieveIdenticon.ts │ ├── alias │ │ └── index.ts │ ├── delegation │ │ ├── types.ts │ │ ├── selectors.ts │ │ ├── hooks.ts │ │ └── index.ts │ ├── flag │ │ ├── selectors.ts │ │ ├── flag.test.ts │ │ └── index.ts │ ├── wallet │ │ ├── hooks.ts │ │ ├── helpers │ │ │ ├── storeIntegrationId.ts │ │ │ ├── connect.ts │ │ │ ├── eagerConnect.ts │ │ │ └── setAccount.ts │ │ ├── selectors.ts │ │ └── types.ts │ ├── room │ │ ├── helpers │ │ │ ├── renameLocalRoom.ts │ │ │ ├── deleteLocalRoom.ts │ │ │ ├── setRoomVisibility.ts │ │ │ ├── getStorageNodes.ts │ │ │ ├── unpinRoom.ts │ │ │ ├── searchRoom.ts │ │ │ ├── getRoomPrivacy.ts │ │ │ ├── deleteRoom.ts │ │ │ └── fetchRoom.ts │ │ ├── types.ts │ │ └── hooks.ts │ ├── avatar │ │ └── index.ts │ ├── permissions │ │ ├── types.ts │ │ └── helpers │ │ │ ├── fetchPermission.ts │ │ │ └── fetchPermissions.ts │ ├── anon │ │ └── index.ts │ └── tokenGatedRooms │ │ └── types.ts ├── contracts │ ├── tokens │ │ ├── ERC20Token.sol │ │ │ └── ERC20.dbg.json │ │ ├── ERC777Token.sol │ │ │ └── ERC777.dbg.json │ │ ├── ERC1155Token.sol │ │ │ └── ERC1155.dbg.json │ │ └── ERC721Token.sol │ │ │ └── ERC721.dbg.json │ ├── JoinPolicies │ │ ├── JoinPolicy.sol │ │ │ └── JoinPolicy.dbg.json │ │ ├── BaseJoinPolicy.sol │ │ │ └── BaseJoinPolicy.dbg.json │ │ ├── CoinJoinPolicy.sol │ │ │ └── CoinJoinPolicy.dbg.json │ │ ├── NFTJoinPolicy.sol │ │ │ └── NFTJoinPolicy.dbg.json │ │ ├── ERC20JoinPolicy.sol │ │ │ └── ERC20JoinPolicy.dbg.json │ │ ├── ERC721JoinPolicy.sol │ │ │ └── ERC721JoinPolicy.dbg.json │ │ ├── ERC777JoinPolicy.sol │ │ │ └── ERC777JoinPolicy.dbg.json │ │ └── ERC1155JoinPolicy.sol │ │ │ └── ERC1155JoinPolicy.dbg.json │ ├── JoinPolicyRegistry.sol │ │ └── JoinPolicyRegistry.dbg.json │ ├── DelegatedAccessRegistry.sol │ │ └── DelegatedAccessRegistry.dbg.json │ └── Factories │ │ ├── ERC20PolicyFactory.sol │ │ └── ERC20PolicyFactory.dbg.json │ │ ├── TokenGateFactory.sol │ │ └── TokenGateFactory.dbg.json │ │ ├── ERC1155PolicyFactory.sol │ │ └── ERC1155PolicyFactory.dbg.json │ │ ├── ERC721PolicyFactory.sol │ │ └── ERC721PolicyFactory.dbg.json │ │ └── ERC777PolicyFactory.sol │ │ └── ERC777PolicyFactory.dbg.json ├── hooks │ ├── useFlag.ts │ ├── useKnownTokens.ts │ ├── useIsDetectingRoomMembers.ts │ ├── useFilteredKnownTokens.ts │ ├── useFromTimestamp.ts │ ├── useSearchResult.ts │ ├── useAvatar.ts │ ├── useIsMemberBeingRemoved.ts │ ├── useRoomMembers.ts │ ├── useIsInviteBeingAccepted.ts │ ├── useTokenInfo.ts │ ├── useMainAccount.ts │ ├── useCachedTokenGate.ts │ ├── useAnonClient.ts │ ├── useTokenStandard.ts │ ├── useAnonAccount.ts │ ├── useAnonPrivateKey.ts │ ├── useDisplayUsername.ts │ ├── usePermissions.ts │ ├── useIsDelegatedAccountBeingPromoted.ts │ ├── useCanGrant.ts │ ├── usePrivacy.ts │ ├── useENSName.ts │ ├── useFetchingTokenMetadataForAnyTokenId.ts │ ├── useTokenMetadata.ts │ ├── useIsRoomPinned.ts │ ├── useIsRoomVisible.ts │ ├── useRecentMessage.ts │ ├── useDetectMembersEffect.ts │ ├── useAlias.ts │ ├── useJustInvited.ts │ ├── useRooms.ts │ ├── useAcceptInvite.ts │ ├── useSelectedRoom.ts │ ├── useMessages.ts │ ├── useResends.ts │ ├── useIsResending.ts │ ├── useSubscriber.ts │ ├── useResendEffect.ts │ ├── useJoin.ts │ ├── usePreselectRoomEffect.ts │ ├── usePromoteDelegatedAccount.ts │ ├── useIntercept.ts │ ├── useAgo.ts │ └── useCopy.ts ├── errors │ ├── TimeoutError.ts │ ├── MemberExistsError.ts │ ├── WalletConnectionError.ts │ ├── MissingWalletClientError.ts │ ├── MissingWalletAccountError.ts │ ├── RedundantRenameError.ts │ ├── MissingWalletProviderError.ts │ ├── MissingDelegatedClientError.ts │ ├── InsufficientFundsError.ts │ └── RoomNotFoundError.ts ├── config.json ├── main.tsx ├── icons │ ├── CopyIcon.tsx │ ├── GatedIcon.tsx │ ├── RemoveUserIcon.tsx │ ├── CheckIcon.tsx │ ├── EditIcon.tsx │ ├── MoreIcon.tsx │ ├── DeleteIcon.tsx │ ├── WalletConnectIcon.tsx │ ├── PinIcon.tsx │ ├── AddMemberIcon.tsx │ ├── ExternalLinkIcon.tsx │ ├── UserIcon.tsx │ ├── PublicIcon.tsx │ ├── CoinbaseWalletIcon.tsx │ ├── ArrowIcon.tsx │ ├── SpyIcon.tsx │ ├── EditMembersIcon.tsx │ └── GearIcon.tsx ├── consts.ts ├── App.test.tsx └── types.ts ├── public └── megaphone.png ├── tsconfig.node.json ├── .prettierrc ├── .gitignore ├── jest.config.mjs ├── .babelrc ├── .eslintrc ├── tailwind.config.js ├── index.html ├── types └── twin.d.ts ├── .github ├── workflows │ └── deployment.yml └── dependabot.yml ├── tsconfig.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/contracts 2 | -------------------------------------------------------------------------------- /mocks/file.ts: -------------------------------------------------------------------------------- 1 | export default '' 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamr-dev/chat/HEAD/src/favicon.png -------------------------------------------------------------------------------- /public/megaphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamr-dev/chat/HEAD/public/megaphone.png -------------------------------------------------------------------------------- /src/utils/isBlank.ts: -------------------------------------------------------------------------------- 1 | export default function isBlank(value: string) { 2 | return /^\s*$/.test(value) 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Page/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamr-dev/chat/HEAD/src/components/Page/background.png -------------------------------------------------------------------------------- /src/fonts/Karelia/KareliaWeb-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamr-dev/chat/HEAD/src/fonts/Karelia/KareliaWeb-Medium.woff -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import createStore from '$/utils/createStore' 2 | 3 | const store = createStore() 4 | 5 | export default store 6 | -------------------------------------------------------------------------------- /src/fonts/Karelia/KareliaWeb-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamr-dev/chat/HEAD/src/fonts/Karelia/KareliaWeb-Medium.woff2 -------------------------------------------------------------------------------- /src/fonts/Karelia/KareliaWeb-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamr-dev/chat/HEAD/src/fonts/Karelia/KareliaWeb-Regular.woff -------------------------------------------------------------------------------- /src/fonts/Karelia/KareliaWeb-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamr-dev/chat/HEAD/src/fonts/Karelia/KareliaWeb-Regular.woff2 -------------------------------------------------------------------------------- /src/utils/getCommitId.ts: -------------------------------------------------------------------------------- 1 | export default function getCommitId(): string | undefined { 2 | return import.meta.env.VITE_COMMIT_ID || undefined 3 | } 4 | -------------------------------------------------------------------------------- /src/features/ens/types.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '$/types' 2 | 3 | export interface IENSName { 4 | content: string 5 | address: Address 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/getExplorerURL.ts: -------------------------------------------------------------------------------- 1 | export default function getExplorerURL(address: string) { 2 | return `https://polygonscan.com/address/${address}` 3 | } 4 | -------------------------------------------------------------------------------- /src/features/misc/selectors.ts: -------------------------------------------------------------------------------- 1 | import { State } from '$/types' 2 | 3 | export function selectNavigate(state: State) { 4 | return state.misc.navigate 5 | } 6 | -------------------------------------------------------------------------------- /src/contracts/tokens/ERC20Token.sol/ERC20.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../../build-info/37b1cc5fa10db56da8819df8fa2a31a6.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/tokens/ERC777Token.sol/ERC777.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/67743dd5c6b12368f235fa3c6801cb82.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/tokens/ERC1155Token.sol/ERC1155.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../../build-info/37b1cc5fa10db56da8819df8fa2a31a6.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/tokens/ERC721Token.sol/ERC721.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../../build-info/37b1cc5fa10db56da8819df8fa2a31a6.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/JoinPolicy.sol/JoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/67743dd5c6b12368f235fa3c6801cb82.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicyRegistry.sol/JoinPolicyRegistry.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/BaseJoinPolicy.sol/BaseJoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4e6ae322e4f5a188f5f74e4761591512.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/CoinJoinPolicy.sol/CoinJoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/NFTJoinPolicy.sol/NFTJoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /src/contracts/DelegatedAccessRegistry.sol/DelegatedAccessRegistry.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/0442b324a66ac9001b29522814909e51.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/Factories/ERC20PolicyFactory.sol/ERC20PolicyFactory.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/Factories/TokenGateFactory.sol/TokenGateFactory.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/ERC20JoinPolicy.sol/ERC20JoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/ERC721JoinPolicy.sol/ERC721JoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/ERC777JoinPolicy.sol/ERC777JoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/0079f169dec74662bb04b2b38d6e343d.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/Factories/ERC1155PolicyFactory.sol/ERC1155PolicyFactory.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/Factories/ERC721PolicyFactory.sol/ERC721PolicyFactory.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/Factories/ERC777PolicyFactory.sol/ERC777PolicyFactory.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/0079f169dec74662bb04b2b38d6e343d.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/contracts/JoinPolicies/ERC1155JoinPolicy.sol/ERC1155JoinPolicy.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../../build-info/4aefe78e3118f4c61dfc51fa0f8c58e9.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/features/drafts/types.ts: -------------------------------------------------------------------------------- 1 | import { IRecord } from '$/types' 2 | import { RoomId } from '../room/types' 3 | 4 | export interface IDraft extends IRecord { 5 | roomId: RoomId 6 | content: string 7 | } 8 | -------------------------------------------------------------------------------- /src/features/drafts/index.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | import { IDraft } from './types' 3 | 4 | export const DraftAction = { 5 | store: createAction('drafts: store'), 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/trunc.ts: -------------------------------------------------------------------------------- 1 | export default function trunc(addr: string) { 2 | if (/^0x[a-f\d]{40}$/i.test(addr)) { 3 | return `${addr.slice(0, 6)}...${addr.slice(-4)}` 4 | } 5 | 6 | return addr 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true, 6 | "printWidth": 100, 7 | "overrides": [{ "files": "*.yml", "options": { "tabWidth": 2 } }] 8 | } 9 | -------------------------------------------------------------------------------- /src/features/rooms/index.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | import { Address } from '$/types' 3 | 4 | export const RoomsAction = { 5 | fetch: createAction<{ requester: Address }>('rooms: fetch'), 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/fallbackIdenticon.ts: -------------------------------------------------------------------------------- 1 | // Transparent pixel (1x1) 2 | const fallbackIdenticon = 3 | 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' 4 | 5 | export default fallbackIdenticon 6 | -------------------------------------------------------------------------------- /src/hooks/useFlag.ts: -------------------------------------------------------------------------------- 1 | import { selectFlag } from '$/features/flag/selectors' 2 | import { useSelector } from 'react-redux' 3 | 4 | export default function useFlag(flag: undefined | string) { 5 | return useSelector(selectFlag(flag)) 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/getSigner.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@web3-react/types' 2 | import { providers } from 'ethers' 3 | 4 | export default function getSigner(provider: Provider) { 5 | return new providers.Web3Provider(provider).getSigner() 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/focus.ts: -------------------------------------------------------------------------------- 1 | export default function focus(input: HTMLInputElement | HTMLTextAreaElement | null): void { 2 | if (input) { 3 | input.focus() 4 | input.setSelectionRange(input.value.length, input.value.length) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/features/message/parser.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | 3 | export function parseChatMessage(content: unknown) { 4 | return z 5 | .object({ 6 | content: z.string(), 7 | }) 8 | .parse(content) 9 | } 10 | -------------------------------------------------------------------------------- /src/features/preferences/index.ts: -------------------------------------------------------------------------------- 1 | import { IPreference } from '$/features/preferences/types' 2 | import { createAction } from '@reduxjs/toolkit' 3 | 4 | export const PreferencesAction = { 5 | set: createAction('preferences: set'), 6 | } 7 | -------------------------------------------------------------------------------- /src/features/identicons/types.ts: -------------------------------------------------------------------------------- 1 | export type IdenticonSeed = string 2 | 3 | export interface IIdenticon { 4 | seed: IdenticonSeed 5 | content: string 6 | } 7 | 8 | export type IdenticonsState = Partial> 9 | -------------------------------------------------------------------------------- /src/utils/isSameAddress.ts: -------------------------------------------------------------------------------- 1 | import { OptionalAddress } from '$/types' 2 | 3 | export default function isSameAddress(addrA: OptionalAddress, addrB: OptionalAddress): boolean { 4 | return (addrA || '').toLowerCase() === (addrB || '').toLowerCase() 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/getBeginningOfDay.ts: -------------------------------------------------------------------------------- 1 | export const DayInMillis = 86400000 2 | 3 | export const TimezoneOffset = new Date().getTimezoneOffset() * 60000 4 | 5 | export default function getBeginningOfDay(timestamp: number): number { 6 | return timestamp - ((timestamp - TimezoneOffset) % DayInMillis) 7 | } 8 | -------------------------------------------------------------------------------- /src/features/alias/index.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '$/types' 2 | import { createAction } from '@reduxjs/toolkit' 3 | 4 | export const AliasAction = { 5 | set: createAction<{ 6 | address: Address 7 | owner: Address 8 | value: string 9 | }>('alias: create'), 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/useKnownTokens.ts: -------------------------------------------------------------------------------- 1 | import { State } from '$/types' 2 | import { useSelector } from 'react-redux' 3 | 4 | function selectKnownTokens(state: State) { 5 | return state.misc.knownTokens 6 | } 7 | 8 | export default function useKnownTokens() { 9 | return useSelector(selectKnownTokens) 10 | } 11 | -------------------------------------------------------------------------------- /src/features/preferences/types.ts: -------------------------------------------------------------------------------- 1 | import { RoomId } from '$/features/room/types' 2 | import { IRecord } from '$/types' 3 | 4 | export interface IPreference extends IRecord { 5 | showHiddenRooms?: boolean 6 | selectedRoomId?: RoomId 7 | retrieveHotWalletImmediately?: boolean 8 | stickyRoomIds?: RoomId[] 9 | } 10 | -------------------------------------------------------------------------------- /src/hooks/useIsDetectingRoomMembers.ts: -------------------------------------------------------------------------------- 1 | import { Flag } from '$/features/flag/types' 2 | import { RoomId } from '$/features/room/types' 3 | import useFlag from '$/hooks/useFlag' 4 | 5 | export default function useIsDetectingRoomMembers(roomId: undefined | RoomId) { 6 | return useFlag(roomId ? Flag.isDetectingMembers(roomId) : undefined) 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/useFilteredKnownTokens.ts: -------------------------------------------------------------------------------- 1 | import { State } from '$/types' 2 | import { useSelector } from 'react-redux' 3 | 4 | function selectFilteredKnownTokens(state: State) { 5 | return state.misc.filteredKnownTokens 6 | } 7 | 8 | export default function useFilteredKnownTokens() { 9 | return useSelector(selectFilteredKnownTokens) 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/addNetwork.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@web3-react/types' 2 | import { Matic } from '$/utils/chains' 3 | 4 | export default async function addNetwork(provider: Provider) { 5 | const [, params] = Matic 6 | 7 | await provider.request({ 8 | method: 'wallet_addEthereumChain', 9 | params: [params], 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/features/ens/index.ts: -------------------------------------------------------------------------------- 1 | import { IENSName } from '$/features/ens/types' 2 | import { Address, IFingerprinted } from '$/types' 3 | import { createAction } from '@reduxjs/toolkit' 4 | 5 | export const EnsAction = { 6 | fetchNames: createAction('ens: fetch names'), 7 | 8 | store: createAction('ens: store'), 9 | } 10 | -------------------------------------------------------------------------------- /src/errors/TimeoutError.ts: -------------------------------------------------------------------------------- 1 | export default class TimeoutError extends Error { 2 | name = 'TimeoutError' 3 | 4 | constructor() { 5 | super('Timeout.') 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, TimeoutError) 9 | } 10 | 11 | Object.setPrototypeOf(this, TimeoutError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Envionment 16 | .env 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "stickyRoomIds": [ 3 | { 4 | "id": "0x9ce0e18d6e7497a3c77e2d23ffa18241540a6667/streamr-chat/room/lobby" 5 | }, 6 | { 7 | "id": "0x9ce0e18d6e7497a3c77e2d23ffa18241540a6667/streamr-chat/room/announcements", 8 | "subtitle": "Public announcements", 9 | "iconSrc": "megaphone.png" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/features/delegation/types.ts: -------------------------------------------------------------------------------- 1 | import StreamrClient from '@streamr/sdk' 2 | import { Address, IRecord } from '$/types' 3 | 4 | export interface DelegationState { 5 | privateKey: undefined | string 6 | client: undefined | StreamrClient 7 | delegations: Record 8 | } 9 | 10 | export interface IDelegation extends IRecord { 11 | encryptedPrivateKey: string 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/getJoinPolicyRegistry.ts: -------------------------------------------------------------------------------- 1 | import { Contract, providers, Signer } from 'ethers' 2 | import { JoinPolicyRegistryAddress } from '$/consts' 3 | import { abi } from '$/contracts/JoinPolicyRegistry.sol/JoinPolicyRegistry.json' 4 | 5 | export default function getJoinPolicyRegistry(signerOrProvider: Signer | providers.Provider) { 6 | return new Contract(JoinPolicyRegistryAddress, abi, signerOrProvider) 7 | } 8 | -------------------------------------------------------------------------------- /src/errors/MemberExistsError.ts: -------------------------------------------------------------------------------- 1 | export default class MemberExistsError extends Error { 2 | name = 'MemberExistsError' 3 | 4 | constructor(readonly member: string) { 5 | super() 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, MemberExistsError) 9 | } 10 | 11 | Object.setPrototypeOf(this, MemberExistsError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/errors/WalletConnectionError.ts: -------------------------------------------------------------------------------- 1 | export default class WalletConnectionError extends Error { 2 | name = 'WalletConnectionError' 3 | 4 | constructor() { 5 | super() 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, WalletConnectionError) 9 | } 10 | 11 | Object.setPrototypeOf(this, WalletConnectionError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/useFromTimestamp.ts: -------------------------------------------------------------------------------- 1 | import { selectFromTimestamp } from '$/features/message/selectors' 2 | import { RoomId } from '$/features/room/types' 3 | import { OptionalAddress } from '$/types' 4 | import { useSelector } from 'react-redux' 5 | 6 | export default function useFromTimestamp(roomId: undefined | RoomId, requester: OptionalAddress) { 7 | return useSelector(selectFromTimestamp(roomId, requester)) 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/useSearchResult.ts: -------------------------------------------------------------------------------- 1 | import { RoomId } from '$/features/room/types' 2 | import { State } from '$/types' 3 | import { useSelector } from 'react-redux' 4 | 5 | function selectSearchResult(roomId: RoomId) { 6 | return (state: State) => state.room.searchResults[roomId] 7 | } 8 | 9 | export default function useSearchResult(roomId: RoomId) { 10 | return useSelector(selectSearchResult(roomId)) 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/getNewStreamrClient.ts: -------------------------------------------------------------------------------- 1 | import StreamrClient, { StreamrClientConfig } from '@streamr/sdk' 2 | 3 | export default function getNewStreamrClient(auth: StreamrClientConfig['auth']) { 4 | return new StreamrClient({ 5 | auth, 6 | gapFill: false, 7 | encryption: { 8 | litProtocolEnabled: true, 9 | litProtocolLogging: false, 10 | }, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Conversation/DateTooltip.tsx: -------------------------------------------------------------------------------- 1 | import Tooltip from '$/components/Tooltip' 2 | import i18n from '$/utils/i18n' 3 | 4 | interface Props { 5 | timestamp?: number 6 | } 7 | 8 | export default function DateTooltip({ timestamp }: Props) { 9 | if (typeof timestamp === 'undefined') { 10 | return
11 | } 12 | 13 | return {i18n('common.compactDate', timestamp)} 14 | } 15 | -------------------------------------------------------------------------------- /src/errors/MissingWalletClientError.ts: -------------------------------------------------------------------------------- 1 | export default class MissingWalletClientError extends Error { 2 | name = 'MissingWalletClientError' 3 | 4 | constructor() { 5 | super() 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, MissingWalletClientError) 9 | } 10 | 11 | Object.setPrototypeOf(this, MissingWalletClientError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/errors/MissingWalletAccountError.ts: -------------------------------------------------------------------------------- 1 | export default class MissingWalletAccountError extends Error { 2 | name = 'MissingWalletAccountError' 3 | 4 | constructor() { 5 | super() 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, MissingWalletAccountError) 9 | } 10 | 11 | Object.setPrototypeOf(this, MissingWalletAccountError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/errors/RedundantRenameError.ts: -------------------------------------------------------------------------------- 1 | export default class RedundantRenameError extends Error { 2 | name = 'RedundantRenameError' 3 | 4 | constructor() { 5 | super('Room name has not changed.') 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, RedundantRenameError) 9 | } 10 | 11 | Object.setPrototypeOf(this, RedundantRenameError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/errors/MissingWalletProviderError.ts: -------------------------------------------------------------------------------- 1 | export default class MissingWalletProviderError extends Error { 2 | name = 'MissingWalletProviderError' 3 | 4 | constructor() { 5 | super() 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, MissingWalletProviderError) 9 | } 10 | 11 | Object.setPrototypeOf(this, MissingWalletProviderError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/useAvatar.ts: -------------------------------------------------------------------------------- 1 | import { State } from '$/types' 2 | import { useSelector } from 'react-redux' 3 | 4 | export function selectAvatar(ens: undefined | null | string) { 5 | if (!ens) { 6 | return () => undefined 7 | } 8 | 9 | return (state: State) => state.avatar.urls[ens] 10 | } 11 | 12 | export default function useAvatar(ens: string | null | undefined) { 13 | return useSelector(selectAvatar(ens)) 14 | } 15 | -------------------------------------------------------------------------------- /src/errors/MissingDelegatedClientError.ts: -------------------------------------------------------------------------------- 1 | export default class MissingDelegatedClientError extends Error { 2 | name = 'MissingDelegatedClientError' 3 | 4 | constructor() { 5 | super() 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, MissingDelegatedClientError) 9 | } 10 | 11 | Object.setPrototypeOf(this, MissingDelegatedClientError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/errors/InsufficientFundsError.ts: -------------------------------------------------------------------------------- 1 | export default class InsufficientFundsError extends Error { 2 | name = 'InsufficientFundsError' 3 | 4 | constructor() { 5 | super("You don't have enough MATIC.") 6 | 7 | if (Error.captureStackTrace) { 8 | Error.captureStackTrace(this, InsufficientFundsError) 9 | } 10 | 11 | Object.setPrototypeOf(this, InsufficientFundsError.prototype) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/features/flag/selectors.ts: -------------------------------------------------------------------------------- 1 | import { FlagState } from '$/features/flag/types' 2 | import { State } from '$/types' 3 | import { createSelector } from '@reduxjs/toolkit' 4 | 5 | function selectSelf(state: State): FlagState { 6 | return state.flag 7 | } 8 | 9 | export function selectFlag(key: undefined | string) { 10 | return createSelector(selectSelf, (substate: FlagState) => 11 | key ? Boolean(substate[key]) : false 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/useIsMemberBeingRemoved.ts: -------------------------------------------------------------------------------- 1 | import { Flag } from '$/features/flag/types' 2 | import { RoomId } from '$/features/room/types' 3 | import useFlag from '$/hooks/useFlag' 4 | import { OptionalAddress } from '$/types' 5 | 6 | export default function useIsMemberBeingRemoved( 7 | roomId: undefined | RoomId, 8 | address: OptionalAddress 9 | ) { 10 | return useFlag(roomId && address ? Flag.isMemberBeingRemoved(roomId, address) : undefined) 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/switchNetwork.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@web3-react/types' 2 | import { Matic } from '$/utils/chains' 3 | 4 | export default async function switchNetwork(provider: Provider) { 5 | const [chainId] = Matic 6 | 7 | await provider.request({ 8 | method: 'wallet_switchEthereumChain', 9 | params: [ 10 | { 11 | chainId: `0x${chainId.toString(16)}`, 12 | }, 13 | ], 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/getDelegatedAccessRegistry.ts: -------------------------------------------------------------------------------- 1 | import { Contract, providers, Signer } from 'ethers' 2 | import { abi } from '$/contracts/DelegatedAccessRegistry.sol/DelegatedAccessRegistry.json' 3 | 4 | const DelegatedAccessRegistryAddress = '0x0143825C65D59CD09F5c896d9DE8b7fe952bc5EB' 5 | 6 | export default function getDelegatedAccessRegistry(signerOrProvider: Signer | providers.Provider) { 7 | return new Contract(DelegatedAccessRegistryAddress, abi, signerOrProvider) 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Hint.tsx: -------------------------------------------------------------------------------- 1 | import { HTMLAttributes } from 'react' 2 | import tw from 'twin.macro' 3 | 4 | export default function Hint(props: HTMLAttributes) { 5 | return ( 6 |

16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/errors/RoomNotFoundError.ts: -------------------------------------------------------------------------------- 1 | import { RoomId } from '$/features/room/types' 2 | 3 | export default class RoomNotFoundError extends Error { 4 | name = 'RoomNotFoundError' 5 | 6 | constructor(readonly roomId: RoomId) { 7 | super() 8 | 9 | if (Error.captureStackTrace) { 10 | Error.captureStackTrace(this, RoomNotFoundError) 11 | } 12 | 13 | Object.setPrototypeOf(this, RoomNotFoundError.prototype) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/chains.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from '$/features/wallet/types' 2 | 3 | export const Matic: Chain = [ 4 | 137, 5 | { 6 | chainId: '0x89', 7 | blockExplorerUrls: ['https://polygonscan.com'], 8 | chainName: 'Polygon Mainnet', 9 | nativeCurrency: { 10 | decimals: 18, 11 | name: 'Matic', 12 | symbol: 'MATIC', 13 | }, 14 | rpcUrls: ['https://polygon-rpc.com'], 15 | }, 16 | ] 17 | -------------------------------------------------------------------------------- /src/utils/isCorrectNetwork.ts: -------------------------------------------------------------------------------- 1 | import { Network } from '@ethersproject/providers' 2 | import { Provider } from '@web3-react/types' 3 | import { providers } from 'ethers' 4 | import { Matic } from './chains' 5 | 6 | export default async function isCorrectNetwork(provider: Provider) { 7 | const [correctChainId] = Matic 8 | 9 | const { chainId }: Network = await new providers.Web3Provider(provider).getNetwork() 10 | 11 | return correctChainId === chainId 12 | } 13 | -------------------------------------------------------------------------------- /src/features/wallet/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux' 2 | import { selectWalletAccount, selectWalletClient, selectWalletIntegrationId } from './selectors' 3 | 4 | export function useWalletIntegrationId() { 5 | return useSelector(selectWalletIntegrationId) 6 | } 7 | 8 | export function useWalletAccount() { 9 | return useSelector(selectWalletAccount) 10 | } 11 | 12 | export function useWalletClient() { 13 | return useSelector(selectWalletClient) 14 | } 15 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './fonts/Karelia.css' 5 | import 'twin.macro' 6 | 7 | const root = document.getElementById('root') 8 | 9 | if (root) { 10 | ReactDOM.createRoot(root).render( 11 | 12 | 13 | 14 | ) 15 | } else { 16 | throw new Error('Missing `#root`. Check `document.getElementById("root")`.') 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/useRoomMembers.ts: -------------------------------------------------------------------------------- 1 | import { RoomId } from '$/features/room/types' 2 | import { State } from '$/types' 3 | import { useSelector } from 'react-redux' 4 | 5 | function selectRoomMembers(roomId: undefined | RoomId) { 6 | return (state: State) => { 7 | return (roomId ? state.permissions.roomMembers[roomId] : undefined) || [] 8 | } 9 | } 10 | 11 | export default function useRoomMembers(roomId: undefined | RoomId) { 12 | return useSelector(selectRoomMembers(roomId)) 13 | } 14 | -------------------------------------------------------------------------------- /src/features/identicons/selectors.ts: -------------------------------------------------------------------------------- 1 | import { State } from '$/types' 2 | import { createSelector } from '@reduxjs/toolkit' 3 | import { IdenticonSeed, IdenticonsState } from './types' 4 | 5 | function selectSelf(state: State): IdenticonsState { 6 | return state.identicons 7 | } 8 | 9 | export function selectIdenticon( 10 | seed: undefined | IdenticonSeed 11 | ): (state: State) => undefined | string { 12 | return createSelector(selectSelf, (substate) => (seed ? substate[seed] : undefined)) 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/useIsInviteBeingAccepted.ts: -------------------------------------------------------------------------------- 1 | import { Flag } from '$/features/flag/types' 2 | import { useSelectedRoomId } from '$/features/room/hooks' 3 | import { useWalletAccount } from '$/features/wallet/hooks' 4 | import useFlag from '$/hooks/useFlag' 5 | 6 | export default function useIsInviteBeingAccepted() { 7 | const member = useWalletAccount() 8 | 9 | const roomId = useSelectedRoomId() 10 | 11 | return useFlag(roomId && member ? Flag.isInviteBeingAccepted(roomId, member) : undefined) 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/useTokenInfo.ts: -------------------------------------------------------------------------------- 1 | import { OptionalAddress, State } from '$/types' 2 | import { useSelector } from 'react-redux' 3 | 4 | function selectTokenInfo(tokenAddress: OptionalAddress) { 5 | if (!tokenAddress) { 6 | return () => undefined 7 | } 8 | 9 | return ({ misc }: State) => misc.knownTokensByAddress[tokenAddress.toLowerCase()] 10 | } 11 | 12 | export default function useTokenInfo(tokenAddress: OptionalAddress) { 13 | return useSelector(selectTokenInfo(tokenAddress)) 14 | } 15 | -------------------------------------------------------------------------------- /src/hooks/useMainAccount.ts: -------------------------------------------------------------------------------- 1 | import { OptionalAddress, State } from '$/types' 2 | import { useSelector } from 'react-redux' 3 | 4 | export function selectMainAccount(delegatedAccount: OptionalAddress) { 5 | return (state: State) => 6 | delegatedAccount ? state.delegation.delegations[delegatedAccount.toLowerCase()] : undefined 7 | } 8 | 9 | export default function useMainAccount(delegatedAccount: OptionalAddress): OptionalAddress { 10 | return useSelector(selectMainAccount(delegatedAccount)) 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | testEnvironment: 'jsdom', 3 | moduleNameMapper: { 4 | '\\$/(.*)': '/src/$1', 5 | '^dexie$': '/node_modules/dexie', 6 | '^multiformats$': '/node_modules/multiformats/cjs/src/index.js', 7 | '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 8 | '/mocks/file.ts', 9 | }, 10 | setupFiles: ['fake-indexeddb/auto'], 11 | } 12 | 13 | export default config 14 | -------------------------------------------------------------------------------- /src/fonts/Karelia.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Karelia'; 3 | src: url('./Karelia/KareliaWeb-Regular.woff2') format('woff2'), 4 | url('./Karelia/KareliaWeb-Regular.woff') format('woff'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'Karelia'; 11 | src: url('./Karelia/KareliaWeb-Medium.woff2') format('woff2'), 12 | url('./Karelia/KareliaWeb-Medium.woff') format('woff'); 13 | font-weight: 500; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/useCachedTokenGate.ts: -------------------------------------------------------------------------------- 1 | import { RoomId } from '$/features/room/types' 2 | import { State } from '$/types' 3 | import { useSelector } from 'react-redux' 4 | 5 | function selectCachedTokenGate(roomId: RoomId | undefined) { 6 | if (!roomId) { 7 | return () => undefined 8 | } 9 | 10 | return ({ room }: State) => room.cache[roomId]?.tokenGate 11 | } 12 | 13 | export default function useCachedTokenGate(roomId: RoomId | undefined) { 14 | return useSelector(selectCachedTokenGate(roomId)) 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/handleError.ts: -------------------------------------------------------------------------------- 1 | import { Reason } from 'toasterhea' 2 | import Dexie from 'dexie' 3 | 4 | export default function handleError(e: any) { 5 | switch (true) { 6 | case e === Reason.Host: 7 | case e === Reason.Unmount: 8 | case e === Reason.Update: 9 | case typeof e === 'undefined': 10 | case e instanceof Dexie.ConstraintError: 11 | // Ignore. 12 | break 13 | default: 14 | console.warn('No default error handler for', e) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/useAnonClient.ts: -------------------------------------------------------------------------------- 1 | import { RoomId } from '$/features/room/types' 2 | import { State } from '$/types' 3 | import { useSelector } from 'react-redux' 4 | 5 | function selectAnonClient(roomId: RoomId | undefined) { 6 | return ({ anon }: State) => { 7 | if (!roomId) { 8 | return undefined 9 | } 10 | 11 | return anon.rooms[roomId]?.client 12 | } 13 | } 14 | 15 | export default function useAnonClient(roomId: RoomId | undefined) { 16 | return useSelector(selectAnonClient(roomId)) 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/useTokenStandard.ts: -------------------------------------------------------------------------------- 1 | import { OptionalAddress, State } from '$/types' 2 | import { useSelector } from 'react-redux' 3 | 4 | export function selectTokenStandard(address: OptionalAddress) { 5 | return (state: State) => { 6 | if (!address) { 7 | return undefined 8 | } 9 | 10 | return state.misc.tokenStandards[address.toLowerCase()] 11 | } 12 | } 13 | 14 | export default function useTokenStandard(address: OptionalAddress) { 15 | return useSelector(selectTokenStandard(address)) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Label.tsx: -------------------------------------------------------------------------------- 1 | import { LabelHTMLAttributes } from 'react' 2 | import tw from 'twin.macro' 3 | 4 | export default function Label(props: LabelHTMLAttributes) { 5 | return ( 6 |