From c2f8efbff45f704c8dc841fc8c4941314edbc542 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Sun, 13 Oct 2024 09:53:49 +0200 Subject: [PATCH 01/19] feat: printer capabilities callback --- src/lib/printer.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/lib/printer.ts diff --git a/src/lib/printer.ts b/src/lib/printer.ts new file mode 100644 index 00000000..5e028e7c --- /dev/null +++ b/src/lib/printer.ts @@ -0,0 +1,33 @@ +import browser from "webextension-polyfill"; + +/** + * Tells Chrome about the virtual printer's + * capabilities in CDD format + */ +export function getCapabilities( + printerId: string, + callback: PrinterCapabilitiesCallback +) { + // only return capabilities for the ArConnect printer + if (printerId !== browser.runtime.id) return; + + callback({ + capabilities: { + version: "1.0", + printer: { + supported_content_type: [ + { content_type: "text/html" }, + { content_type: "application/pdf" }, + { content_type: "text/plain" } + ] + } + } + }); +} + +/** + * Printer capabilities request callback type + */ +type PrinterCapabilitiesCallback = ( + p: chrome.printerProvider.PrinterCapabilities +) => void; From 224167fc003bb7a48fafdb6815894437b1b0c798 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Sun, 13 Oct 2024 09:55:58 +0200 Subject: [PATCH 02/19] feat: add callback for printers list --- src/background.ts | 7 +++++++ src/lib/printer.ts | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/background.ts b/src/background.ts index 40a15215..22a8601d 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,6 +1,7 @@ import { addressChangeListener, walletsChangeListener } from "~wallets/event"; import { keyRemoveAlarmListener, onWindowClose } from "~wallets/auth"; import { appConfigChangeListener } from "~applications/events"; +import { getCapabilities, getPrinters } from "~lib/printer"; import { handleApiCalls, handleChunkCalls } from "~api"; import { handleGatewayUpdate } from "~gateways/cache"; import { onMessage } from "@arconnect/webext-bridge"; @@ -83,4 +84,10 @@ browser.runtime.onInstalled.addListener(onInstalled); // handle ar:// protocol browser.webNavigation.onBeforeNavigate.addListener(protocolHandler); +// print to the permaweb (only on chrome) +if (typeof chrome !== "undefined") { + chrome.printerProvider.onGetCapabilityRequested.addListener(getCapabilities); + chrome.printerProvider.onGetPrintersRequested.addListener(getPrinters); +} + export {}; diff --git a/src/lib/printer.ts b/src/lib/printer.ts index 5e028e7c..296c2c76 100644 --- a/src/lib/printer.ts +++ b/src/lib/printer.ts @@ -31,3 +31,23 @@ export function getCapabilities( type PrinterCapabilitiesCallback = ( p: chrome.printerProvider.PrinterCapabilities ) => void; + +/** + * Returns a list of "virtual" printers, + * in our case "Print/Publish to Arweave" + */ +export function getPrinters(callback: PrinterInfoCallback) { + callback([ + { + id: browser.runtime.id, + name: "Print to Arweave", + description: + "Publish the content you want to print on Arweave, permanently." + } + ]); +} + +/** + * Printer info request callback type + */ +type PrinterInfoCallback = (p: chrome.printerProvider.PrinterInfo[]) => void; From 2df851ffdc71977162284aeb62abfc1319a0395f Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Sun, 13 Oct 2024 10:02:14 +0200 Subject: [PATCH 03/19] fix: add printer permission --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ea4701a1..a0840d2a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "contextMenus", "tabs", "webNavigation", - "notifications" + "notifications", + "printerProvider" ], "web_accessible_resources": [ { From 444eea9b2b8cfe956cc9c8d8271d51d63d189596 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Sun, 13 Oct 2024 11:55:05 +0200 Subject: [PATCH 04/19] feat: upload to arweave & correct printer capabilities --- src/background.ts | 3 +- src/lib/printer.ts | 150 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 140 insertions(+), 13 deletions(-) diff --git a/src/background.ts b/src/background.ts index 22a8601d..e594255d 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,7 +1,7 @@ +import { getCapabilities, getPrinters, handlePrintRequest } from "~lib/printer"; import { addressChangeListener, walletsChangeListener } from "~wallets/event"; import { keyRemoveAlarmListener, onWindowClose } from "~wallets/auth"; import { appConfigChangeListener } from "~applications/events"; -import { getCapabilities, getPrinters } from "~lib/printer"; import { handleApiCalls, handleChunkCalls } from "~api"; import { handleGatewayUpdate } from "~gateways/cache"; import { onMessage } from "@arconnect/webext-bridge"; @@ -88,6 +88,7 @@ browser.webNavigation.onBeforeNavigate.addListener(protocolHandler); if (typeof chrome !== "undefined") { chrome.printerProvider.onGetCapabilityRequested.addListener(getCapabilities); chrome.printerProvider.onGetPrintersRequested.addListener(getPrinters); + chrome.printerProvider.onPrintRequested.addListener(handlePrintRequest); } export {}; diff --git a/src/lib/printer.ts b/src/lib/printer.ts index 296c2c76..558e0ce5 100644 --- a/src/lib/printer.ts +++ b/src/lib/printer.ts @@ -1,4 +1,11 @@ +import { getActiveKeyfile, type DecryptedWallet } from "~wallets"; +import { freeDecryptedWallet } from "~wallets/encryption"; +import { concatGatewayURL } from "~gateways/utils"; +import { findGateway } from "~gateways/wayfinder"; import browser from "webextension-polyfill"; +import Arweave from "arweave"; + +const ARCONNECT_PRINTER_ID = "arconnect-permaweb-printer"; /** * Tells Chrome about the virtual printer's @@ -9,16 +16,56 @@ export function getCapabilities( callback: PrinterCapabilitiesCallback ) { // only return capabilities for the ArConnect printer - if (printerId !== browser.runtime.id) return; + if (printerId !== ARCONNECT_PRINTER_ID) return; + // mimic a regular printer's capabilities callback({ - capabilities: { - version: "1.0", - printer: { - supported_content_type: [ - { content_type: "text/html" }, - { content_type: "application/pdf" }, - { content_type: "text/plain" } + version: "1.0", + printer: { + supported_content_type: [ + { content_type: "application/pdf" }, + { content_type: "image/pwg-raster" } + ], + color: { + option: [ + { type: "STANDARD_COLOR", is_default: true }, + { type: "STANDARD_MONOCHROME" } + ] + }, + copies: { + default_copies: 1, + max_copies: 100 + }, + media_size: { + option: [ + { + name: "ISO_A4", + width_microns: 210000, + height_microns: 297000, + is_default: true + }, + { + name: "NA_LETTER", + width_microns: 215900, + height_microns: 279400 + } + ] + }, + page_orientation: { + option: [ + { + type: "PORTRAIT", + is_default: true + }, + { type: "LANDSCAPE" }, + { type: "AUTO" } + ] + }, + duplex: { + option: [ + { type: "NO_DUPLEX", is_default: true }, + { type: "LONG_EDGE" }, + { type: "SHORT_EDGE" } ] } } @@ -28,9 +75,7 @@ export function getCapabilities( /** * Printer capabilities request callback type */ -type PrinterCapabilitiesCallback = ( - p: chrome.printerProvider.PrinterCapabilities -) => void; +type PrinterCapabilitiesCallback = (p: unknown) => void; /** * Returns a list of "virtual" printers, @@ -39,7 +84,7 @@ type PrinterCapabilitiesCallback = ( export function getPrinters(callback: PrinterInfoCallback) { callback([ { - id: browser.runtime.id, + id: ARCONNECT_PRINTER_ID, name: "Print to Arweave", description: "Publish the content you want to print on Arweave, permanently." @@ -51,3 +96,84 @@ export function getPrinters(callback: PrinterInfoCallback) { * Printer info request callback type */ type PrinterInfoCallback = (p: chrome.printerProvider.PrinterInfo[]) => void; + +/** + * Handles the request from the user to print the page to Arweave + */ +export async function handlePrintRequest( + printJob: chrome.printerProvider.PrintJob, + resultCallback: PrintCallback +) { + // only print for the ArConnect printer + if (printJob.printerId !== ARCONNECT_PRINTER_ID) return; + + // wallet + let decryptedWallet: DecryptedWallet; + + try { + // build data blog + const data = new Blob([printJob.document], { type: "application/pdf" }); + + // get user wallet + decryptedWallet = await getActiveKeyfile(); + + if (decryptedWallet.type === "hardware") + throw new Error("Cannot print with a hardware wallet."); + + // extension manifest + const manifest = browser.runtime.getManifest(); + + // get a gateway and setup arweave client + const gateway = await findGateway({}); + const arweave = new Arweave(gateway); + + // create tx + const transaction = await arweave.createTransaction( + { data: await data.arrayBuffer() }, + decryptedWallet.keyfile + ); + const tags = [ + { name: "App-Name", value: manifest.name }, + { name: "App-Version", value: manifest.version }, + { name: "Type", value: "Archive" }, + { name: "Content-Type", value: printJob.contentType }, + { name: "print:title", value: printJob.title }, + { name: "print:timestamp", value: new Date().getTime().toString() } + ]; + + // add tags + for (const tag of tags) { + transaction.addTag(tag.name, tag.value); + } + + // sign + await arweave.transactions.sign(transaction, decryptedWallet.keyfile); + + // upload + const uploader = await arweave.transactions.getUploader(transaction); + + while (!uploader.isComplete) { + await uploader.uploadChunk(); + } + + // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK + resultCallback("OK"); + + // open in new tab + await chrome.tabs.create({ + url: `${concatGatewayURL(gateway)}/${transaction.id}` + }); + } catch (e) { + console.log("Printing failed:\n", e); + resultCallback("FAILED"); + } + + // free wallet from memory + if (decryptedWallet?.type == "local") + freeDecryptedWallet(decryptedWallet.keyfile); +} + +/** + * Print request (result) callback + */ +type PrintCallback = (result: string) => void; From fae86a9bfe2d0103154daeebe21ba3e5e938870b Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Sun, 13 Oct 2024 12:33:39 +0200 Subject: [PATCH 05/19] feat: upload as a bundled tx, fix type errors --- src/background.ts | 1 + src/lib/printer.ts | 43 ++++++++++++++++++------------------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/background.ts b/src/background.ts index e594255d..bec17175 100644 --- a/src/background.ts +++ b/src/background.ts @@ -86,6 +86,7 @@ browser.webNavigation.onBeforeNavigate.addListener(protocolHandler); // print to the permaweb (only on chrome) if (typeof chrome !== "undefined") { + // @ts-expect-error chrome.printerProvider.onGetCapabilityRequested.addListener(getCapabilities); chrome.printerProvider.onGetPrintersRequested.addListener(getPrinters); chrome.printerProvider.onPrintRequested.addListener(handlePrintRequest); diff --git a/src/lib/printer.ts b/src/lib/printer.ts index 558e0ce5..10bdad26 100644 --- a/src/lib/printer.ts +++ b/src/lib/printer.ts @@ -1,9 +1,10 @@ +import { uploadDataToTurbo } from "~api/modules/dispatch/uploader"; import { getActiveKeyfile, type DecryptedWallet } from "~wallets"; import { freeDecryptedWallet } from "~wallets/encryption"; +import { createData, ArweaveSigner } from "arbundles"; import { concatGatewayURL } from "~gateways/utils"; import { findGateway } from "~gateways/wayfinder"; import browser from "webextension-polyfill"; -import Arweave from "arweave"; const ARCONNECT_PRINTER_ID = "arconnect-permaweb-printer"; @@ -112,7 +113,7 @@ export async function handlePrintRequest( try { // build data blog - const data = new Blob([printJob.document], { type: "application/pdf" }); + const data = new Blob([printJob.document], { type: printJob.contentType }); // get user wallet decryptedWallet = await getActiveKeyfile(); @@ -123,15 +124,7 @@ export async function handlePrintRequest( // extension manifest const manifest = browser.runtime.getManifest(); - // get a gateway and setup arweave client - const gateway = await findGateway({}); - const arweave = new Arweave(gateway); - - // create tx - const transaction = await arweave.createTransaction( - { data: await data.arrayBuffer() }, - decryptedWallet.keyfile - ); + // setup tags const tags = [ { name: "App-Name", value: manifest.name }, { name: "App-Version", value: manifest.version }, @@ -141,27 +134,27 @@ export async function handlePrintRequest( { name: "print:timestamp", value: new Date().getTime().toString() } ]; - // add tags - for (const tag of tags) { - transaction.addTag(tag.name, tag.value); - } - - // sign - await arweave.transactions.sign(transaction, decryptedWallet.keyfile); - - // upload - const uploader = await arweave.transactions.getUploader(transaction); + // create data item + const dataSigner = new ArweaveSigner(decryptedWallet.keyfile); + const dataEntry = createData( + new Uint8Array(await data.arrayBuffer()), + dataSigner, + { tags } + ); - while (!uploader.isComplete) { - await uploader.uploadChunk(); - } + // sign an upload data + await dataEntry.sign(dataSigner); + await uploadDataToTurbo(dataEntry, "https://turbo.ardrive.io"); // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK resultCallback("OK"); + // find a gateway to display the result + const gateway = await findGateway({}); + // open in new tab await chrome.tabs.create({ - url: `${concatGatewayURL(gateway)}/${transaction.id}` + url: `${concatGatewayURL(gateway)}/${dataEntry.id}` }); } catch (e) { console.log("Printing failed:\n", e); From fc0c5c3ada2c8a228a641f2e37a1bcfb4975792f Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 15 Oct 2024 15:44:46 +0545 Subject: [PATCH 06/19] fix: hide fiat value for AO tokens received from redstone --- src/components/popup/Token.tsx | 4 ++-- src/lib/redstone.ts | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index 840b8cc6..f23afbe2 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -74,11 +74,11 @@ export default function Token({ onClick, ...props }: Props) { }, [fractBalance.toString()]); // token price - const { price, currency } = usePrice(props.ticker); + const { price, currency } = usePrice(props.ticker, props.ao); // fiat balance const fiatBalance = useMemo(() => { - if (!price) return
; + if (!price || props.ao) return
; const estimate = fractBalance.multipliedBy(price); diff --git a/src/lib/redstone.ts b/src/lib/redstone.ts index 1e9421fe..7bc67cd0 100644 --- a/src/lib/redstone.ts +++ b/src/lib/redstone.ts @@ -9,9 +9,14 @@ import BigNumber from "bignumber.js"; * Hook for the redstone token price API * * @param symbol Token symbol + * @param isAoToken Token is ao token or not * @param opts Custom Redstone API "getPrice" options */ -export function usePrice(symbol?: string, opts?: GetPriceOptions) { +export function usePrice( + symbol?: string, + isAoToken?: boolean, + opts?: GetPriceOptions +) { const [price, setPrice] = useState(); const [loading, setLoading] = useState(false); @@ -20,7 +25,7 @@ export function usePrice(symbol?: string, opts?: GetPriceOptions) { useEffect(() => { (async () => { - if (!symbol) { + if (!symbol || isAoToken) { return; } From 62b98556db6bbfffa828f5c00998dc4370388d96 Mon Sep 17 00:00:00 2001 From: 7i7o Date: Tue, 15 Oct 2024 14:20:46 +0200 Subject: [PATCH 07/19] add tokenBalance api endpoint --- src/api/background.ts | 3 +++ src/api/foreground.ts | 3 +++ src/api/modules/token_balance/index.ts | 11 ++++++++ .../token_balance/token_balance.background.ts | 27 +++++++++++++++++++ .../token_balance/token_balance.foreground.ts | 6 +++++ 5 files changed, 50 insertions(+) create mode 100644 src/api/modules/token_balance/index.ts create mode 100644 src/api/modules/token_balance/token_balance.background.ts create mode 100644 src/api/modules/token_balance/token_balance.foreground.ts diff --git a/src/api/background.ts b/src/api/background.ts index 0be5da8e..2b5df252 100644 --- a/src/api/background.ts +++ b/src/api/background.ts @@ -45,6 +45,8 @@ import subscriptionModule from "./modules/subscription"; import subscription from "./modules/subscription/subscription.background"; import userTokensModule from "./modules/user_tokens"; import userTokens from "./modules/user_tokens/user_tokens.background"; +import tokenBalanceModule from "./modules/token_balance"; +import tokenBalance from "./modules/token_balance/token_balance.background"; /** Background modules */ const modules: BackgroundModule[] = [ @@ -69,6 +71,7 @@ const modules: BackgroundModule[] = [ { ...signDataItemModule, function: signDataItem }, { ...subscriptionModule, function: subscription }, { ...userTokensModule, function: userTokens }, + { ...tokenBalanceModule, function: tokenBalance }, { ...batchSignDataItemModule, function: batchSignDataItem } ]; diff --git a/src/api/foreground.ts b/src/api/foreground.ts index cd0ca12b..bd079a7b 100644 --- a/src/api/foreground.ts +++ b/src/api/foreground.ts @@ -65,6 +65,8 @@ import signDataItem, { } from "./modules/sign_data_item/sign_data_item.foreground"; import userTokensModule from "./modules/user_tokens"; import userTokens from "./modules/user_tokens/user_tokens.foreground"; +import tokenBalanceModule from "./modules/token_balance"; +import tokenBalance from "./modules/token_balance/token_balance.foreground"; /** Foreground modules */ const modules: ForegroundModule[] = [ @@ -101,6 +103,7 @@ const modules: ForegroundModule[] = [ }, { ...subscriptionModule, function: subscription }, { ...userTokensModule, function: userTokens }, + { ...tokenBalanceModule, function: tokenBalance }, { ...batchSignDataItemModule, function: batchSignDataItem, diff --git a/src/api/modules/token_balance/index.ts b/src/api/modules/token_balance/index.ts new file mode 100644 index 00000000..9e341fac --- /dev/null +++ b/src/api/modules/token_balance/index.ts @@ -0,0 +1,11 @@ +import type { PermissionType } from "~applications/permissions"; +import type { ModuleProperties } from "~api/module"; + +const permissions: PermissionType[] = ["ACCESS_TOKENS"]; + +const tokenBalanceModule: ModuleProperties = { + functionName: "tokenBalance", + permissions +}; + +export default tokenBalanceModule; diff --git a/src/api/modules/token_balance/token_balance.background.ts b/src/api/modules/token_balance/token_balance.background.ts new file mode 100644 index 00000000..eb872324 --- /dev/null +++ b/src/api/modules/token_balance/token_balance.background.ts @@ -0,0 +1,27 @@ +import type { ModuleFunction } from "~api/background"; +import { ExtensionStorage } from "~utils/storage"; +import { getAoTokenBalance, getNativeTokenBalance } from "~tokens/aoTokens/ao"; +import { AO_NATIVE_TOKEN } from "~utils/ao_import"; +import { isAddress } from "~utils/assertions"; + +const background: ModuleFunction = async (_, id?: string) => { + // validate input + isAddress(id); + const address = await ExtensionStorage.get("active_address"); + + let balance: string | null = null; + try { + if (id === AO_NATIVE_TOKEN) { + balance = await getNativeTokenBalance(address); + } else { + const balanceResult = await getAoTokenBalance(address, id); + balance = balanceResult.toString(); + } + } catch (error) { + console.error(`Error fetching balance for token ${id}:`, error); + } + + return balance; +}; + +export default background; diff --git a/src/api/modules/token_balance/token_balance.foreground.ts b/src/api/modules/token_balance/token_balance.foreground.ts new file mode 100644 index 00000000..220afbdb --- /dev/null +++ b/src/api/modules/token_balance/token_balance.foreground.ts @@ -0,0 +1,6 @@ +import type { ModuleFunction } from "~api/module"; + +// no need to transform anything in the foreground +const foreground: ModuleFunction = () => {}; + +export default foreground; From 5d6a476ea84d366175750c8770b68e7629ebb016 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 15 Oct 2024 21:15:00 +0545 Subject: [PATCH 08/19] fix: Update logic for showing add AR to get started --- src/routes/popup/index.tsx | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/routes/popup/index.tsx b/src/routes/popup/index.tsx index 856b024c..c039e78d 100644 --- a/src/routes/popup/index.tsx +++ b/src/routes/popup/index.tsx @@ -22,6 +22,7 @@ import BuyButton from "~components/popup/home/BuyButton"; import Tabs from "~components/popup/home/Tabs"; import AoBanner from "~components/popup/home/AoBanner"; import { scheduleImportAoTokens } from "~tokens/aoTokens/sync"; +import BigNumber from "bignumber.js"; export default function Home() { // get if the user has no balance @@ -38,6 +39,14 @@ export default function Home() { instance: ExtensionStorage }); + const [historicalBalance] = useStorage( + { + key: "historical_balance", + instance: ExtensionStorage + }, + [] + ); + const balance = useBalance(); // all tokens @@ -59,23 +68,18 @@ export default function Home() { if (!activeAddress) return; const findBalances = async (assets, aoTokens) => { - const t = [...assets, ...aoTokens]; - const tokens = t.find((token) => token.balance !== 0); - if (tokens) { - setNoBalance(false); - return; - } else if (balance.toNumber()) { + const hasTokensWithBalance = [...assets, ...aoTokens].some((token) => + BigNumber(token.balance || "0").gt(0) + ); + + if ( + hasTokensWithBalance || + balance.toNumber() || + historicalBalance[historicalBalance.length - 1] !== 0 + ) { setNoBalance(false); - return; } else { - const history = await ExtensionStorage.get("historical_balance"); - // @ts-ignore - if (history[0] !== 0) { - setNoBalance(false); - return; - } else { - setNoBalance(true); - } + setNoBalance(true); } }; @@ -84,7 +88,7 @@ export default function Home() { } catch (error) { console.log(error); } - }, [activeAddress, assets, aoTokens]); + }, [activeAddress, assets, aoTokens, balance, historicalBalance]); useEffect(() => { const trackEventAndPage = async () => { From 3cc6936d3c426fb05fc5d4a7e7da04832fb82098 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 15 Oct 2024 21:42:54 +0545 Subject: [PATCH 09/19] refactor: Delay 'Add AR to get started' until AO balances are fetched --- src/routes/popup/index.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/routes/popup/index.tsx b/src/routes/popup/index.tsx index c039e78d..cf917353 100644 --- a/src/routes/popup/index.tsx +++ b/src/routes/popup/index.tsx @@ -53,7 +53,7 @@ export default function Home() { const tokens = useTokens(); // ao Tokens - const [aoTokens] = useAoTokens(); + const [aoTokens, aoTokensLoading] = useAoTokens(); // checking to see if it's a hardware wallet const wallet = useActiveWallet(); @@ -68,9 +68,11 @@ export default function Home() { if (!activeAddress) return; const findBalances = async (assets, aoTokens) => { - const hasTokensWithBalance = [...assets, ...aoTokens].some((token) => - BigNumber(token.balance || "0").gt(0) - ); + const hasTokensWithBalance = + aoTokensLoading || + [...assets, ...aoTokens].some((token) => + BigNumber(token.balance || "0").gt(0) + ); if ( hasTokensWithBalance || @@ -88,7 +90,14 @@ export default function Home() { } catch (error) { console.log(error); } - }, [activeAddress, assets, aoTokens, balance, historicalBalance]); + }, [ + activeAddress, + assets, + aoTokens, + balance, + historicalBalance, + aoTokensLoading + ]); useEffect(() => { const trackEventAndPage = async () => { From d8af6eb73396df127295ed40fa4d8edf4aed6d69 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 15 Oct 2024 23:44:49 +0545 Subject: [PATCH 10/19] refactor: improve error handling for AO token balance retrieval --- .../token_balance/token_balance.background.ts | 15 ++++----------- src/tokens/aoTokens/ao.ts | 7 +++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/api/modules/token_balance/token_balance.background.ts b/src/api/modules/token_balance/token_balance.background.ts index eb872324..6abd5eea 100644 --- a/src/api/modules/token_balance/token_balance.background.ts +++ b/src/api/modules/token_balance/token_balance.background.ts @@ -9,17 +9,10 @@ const background: ModuleFunction = async (_, id?: string) => { isAddress(id); const address = await ExtensionStorage.get("active_address"); - let balance: string | null = null; - try { - if (id === AO_NATIVE_TOKEN) { - balance = await getNativeTokenBalance(address); - } else { - const balanceResult = await getAoTokenBalance(address, id); - balance = balanceResult.toString(); - } - } catch (error) { - console.error(`Error fetching balance for token ${id}:`, error); - } + const balance = + id === AO_NATIVE_TOKEN + ? await getNativeTokenBalance(address) + : (await getAoTokenBalance(address, id)).toString(); return balance; }; diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index 648a9b70..e686d58e 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -225,6 +225,10 @@ export async function getAoTokenBalance( const aoToken = aoTokens.find((token) => token.processId === process); + if (!aoToken) { + throw new Error(`Token not found for address '${address}'.`); + } + const res = await dryrun({ Id, Owner: address, @@ -239,6 +243,9 @@ export async function getAoTokenBalance( return new Quantity(BigInt(balance), BigInt(aoToken.Denomination)); } } + + // default return + return new Quantity(0, BigInt(aoToken.Denomination)); } export async function getNativeTokenBalance(address: string): Promise { From 323c014f5ae420f959e9ef5a5f16110b111fd176 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Wed, 16 Oct 2024 16:05:13 +0545 Subject: [PATCH 11/19] fix: Update useDecryptionKey with the correct hook usage --- src/components/arlocal/Transaction.tsx | 1 + src/wallets/index.ts | 16 +++++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/components/arlocal/Transaction.tsx b/src/components/arlocal/Transaction.tsx index aaeb1f6b..e80d23a2 100644 --- a/src/components/arlocal/Transaction.tsx +++ b/src/components/arlocal/Transaction.tsx @@ -109,6 +109,7 @@ export default function Transaction({ arweave }: Props) { content: browser.i18n.getMessage("invalidPassword"), duration: 2400 }); + setSendingTx(false); return; } } diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 0655bb0c..b2e27673 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -91,20 +91,14 @@ export const useNoWallets = () => { * Hook for decryption key */ export function useDecryptionKey(): [string, (val: string) => void] { - const [decryptionKey, setDecryptionKey] = useStorage( - { - key: "decryption_key", - instance: ExtensionStorage - }, - (val) => { - if (!val) return undefined; - return atob(val); - } - ); + const [decryptionKey, setDecryptionKey] = useStorage({ + key: "decryption_key", + instance: ExtensionStorage + }); const set = (val: string) => setDecryptionKey(btoa(val)); - return [decryptionKey, set]; + return [decryptionKey ? atob(decryptionKey) : undefined, set]; } /** From 971fa76c60bd0b64f48aaea7e6b2ac6150bf5754 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Wed, 16 Oct 2024 23:16:30 +0545 Subject: [PATCH 12/19] refactor: Update getAoTokenBalance with error handling --- src/tokens/aoTokens/ao.ts | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index e686d58e..44900256 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -16,6 +16,8 @@ import { import type { Alarms } from "webextension-polyfill"; import type { KeystoneSigner } from "~wallets/hardware/keystone"; import browser from "webextension-polyfill"; +import { fetchTokenByProcessId } from "~lib/transactions"; +import { tokenTypeRegistry } from "~tokens/token"; export type AoInstance = ReturnType; @@ -223,11 +225,7 @@ export async function getAoTokenBalance( ): Promise { const aoTokens = (await ExtensionStorage.get("ao_tokens")) || []; - const aoToken = aoTokens.find((token) => token.processId === process); - - if (!aoToken) { - throw new Error(`Token not found for address '${address}'.`); - } + let aoToken = aoTokens.find((token) => token.processId === process); const res = await dryrun({ Id, @@ -236,16 +234,31 @@ export async function getAoTokenBalance( tags: [{ name: "Action", value: "Balance" }] }); + if (!res?.Messages) { + throw new Error("Balance handler missing"); + } + + if ((res as any)?.error) { + throw new Error((res as any)?.error); + } + for (const msg of res.Messages as Message[]) { const balance = getTagValue("Balance", msg.Tags); - if (balance && aoToken) { + if (balance && +balance) { + if (!aoToken) { + aoToken = await fetchTokenByProcessId(process); + if (!aoToken) { + throw new Error("Could not load token info."); + } + } + return new Quantity(BigInt(balance), BigInt(aoToken.Denomination)); } } // default return - return new Quantity(0, BigInt(aoToken.Denomination)); + return new Quantity(0n, 12n); } export async function getNativeTokenBalance(address: string): Promise { From b5528b5ffe77b57528b358b3f584416d8f3692b6 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 17 Oct 2024 10:55:19 +0545 Subject: [PATCH 13/19] refactor: throw error if result Messages is empty --- src/tokens/aoTokens/ao.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index 44900256..7be33ac2 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -234,12 +234,16 @@ export async function getAoTokenBalance( tags: [{ name: "Action", value: "Balance" }] }); - if (!res?.Messages) { - throw new Error("Balance handler missing"); + const errorMessage = (res as any)?.error || res?.Error; + + if (errorMessage) { + throw new Error(errorMessage); } - if ((res as any)?.error) { - throw new Error((res as any)?.error); + if (res.Messages.length === 0) { + throw new Error( + "Invalid token process: Balance action handler missing or unsupported." + ); } for (const msg of res.Messages as Message[]) { From fe8cd95335a60647975824476541623c45dfcd36 Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Thu, 17 Oct 2024 10:35:45 -0700 Subject: [PATCH 14/19] fix: replaced receive ar to view all assets --- assets/_locales/en/messages.json | 4 ++++ assets/_locales/zh_CN/messages.json | 4 ++++ src/components/popup/home/NoBalance.tsx | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index b51ba1e8..f9965cdc 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -1083,6 +1083,10 @@ "message": "View all", "description": "View all link text" }, + "view_all_assets": { + "message": "View all assets", + "description": "View all link text" + }, "setting_tokens": { "message": "Tokens", "description": "Tokens setting title" diff --git a/assets/_locales/zh_CN/messages.json b/assets/_locales/zh_CN/messages.json index e89c4283..a7bfdfb1 100644 --- a/assets/_locales/zh_CN/messages.json +++ b/assets/_locales/zh_CN/messages.json @@ -1071,6 +1071,10 @@ "message": "查看全部", "description": "View all link text" }, + "view_all_assets": { + "message": "查看所有资产", + "description": "View all assets link text" + }, "setting_tokens": { "message": "代币", "description": "Tokens setting title" diff --git a/src/components/popup/home/NoBalance.tsx b/src/components/popup/home/NoBalance.tsx index 827bf225..051d98e0 100644 --- a/src/components/popup/home/NoBalance.tsx +++ b/src/components/popup/home/NoBalance.tsx @@ -18,12 +18,12 @@ export default function NoBalance() { push("/receive")} + onClick={() => push("/tokens")} secondary fullWidth className="normal-font-weight" > - {browser.i18n.getMessage("receive_AR_button")} + {browser.i18n.getMessage("view_all_assets")} From d1847a074764ca3c959d82c0c1b6dd9165482b9c Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 17 Oct 2024 23:51:59 +0545 Subject: [PATCH 15/19] feat: add arweave upload fallback if turbo fails --- src/lib/printer.ts | 61 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/lib/printer.ts b/src/lib/printer.ts index 10bdad26..84149ef5 100644 --- a/src/lib/printer.ts +++ b/src/lib/printer.ts @@ -5,6 +5,7 @@ import { createData, ArweaveSigner } from "arbundles"; import { concatGatewayURL } from "~gateways/utils"; import { findGateway } from "~gateways/wayfinder"; import browser from "webextension-polyfill"; +import Arweave from "arweave"; const ARCONNECT_PRINTER_ID = "arconnect-permaweb-printer"; @@ -134,27 +135,57 @@ export async function handlePrintRequest( { name: "print:timestamp", value: new Date().getTime().toString() } ]; - // create data item - const dataSigner = new ArweaveSigner(decryptedWallet.keyfile); - const dataEntry = createData( - new Uint8Array(await data.arrayBuffer()), - dataSigner, - { tags } - ); + let transactionId: string; - // sign an upload data - await dataEntry.sign(dataSigner); - await uploadDataToTurbo(dataEntry, "https://turbo.ardrive.io"); + // find a gateway to upload and display the result + const gateway = await findGateway({}); - // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK - resultCallback("OK"); + const transactionData = new Uint8Array(await data.arrayBuffer()); - // find a gateway to display the result - const gateway = await findGateway({}); + try { + // create data item + const dataSigner = new ArweaveSigner(decryptedWallet.keyfile); + const dataEntry = createData(transactionData, dataSigner, { tags }); + + // sign an upload data + await dataEntry.sign(dataSigner); + await uploadDataToTurbo(dataEntry, "https://turbo.ardrive.io"); + + // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK + resultCallback("OK"); + + transactionId = dataEntry.id; + } catch (error) { + // sign & post if there is something wrong with turbo + + const arweave = Arweave.init(gateway); + + const transaction = await arweave.createTransaction( + { data: transactionData }, + decryptedWallet.keyfile + ); + + for (const tag of tags) { + transaction.addTag(tag.name, tag.value); + } + + // sign and upload + await arweave.transactions.sign(transaction, decryptedWallet.keyfile); + const uploader = await arweave.transactions.getUploader(transaction); + + while (!uploader.isComplete) { + await uploader.uploadChunk(); + } + + // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK + resultCallback("OK"); + + transactionId = transaction.id; + } // open in new tab await chrome.tabs.create({ - url: `${concatGatewayURL(gateway)}/${dataEntry.id}` + url: `${concatGatewayURL(gateway)}/${transactionId}` }); } catch (e) { console.log("Printing failed:\n", e); From 511ee6f56476e233ce4d2197c074b8d583f3e6af Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 17 Oct 2024 23:53:46 +0545 Subject: [PATCH 16/19] refactor: add sign transaction popup for print authorization --- src/lib/printer.ts | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/lib/printer.ts b/src/lib/printer.ts index 84149ef5..211e4d0f 100644 --- a/src/lib/printer.ts +++ b/src/lib/printer.ts @@ -6,6 +6,8 @@ import { concatGatewayURL } from "~gateways/utils"; import { findGateway } from "~gateways/wayfinder"; import browser from "webextension-polyfill"; import Arweave from "arweave"; +import { signAuth } from "~api/modules/sign/sign_auth"; +import { getActiveTab } from "~applications"; const ARCONNECT_PRINTER_ID = "arconnect-permaweb-printer"; @@ -129,7 +131,7 @@ export async function handlePrintRequest( const tags = [ { name: "App-Name", value: manifest.name }, { name: "App-Version", value: manifest.version }, - { name: "Type", value: "Archive" }, + { name: "Type", value: "Print-Archive" }, { name: "Content-Type", value: printJob.contentType }, { name: "print:title", value: printJob.title }, { name: "print:timestamp", value: new Date().getTime().toString() } @@ -139,14 +141,29 @@ export async function handlePrintRequest( // find a gateway to upload and display the result const gateway = await findGateway({}); + const arweave = Arweave.init(gateway); + // create data item + const dataSigner = new ArweaveSigner(decryptedWallet.keyfile); const transactionData = new Uint8Array(await data.arrayBuffer()); + const dataEntry = createData(transactionData, dataSigner, { tags }); - try { - // create data item - const dataSigner = new ArweaveSigner(decryptedWallet.keyfile); - const dataEntry = createData(transactionData, dataSigner, { tags }); + // calculate reward for the transaction + const reward = await arweave.transactions.getPrice( + transactionData.byteLength + ); + + // get active tab + const activeTab = await getActiveTab(); + + await signAuth( + activeTab.url, + // @ts-expect-error + { ...dataEntry.toJSON(), reward }, + decryptedWallet.address + ); + try { // sign an upload data await dataEntry.sign(dataSigner); await uploadDataToTurbo(dataEntry, "https://turbo.ardrive.io"); @@ -158,8 +175,6 @@ export async function handlePrintRequest( } catch (error) { // sign & post if there is something wrong with turbo - const arweave = Arweave.init(gateway); - const transaction = await arweave.createTransaction( { data: transactionData }, decryptedWallet.keyfile From ba003579d2f13398700833e519071ca1464647f3 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 18 Oct 2024 13:33:40 +0545 Subject: [PATCH 17/19] refactor: add small delay before opening the tab --- src/lib/printer.ts | 11 ++++++++++- src/routes/auth/sign.tsx | 2 +- src/utils/sleep.ts | 9 +++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/utils/sleep.ts diff --git a/src/lib/printer.ts b/src/lib/printer.ts index 211e4d0f..330fb040 100644 --- a/src/lib/printer.ts +++ b/src/lib/printer.ts @@ -8,6 +8,7 @@ import browser from "webextension-polyfill"; import Arweave from "arweave"; import { signAuth } from "~api/modules/sign/sign_auth"; import { getActiveTab } from "~applications"; +import { sleep } from "~utils/sleep"; const ARCONNECT_PRINTER_ID = "arconnect-permaweb-printer"; @@ -159,7 +160,11 @@ export async function handlePrintRequest( await signAuth( activeTab.url, // @ts-expect-error - { ...dataEntry.toJSON(), reward }, + { + ...dataEntry.toJSON(), + reward, + sizeInBytes: transactionData.byteLength + }, decryptedWallet.address ); @@ -168,6 +173,8 @@ export async function handlePrintRequest( await dataEntry.sign(dataSigner); await uploadDataToTurbo(dataEntry, "https://turbo.ardrive.io"); + await sleep(2000); + // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK resultCallback("OK"); @@ -192,6 +199,8 @@ export async function handlePrintRequest( await uploader.uploadChunk(); } + await sleep(2000); + // this has to be one of FAILED, INVALID_DATA, INVALID_TICKET, OK resultCallback("OK"); diff --git a/src/routes/auth/sign.tsx b/src/routes/auth/sign.tsx index 118ce340..9e234fa3 100644 --- a/src/routes/auth/sign.tsx +++ b/src/routes/auth/sign.tsx @@ -140,7 +140,7 @@ export default function Sign() { const size = useMemo(() => { if (!transaction) return 0; - return transaction.data.length; + return transaction?.sizeInBytes ?? transaction.data.length; }, [transaction]); // authorize diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 00000000..4081f840 --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,9 @@ +/** + * Pauses execution for a given number of milliseconds. + * + * @param {number} ms - Duration to sleep in milliseconds. + * @returns {Promise} Resolves after the specified delay. + */ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} From 44188b9593f0895647c739dbfd8731d6104861f5 Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Fri, 18 Oct 2024 11:03:02 -0700 Subject: [PATCH 18/19] fix: update bazar and alex links --- logs | 17 +++++++++++++++++ src/utils/apps.ts | 7 ++++--- 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 logs diff --git a/logs b/logs new file mode 100644 index 00000000..6c541578 --- /dev/null +++ b/logs @@ -0,0 +1,17 @@ +[10/17/2024, 3:15:21 PM] GET / 200 80ms +[10/17/2024, 3:15:23 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 10ms +[10/17/2024, 3:15:38 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 11ms +[10/17/2024, 3:16:05 PM] GET / 200 3ms +[10/17/2024, 3:16:06 PM] GET / 200 1ms +[10/17/2024, 3:16:07 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 10ms +[10/17/2024, 3:16:39 PM] GET / 200 3ms +[10/17/2024, 3:20:31 PM] GET / 200 3ms +[10/17/2024, 3:21:07 PM] GET / 200 3ms +[10/17/2024, 3:21:08 PM] GET / 200 2ms +[10/17/2024, 3:21:09 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 16ms +[10/17/2024, 3:21:36 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 8ms +[10/17/2024, 3:21:40 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 8ms +[10/17/2024, 3:21:48 PM] GET / 200 2ms +[10/17/2024, 3:21:57 PM] GET / 200 1ms +[10/17/2024, 3:21:58 PM] GET / 200 2ms +[10/17/2024, 3:21:59 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 6ms diff --git a/src/utils/apps.ts b/src/utils/apps.ts index e0fc6028..acdf2f34 100644 --- a/src/utils/apps.ts +++ b/src/utils/apps.ts @@ -128,8 +128,9 @@ export const apps: App[] = [ lightBackground: "rgba(230, 235, 240, 1)" }, links: { - website: "https://bazar.arweave.dev", - twitter: "https://twitter.com/OurBazAR" + website: "https://bazar.arweave.net", + twitter: "https://twitter.com/OurBazAR", + discord: "https://discord.gg/weavers" } }, { @@ -318,7 +319,7 @@ export const apps: App[] = [ lightBackground: "rgba(230, 235, 240, 1)" }, links: { - website: "https://alex.arweave.dev/", + website: "https://alex.arweave.net/", twitter: "https://twitter.com/thealexarchive", discord: "http://discord.gg/2uZsWuTNvN" } From 242e859557b7443b1ea1d6b96cbf57e6bc33e723 Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Fri, 18 Oct 2024 11:04:10 -0700 Subject: [PATCH 19/19] chore: remove logs --- logs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 logs diff --git a/logs b/logs deleted file mode 100644 index 6c541578..00000000 --- a/logs +++ /dev/null @@ -1,17 +0,0 @@ -[10/17/2024, 3:15:21 PM] GET / 200 80ms -[10/17/2024, 3:15:23 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 10ms -[10/17/2024, 3:15:38 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 11ms -[10/17/2024, 3:16:05 PM] GET / 200 3ms -[10/17/2024, 3:16:06 PM] GET / 200 1ms -[10/17/2024, 3:16:07 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 10ms -[10/17/2024, 3:16:39 PM] GET / 200 3ms -[10/17/2024, 3:20:31 PM] GET / 200 3ms -[10/17/2024, 3:21:07 PM] GET / 200 3ms -[10/17/2024, 3:21:08 PM] GET / 200 2ms -[10/17/2024, 3:21:09 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 16ms -[10/17/2024, 3:21:36 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 8ms -[10/17/2024, 3:21:40 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 8ms -[10/17/2024, 3:21:48 PM] GET / 200 2ms -[10/17/2024, 3:21:57 PM] GET / 200 1ms -[10/17/2024, 3:21:58 PM] GET / 200 2ms -[10/17/2024, 3:21:59 PM] GET /mint/z0mMFESgKdR4rOo_j62KG58Tkkf2403bl_m4vPr8vXM/1000000000000 200 6ms