diff --git a/package.json b/package.json index aab7b960..dd4329db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Hoogii", - "version": "1.0.13-dev.0", + "version": "1.0.13-dev.3", "private": true, "scripts": { "dev": "vite --port 3000", diff --git a/public/images/bg/popup.png b/public/images/bg/popup.png new file mode 100644 index 00000000..91e52359 Binary files /dev/null and b/public/images/bg/popup.png differ diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 66856857..59b0f105 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -137,7 +137,8 @@ "status-error-description": "There's something wrong! Please try again later.", "status-success-description": "Now you can **Open Hoogii Extension Wallet** to start exploring the decentralized web on Chia Network.", "switch": "switch", - "switch-to-chia": "Switch to Chia", + "switch-to-chia": "Switch to Chia {{chainName}}?", + "switch-chain_id": "Chain ID: {{chainId}}", "tooltip-connected_sites": "Hoogii would not automatically connect to websites below, you still need to give access as first time you connect with.", "tooltip-copied": "Copied", "tooltip-copy_address": "Copy Address", diff --git a/src/components/FeesRadio/index.tsx b/src/components/FeesRadio/index.tsx index 32526cba..c2518f49 100644 --- a/src/components/FeesRadio/index.tsx +++ b/src/components/FeesRadio/index.tsx @@ -30,7 +30,7 @@ const FeesRadio = ({ }: IProps & IReactHookFormProps) => { const { i18n } = useTranslation() return ( -
+
{fees.map((item) => (
+ + ) +} + +export default PopupLayout diff --git a/src/popup/components/addressInfo.tsx b/src/popup/components/addressInfo.tsx new file mode 100644 index 00000000..6f7ee897 --- /dev/null +++ b/src/popup/components/addressInfo.tsx @@ -0,0 +1,21 @@ +import { t } from 'i18next' +import { observer } from 'mobx-react-lite' + +import rootStore from '~/store' +import { shortenHash } from '~/utils' + +const AddressInfo = () => { + const { + walletStore: { address }, + } = rootStore + const shortenAddress = shortenHash(address) + + return ( +
+ {t('current-address')} +
{shortenAddress}
+
+ ) +} + +export default observer(AddressInfo) diff --git a/src/popup/components/connectSiteInfo.tsx b/src/popup/components/connectSiteInfo.tsx index 467ae2fd..5c7adff2 100644 --- a/src/popup/components/connectSiteInfo.tsx +++ b/src/popup/components/connectSiteInfo.tsx @@ -10,7 +10,11 @@ const connectSiteInfo = ({ title={request.origin} className="flex max-w-full gap-2 px-4 py-2 text-button2 text-primary-100 items-center border-primary-100/20 border rounded-lg" > - icon + icon
{request.origin}
diff --git a/src/popup/components/index.ts b/src/popup/components/index.ts new file mode 100644 index 00000000..03a8c77f --- /dev/null +++ b/src/popup/components/index.ts @@ -0,0 +1,7 @@ +export { default as AddressInfo } from './addressInfo' +export { default as ConnectSiteInfo } from './connectSiteInfo' +export { default as OfferInfo } from './offerInfo' +export { default as SpendBundleInfo } from './spendBundleInfo' +export * from './transactionInfo' +export { default as TransactionInfo } from './transactionInfo' +export { default as TransferInfo } from './transferInfo' diff --git a/src/popup/components/offerInfo.tsx b/src/popup/components/offerInfo.tsx index 29c5ae5c..5649ec90 100644 --- a/src/popup/components/offerInfo.tsx +++ b/src/popup/components/offerInfo.tsx @@ -1,35 +1,17 @@ -import { observer } from 'mobx-react-lite' -import React, { useMemo } from 'react' +import { ComponentProps, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import AssetIcon from '~/components/AssetIcon' -import rootStore from '~/store' -import { - MethodEnum, - OfferAsset, - OfferParams, - OfferTypeEnum, -} from '~/types/extension' -import { shortenHash } from '~/utils' -import { mojoToBalance } from '~/utils/CoinConverter' +import { MethodEnum, OfferParams } from '~/types/extension' import { IPopupPageProps } from '../types' - -interface IOfferAssets extends OfferAsset { - offerType: OfferTypeEnum -} +import TransactionInfo from './transactionInfo' function offerInfo({ request }: IPopupPageProps) { const { t } = useTranslation() - const { - assetsStore: { XCH, getAssetByAssetId }, - walletStore: { address }, - } = rootStore - - const shortenAddress = shortenHash(address) - - const offerAssets = useMemo(() => { + const assets = useMemo< + ComponentProps['assets'] + >(() => { if (!request.data?.params) { return [] } @@ -39,75 +21,15 @@ function offerInfo({ request }: IPopupPageProps) { return [ ...requestAssets.map((asset) => ({ ...asset, - offerType: OfferTypeEnum.REQUEST, })), ...offerAssets.map((asset) => ({ ...asset, - offerType: OfferTypeEnum.OFFER, + amount: -asset.amount, })), ] - }, [request.data?.params]) - return ( - <> -
-
- {t('address')} -
-
- {shortenAddress} -
-
-
-
- {t('transaction')} -
-
-
- {t('offer')} -
- {offerAssets.map((asset) => { - const amount = mojoToBalance( - asset.amount.toString(), - asset.assetId - ) - const foundAsset = getAssetByAssetId( - asset.assetId ?? XCH.assetId - ) + }, []) - return ( -
-
- - {foundAsset?.code || - `CAT ${shortenHash(asset.assetId)}`} -
-
- {asset.offerType === OfferTypeEnum.REQUEST - ? '+' - : '-'} - {amount} -
-
- ) - })} -
-
- - ) + return } -export default observer(offerInfo) +export default offerInfo diff --git a/src/popup/components/spendBundleInfo.tsx b/src/popup/components/spendBundleInfo.tsx index 299d4ab3..3a1a3394 100644 --- a/src/popup/components/spendBundleInfo.tsx +++ b/src/popup/components/spendBundleInfo.tsx @@ -11,7 +11,6 @@ import { MethodEnum, RequestMethodEnum } from '~/types/extension' import { shortenHash } from '~/utils' import { mojoToBalance } from '~/utils/CoinConverter' import { add0x } from '~/utils/encryption' -import { puzzleHashToAddress } from '~/utils/signature' import { IPopupPageProps } from '../types' function spendBundleInfo({ request }: IPopupPageProps) { @@ -68,18 +67,6 @@ function spendBundleInfo({ request }: IPopupPageProps) {
{parseBundle ? ( <> -
-
- {t('address')} -
-
- {shortenHash( - puzzleHashToAddress( - metadata?.to_puzzle_hashes?.[0] - ) - )} -
-
{t('transaction')} diff --git a/src/popup/components/transactionInfo.tsx b/src/popup/components/transactionInfo.tsx new file mode 100644 index 00000000..8c913257 --- /dev/null +++ b/src/popup/components/transactionInfo.tsx @@ -0,0 +1,82 @@ +import { t } from 'i18next' +import { observer } from 'mobx-react-lite' +import { ReactNode } from 'react' + +import AssetIcon from '~/components/AssetIcon' +import rootStore from '~/store' +import { OfferAsset } from '~/types/extension' +import { shortenHash } from '~/utils' +import { mojoToBalance, mojoToXch } from '~/utils/CoinConverter' + +interface IAssetItemProps extends Omit {} + +export const AssetItem = observer(({ assetId, amount }: IAssetItemProps) => { + const { + assetsStore: { XCH, getAssetByAssetId }, + } = rootStore + const amountDisplay = mojoToBalance(Math.abs(amount), assetId) + const className = amount >= 0 ? 'text-status-receive' : 'text-status-send' + const foundAsset = getAssetByAssetId(assetId ?? XCH.assetId) + + return ( +
+
+ + {foundAsset?.code || `CAT ${shortenHash(assetId)}`} +
+
{`${ + amount >= 0 ? '+' : '-' + }${amountDisplay}`}
+
+ ) +}) + +export const FeeInfo = observer(({ fee }: { fee: number }) => { + const { + assetsStore: { XCH }, + } = rootStore + return ( +
+ + + {XCH.code} + + + -{mojoToXch(fee).toString()} + +
+ ) +}) + +interface IProps { + title: ReactNode + assets: IAssetItemProps[] +} + +const TransactionInfo = ({ title, assets }: IProps) => { + return ( +
+ {t('transaction')} +
+
+ {title} +
+ {assets.map((asset) => { + const key = `${asset.assetId}-${asset.amount}` + + return + })} +
+
+ ) +} + +export default TransactionInfo diff --git a/src/popup/components/transferInfo.tsx b/src/popup/components/transferInfo.tsx index 5100201e..adf5582a 100644 --- a/src/popup/components/transferInfo.tsx +++ b/src/popup/components/transferInfo.tsx @@ -1,71 +1,29 @@ import { t } from 'i18next' -import { observer } from 'mobx-react-lite' +import { ComponentProps, useMemo } from 'react' -import AssetIcon from '~/components/AssetIcon' -import rootStore from '~/store' -import { MethodEnum } from '~/types/extension' -import { shortenHash } from '~/utils' -import { mojoToBalance } from '~/utils/CoinConverter' +import { TransactionInfo } from '~/popup/components' +import { MethodEnum, TransferParams } from '~/types/extension' import { IPopupPageProps } from '../types' function transferInfo({ request }: IPopupPageProps) { - const { - assetsStore: { XCH, availableAssets }, - walletStore: { address }, - } = rootStore + const assets = useMemo< + ComponentProps['assets'] + >(() => { + if (!request.data?.params) { + return [] + } + const params = request.data.params as TransferParams - const finsAsset = availableAssets?.data?.find( - (availableAsset) => - request?.data?.params.assetId === availableAsset.asset_id - ) - return ( - <> -
-
- {t('address')} -
-
- {shortenHash(address)} -
-
-
-
- {t('transaction')} -
-
-
- {t('send-title')} -
-
-
- + return [ + { + assetId: params.assetId, + amount: -params.amount, + }, + ] + }, []) - {request?.data?.params.assetId - ? finsAsset?.name || - `CAT ${shortenHash( - request?.data?.params.assetId - )}` - : XCH.code} -
-
- - - {mojoToBalance( - request?.data?.params.amount, - request?.data?.params.assetId - )} -
-
-
-
- - ) + return } -export default observer(transferInfo) +export default transferInfo diff --git a/src/popup/controller.ts b/src/popup/controller.ts index 9cae72f4..0b70e080 100644 --- a/src/popup/controller.ts +++ b/src/popup/controller.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction } from 'mobx' +import { makeAutoObservable, reaction, runInAction } from 'mobx' import { lockFromBackground, savePassword } from '~/api/extension' import { WalletDexie } from '~/db' @@ -30,11 +30,22 @@ export class InternalControllerStore { name: ConnectionName[PopupEnum.INTERNAL], }) + reaction( + () => this.locked, + () => + this.init( + this.connected + ? this.request?.data.method + : RequestMethodEnum.CONNECT + ) + ) + this.requestData() } lock = async () => { await lockFromBackground() + await rootStore.walletStore.lock() runInAction(() => { this.locked = true }) @@ -42,6 +53,7 @@ export class InternalControllerStore { unlock = async (password: string) => { await savePassword(password) + await rootStore.walletStore.unlock(password) runInAction(() => { this.locked = false }) @@ -61,6 +73,7 @@ export class InternalControllerStore { init = async (method: RequestMethodEnum) => { switch (method) { + case RequestMethodEnum.CONNECT: case RequestMethodEnum.CREATE_OFFER: case RequestMethodEnum.TRANSFER: case RequestMethodEnum.SIGN_COIN_SPENDS: @@ -89,7 +102,11 @@ export class InternalControllerStore { this.request = response this.locked = Boolean(response?.isLocked) this.connected = Boolean(response?.isConnected) - this.init(response?.data.method) + this.init( + this.connected + ? response?.data.method + : RequestMethodEnum.CONNECT + ) }) } this.port.onMessage.addListener(messageHandler) diff --git a/src/popup/layout.tsx b/src/popup/layout.tsx index baaa5eb4..f439b2a7 100644 --- a/src/popup/layout.tsx +++ b/src/popup/layout.tsx @@ -2,7 +2,7 @@ import { Outlet } from 'react-router-dom' const Layout = () => { return ( -
+
) diff --git a/src/popup/pages/connecting.tsx b/src/popup/pages/connecting.tsx index 2d562db1..ecd7fb79 100644 --- a/src/popup/pages/connecting.tsx +++ b/src/popup/pages/connecting.tsx @@ -1,11 +1,13 @@ import { t } from 'i18next' import { useEffect } from 'react' +import PopupLayout from '~/layouts/Popup' import { APIError, MethodEnum } from '~/types/extension' import { IPopupPageProps } from '../types' -const roundStyle = 'justify-center p-4 items-center bg-white m-1 rounded-full' +const roundStyle = + 'flex-center p-2.5 bg-dark-scale-100 rounded-full w-20 h-20 child:full overflow-hidden' const Connecting = ({ controller, @@ -17,51 +19,37 @@ const Connecting = ({ }, 2000) }) return ( -
-
-
- {t('connecting')} + { + if (request.method === MethodEnum.REQUEST) { + controller.returnData({ + data: false, + }) + } else { + controller.returnData({ + error: APIError.REFUSED, + }) + } + window.close() + }, + }, + ]} + className="pt-[60px]" + > +
+
+ logo
-
-
- logo -
-
-–––-
-
- icon -
+
+
+ icon
-
-
- -
-
-
+
) } diff --git a/src/popup/pages/refuse.tsx b/src/popup/pages/refuse.tsx index a6e627cf..84ba8bc4 100644 --- a/src/popup/pages/refuse.tsx +++ b/src/popup/pages/refuse.tsx @@ -1,9 +1,12 @@ import { t } from 'i18next' +import { observer } from 'mobx-react-lite' import { useNavigate } from 'react-router-dom' import { sendMeasurement } from '~/api/ga' -import ConnectSiteInfo from '~/popup/components/connectSiteInfo' +import PopupLayout from '~/layouts/Popup' +import { AddressInfo } from '~/popup/components' import { APIError, MethodEnum } from '~/types/extension' +import CheckedIcon from '~icons/hoogii/checked-rounded.jsx' import { IPopupPageProps } from '../types' @@ -13,71 +16,72 @@ const Refuse = ({ }: IPopupPageProps) => { const navigate = useNavigate() + const cancel = () => { + if (request.method === MethodEnum.REQUEST) { + controller.returnData({ + data: false, + }) + } else { + controller.returnData({ + error: APIError.REFUSED, + }) + } + window.close() + } + + const access = () => { + // await rootStore.walletStore.db.connectedSites.add({ + // name: request.origin, + // url: request.origin, + // }) + // controller.checkIsConnectedSite() + sendMeasurement({ + events: [ + { + name: 'connect_site', + params: { + category: 'connent_site', + action: 'click', + value: request.origin, + }, + }, + ], + }) + navigate('/connecting') + } + return ( -
-
- -
- {t('refuse-connect-with-hoogii')} -
-
- {t('refuse-this-app-would-like-to')} -
-
- {t('refuse-authorization-contents')} -
+ + +
+ {t('refuse-you-allow-to')} +
    + {Array.from({ length: 2 }).map((_, index) => { + const key = `refuse-permission-${index + 1}` + return ( +
  • + + {t(key)} +
  • + ) + })} +
-
-
- {t('refuse-only-connect-with-sites-you-trust')} -
-
- - -
+
+
+ {t('refuse-only-connect-with-sites-you-trust')}
-
+
) } -export default Refuse +export default observer(Refuse) diff --git a/src/popup/pages/switchChain.tsx b/src/popup/pages/switchChain.tsx index e1fbb411..1359c33e 100644 --- a/src/popup/pages/switchChain.tsx +++ b/src/popup/pages/switchChain.tsx @@ -1,5 +1,6 @@ import { t } from 'i18next' +import PopupLayout from '~/layouts/Popup' import { MethodEnum } from '~/types/extension' import { chains } from '~/utils/constants' @@ -9,44 +10,38 @@ const SwitchChain = ({ controller, request, }: IPopupPageProps) => { - const ChainName = chains[request?.data?.params?.chainId] + const chainName = chains[request?.data?.params?.chainId]?.name || 'NewWork' + const chainId = request?.data?.params?.chainId + + const cancel = () => { + controller.returnData({ + data: false, + }) + window.close() + } + + const switchChain = () => { + controller.returnData({ + data: true, + }) + window.close() + } + return ( -
-
-
- {t('switch-to-chia')} {ChainName?.name || 'NewWork'}? -
-
- Chain ID: {request?.data?.params?.chainId} -
-
-
-
- - -
-
-
+ ) } diff --git a/src/popup/pages/transaction.tsx b/src/popup/pages/transaction.tsx index f30946ff..125e237d 100644 --- a/src/popup/pages/transaction.tsx +++ b/src/popup/pages/transaction.tsx @@ -4,14 +4,13 @@ import { useCallback, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import AssetIcon from '~/components/AssetIcon' import FeesRadio, { createDefaultFeeOptions, createFeeOptions, useDynamicFeeOptions, } from '~/components/FeesRadio' import { ErrorPopup } from '~/components/Popup' -import ConnectSiteInfo from '~/popup/components/connectSiteInfo' +import PopupLayout from '~/layouts/Popup' import OfferInfo from '~/popup/components/offerInfo' import SpendBundleInfo from '~/popup/components/spendBundleInfo' import TransferInfo from '~/popup/components/transferInfo' @@ -22,9 +21,10 @@ import { RequestMethodEnum, TransferParams, } from '~/types/extension' -import { mojoToXch, xchToMojo } from '~/utils/CoinConverter' +import { xchToMojo } from '~/utils/CoinConverter' import Offer from '~/utils/Offer' +import { AddressInfo, FeeInfo } from '../components' import { IPopupPageProps } from '../types' const withoutFee = [ RequestMethodEnum.SEND_TRANSACTION, @@ -123,8 +123,6 @@ const Transaction = ({ fee ) - console.log('secureBundle', secureBundle) - return new Offer(secureBundle) } @@ -181,98 +179,86 @@ const Transaction = ({ } } + const cancel = () => { + controller.returnData({ + data: false, + }) + window.close() + } + return ( -
handleSubmit(onSubmit)(e).catch((error) => { - console.error('error', error) setSubmitError(error as Error) }) } - className="container flex flex-col justify-between w-full h-full py-12" + title={t('offier-request-signature-for')} + request={request} + controller={controller} + actions={[ + { + children: t('btn-cancel'), + onClick: cancel, + disabled: isSubmitting, + }, + { + children: t('btn-sign'), + type: 'submit', + }, + ]} + className="overflow-hidden gap-4 pt-8 pb-10" > -
- -
- {t('offier-request-signature-for')} -
-
- {request.data?.method === RequestMethodEnum.CREATE_OFFER && ( - - )} + +
+ {request.data?.method === RequestMethodEnum.CREATE_OFFER && ( + + )} - {request.data?.method === RequestMethodEnum.TRANSFER && ( - - )} + {request.data?.method === RequestMethodEnum.TRANSFER && ( + + )} - {request.data?.method === RequestMethodEnum.SEND_TRANSACTION && ( - - )} + {request.data?.method === + RequestMethodEnum.SEND_TRANSACTION && ( + + )} - {request.data?.method === RequestMethodEnum.SIGN_COIN_SPENDS && ( - - )} + {request.data?.method === + RequestMethodEnum.SIGN_COIN_SPENDS && ( + + )} - {!withoutFee.some((method) => request.data?.method === method) && ( -
-
+ {!withoutFee.some( + (method) => request.data?.method === method + ) && ( +
{request?.data?.params.fee ? t('fee') : t('send-fee-description')} + {request?.data?.params.fee && + request.data.params.fee > 0 ? ( + + ) : ( + + )}
- {request?.data?.params.fee ? ( -
- - - {XCH.code} - - - - - {mojoToXch( - request?.data?.params.fee - ).toString()} - -
- ) : ( - - )} -
- )} - -
-
- - -
+ )}
{isSubmitting && (
@@ -288,7 +274,7 @@ const Transaction = ({ }} /> )} - + ) } diff --git a/src/utils/CAT.ts b/src/utils/CAT.ts index 94fee47c..a8521516 100644 --- a/src/utils/CAT.ts +++ b/src/utils/CAT.ts @@ -141,7 +141,7 @@ export class CAT extends Program { additionalMemoList.push(Program.fromText(memo)) }) } - console.log('additionalMemoList', additionalMemoList) + return Program.fromList([ Program.fromHex(sanitizeHex(ConditionOpcode.CREATE_COIN)), Program.fromHex(primary.puzzlehash), diff --git a/tailwind.config.js b/tailwind.config.js index 52733351..f7e5077f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -33,7 +33,9 @@ function createStyles(prefix, object) { ) { darkScale = 100 } else darkScale = 900 - } catch (error) {} + } catch (error) { + // + } const color = `rgb(var(--color-dark-scale-${darkScale}))` let contrastColor = backgroundColor if ( @@ -119,6 +121,7 @@ module.exports = { backgroundImage: { main: 'url("/images/bg/main.svg")', landing: 'url("/images/bg/landing.png")', + internal: 'url("/images/bg/popup.png")', welcome: 'url("/images/bg/welcome.png")', close: 'url("/images/icons/close.svg")', checked: `url("${svgToMiniDataURI(` diff --git a/tools/tools.tsx b/tools/tools.tsx index c3f0ce3b..e733a91a 100644 --- a/tools/tools.tsx +++ b/tools/tools.tsx @@ -9,8 +9,10 @@ import ReactDOM from 'react-dom/client' // TODO: for dev in the future // import Components from '~/container/Components' import App from '~/App' +import { ChainEnum } from '~/types/chia' +import { RequestMethodEnum } from '~/types/extension' -import { createMockOffer, createMockTransfer } from './utils' +import { createMockOffer, createMockRequest, createMockTransfer } from './utils' window.global = window window.Buffer = Buffer @@ -30,6 +32,24 @@ const DevTools = () => { > Transfer + +
diff --git a/tools/utils.ts b/tools/utils.ts index c8404381..8eb31554 100644 --- a/tools/utils.ts +++ b/tools/utils.ts @@ -1,7 +1,11 @@ import Messaging from '~/api/extension/messaging' import { MethodEnum, RequestMethodEnum, SenderEnum } from '~/types/extension' -export const createMockRequest = (method: RequestMethodEnum, params: any) => { +export const createMockRequest = ( + method: RequestMethodEnum, + params: any, + isConnected: boolean = true +) => { const favicon = document.querySelector( 'link[rel~="icon"]' ) as HTMLLinkElement @@ -17,7 +21,7 @@ export const createMockRequest = (method: RequestMethodEnum, params: any) => { origin, iconUrl, isLocked: false, - isConnected: true, + isConnected, }) } export const createMockTransfer = () =>