From bfbc1a50625183fadb90966dcf50a533ee2c9024 Mon Sep 17 00:00:00 2001 From: "Ramon \"9Tails\" Canales" Date: Sun, 7 Apr 2024 16:30:38 +0100 Subject: [PATCH 1/2] feat(analytics): adding masa analytics and bridge events (#165) Co-authored-by: Jack Hamer --- .../zksync/useWithdrawalFinalization.ts | 6 ++ nuxt.config.ts | 17 +++- store/onboard.ts | 1 + types/index.d.ts | 11 +++ utils/analytics.ts | 80 +++++++++++++------ views/transactions/Deposit.vue | 5 ++ views/transactions/Transfer.vue | 5 ++ 7 files changed, 100 insertions(+), 25 deletions(-) diff --git a/composables/zksync/useWithdrawalFinalization.ts b/composables/zksync/useWithdrawalFinalization.ts index 64dc508e1..3d91bf7b1 100644 --- a/composables/zksync/useWithdrawalFinalization.ts +++ b/composables/zksync/useWithdrawalFinalization.ts @@ -147,6 +147,12 @@ export default (transactionInfo: ComputedRef) => { }, }); + trackEvent("withdrawal-finalized", { + token: transactionInfo.value!.token.symbol, + amount: transactionInfo.value!.token.amount, + to: transactionInfo.value!.to.address, + }); + status.value = "done"; return receipt; } catch (err) { diff --git a/nuxt.config.ts b/nuxt.config.ts index 36dc688a6..04a94db2e 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -44,6 +44,11 @@ export default defineNuxtConfig({ src: "https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js", defer: true, }, + { + hid: "MASA-JS", + src: "https://cdn.jsdelivr.net/npm/@masa-finance/analytics-sdk@latest/dist/browser/masa-analytics.min.js", + defer: true, + }, ], }, }, @@ -79,8 +84,16 @@ export default defineNuxtConfig({ public: { ankrToken: process.env.ANKR_TOKEN, screeningApiUrl: process.env.SCREENING_API_URL, - dataplaneUrl: process.env.DATAPLANE_URL, - rudderKey: process.env.RUDDER_KEY, + analytics: { + rudder: { + key: process.env.RUDDER_KEY, + dataplaneUrl: process.env.DATAPLANE_URL, + }, + masa: { + clientId: process.env.MASA_KEY, + appId: process.env.MASA_APP_ID, + }, + }, }, }, vite: { diff --git a/store/onboard.ts b/store/onboard.ts index 2a3661f62..f80979908 100644 --- a/store/onboard.ts +++ b/store/onboard.ts @@ -79,6 +79,7 @@ export const useOnboardStore = defineStore("onboard", () => { await identifyWalletName(); account.value = updatedAccount; connectorName.value = updatedAccount.connector?.name; + identifyWallet(updatedAccount.address, walletName.value); } catch (err) { disconnect(); const error = formatError(err as Error); diff --git a/types/index.d.ts b/types/index.d.ts index 2cc242a95..91a8b96c1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -146,5 +146,16 @@ declare global { track: (eventName: string, params?: unknown) => void; initialized: boolean; }; + MA?: { + MasaAnalytics: { + new ({ clientId }); + }; + }; + masaAnalytics?: { + trackCustomEvent: ({ eventName, additionalEventData }) => void; + firePageViewEvent: ({ page, additionalEventData }) => void; + fireConnectWalletEvent: ({ user_address, wallet_type, additionalEventData }) => void; + initialized: boolean; + }; } } diff --git a/utils/analytics.ts b/utils/analytics.ts index ef733b870..c5c987f18 100644 --- a/utils/analytics.ts +++ b/utils/analytics.ts @@ -1,46 +1,80 @@ +import type { Hash } from "@/types"; + let analyticsLoaded = false; -const isReady = (): Promise => { - return new Promise((resolve, reject) => { - if (!window.rudderanalytics) { - reject(new Error("Rudder not loaded")); - } - window.rudderanalytics?.ready(() => { - resolve(); - }); +async function loadRudder() { + if (!window.rudderanalytics) { + await new Promise((resolve) => setTimeout(resolve, 250)); + throw new Error("Rudder not loaded"); + } + const runtimeConfig = useRuntimeConfig(); + window.rudderanalytics.load( + runtimeConfig.public.analytics.rudder.key, + runtimeConfig.public.analytics.rudder.dataplaneUrl + ); +} + +async function loadMasa() { + if (!window.MA) { + await new Promise((resolve) => setTimeout(resolve, 250)); + throw new Error("Masa not loaded"); + } + const runtimeConfig = useRuntimeConfig(); + window.masaAnalytics = new window.MA.MasaAnalytics({ + clientId: runtimeConfig.public.analytics.masa.clientId, }); -}; +} export async function initAnalytics(): Promise { + if (analyticsLoaded) return true; + const runtimeConfig = useRuntimeConfig(); - if (!runtimeConfig.public.rudderKey || !runtimeConfig.public.dataplaneUrl) { + const useRudder = Boolean( + runtimeConfig.public.analytics.rudder.key && runtimeConfig.public.analytics.rudder.dataplaneUrl + ); + const useMasa = Boolean(runtimeConfig.public.analytics.masa.clientId && runtimeConfig.public.analytics.masa.appId); + if ((!useRudder && !useMasa) || analyticsLoaded) { return false; } - if (analyticsLoaded) { - await isReady(); - return true; - } - await retry(async () => { - if (!window.rudderanalytics) { - await new Promise((resolve) => setTimeout(resolve, 250)); - throw new Error("Rudder not loaded"); - } - }); - window.rudderanalytics?.load(runtimeConfig.public.rudderKey, runtimeConfig.public.dataplaneUrl); + const services = []; + if (useRudder) services.push(loadRudder()); + if (useMasa) services.push(loadMasa()); + + await Promise.all(services); analyticsLoaded = true; - await isReady(); return true; } export async function trackPage(): Promise { if (await initAnalytics()) { + const runtimeConfig = useRuntimeConfig(); window.rudderanalytics?.page(); + window.masaAnalytics?.firePageViewEvent({ + page: window.location.href, + additionalEventData: { appId: runtimeConfig.public.analytics.masa.appId }, + }); } } -export async function trackEvent(eventName: string, params?: unknown): Promise { +export async function trackEvent(eventName: string, params?: object): Promise { if (await initAnalytics()) { + const runtimeConfig = useRuntimeConfig(); window.rudderanalytics?.track(eventName, params); + window.masaAnalytics?.trackCustomEvent({ + eventName, + additionalEventData: { appId: runtimeConfig.public.analytics.masa.appId, ...params }, + }); + } +} + +export async function identifyWallet(userAddress: Hash | undefined, walletType?: string): Promise { + if (await initAnalytics()) { + const runtimeConfig = useRuntimeConfig(); + window.masaAnalytics?.fireConnectWalletEvent({ + user_address: userAddress, + wallet_type: walletType, + additionalEventData: { appId: runtimeConfig.public.analytics.masa.appId }, + }); } } diff --git a/views/transactions/Deposit.vue b/views/transactions/Deposit.vue index 2ba75be52..6aa9d8d55 100644 --- a/views/transactions/Deposit.vue +++ b/views/transactions/Deposit.vue @@ -719,6 +719,11 @@ const makeTransaction = async () => { waitForCompletion(transactionInfo.value) .then((completedTransaction) => { transactionInfo.value = completedTransaction; + trackEvent("deposit", { + token: transaction.value!.token.symbol, + amount: transaction.value!.token.amount, + to: transaction.value!.to.address, + }); setTimeout(() => { transfersHistoryStore.reloadRecentTransfers().catch(() => undefined); eraWalletStore.requestBalance({ force: true }).catch(() => undefined); diff --git a/views/transactions/Transfer.vue b/views/transactions/Transfer.vue index f412ba126..0695c9495 100644 --- a/views/transactions/Transfer.vue +++ b/views/transactions/Transfer.vue @@ -650,6 +650,11 @@ const makeTransaction = async () => { waitForCompletion(transactionInfo.value) .then((completedTransaction) => { transactionInfo.value = completedTransaction; + trackEvent(transaction.value!.type, { + token: transaction.value!.token.symbol, + amount: transaction.value!.token.amount, + to: transaction.value!.to.address, + }); setTimeout(() => { transfersHistoryStore.reloadRecentTransfers().catch(() => undefined); walletStore.requestBalance({ force: true }).catch(() => undefined); From 0dfc1ee4e1dd0116cd8fec45204a95448f51231b Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Sun, 7 Apr 2024 18:34:04 +0300 Subject: [PATCH 2/2] fix: add failed transactions storage --- store/zksync/transactionStatus.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/store/zksync/transactionStatus.ts b/store/zksync/transactionStatus.ts index f52b78cf8..bb760b149 100644 --- a/store/zksync/transactionStatus.ts +++ b/store/zksync/transactionStatus.ts @@ -30,6 +30,14 @@ export const useZkSyncTransactionStatusStore = defineStore("zkSyncTransactionSta const { account } = storeToRefs(onboardStore); const { eraNetwork } = storeToRefs(providerStore); + const failedTransaction = useStorage("zksync-bridge-failed-transaction", []); + const addFailedTransaction = (transaction: TransactionInfo) => { + if (failedTransaction.value.some((tx) => tx.transactionHash === transaction.transactionHash)) { + return; + } + failedTransaction.value = [...failedTransaction.value, transaction]; + }; + const storageSavedTransactions = useStorage<{ [networkKey: string]: TransactionInfo[] }>( "zksync-bridge-transactions", {} @@ -88,6 +96,7 @@ export const useZkSyncTransactionStatusStore = defineStore("zkSyncTransactionSta transaction.info.withdrawalFinalizationAvailable = false; transaction.info.failed = true; transaction.info.completed = true; + addFailedTransaction(transaction); return transaction; } if (transactionDetails.status !== "verified") { @@ -106,10 +115,11 @@ export const useZkSyncTransactionStatusStore = defineStore("zkSyncTransactionSta const transactionReceipt = await providerStore.requestProvider().getTransactionReceipt(transaction.transactionHash); if (!transactionReceipt) return transaction; const transactionDetails = await providerStore.requestProvider().getTransactionDetails(transaction.transactionHash); + transaction.info.completed = true; if (transactionDetails.status === "failed") { transaction.info.failed = true; + addFailedTransaction(transaction); } - transaction.info.completed = true; return transaction; }; const waitForCompletion = async (transaction: TransactionInfo) => {