From aad420778043541df9870db21667f6d1a63069cd Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 15 Aug 2023 13:13:03 +0200 Subject: [PATCH 01/29] fix: migration + password not existing --- src/components/welcome/load/Migrate.tsx | 51 +++++++++++++++++++++---- src/routes/welcome/load/password.tsx | 5 +-- src/routes/welcome/setup.tsx | 7 ++++ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/components/welcome/load/Migrate.tsx b/src/components/welcome/load/Migrate.tsx index 7e92f1193..9904e8765 100644 --- a/src/components/welcome/load/Migrate.tsx +++ b/src/components/welcome/load/Migrate.tsx @@ -1,8 +1,10 @@ +import { ChevronDownIcon, ChevronUpIcon } from "@iconicicons/react"; import type { JWKInterface } from "arweave/node/lib/wallet"; import { Spacer, Text } from "@arconnect/components"; import { useWalletsDetails } from "~wallets/hooks"; import browser from "webextension-polyfill"; import styled from "styled-components"; +import { useState } from "react"; import Wallet from "./Wallet"; export default function Migrate({ @@ -11,6 +13,7 @@ export default function Migrate({ onMigrateClick }: Props) { const walletDetails = useWalletsDetails(wallets); + const [showAll, setShowAll] = useState(false); return ( <> @@ -22,16 +25,35 @@ export default function Migrate({ - {walletDetails.map((wallet, i) => ( - - ))} + {walletDetails + .slice(0, showAll ? walletDetails.length : 5) + .map((wallet, i) => ( + + ))} {noMigration && ( <> - - {browser.i18n.getMessage("migrate_anyway")} - + + + {browser.i18n.getMessage("migrate_anyway")} + + {walletDetails.length > 5 && ( + setShowAll((val) => !val)}> + {browser.i18n.getMessage("view_all")} + {(showAll && ) || } + + )} + + + )} + {walletDetails.length > 5 && !noMigration && ( + <> + + setShowAll((val) => !val)}> + {browser.i18n.getMessage("view_all")} + {(showAll && ) || } + )} @@ -44,14 +66,29 @@ const Title = styled(Text).attrs({ color: rgb(${(props) => props.theme.theme}); `; -const MigrateAnyway = styled(Title)` +const MigrateFlex = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +const LinkBtn = styled(Title)` + display: flex; + align-items: center; cursor: pointer; width: max-content; + gap: 0.1rem; transition: all 0.23s ease-in-out; &:hover { opacity: 0.8; } + + svg { + font-size: 1.2em; + width: 1em; + height: 1em; + } `; const Wallets = styled.div` diff --git a/src/routes/welcome/load/password.tsx b/src/routes/welcome/load/password.tsx index c8797ddf2..1f5f6b9df 100644 --- a/src/routes/welcome/load/password.tsx +++ b/src/routes/welcome/load/password.tsx @@ -1,5 +1,5 @@ import PasswordStrength from "../../../components/welcome/PasswordStrength"; -import { AnimatePresence, motion, type Variants } from "framer-motion"; +import PasswordMatch from "~components/welcome/PasswordMatch"; import { checkPasswordValid } from "~wallets/generator"; import { ArrowRightIcon } from "@iconicicons/react"; import { useLocation, useRoute } from "wouter"; @@ -7,7 +7,6 @@ import Paragraph from "~components/Paragraph"; import { useContext, useMemo } from "react"; import browser from "webextension-polyfill"; import { PasswordContext } from "../setup"; -import styled from "styled-components"; import { Button, Input, @@ -16,7 +15,6 @@ import { useInput, useToasts } from "@arconnect/components"; -import PasswordMatch from "~components/welcome/PasswordMatch"; export default function Password() { // input controls @@ -87,6 +85,7 @@ export default function Password() { if (e.key !== "Enter") return; done(); }} + autoFocus /> { + if (page !== 1 && password === "") { + setLocation(`/${setupMode}/1`); + } + }, [page, password]); + // is the setup mode "wallet generation" const [isGenerateWallet] = useRoute("/generate/:page"); From 9becab91dfe2a2c5c2ad143fdc8a85493ef098a3 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 15 Aug 2023 13:25:42 +0200 Subject: [PATCH 02/29] fix: wallet overflow in switcher --- src/components/popup/WalletSwitcher.tsx | 142 +++++++++++++----------- 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/src/components/popup/WalletSwitcher.tsx b/src/components/popup/WalletSwitcher.tsx index 407fdac22..053de457d 100644 --- a/src/components/popup/WalletSwitcher.tsx +++ b/src/components/popup/WalletSwitcher.tsx @@ -146,76 +146,81 @@ export default function WalletSwitcher({ e.preventDefault(); }} > - {wallets.map((wallet, i) => ( - { - setActiveAddress(wallet.address); - setToast({ - type: "success", - content: browser.i18n.getMessage("switchedToWallet", [ - wallet.name - ]), - duration: 1100 - }); - close(); - }} - > - - - {wallet.name} - - ( - {formatAddress( - wallet.address, - wallet.name.length > 14 ? 3 : 6 + + {wallets.map((wallet, i) => ( + { + setActiveAddress(wallet.address); + setToast({ + type: "success", + content: browser.i18n.getMessage("switchedToWallet", [ + wallet.name + ]), + duration: 1100 + }); + close(); + }} + > + + + {wallet.name} + + ( + {formatAddress( + wallet.address, + wallet.name.length > 14 ? 3 : 4 + )} + ) + + {wallet.address === activeAddress && ( + )} - ) - - {wallet.address === activeAddress && } - - - {formatTokenBalance(wallet.balance)} - AR - - - - {!wallet.avatar && } - {wallet.api === "keystone" && ( - - )} - - - ))} + + + {formatTokenBalance(wallet.balance)} + AR + + + + {!wallet.avatar && } + {wallet.api === "keystone" && ( + + )} + + + ))} + {showOptions && ( - <> - - + + browser.tabs.create({ + url: browser.runtime.getURL( + "tabs/dashboard.html#/wallets/new" + ) + }) + } + > + + {browser.i18n.getMessage("add_wallet")} + + + browser.tabs.create({ url: browser.runtime.getURL( - "tabs/dashboard.html#/wallets/new" + `tabs/dashboard.html#/wallets/${activeAddress}` ) }) } - > - - {browser.i18n.getMessage("add_wallet")} - - - - browser.tabs.create({ - url: browser.runtime.getURL( - `tabs/dashboard.html#/wallets/${activeAddress}` - ) - }) - } - /> - - - + /> + + )} @@ -266,6 +271,12 @@ const Wrapper = styled(Section)<{ noPadding: boolean }>` `; const WalletsCard = styled(Card)` + max-height: 80vh; + overflow-y: auto; + padding: 0; +`; + +const Wallets = styled.div` padding: 0.3rem; `; @@ -373,10 +384,15 @@ const NoAvatarIcon = styled(WalletIcon)` `; const ActionBar = styled.div` + position: sticky; + bottom: 0; + left: 0; + right: 0; display: flex; align-items: center; gap: 1rem; - padding: 0.45rem ${wallet_side_padding}; + padding: 0.75rem calc(${wallet_side_padding} + 0.3rem); + background-color: rgb(${(props) => props.theme.cardBackground}); `; const AddWalletButton = styled(Button).attrs({ From 57a69a9d9bd398e1897a8d1ace57d49408a0f45a Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 15 Aug 2023 13:43:49 +0200 Subject: [PATCH 03/29] feat: move add wallet button --- src/components/dashboard/Wallets.tsx | 33 ++++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/components/dashboard/Wallets.tsx b/src/components/dashboard/Wallets.tsx index c87d54eb1..458dc5123 100644 --- a/src/components/dashboard/Wallets.tsx +++ b/src/components/dashboard/Wallets.tsx @@ -110,11 +110,15 @@ export default function Wallets() { return ( <> - + + + setLocation("/wallets/new")}> + {browser.i18n.getMessage("add_wallet")} + + {wallets && ( )} - setLocation("/wallets/new")}> - - ); } @@ -149,17 +150,25 @@ const Wrapper = styled.div` position: relative; `; +const SearchWrapper = styled.div` + position: sticky; + display: grid; + gap: 1rem; + top: 0; + left: 0; + right: 0; + z-index: 20; + grid-template-columns: auto auto; +`; + const AddWalletButton = styled(IconButton).attrs({ secondary: true })` - position: absolute; - bottom: 0.5rem; - right: 0.5rem; - z-index: 20; background: linear-gradient( 0deg, rgba(${(props) => props.theme.theme}, 0.2), rgba(${(props) => props.theme.theme}, 0.2) ), rgb(${(props) => props.theme.background}); + padding: 0 0.25rem !important; `; From 9fb48f02b258e50cdef7bf547eaae1f6d4b5fbd3 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 15 Aug 2023 17:37:22 +0200 Subject: [PATCH 04/29] feat: whitelist for signature approval --- .../modules/signature/signature.background.ts | 20 ++++++++++--------- src/api/modules/signature/whitelist.ts | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/api/modules/signature/signature.background.ts b/src/api/modules/signature/signature.background.ts index e2b8189e4..d5a0ead64 100644 --- a/src/api/modules/signature/signature.background.ts +++ b/src/api/modules/signature/signature.background.ts @@ -26,20 +26,22 @@ const background: ModuleFunction = async ( isSignatureAlgorithm(algorithm); // temporary whitelist - //const whitelisted = appData.appURL.match(getWhitelistRegExp()); + const whitelisted = appData.appURL.match(getWhitelistRegExp()); //isNotNull(whitelisted, "The signature() API is deprecated."); //isNotUndefined(whitelisted, "The signature() API is deprecated."); // request user to authorize - try { - await authenticate({ - type: "signature", - url: appData.appURL, - message: data - }); - } catch { - throw new Error("User rejected the signature request"); + if (!whitelisted) { + try { + await authenticate({ + type: "signature", + url: appData.appURL, + message: data + }); + } catch { + throw new Error("User rejected the signature request"); + } } // grab the user's keyfile diff --git a/src/api/modules/signature/whitelist.ts b/src/api/modules/signature/whitelist.ts index 4185072cb..75ea875c6 100644 --- a/src/api/modules/signature/whitelist.ts +++ b/src/api/modules/signature/whitelist.ts @@ -1,5 +1,5 @@ export const whitelistedSites: RegExp[] = [ - /app\.ardive\.io/, + /app\.ardrive\.io/, /staging\.ardrive\.io/, /dapp_ardrive\.ar-io\.dev/, /og_dapp_ardrive\.ar-io\.dev/, From 2cae4592f3002f0208824d10a92212e7b2495f21 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Fri, 18 Aug 2023 10:53:54 +0200 Subject: [PATCH 05/29] chore: update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9cc88520..ecabba28b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arconnect", "displayName": "ArConnect", - "version": "1.0.0", + "version": "1.0.1", "description": "__MSG_extensionDescription__", "author": "th8ta", "packageManager": "yarn@1.22.18", From a529471d14c3a1d1cdc112b286766478ceee50f7 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Fri, 18 Aug 2023 11:27:07 +0200 Subject: [PATCH 06/29] fix: allowance reset & display --- src/api/modules/sign/allowance.ts | 4 ++++ src/components/auth/App.tsx | 24 ++++++++++++++++++------ src/routes/auth/allowance.tsx | 13 ++++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/api/modules/sign/allowance.ts b/src/api/modules/sign/allowance.ts index 151fe5003..347dfe273 100644 --- a/src/api/modules/sign/allowance.ts +++ b/src/api/modules/sign/allowance.ts @@ -69,6 +69,10 @@ export async function allowanceAuth( spendingLimitReached: !hasEnoughAllowance }); + // get updated allowance + const app = new Application(tabURL); + allowance = await app.getAllowance(); + // call this function again, to check if the allowance // was reset or updated await allowanceAuth(allowance, tabURL, price); diff --git a/src/components/auth/App.tsx b/src/components/auth/App.tsx index b1f3f3d6b..594d5ec8a 100644 --- a/src/components/auth/App.tsx +++ b/src/components/auth/App.tsx @@ -27,12 +27,22 @@ export default function App({ const spent = useMemo(() => { if (!allowance) return 0; - // calculate + return winstonToArFormatted(allowance.spent); + }, [allowance]); + + // allowance limit in AR + const limit = useMemo(() => { + if (!allowance) return 0; + + return winstonToArFormatted(allowance.limit); + }, [allowance]); + + function winstonToArFormatted(val: number) { const arweave = new Arweave(defaultGateway); - const arVal = arweave.ar.winstonToAr(allowance.limit.toString()); + const arVal = arweave.ar.winstonToAr(val.toString()); return parseFloat(arVal); - }, [allowance]); + } // display theme const theme = useDisplayTheme(); @@ -64,9 +74,11 @@ export default function App({ )) || (allowance && ( - {browser.i18n.getMessage("limit")} + {browser.i18n.getMessage("spent")} {": "} - {formatTokenBalance(spent)} + {spent.toLocaleString(undefined, { + maximumFractionDigits: 8 + })} {" AR"} ))} @@ -74,7 +86,7 @@ export default function App({ {allowance && ( - {allowance.spent} + {formatTokenBalance(limit)} {" AR"} )} diff --git a/src/routes/auth/allowance.tsx b/src/routes/auth/allowance.tsx index 1c42ce16c..466e48c27 100644 --- a/src/routes/auth/allowance.tsx +++ b/src/routes/auth/allowance.tsx @@ -17,6 +17,7 @@ import browser from "webextension-polyfill"; import Head from "~components/popup/Head"; import App from "~components/auth/App"; import Arweave from "arweave"; +import styled from "styled-components"; export default function Allowance() { const arweave = new Arweave(defaultGateway); @@ -141,7 +142,7 @@ export default function Allowance() { />
- ); } + +const NumberInput = styled(Input)` + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + appearance: textfield; +`; From bfed8317c9ac7a8606e508861d581ad919787702 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Fri, 18 Aug 2023 12:03:14 +0200 Subject: [PATCH 07/29] fix: allowances in signDataItem() --- src/api/modules/sign/sign.background.ts | 7 +++- .../sign_data_item.background.ts | 35 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/api/modules/sign/sign.background.ts b/src/api/modules/sign/sign.background.ts index df812222f..7672309db 100644 --- a/src/api/modules/sign/sign.background.ts +++ b/src/api/modules/sign/sign.background.ts @@ -87,7 +87,12 @@ const background: ModuleFunction = async ( if (allowance.enabled && activeWallet.type === "local") { // authenticate user if the allowance // limit is reached - await allowanceAuth(allowance, appData.appURL, price); + try { + await allowanceAuth(allowance, appData.appURL, price); + } catch (e) { + freeDecryptedWallet(keyfile); + throw new Error(e?.message || e); + } } else { // get address of keyfile const addr = diff --git a/src/api/modules/sign_data_item/sign_data_item.background.ts b/src/api/modules/sign_data_item/sign_data_item.background.ts index 5e9b9f916..b52eeb1b9 100644 --- a/src/api/modules/sign_data_item/sign_data_item.background.ts +++ b/src/api/modules/sign_data_item/sign_data_item.background.ts @@ -1,11 +1,14 @@ import { isLocalWallet, isRawDataItem } from "~utils/assertions"; +import { freeDecryptedWallet } from "~wallets/encryption"; import type { ModuleFunction } from "~api/background"; import { ArweaveSigner, createData } from "arbundles"; import Application from "~applications/application"; -import { allowanceAuth } from "../sign/allowance"; +import { allowanceAuth, updateAllowance } from "../sign/allowance"; import { getPrice } from "../dispatch/uploader"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; +import { signAuth } from "../sign/sign_auth"; +import Arweave from "arweave"; const background: ModuleFunction = async ( appData, @@ -29,6 +32,9 @@ const background: ModuleFunction = async ( // create app const app = new Application(appData.appURL); + // create arweave client + const arweave = new Arweave(await app.getGatewayConfig()); + // get options and data const { data, ...options } = dataItem; const binaryData = new Uint8Array(data); @@ -41,13 +47,36 @@ const background: ModuleFunction = async ( const price = await getPrice(dataEntry, await app.getBundler()); const allowance = await app.getAllowance(); - if (allowance.enabled) { - await allowanceAuth(allowance, appData.appURL, price); + try { + if (allowance.enabled) { + await allowanceAuth(allowance, appData.appURL, price); + } else { + // get address + const address = await arweave.wallets.jwkToAddress( + decryptedWallet.keyfile + ); + + await signAuth( + appData.appURL, + // @ts-expect-error + dataEntry.toJSON(), + address + ); + } + } catch (e) { + freeDecryptedWallet(decryptedWallet.keyfile); + throw new Error(e?.message || e); } // sign item await dataEntry.sign(dataSigner); + // update allowance spent amount (in winstons) + await updateAllowance(appData.appURL, price); + + // remove keyfile + freeDecryptedWallet(decryptedWallet.keyfile); + return Array.from(dataEntry.getRaw()); }; From 9d52b1e243b7f574d6b8207499a6698531a248e1 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Fri, 18 Aug 2023 12:33:36 +0200 Subject: [PATCH 08/29] fix: allowances in dispatch() --- src/api/modules/dispatch/allowance.ts | 40 +++++++++++++++++++ .../modules/dispatch/dispatch.background.ts | 35 ++++++++++++++-- .../sign_data_item.background.ts | 3 +- 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/api/modules/dispatch/allowance.ts diff --git a/src/api/modules/dispatch/allowance.ts b/src/api/modules/dispatch/allowance.ts new file mode 100644 index 000000000..bc3ddc46b --- /dev/null +++ b/src/api/modules/dispatch/allowance.ts @@ -0,0 +1,40 @@ +import { freeDecryptedWallet } from "~wallets/encryption"; +import type { Allowance } from "~applications/allowance"; +import { defaultGateway } from "~applications/gateway"; +import type { ModuleAppData } from "~api/background"; +import type { JWKInterface } from "warp-contracts"; +import { allowanceAuth } from "../sign/allowance"; +import { signAuth } from "../sign/sign_auth"; +import Arweave from "arweave"; + +/** + * Ensure allowance for dispatch + */ +export async function ensureAllowanceDispatch( + appData: ModuleAppData, + allowance: Allowance, + keyfile: JWKInterface, + price: number +) { + const arweave = new Arweave(defaultGateway); + + // allowance or sign auth + try { + if (allowance.enabled) { + await allowanceAuth(allowance, appData.appURL, price); + } else { + // get address + const address = await arweave.wallets.jwkToAddress(keyfile); + + await signAuth( + appData.appURL, + // @ts-expect-error + dataEntry.toJSON(), + address + ); + } + } catch (e) { + freeDecryptedWallet(keyfile); + throw new Error(e?.message || e); + } +} diff --git a/src/api/modules/dispatch/dispatch.background.ts b/src/api/modules/dispatch/dispatch.background.ts index 41434ca92..3be9e02b7 100644 --- a/src/api/modules/dispatch/dispatch.background.ts +++ b/src/api/modules/dispatch/dispatch.background.ts @@ -5,7 +5,7 @@ import { cleanUpChunks, getChunks } from "../sign/chunks"; import { freeDecryptedWallet } from "~wallets/encryption"; import type { ModuleFunction } from "~api/background"; import { createData, ArweaveSigner } from "arbundles"; -import { uploadDataToBundlr } from "./uploader"; +import { getPrice, uploadDataToBundlr } from "./uploader"; import type { DispatchResult } from "./index"; import { signedTxTags } from "../sign/tags"; import { getActiveKeyfile } from "~wallets"; @@ -13,6 +13,8 @@ import { isString } from "typed-assert"; import Application from "~applications/application"; import browser from "webextension-polyfill"; import Arweave from "arweave"; +import { ensureAllowanceDispatch } from "./allowance"; +import { updateAllowance } from "../sign/allowance"; type ReturnType = { arConfetti: string | false; @@ -69,16 +71,32 @@ const background: ModuleFunction = async ( // add ArConnect tags to the tag list tags.push(...signedTxTags); + // get allowance + const allowance = await app.getAllowance(); + // attempt to create a bundle try { // create bundlr tx as a data entry const dataSigner = new ArweaveSigner(keyfile); const dataEntry = createData(data, dataSigner, { tags }); + // check allowance + const price = await getPrice(dataEntry, await app.getBundler()); + + await ensureAllowanceDispatch( + appData, + allowance, + decryptedWallet.keyfile, + price + ); + // sign and upload bundler tx await dataEntry.sign(dataSigner); await uploadDataToBundlr(dataEntry, await app.getBundler()); + // update allowance spent amount (in winstons) + await updateAllowance(appData.appURL, price); + // show notification await signNotification(0, dataEntry.id, appData.appURL, "dispatch"); @@ -98,7 +116,18 @@ const background: ModuleFunction = async ( for (const arcTag of signedTxTags) { transaction.addTag(arcTag.name, arcTag.value); } + // calculate price + const price = +transaction.reward + parseInt(transaction.quantity); + + // ensure allowance + await ensureAllowanceDispatch( + appData, + allowance, + decryptedWallet.keyfile, + price + ); + // sign and upload await arweave.transactions.sign(transaction, keyfile); const uploader = await arweave.transactions.getUploader(transaction); @@ -106,8 +135,8 @@ const background: ModuleFunction = async ( await uploader.uploadChunk(); } - // calculate price - const price = +transaction.reward + parseInt(transaction.quantity); + // update allowance spent amount (in winstons) + await updateAllowance(appData.appURL, price); // show notification await signNotification(price, transaction.id, appData.appURL); diff --git a/src/api/modules/sign_data_item/sign_data_item.background.ts b/src/api/modules/sign_data_item/sign_data_item.background.ts index b52eeb1b9..b27fabef5 100644 --- a/src/api/modules/sign_data_item/sign_data_item.background.ts +++ b/src/api/modules/sign_data_item/sign_data_item.background.ts @@ -1,9 +1,9 @@ +import { allowanceAuth, updateAllowance } from "../sign/allowance"; import { isLocalWallet, isRawDataItem } from "~utils/assertions"; import { freeDecryptedWallet } from "~wallets/encryption"; import type { ModuleFunction } from "~api/background"; import { ArweaveSigner, createData } from "arbundles"; import Application from "~applications/application"; -import { allowanceAuth, updateAllowance } from "../sign/allowance"; import { getPrice } from "../dispatch/uploader"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; @@ -47,6 +47,7 @@ const background: ModuleFunction = async ( const price = await getPrice(dataEntry, await app.getBundler()); const allowance = await app.getAllowance(); + // allowance or sign auth try { if (allowance.enabled) { await allowanceAuth(allowance, appData.appURL, price); From 8ad84c79382d96313257aa6e9a5814423a1e1e4d Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Fri, 18 Aug 2023 18:46:16 +0200 Subject: [PATCH 09/29] feat: add node --- src/lib/warp.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/warp.ts b/src/lib/warp.ts index 735d9aff9..94ab7ceec 100644 --- a/src/lib/warp.ts +++ b/src/lib/warp.ts @@ -4,6 +4,7 @@ import { getSetting } from "~settings"; * List of DRE nodes supported */ export const dreNodes: Record = { + "WARP DRE-U": "https://dre-u.warp.cc", "WARP DRE-1": "https://dre-1.warp.cc", "WARP DRE-2": "https://dre-2.warp.cc", "WARP DRE-3": "https://dre-3.warp.cc", From bcb2dca6daffd2df7658e488bd056a9f51eaaef5 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Fri, 18 Aug 2023 18:47:30 +0200 Subject: [PATCH 10/29] feat: update default dre node --- src/lib/warp.ts | 2 +- src/settings/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/warp.ts b/src/lib/warp.ts index 94ab7ceec..b9e5ae91b 100644 --- a/src/lib/warp.ts +++ b/src/lib/warp.ts @@ -24,7 +24,7 @@ export async function getContract( ): Promise> { // get DRE setting const activeDREKey: string = await getSetting("dre_node").getValue(); - const dreURL = dreNodes[activeDREKey] || dreNodes[Object.keys(dreNodes)[3]]; + const dreURL = dreNodes[activeDREKey] || dreNodes[Object.keys(dreNodes)[0]]; // create call url const url = new URL("contract", dreURL); diff --git a/src/settings/index.ts b/src/settings/index.ts index 8886fa57c..2abc24919 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -39,7 +39,7 @@ const settings: Setting[] = [ description: "setting_setting_dre_node_description", type: "pick", options: Object.keys(dreNodes), - defaultValue: Object.keys(dreNodes)[3] + defaultValue: Object.keys(dreNodes)[0] }), /*new Setting({ name: "arverify", From 58c8afa57f957851e7cde400236c3df3eb975dd9 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sun, 20 Aug 2023 09:20:55 -0600 Subject: [PATCH 11/29] fix(events): fixed app config emmiter --- src/applications/events.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/applications/events.ts b/src/applications/events.ts index a5fd46642..4f4ff98cf 100644 --- a/src/applications/events.ts +++ b/src/applications/events.ts @@ -28,15 +28,30 @@ export async function appConfigChangeListener( for (const key in changes) { // continue if not an app config change and // if the app is added to the stored apps - if (!key.startsWith(PREFIX) || !storedApps.includes(key)) continue; + + // this was changed by atticus because apps are stored without a prefix + // this is a brittle check, app_ can be included in a websites URL - recommend changing this to something else. + if ( + !key.startsWith(PREFIX) || + !storedApps.includes( + key.startsWith(PREFIX) ? key.replace(PREFIX, "") : key + ) + ) + continue; // get values and app - const { oldValue, newValue } = changes[key]; + const { oldValue: storedOldValue, newValue: storedNewValue } = changes[key]; const appURL = key.replace(PREFIX, ""); const app = new Application(appURL); + // this was changed by atticus because values in storage are stored stringified + const oldValue = JSON.parse( + storedOldValue as unknown as string + ) as InitAppParams; + const newValue = JSON.parse( + storedNewValue as unknown as string + ) as InitAppParams; // check if permission event emiting is needed - // get missing permissions const missingPermissions = getMissingPermissions( oldValue?.permissions || [], @@ -62,6 +77,7 @@ export async function appConfigChangeListener( ]); // compare gateway objects + if ( !compareGateways(oldValue?.gateway || {}, newValue?.gateway || {}) && hasGwPermission @@ -76,7 +92,7 @@ export async function appConfigChangeListener( } } - // send permissins to the appropriate tab + // send permissions to the appropriate tab await forEachTab(async (tab) => { // return if no tab url is present if (!tab?.url || !tab?.id) return; @@ -85,7 +101,6 @@ export async function appConfigChangeListener( const eventsForTab = events .filter(({ appURL }) => getAppURL(tab.url) === appURL) .map((e) => e.event); - // send the events for (const event of eventsForTab) { // trigger emiter From 034b4012daab3e47c29792cb24d0c8f8fbf75bc9 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 22 Aug 2023 18:09:30 +0200 Subject: [PATCH 12/29] fix: ar:// protocol interception --- package.json | 1 - src/ar_protocol/chromium.ts | 109 ------------------------------------ src/ar_protocol/firefox.ts | 55 ------------------ src/ar_protocol/index.ts | 52 ++++++++--------- src/ar_protocol/parser.ts | 10 +++- src/background.ts | 4 +- 6 files changed, 32 insertions(+), 199 deletions(-) delete mode 100644 src/ar_protocol/chromium.ts delete mode 100644 src/ar_protocol/firefox.ts diff --git a/package.json b/package.json index ecabba28b..83d64c23c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "alarms", "contextMenus", "tabs", - "declarativeNetRequest", "webNavigation", "notifications", "" diff --git a/src/ar_protocol/chromium.ts b/src/ar_protocol/chromium.ts deleted file mode 100644 index 0f8f7c741..000000000 --- a/src/ar_protocol/chromium.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { Gateway } from "~applications/gateway"; -import { getRedirectURL } from "./parser"; -import browser from "webextension-polyfill"; - -/** - * Add handler for the "ar://" protocol for - * chromium based browsers. - */ -export default async function addChromiumHandler() { - // add interceptor - await addInterceptor(); - - // add middleware for parsing the request - await addMiddleware(); -} - -const INTERCEPTOR_ID = 1; - -/** - * Add chrome.declarativeNetRequest based interceptor, - * that we use to intercept requests to the "ar://" - * protocol. - */ -async function addInterceptor() { - // remove active rules - await disableHandler(); - - // add updated rules - await chrome.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: INTERCEPTOR_ID, - priority: 1, - action: { - type: chrome.declarativeNetRequest.RuleActionType.REDIRECT, - redirect: { - // redirect to the middleware that parses the request - // and redirects to the appropriate arweave URL - regexSubstitution: `${browser.runtime.getURL("/redirect")}\\1` - } - }, - condition: { - // filter requests to ar:// protocol. - // Protocol handler created based on the - // issue in ipfs/ipfs-companion: - // https://github.com/ipfs/ipfs-companion/issues/164#issuecomment-328374052 - // - // basically if the protocol is unrecognised, the browser - // creates a search query, containing the urlencoded "ar://" - // string - // we filter requests that include this encoded string here - regexFilter: `^https://.*/.*(\\?.*${encodeURIComponent("ar://")}.*)`, - resourceTypes: [ - chrome.declarativeNetRequest.ResourceType.MAIN_FRAME, - chrome.declarativeNetRequest.ResourceType.SUB_FRAME, - chrome.declarativeNetRequest.ResourceType.XMLHTTPREQUEST, - chrome.declarativeNetRequest.ResourceType.OTHER - ] - } - } - ] - }); -} - -/** - * Disable handler for "ar://" protocol - */ -export async function disableHandler() { - // remove active rules - await chrome.declarativeNetRequest.updateSessionRules({ - removeRuleIds: [INTERCEPTOR_ID] - }); -} - -/** - * Add middleware that redirects to - * the appropriate arweave tx, permapage - * URL or ANS name - */ -async function addMiddleware() { - // TODO: get active gateway - const middleware = createMiddleware({ - host: "arweave.net", - port: 443, - protocol: "https" - }); - - removeEventListener("fetch", middleware); - addEventListener("fetch", middleware); -} - -const createMiddleware = (gateway: Gateway) => - async function (e: FetchEvent) { - const url = new URL(e.request.url); - - // check host - if (url.host !== browser.runtime.id) return; - - // check path - if (url.pathname !== "/redirect") return; - - // parse redirect url - const redirectURL = getRedirectURL(url, gateway); - - if (!redirectURL) return; - - // send redirect response - e.respondWith(Response.redirect(redirectURL, 302)); - }; diff --git a/src/ar_protocol/firefox.ts b/src/ar_protocol/firefox.ts deleted file mode 100644 index b93be59b1..000000000 --- a/src/ar_protocol/firefox.ts +++ /dev/null @@ -1,55 +0,0 @@ -import browser, { type WebRequest } from "webextension-polyfill"; -import type { Gateway } from "~applications/gateway"; -import { getRedirectURL } from "./parser"; - -/** - * Add handler for the "ar://" protocol for - * firefox based browsers. - */ -export default async function addFirefoxHandler() { - browser.webRequest.onBeforeRequest.addListener( - middleware, - { urls: [""] }, - ["blocking"] - ); -} - -/** - * Disable handler for "ar://" protocol - */ -export async function disableHandler() { - if (!browser.webRequest.onBeforeRequest.hasListener(middleware)) { - return; - } - - browser.webRequest.onBeforeRequest.removeListener(middleware); -} - -/** - * Add middleware that redirects to - * the appropriate arweave tx, permapage - * URL or ANS name - */ -async function middleware( - details: WebRequest.OnBeforeRequestDetailsType -): Promise { - if (!mayContainArProtocol(details)) return; - - const url = new URL(details.url); - // TODO: get active gateway - const gateway: Gateway = { - host: "arweave.net", - port: 443, - protocol: "https" - }; - - // parse redirect url - const redirectUrl = getRedirectURL(url, gateway); - - if (!redirectUrl) return; - - return { redirectUrl }; -} - -const mayContainArProtocol = (request: WebRequest.OnBeforeRequestDetailsType) => - request.url.includes(encodeURIComponent("ar://")); diff --git a/src/ar_protocol/index.ts b/src/ar_protocol/index.ts index b21eb56dd..349173f13 100644 --- a/src/ar_protocol/index.ts +++ b/src/ar_protocol/index.ts @@ -1,38 +1,32 @@ -import addChromiumHandler, { - disableHandler as disableChromiumHandler -} from "./chromium"; -import addFirefoxHandler, { - disableHandler as disableFirefoxHandler -} from "./firefox"; +import browser, { type WebNavigation } from "webextension-polyfill"; +import { defaultGateway } from "~applications/gateway"; +import { getRedirectURL } from "./parser"; /** * Handle custom ar:// protocol, using the - * browser.webRequest.onBeforeRequest API - * or for MV3 the declarativeNetRequest and - * service worker fetch event APIs. + * browser.webNavigation.onBeforeNavigate API. * - * Protocol handler created based on the - * issue in ipfs/ipfs-companion: + * This is based on the issue in ipfs/ipfs-companion: * https://github.com/ipfs/ipfs-companion/issues/164#issuecomment-328374052 + * + * Thank you ar.io for the updated method: + * https://github.com/ar-io/wayfinder/blob/main/background.js#L13 */ -export default async function registerProtocolHandler() { - // register for chromium-based browsers - if (chrome) { - return await addChromiumHandler(); - } - - // register for MV2-based browsers - await addFirefoxHandler(); -} +export default async function protocolHandler( + details: WebNavigation.OnBeforeNavigateDetailsType +) { + // parse redirect url + const redirectUrl = getRedirectURL( + new URL(details.url), + // TODO: use central gateway config for this + defaultGateway + ); -/** - * Disable ar:// protocol handling - */ -export async function unregisterProtocolHandler() { - // unregister for chromium-based browsers - if (chrome) { - return await disableChromiumHandler(); - } + // don't do anything if it is not a protocol call + if (!redirectUrl) return; - await disableFirefoxHandler(); + // update tab + browser.tabs.update(details.tabId, { + url: redirectUrl + }); } diff --git a/src/ar_protocol/parser.ts b/src/ar_protocol/parser.ts index b93ce34de..cae1ad9b7 100644 --- a/src/ar_protocol/parser.ts +++ b/src/ar_protocol/parser.ts @@ -1,6 +1,10 @@ import { concatGatewayURL, type Gateway } from "~applications/gateway"; import { isAddressFormat } from "~utils/format"; +/** + * Get gateway redirect URL for ar:// protocol + * or return false if it is not a protocol request. + */ export function getRedirectURL(url: URL, gateway: Gateway) { let redirectURL: string; @@ -8,21 +12,21 @@ export function getRedirectURL(url: URL, gateway: Gateway) { const arURL = url.searchParams.get("q"); if (!arURL || arURL === "") { - return redirectURL; + return false; } // value (address / permapage / id) const value = arURL.replace("ar://", ""); if (!value || value === "") { - return redirectURL; + return false; } redirectURL = concatGatewayURL(gateway) + "/" + value; // if it is not an Arweave ID, redirect to permapages if (!isAddressFormat(value)) { - redirectURL = `https://${value}.arweave.dev`; + redirectURL = `${gateway.protocol}://${value}.${gateway.host}:${gateway.port}`; } return redirectURL; diff --git a/src/background.ts b/src/background.ts index 93ceab681..2c30cbc2f 100644 --- a/src/background.ts +++ b/src/background.ts @@ -8,9 +8,9 @@ import { appsChangeListener } from "~applications"; import { ExtensionStorage } from "~utils/storage"; import { onInstalled } from "~utils/runtime"; import { syncLabels } from "~wallets"; -import registerProtocolHandler from "~ar_protocol"; import handleFeeAlarm from "~api/modules/sign/fee"; import browser from "webextension-polyfill"; +import protocolHandler from "~ar_protocol"; // watch for API calls onMessage("api_call", handleApiCalls); @@ -51,6 +51,6 @@ browser.storage.onChanged.addListener(appConfigChangeListener); browser.runtime.onInstalled.addListener(onInstalled); // handle ar:// protocol -registerProtocolHandler(); +browser.webNavigation.onBeforeNavigate.addListener(protocolHandler); export {}; From 5c07616d22f4602ec322db57648020fbc7dc67e9 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 22 Aug 2023 18:18:32 +0200 Subject: [PATCH 13/29] fix: redirect url Co-authored-by: Phil --- src/ar_protocol/index.ts | 10 ++++++++-- src/ar_protocol/parser.ts | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ar_protocol/index.ts b/src/ar_protocol/index.ts index 349173f13..87978a7d2 100644 --- a/src/ar_protocol/index.ts +++ b/src/ar_protocol/index.ts @@ -1,5 +1,5 @@ import browser, { type WebNavigation } from "webextension-polyfill"; -import { defaultGateway } from "~applications/gateway"; +import type { Gateway } from "~applications/gateway"; import { getRedirectURL } from "./parser"; /** @@ -15,11 +15,17 @@ import { getRedirectURL } from "./parser"; export default async function protocolHandler( details: WebNavigation.OnBeforeNavigateDetailsType ) { + const devGateway: Gateway = { + host: "arweave.dev", + port: 443, + protocol: "https" + }; + // parse redirect url const redirectUrl = getRedirectURL( new URL(details.url), // TODO: use central gateway config for this - defaultGateway + devGateway ); // don't do anything if it is not a protocol call diff --git a/src/ar_protocol/parser.ts b/src/ar_protocol/parser.ts index cae1ad9b7..2fb3d528a 100644 --- a/src/ar_protocol/parser.ts +++ b/src/ar_protocol/parser.ts @@ -1,6 +1,8 @@ import { concatGatewayURL, type Gateway } from "~applications/gateway"; import { isAddressFormat } from "~utils/format"; +const PROTOCOL_PREFIX = "ar"; + /** * Get gateway redirect URL for ar:// protocol * or return false if it is not a protocol request. @@ -11,12 +13,12 @@ export function getRedirectURL(url: URL, gateway: Gateway) { // "ar://" url const arURL = url.searchParams.get("q"); - if (!arURL || arURL === "") { + if (!arURL || arURL === "" || !arURL.includes(`${PROTOCOL_PREFIX}://`)) { return false; } // value (address / permapage / id) - const value = arURL.replace("ar://", ""); + const value = arURL.replace(`${PROTOCOL_PREFIX}://`, ""); if (!value || value === "") { return false; From 461641af6fee9e228f1feea7e8d4b6588d38afe1 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 22 Aug 2023 18:23:54 +0200 Subject: [PATCH 14/29] fix: ar:// protocol paths --- src/ar_protocol/parser.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ar_protocol/parser.ts b/src/ar_protocol/parser.ts index 2fb3d528a..0c04cdbe6 100644 --- a/src/ar_protocol/parser.ts +++ b/src/ar_protocol/parser.ts @@ -28,7 +28,13 @@ export function getRedirectURL(url: URL, gateway: Gateway) { // if it is not an Arweave ID, redirect to permapages if (!isAddressFormat(value)) { - redirectURL = `${gateway.protocol}://${value}.${gateway.host}:${gateway.port}`; + // split path + const paths = value.split("/"); + + redirectURL = + `${gateway.protocol}://` + + `${paths[0]}.${gateway.host}:${gateway.port}` + + `/${paths.slice(1, paths.length).join("/")}`; } return redirectURL; From bd4a5061b6cef04cdfa9614e2f77f5f1fdc4bf58 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 10:36:05 +0200 Subject: [PATCH 15/29] fix: token state validation and move it into its own assertion --- src/tokens/index.ts | 9 +++++---- src/tokens/token.ts | 32 -------------------------------- src/utils/assertions.ts | 31 ++++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/tokens/index.ts b/src/tokens/index.ts index 3b61aeaec..c643fa800 100644 --- a/src/tokens/index.ts +++ b/src/tokens/index.ts @@ -2,6 +2,7 @@ import type { EvalStateResult } from "warp-contracts"; import type { Gateway } from "~applications/gateway"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; +import { isTokenState } from "~utils/assertions"; import { useEffect, useState } from "react"; import { getActiveAddress } from "~wallets"; import { getContract } from "~lib/warp"; @@ -9,8 +10,7 @@ import { getSettings, type Token, type TokenState, - type TokenType, - validateTokenState + type TokenType } from "./token"; /** Default tokens */ @@ -56,9 +56,10 @@ export async function getTokens() { * @param id ID of the token contract */ export async function addToken(id: string, type: TokenType, gateway?: Gateway) { - const { state } = await getContract(id); + const { state } = await getContract(id); - validateTokenState(state); + // validate state + isTokenState(state); // add token const activeAddress = await getActiveAddress(); diff --git a/src/tokens/token.ts b/src/tokens/token.ts index df84057f4..0a6473199 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -31,38 +31,6 @@ export interface TokenState { export type TokenType = "asset" | "collectible"; -/** - * Check if a contract state is a - * valid token state - */ -export function validateTokenState(state: TokenState) { - if (!state) { - throw new Error("No state for token"); - } - - if (!state.ticker || typeof state.ticker !== "string") { - throw new Error("Invalid ticker"); - } - - if (!state.balances) { - throw new Error("No balances object"); - } - - for (const address in state.balances) { - if (typeof address !== "string") { - throw new Error( - "Balances object contains an invalid address that is not a string" - ); - } - - if (typeof state.balances[address] !== "number") { - throw new Error( - "Balances object contains an invalid balance that is not a number" - ); - } - } -} - /** * Get the initial state of a contract * diff --git a/src/utils/assertions.ts b/src/utils/assertions.ts index 51217c571..eb0432e51 100644 --- a/src/utils/assertions.ts +++ b/src/utils/assertions.ts @@ -5,13 +5,13 @@ import type { SignMessageOptions } from "~api/modules/sign_message/types"; import type { SignatureAlgorithm } from "~api/modules/signature/types"; import type { RawDataItem } from "~api/modules/sign_data_item/types"; import type { DecryptedWallet, LocalWallet } from "~wallets"; +import type { TokenState, TokenType } from "~tokens/token"; import type { JWKInterface } from "arweave/web/lib/wallet"; import type Transaction from "arweave/web/lib/transaction"; import type { DecodedTag } from "~api/modules/sign/tags"; import type { AppInfo } from "~applications/application"; import type { Chunk } from "~api/modules/sign/chunks"; import type { Gateway } from "~applications/gateway"; -import type { TokenType } from "~tokens/token"; import { isAddressFormat } from "./format"; import type { EncryptionAlgorithm, @@ -243,3 +243,32 @@ export function isSignatureAlgorithm( ): asserts input is SignatureAlgorithm { isEncryptionAlgorithm(input); } + +export function isNull( + input: unknown, + message?: string +): asserts input is null { + assert(input === null, message); +} + +export function isUndefined( + input: unknown, + message?: string +): asserts input is undefined { + assert(typeof input === "undefined", message); +} + +export function isValidBalance(input: unknown): asserts input is number { + isOneOfType(input, [isNumber, isNull, isUndefined], "Invalid balance."); +} + +export function isTokenState(input: unknown): asserts input is TokenState { + isRecord(input, "Invalid or no token state."); + isString(input.ticker, "Invalid token ticker: not a string."); + isRecord(input.balances, "Invalid balances object: not a record."); + + for (const address in input.balances) { + isAddress(address); + isValidBalance(input.balances[address]); + } +} From 4ef06702b9516d699cc8fc3ad38611eb092bee12 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 11:37:53 +0200 Subject: [PATCH 16/29] fix: one token breaks all token loading --- src/tokens/index.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tokens/index.ts b/src/tokens/index.ts index c643fa800..092445fa0 100644 --- a/src/tokens/index.ts +++ b/src/tokens/index.ts @@ -127,15 +127,17 @@ export function useTokens() { setTokens( await Promise.all( tokens.map(async (token) => { - // get state - const { state } = await getContract(token.id); + try { + // get state + const { state } = await getContract(token.id); - // parse settings - const settings = getSettings(state); + // parse settings + const settings = getSettings(state); - token.balance = state.balances[activeAddress] || 0; - token.divisibility = state.divisibility; - token.defaultLogo = settings.get("communityLogo"); + token.balance = state.balances[activeAddress] || 0; + token.divisibility = state.divisibility; + token.defaultLogo = settings.get("communityLogo"); + } catch {} return token; }) From af3c44a003409a72fb553959f57fba19c8373850 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 13:02:11 +0200 Subject: [PATCH 17/29] feat: revamp decimals/divisibilities --- src/components/popup/Collectible.tsx | 14 +++- src/components/popup/Token.tsx | 22 ++++-- src/components/popup/home/Collectibles.tsx | 1 + src/routes/popup/collectibles.tsx | 1 + src/routes/popup/send/index.tsx | 28 ++++++-- src/routes/popup/token/[id].tsx | 15 ++-- src/tokens/currency.ts | 82 ++++++++++++++++++++++ src/tokens/token.ts | 1 + 8 files changed, 146 insertions(+), 18 deletions(-) diff --git a/src/components/popup/Collectible.tsx b/src/components/popup/Collectible.tsx index 4f9b63ff1..49335ddaf 100644 --- a/src/components/popup/Collectible.tsx +++ b/src/components/popup/Collectible.tsx @@ -1,14 +1,21 @@ import { concatGatewayURL, defaultGateway } from "~applications/gateway"; import { type MouseEventHandler, useMemo } from "react"; -import { formatTokenBalance } from "~tokens/currency"; +import { formatTokenBalance, balanceToFractioned } from "~tokens/currency"; import { hoverEffect } from "~utils/theme"; import styled from "styled-components"; export default function Collectible({ id, onClick, ...props }: Props) { // balance const balance = useMemo( - () => formatTokenBalance((props.balance || 0) / (props.divisibility || 1)), - [props] + () => + formatTokenBalance( + balanceToFractioned(props.balance, { + id, + decimals: props.decimals, + divisibility: props.divisibility + }) + ), + [props, id] ); return ( @@ -82,5 +89,6 @@ interface Props { name: string; balance: number; divisibility?: number; + decimals?: number; onClick?: MouseEventHandler; } diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 26ecf9692..04f7dd9a7 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -1,4 +1,8 @@ -import { formatFiatBalance, formatTokenBalance } from "~tokens/currency"; +import { + formatFiatBalance, + formatTokenBalance, + balanceToFractioned +} from "~tokens/currency"; import { type MouseEventHandler, useEffect, useMemo, useState } from "react"; import { defaultGateway } from "~applications/gateway"; import { hoverEffect, useTheme } from "~utils/theme"; @@ -21,11 +25,21 @@ export default function Token({ onClick, ...props }: Props) { const theme = useTheme(); // token balance - const balance = useMemo( - () => formatTokenBalance(props.balance / (props.divisibility || 1)), + const fractBalance = useMemo( + () => + balanceToFractioned(props.balance, { + id: props.id, + decimals: props.decimals, + divisibility: props.divisibility + }), [props] ); + const balance = useMemo( + () => formatTokenBalance(fractBalance), + [fractBalance] + ); + // token price const { price, currency } = usePrice(props.ticker); @@ -33,7 +47,7 @@ export default function Token({ onClick, ...props }: Props) { const fiatBalance = useMemo(() => { if (!price) return undefined; - return (props.balance / (props.divisibility || 1)) * price; + return fractBalance * price; }, [price, balance]); // token logo diff --git a/src/components/popup/home/Collectibles.tsx b/src/components/popup/home/Collectibles.tsx index a3b4b2f82..63d07f0cb 100644 --- a/src/components/popup/home/Collectibles.tsx +++ b/src/components/popup/home/Collectibles.tsx @@ -45,6 +45,7 @@ export default function Collectibles() { name={collectible.name || collectible.ticker} balance={collectible.balance} divisibility={collectible.divisibility} + decimals={collectible.decimals} onClick={() => push(`/collectible/${collectible.id}`)} key={i} /> diff --git a/src/routes/popup/collectibles.tsx b/src/routes/popup/collectibles.tsx index 8a0e27b57..281a6af96 100644 --- a/src/routes/popup/collectibles.tsx +++ b/src/routes/popup/collectibles.tsx @@ -30,6 +30,7 @@ export default function Collectibles() { name={collectible.name || collectible.ticker} balance={collectible.balance} divisibility={collectible.divisibility} + decimals={collectible.decimals} onClick={() => push(`/collectible/${collectible.id}`)} key={i} /> diff --git a/src/routes/popup/send/index.tsx b/src/routes/popup/send/index.tsx index 48742e562..76702fbb8 100644 --- a/src/routes/popup/send/index.tsx +++ b/src/routes/popup/send/index.tsx @@ -1,10 +1,15 @@ +import { + balanceToFractioned, + formatFiatBalance, + fractionedToBalance, + getDecimals +} from "~tokens/currency"; import { InputWithBtn, InputWrapper } from "~components/arlocal/InputWrapper"; import { getAnsProfile, type AnsUser, getAnsProfileByLabel } from "~lib/ans"; import { concatGatewayURL, defaultGateway } from "~applications/gateway"; import { motion, AnimatePresence, type Variants } from "framer-motion"; import { formatAddress, isAddressFormat } from "~utils/format"; import { useState, useEffect, useMemo } from "react"; -import { formatFiatBalance } from "~tokens/currency"; import { useStorage } from "@plasmohq/storage/hook"; import { IconButton } from "~components/IconButton"; import type { TokenState } from "~tokens/token"; @@ -187,20 +192,28 @@ export default function Send({ id }: Props) { instance: ExtensionStorage }); - // divisibility for PSTs - const [divisibility, setDivisibility] = useState(1); + // decimals for PSTs + const [decimals, setDecimals] = useState(0); useEffect(() => { (async () => { if (selectedToken === "AR") { setBalance(arBalance); - return setDivisibility(1); + return setDecimals(0); } + // fetch token state const { state } = await getContract(id); + const parsedDecimals = getDecimals({ + id, + decimals: state.decimals, + divisibility: state.divisibility + }); - setDivisibility(state.divisibility || 1); - setBalance(state.balances[activeAddress] / (state.divisibility || 1)); + setDecimals(parsedDecimals); + setBalance( + balanceToFractioned(state.balances[activeAddress], { decimals }) + ); })(); }, [selectedToken, arBalance, activeAddress]); @@ -266,7 +279,7 @@ export default function Send({ id }: Props) { JSON.stringify({ function: "transfer", target: target.address, - qty: Number(amount) * divisibility + qty: fractionedToBalance(Number(amount), { decimals }) }) ); } @@ -583,6 +596,7 @@ export default function Send({ id }: Props) { name={token.name || token.ticker} balance={token.balance} divisibility={token.divisibility} + decimals={token.decimals} onClick={() => { setSelectedToken(token.id); setShownTokenSelector(false); diff --git a/src/routes/popup/token/[id].tsx b/src/routes/popup/token/[id].tsx index c4dc563d7..288f1a286 100644 --- a/src/routes/popup/token/[id].tsx +++ b/src/routes/popup/token/[id].tsx @@ -1,4 +1,8 @@ -import { formatFiatBalance, formatTokenBalance } from "~tokens/currency"; +import { + balanceToFractioned, + formatFiatBalance, + formatTokenBalance +} from "~tokens/currency"; import { concatGatewayURL, defaultGateway } from "~applications/gateway"; import { AnimatePresence, motion, type Variants } from "framer-motion"; import { Loading, Section, Spacer, Text } from "@arconnect/components"; @@ -88,11 +92,14 @@ export default function Asset({ id }: Props) { const tokenBalance = useMemo(() => { if (!state) return "0"; - const val = - (state.balances?.[activeAddress] || 0) / (state.divisibility || 1); + const val = balanceToFractioned(state.balances?.[activeAddress] || 0, { + id, + decimals: state.decimals, + divisibility: state.divisibility + }); return formatTokenBalance(val); - }, [state, activeAddress]); + }, [id, state, activeAddress]); // router push const [push] = useHistory(); diff --git a/src/tokens/currency.ts b/src/tokens/currency.ts index 275bee460..bfe70bcaa 100644 --- a/src/tokens/currency.ts +++ b/src/tokens/currency.ts @@ -30,3 +30,85 @@ export function formatFiatBalance(balance: string | number, currency?: string) { currency: currency?.toLowerCase() }); } + +/** + * Manual config for legacy token decimals + */ +const MANUAL_DECIMALS = { + "TlqASNDLA1Uh8yFiH-BzR_1FDag4s735F3PoUFEv2Mo": 12 +}; + +/** + * Get decimals for a token. This can be used later + * to adjust a wallet balance from the token state. + */ +export function getDecimals(cfg: DivisibilityOrDecimals) { + // manually adjust if ID is provided + if (Object.keys(MANUAL_DECIMALS).includes(cfg.id)) { + return MANUAL_DECIMALS[cfg.id]; + } + + let decimals = cfg.decimals || 0; + + // no fractions support + if ( + (!cfg.divisibility && !decimals) || + cfg.divisibility <= 0 || + decimals < 0 + ) { + return 0; + } + + // handle legacy divisibility field + if (cfg.divisibility) { + if (cfg.divisibility % 10 === 0) { + decimals = Math.log10(cfg.divisibility); + } else { + decimals = cfg.divisibility; + } + } + + return decimals; +} + +/** + * Adjust token balance with fractions. + * + * Some legacy tokens are need to be manually updated to support this. + * See the specs at specs.arweave.dev + */ +export function balanceToFractioned( + balance: number, + cfg: DivisibilityOrDecimals +) { + if (!balance) return 0; + + // parse decimals + const decimals = getDecimals(cfg); + + // divide base balance using the decimals + return balance / Math.pow(10, decimals); +} + +/** + * Convert displayed (fractioned) token balance back to + * the units used by the contract. + */ +export function fractionedToBalance( + balance: number, + cfg: DivisibilityOrDecimals +) { + if (!balance) return 0; + + // parse decimals + const decimals = getDecimals(cfg); + + // multiply fractioned balance using the decimals + return balance * Math.pow(10, decimals); +} + +interface DivisibilityOrDecimals { + id?: string; + decimals?: number; + divisibility?: number; +} diff --git a/src/tokens/token.ts b/src/tokens/token.ts index 0a6473199..824d1a094 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -19,6 +19,7 @@ export interface Token { gateway?: Gateway; balance: number; divisibility?: number; + decimals?: number; defaultLogo?: string; } From 5b9f83bcd9fab3deafa0a896e451fba399012ff3 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 15:03:06 +0200 Subject: [PATCH 18/29] feat: finish up decimals --- src/routes/popup/token/[id].tsx | 11 +++++++---- src/tokens/currency.ts | 5 ++++- src/tokens/index.ts | 2 ++ src/tokens/token.ts | 10 +++++++--- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/routes/popup/token/[id].tsx b/src/routes/popup/token/[id].tsx index 288f1a286..248f641f7 100644 --- a/src/routes/popup/token/[id].tsx +++ b/src/routes/popup/token/[id].tsx @@ -92,7 +92,7 @@ export default function Asset({ id }: Props) { const tokenBalance = useMemo(() => { if (!state) return "0"; - const val = balanceToFractioned(state.balances?.[activeAddress] || 0, { + const val = balanceToFractioned(state.balances?.[activeAddress], { id, decimals: state.decimals, divisibility: state.divisibility @@ -171,11 +171,14 @@ export default function Asset({ id }: Props) { return `?? ${currency.toUpperCase()}`; } - const bal = - (state.balances?.[activeAddress] || 0) / (state.divisibility || 1); + const bal = balanceToFractioned(state.balances?.[activeAddress], { + id, + decimals: state.decimals, + divisibility: state.divisibility + }); return formatFiatBalance(price * bal, currency); - }, [state, activeAddress, price, currency]); + }, [id, state, activeAddress, price, currency]); const [priceWarningShown, setPriceWarningShown] = useStorage({ key: "price_warning_shown", diff --git a/src/tokens/currency.ts b/src/tokens/currency.ts index bfe70bcaa..c482b9f01 100644 --- a/src/tokens/currency.ts +++ b/src/tokens/currency.ts @@ -43,6 +43,9 @@ const MANUAL_DECIMALS = { * to adjust a wallet balance from the token state. */ export function getDecimals(cfg: DivisibilityOrDecimals) { + // if there is no config, there are no decimals + if (!cfg) return 0; + // manually adjust if ID is provided if (Object.keys(MANUAL_DECIMALS).includes(cfg.id)) { return MANUAL_DECIMALS[cfg.id]; @@ -107,7 +110,7 @@ export function fractionedToBalance( return balance * Math.pow(10, decimals); } -interface DivisibilityOrDecimals { +export interface DivisibilityOrDecimals { id?: string; decimals?: number; divisibility?: number; diff --git a/src/tokens/index.ts b/src/tokens/index.ts index 092445fa0..f093554f3 100644 --- a/src/tokens/index.ts +++ b/src/tokens/index.ts @@ -82,6 +82,7 @@ export async function addToken(id: string, type: TokenType, gateway?: Gateway) { gateway, balance: state.balances[activeAddress] || 0, divisibility: state.divisibility, + decimals: state.decimals, defaultLogo: settings.get("communityLogo") as string }); await ExtensionStorage.set("tokens", tokens); @@ -136,6 +137,7 @@ export function useTokens() { token.balance = state.balances[activeAddress] || 0; token.divisibility = state.divisibility; + token.decimals = state.decimals; token.defaultLogo = settings.get("communityLogo"); } catch {} diff --git a/src/tokens/token.ts b/src/tokens/token.ts index 824d1a094..8e4e7030e 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -1,6 +1,10 @@ +import { + formatTokenBalance, + type DivisibilityOrDecimals, + balanceToFractioned +} from "./currency"; import type { GQLEdgeInterface } from "ar-gql/dist/faces"; import type { DisplayTheme } from "@arconnect/components"; -import { formatTokenBalance } from "./currency"; import * as viewblock from "~lib/viewblock"; import { concatGatewayURL, @@ -197,7 +201,7 @@ export function parseInteractions( interactions: GQLEdgeInterface[], activeAddress: string, ticker?: string, - divisibility?: number + fractionsCfg?: DivisibilityOrDecimals ): TokenInteraction[] { return interactions.map((tx) => { // interaction input @@ -213,7 +217,7 @@ export function parseInteractions( if (input.function === "transfer") { type = (recipient === activeAddress && "in") || "out"; - qty = Number(input.qty) / (divisibility || 1); + qty = balanceToFractioned(Number(input.qty), fractionsCfg); otherAddress = (recipient === activeAddress && tx.node.owner.address) || recipient; } From 759b4ed05163cdf08065a9bd657abe48df577d17 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 15:37:21 +0200 Subject: [PATCH 19/29] fix: decimals in send token --- src/routes/popup/send/index.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/routes/popup/send/index.tsx b/src/routes/popup/send/index.tsx index 76702fbb8..c03e155e1 100644 --- a/src/routes/popup/send/index.tsx +++ b/src/routes/popup/send/index.tsx @@ -203,9 +203,9 @@ export default function Send({ id }: Props) { } // fetch token state - const { state } = await getContract(id); + const { state } = await getContract(selectedToken); const parsedDecimals = getDecimals({ - id, + id: selectedToken, decimals: state.decimals, divisibility: state.divisibility }); @@ -226,9 +226,7 @@ export default function Send({ id }: Props) { // send tx async function send() { // return if target is undefined - if (!target) { - return; - } + if (!target) return; // check amount const amountInt = Number(amount); @@ -255,7 +253,7 @@ export default function Send({ id }: Props) { let arweave = new Arweave(defaultGateway); let tx = await arweave.createTransaction({ target: target.address, - quantity: arweave.ar.arToWinston(amount.toString()) + quantity: arweave.ar.arToWinston(amount) }); if (selectedToken !== "AR") { @@ -279,7 +277,7 @@ export default function Send({ id }: Props) { JSON.stringify({ function: "transfer", target: target.address, - qty: fractionedToBalance(Number(amount), { decimals }) + qty: fractionedToBalance(amountInt, { decimals }) }) ); } From 9b7f4291b5f156c0a7f41d4f937c5fa2d09159df Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 15:55:38 +0200 Subject: [PATCH 20/29] fix: collectible positioning --- src/components/popup/Collectible.tsx | 1 + src/components/popup/asset/Thumbnail.tsx | 28 ++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/components/popup/Collectible.tsx b/src/components/popup/Collectible.tsx index 49335ddaf..add7b46f1 100644 --- a/src/components/popup/Collectible.tsx +++ b/src/components/popup/Collectible.tsx @@ -52,6 +52,7 @@ const Image = styled.div<{ src: string }>` position: relative; background-image: url(${(props) => props.src}); background-size: cover; + background-position: center; padding-top: 100%; border-radius: 12px; `; diff --git a/src/components/popup/asset/Thumbnail.tsx b/src/components/popup/asset/Thumbnail.tsx index 90f54ee86..34fc41dad 100644 --- a/src/components/popup/asset/Thumbnail.tsx +++ b/src/components/popup/asset/Thumbnail.tsx @@ -2,21 +2,39 @@ import type { DisplayTheme } from "@arconnect/components"; import { MaximizeIcon } from "@iconicicons/react"; import { useTheme } from "~utils/theme"; import styled from "styled-components"; +import { useState } from "react"; export default function Thumbnail({ src }: Props) { // display theme const theme = useTheme(); + // full view + const [fullView, setFullView] = useState(false); + return ( - - + setFullView((v) => !v)} + fullView={fullView} + > + e.stopPropagation()} + > ); } -const ThumbnailImage = styled.div<{ displayTheme: DisplayTheme; src: string }>` +const ThumbnailImage = styled.div<{ + displayTheme: DisplayTheme; + src: string; + fullView: boolean; +}>` position: relative; width: 100%; padding-top: 100%; @@ -24,8 +42,10 @@ const ThumbnailImage = styled.div<{ displayTheme: DisplayTheme; src: string }>` ${(props) => props.displayTheme === "light" ? "0, 0, 0" : props.theme.cardBackground} ); - background-size: cover; + background-size: ${(props) => (props.fullView ? "contain" : "cover")}; background-image: url(${(props) => props.src}); + background-position: center; + background-repeat: no-repeat; overflow: hidden; `; From 0c6f1eb1565adec55c2a59d2203044254e315c9b Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 16:06:32 +0200 Subject: [PATCH 21/29] feat: bazar link --- src/routes/popup/collectible/[id].tsx | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/routes/popup/collectible/[id].tsx b/src/routes/popup/collectible/[id].tsx index fa583d7fc..867f6626a 100644 --- a/src/routes/popup/collectible/[id].tsx +++ b/src/routes/popup/collectible/[id].tsx @@ -116,6 +116,11 @@ export default function Collectible({ id }: Props) { )} {(!loading && ( <> + + + Bazar + + Sonar @@ -157,3 +162,44 @@ const Description = styled(Text).attrs({ font-size: 0.9rem; text-align: justify; `; + +const BazarIcon = () => ( + + + + + + + + + + + + +); From a8729b4ecd66b5dd958c3a9c4e1841c29cbc7d7b Mon Sep 17 00:00:00 2001 From: Atticus Date: Thu, 24 Aug 2023 09:22:40 -0600 Subject: [PATCH 22/29] Update src/applications/events.ts Co-authored-by: Marton Lederer <30638105+martonlederer@users.noreply.github.com> --- src/applications/events.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/applications/events.ts b/src/applications/events.ts index 4f4ff98cf..487972c0e 100644 --- a/src/applications/events.ts +++ b/src/applications/events.ts @@ -33,9 +33,7 @@ export async function appConfigChangeListener( // this is a brittle check, app_ can be included in a websites URL - recommend changing this to something else. if ( !key.startsWith(PREFIX) || - !storedApps.includes( - key.startsWith(PREFIX) ? key.replace(PREFIX, "") : key - ) + !storedApps.includes(key.replace(PREFIX, "")) ) continue; From 336bafd87c0ba05b6164a0f4e44038abdebc4cd6 Mon Sep 17 00:00:00 2001 From: Atticus Date: Thu, 24 Aug 2023 09:24:47 -0600 Subject: [PATCH 23/29] remove comment for old check --- src/applications/events.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/applications/events.ts b/src/applications/events.ts index 487972c0e..eb08d408a 100644 --- a/src/applications/events.ts +++ b/src/applications/events.ts @@ -29,8 +29,6 @@ export async function appConfigChangeListener( // continue if not an app config change and // if the app is added to the stored apps - // this was changed by atticus because apps are stored without a prefix - // this is a brittle check, app_ can be included in a websites URL - recommend changing this to something else. if ( !key.startsWith(PREFIX) || !storedApps.includes(key.replace(PREFIX, "")) From 3156cad894b42d564aed9db5f90d9f3812096e62 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 20:27:24 +0200 Subject: [PATCH 24/29] feat: more tokens on the homepage and smaller token components --- src/components/popup/Token.tsx | 42 +++++++++++----------- src/components/popup/home/Collectibles.tsx | 2 +- src/components/popup/home/Tokens.tsx | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 04f7dd9a7..6f46124dd 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -45,10 +45,12 @@ export default function Token({ onClick, ...props }: Props) { // fiat balance const fiatBalance = useMemo(() => { - if (!price) return undefined; + if (!price) return "--"; - return fractBalance * price; - }, [price, balance]); + const estimate = fractBalance * price; + + return formatFiatBalance(estimate, currency.toLowerCase()); + }, [price, balance, currency]); // token logo const [logo, setLogo] = useState(); @@ -73,14 +75,10 @@ export default function Token({ onClick, ...props }: Props) { {props.name || props.ticker || "???"} - - {(typeof fiatBalance !== "undefined" && - formatFiatBalance(fiatBalance, currency.toLowerCase())) || - "--"} - {balance} {props.ticker} + {fiatBalance} ); @@ -110,13 +108,14 @@ const Wrapper = styled.div` const LogoAndDetails = styled.div` display: flex; align-items: center; - gap: 0.75rem; + gap: 0.8rem; `; const LogoWrapper = styled(Squircle)` position: relative; - width: 3rem; - height: 3rem; + width: 2.8rem; + height: 2.8rem; + flex-shrink: 0; color: rgba(${(props) => props.theme.theme}, 0.2); `; @@ -125,8 +124,8 @@ const Logo = styled.img.attrs({ })` position: absolute; user-select: none; - width: 52%; - height: 52%; + width: 55%; + height: 55%; top: 50%; left: 50%; object-fit: contain; @@ -139,19 +138,19 @@ const TokenName = styled(Text).attrs({ display: flex; align-items: center; gap: 0.34rem; - font-size: 1.1rem; + font-size: 1em; color: rgb(${(props) => props.theme.primaryText}); `; -const FiatBalance = styled(Text).attrs({ +const NativeBalance = styled(Text).attrs({ noMargin: true })` - font-size: 0.98rem; + font-size: 0.95rem; font-weight: 400; - color: rgb(${(props) => props.theme.primaryText}); + color: rgba(${(props) => props.theme.primaryText}, 0.83); `; -const NativeBalance = styled.span` +const FiatBalance = styled.span` font-size: 0.75rem; color: rgb(${(props) => props.theme.secondaryText}); font-weight: 400; @@ -161,6 +160,7 @@ const BalanceSection = styled.div` display: flex; flex-direction: column; justify-content: right; + flex-shrink: 0; p, span { @@ -222,13 +222,13 @@ export function ArToken({ onClick }: ArTokenProps) { Arweave - - {formatFiatBalance(fiatBalance, currency.toLowerCase())} - {balance} {" AR"} + + {formatFiatBalance(fiatBalance, currency.toLowerCase())} + ); diff --git a/src/components/popup/home/Collectibles.tsx b/src/components/popup/home/Collectibles.tsx index 63d07f0cb..c7af6ce3b 100644 --- a/src/components/popup/home/Collectibles.tsx +++ b/src/components/popup/home/Collectibles.tsx @@ -39,7 +39,7 @@ export default function Collectibles() { {browser.i18n.getMessage("no_collectibles")} )} - {collectibles.slice(0, 4).map((collectible, i) => ( + {collectibles.slice(0, 6).map((collectible, i) => ( {browser.i18n.getMessage("no_assets")} )} - {assets.slice(0, 3).map((token, i) => ( + {assets.slice(0, 8).map((token, i) => ( push(`/token/${token.id}`)} From 677dcb5e07783d6e0884ef60bbaef74fcfc2653c Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 20:28:17 +0200 Subject: [PATCH 25/29] fix: token name --- src/components/popup/Token.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 6f46124dd..7f6b24e4d 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -138,7 +138,7 @@ const TokenName = styled(Text).attrs({ display: flex; align-items: center; gap: 0.34rem; - font-size: 1em; + font-size: 1rem; color: rgb(${(props) => props.theme.primaryText}); `; From a19a3789c773d5580a64539000e417e1899dea88 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 20:30:04 +0200 Subject: [PATCH 26/29] fix: native balance size --- src/components/popup/Token.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 7f6b24e4d..50d967604 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -145,7 +145,7 @@ const TokenName = styled(Text).attrs({ const NativeBalance = styled(Text).attrs({ noMargin: true })` - font-size: 0.95rem; + font-size: 0.9rem; font-weight: 400; color: rgba(${(props) => props.theme.primaryText}, 0.83); `; From a16d68dbbaa3cc60bb9d6b07a92fe6296eed9bdf Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 22:50:56 +0200 Subject: [PATCH 27/29] feat: load token logo from data of the contract init tx --- src/components/popup/Token.tsx | 5 +---- src/tokens/token.ts | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 50d967604..51addf278 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -59,12 +59,9 @@ export default function Token({ onClick, ...props }: Props) { (async () => { if (!props?.id || logo) return; setLogo(viewblock.getTokenLogo(props.id)); - - if (!props.defaultLogo) return; - setLogo(await loadTokenLogo(props.id, props.defaultLogo, theme)); })(); - }, [props, theme]); + }, [props, theme, logo]); return ( diff --git a/src/tokens/token.ts b/src/tokens/token.ts index 8e4e7030e..2fbf06596 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -261,7 +261,7 @@ export interface TokenInteraction { */ export async function loadTokenLogo( id: string, - defaultLogo: string, + defaultLogo?: string, theme?: DisplayTheme ) { const viewblockURL = viewblock.getTokenLogo(id, theme); @@ -286,8 +286,21 @@ export async function loadTokenLogo( return `${concatGatewayURL(defaultGateway)}/${defaultLogo}`; } - // if there are no logos in settings, return the AR logo - return theme === "dark" ? arLogoDark : arLogoLight; + try { + // try to see if the token logo is the data + // of the token contract creation transaction + const res = await fetch(`${concatGatewayURL(defaultGateway)}/${id}`); + const contentType = res.headers.get("content-type"); + + if (!contentType.includes("image")) { + throw new Error(); + } + + return URL.createObjectURL(await res.blob()); + } catch { + // if there are no logos in settings, return the AR logo + return theme === "dark" ? arLogoLight : arLogoDark; + } } } From 2cd221c62b3cef01c7a0a58bd2c66923a5f25700 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 22:58:55 +0200 Subject: [PATCH 28/29] feat: manual registry for well known tokens --- src/routes/auth/token.tsx | 22 ++++++++++++++-------- src/tokens/token.ts | 7 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/routes/auth/token.tsx b/src/routes/auth/token.tsx index 87ecb92f0..41caa15df 100644 --- a/src/routes/auth/token.tsx +++ b/src/routes/auth/token.tsx @@ -1,6 +1,7 @@ import { getSettings, loadTokenLogo, + tokenTypeRegistry, type TokenState, type TokenType } from "~tokens/token"; @@ -82,14 +83,19 @@ export default function Token() { let type = params.tokenType; if (!type) { - // fetch data - const data = await fetch( - `${concatGatewayURL(defaultGateway)}/${params.tokenID}` - ); - - type = data.headers.get("content-type").includes("application/json") - ? "asset" - : "collectible"; + if (tokenTypeRegistry[params.tokenID]) { + // manual override + type = tokenTypeRegistry[params.tokenID]; + } else { + // fetch data + const data = await fetch( + `${concatGatewayURL(defaultGateway)}/${params.tokenID}` + ); + + type = data.headers.get("content-type").includes("application/json") + ? "asset" + : "collectible"; + } } setTokenType(type); diff --git a/src/tokens/token.ts b/src/tokens/token.ts index 2fbf06596..aca13f120 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -310,3 +310,10 @@ export async function loadTokenLogo( export function getSettings(state: TokenState) { return new Map(state?.settings || []); } + +/** + * Overrides for well-known tokens' types + */ +export const tokenTypeRegistry: Record = { + "tfalT8Z-88riNtoXdF5ldaBtmsfcSmbMqWLh2DHJIbg": "asset" +}; From 0b81da45560b3dd98eea6442dd25600f5761d234 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Thu, 24 Aug 2023 23:11:10 +0200 Subject: [PATCH 29/29] v1.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83d64c23c..336a6f325 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arconnect", "displayName": "ArConnect", - "version": "1.0.1", + "version": "1.0.2", "description": "__MSG_extensionDescription__", "author": "th8ta", "packageManager": "yarn@1.22.18",