Skip to content

Commit

Permalink
Analytics Improvements (#1159)
Browse files Browse the repository at this point in the history
* chore: remove some properties from posthog unsafe properties

* chore: set some analytics values on user profile

* chore: add isOnboarded property to account create analytics event

* feat: add analytics event for asset transfer fiat value

* feat: check for ghosts of the past nft

* chore: respond to pr review
  • Loading branch information
chidg authored Nov 29, 2023
1 parent fcb9cff commit 3e1fdde
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 36 deletions.
3 changes: 0 additions & 3 deletions apps/extension/src/core/config/posthog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ const unsafeProperties = [
"$os",
"$browser",
"$device_type",
"$current_url",
"$host",
"$pathname",
"$browser_version",
"$screen_height",
"$screen_width",
Expand Down
32 changes: 20 additions & 12 deletions apps/extension/src/core/domains/accounts/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ import { combineLatest } from "rxjs"
import { SOURCES } from "../mnemonics/store"

export default class AccountsHandler extends ExtensionHandler {
private async captureAccountCreateEvent(type: string | undefined, method: string) {
talismanAnalytics.capture("account create", {
type,
method,
isOnboarded: await this.stores.app.getIsOnboarded(),
})
}

private async accountCreate({ name, type, ...options }: RequestAccountCreate): Promise<string> {
const password = this.stores.password.getPassword()
assert(password, "Not logged in")
Expand Down Expand Up @@ -105,7 +113,7 @@ export default class AccountsHandler extends ExtensionHandler {
type
)

talismanAnalytics.capture("account create", { type, method: "derived" })
this.captureAccountCreateEvent(type, "derived")
return pair.address
}

Expand Down Expand Up @@ -166,7 +174,7 @@ export default class AccountsHandler extends ExtensionHandler {
type // if undefined, defaults to keyring's default (sr25519 atm)
)

talismanAnalytics.capture("account create", { type, method: "seed" })
this.captureAccountCreateEvent(type, "seed")

return pair.address
} catch (error) {
Expand Down Expand Up @@ -203,7 +211,7 @@ export default class AccountsHandler extends ExtensionHandler {
keyring.encryptAccount(pair, password)
addresses.push(pair.address)

talismanAnalytics.capture("account create", { type: pair.type, method: "json" })
this.captureAccountCreateEvent(pair.type, "json")
}

return addresses
Expand All @@ -213,7 +221,7 @@ export default class AccountsHandler extends ExtensionHandler {
name,
address,
path,
}: RequestAccountCreateLedgerEthereum): string {
}: RequestAccountCreateLedgerEthereum) {
assert(isEthereumAddress(address), "Not an Ethereum address")

// ui-keyring's addHardware method only supports substrate accounts, cannot set ethereum type
Expand Down Expand Up @@ -241,7 +249,7 @@ export default class AccountsHandler extends ExtensionHandler {
keyring.keyring.addPair(pair)
keyring.saveAccount(pair)

talismanAnalytics.capture("account create", { type: "ethereum", method: "hardware" })
this.captureAccountCreateEvent("ethereum", "hardware")

return pair.address
}
Expand Down Expand Up @@ -292,7 +300,7 @@ export default class AccountsHandler extends ExtensionHandler {
keyring.keyring.addPair(pair)
keyring.saveAccount(pair)

talismanAnalytics.capture("account create", { type, method: "dcent" })
this.captureAccountCreateEvent(type, "dcent")

return pair.address
}
Expand All @@ -312,7 +320,7 @@ export default class AccountsHandler extends ExtensionHandler {
origin: AccountType.Ledger,
})

talismanAnalytics.capture("account create", { type: "substrate", method: "hardware" })
this.captureAccountCreateEvent("substrate", "hardware")

return pair.address
}
Expand All @@ -333,7 +341,7 @@ export default class AccountsHandler extends ExtensionHandler {
origin: AccountType.Qr,
})

talismanAnalytics.capture("account create", { type: "substrate", method: "qr" })
this.captureAccountCreateEvent("substrate", "qr")

return pair.address
}
Expand Down Expand Up @@ -376,10 +384,10 @@ export default class AccountsHandler extends ExtensionHandler {
keyring.keyring.addPair(pair)
keyring.saveAccount(pair)

talismanAnalytics.capture("add watched account", {
type: isEthereumAddress(safeAddress) ? "ethereum" : "substrate",
method: "watched",
})
this.captureAccountCreateEvent(
isEthereumAddress(safeAddress) ? "ethereum" : "substrate",
"watched"
)

return pair.address
}
Expand Down
43 changes: 27 additions & 16 deletions apps/extension/src/core/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { MessageTypes, RequestType, ResponseType } from "@core/types"
import { Port, RequestIdOnly } from "@core/types/base"
import { awaitKeyringLoaded } from "@core/util/awaitKeyringLoaded"
import { CONFIG_RATE_LIMIT_ERROR, getConfig } from "@core/util/getConfig"
import { hasGhostsOfThePast } from "@core/util/hasGhostsOfThePast"
import { fetchHasSpiritKey } from "@core/util/hasSpiritKey"
import keyring from "@polkadot/ui-keyring"
import * as Sentry from "@sentry/browser"
Expand Down Expand Up @@ -93,6 +94,9 @@ export default class Extension extends ExtensionHandler {
await stores.sites.updateSite(url, autoAddSite)
})
})

this.checkSpiritKeyOwnership()
this.checkGhostsOfThePastOwnership()
})

// setup polling for config from github
Expand All @@ -103,7 +107,6 @@ export default class Extension extends ExtensionHandler {

this.initDb()
this.initWalletFunding()
this.checkSpiritKeyOwnership()
this.cleanup()
}

Expand Down Expand Up @@ -205,23 +208,19 @@ export default class Extension extends ExtensionHandler {
})
}

private checkSpiritKeyOwnership() {
// wait 10 seconds as this check is low priority
// also need to be wait for keyring to be loaded and accounts populated
awaitKeyringLoaded().then(async () => {
try {
const hasSpiritKey = await fetchHasSpiritKey()
const currentSpiritKey = await this.stores.app.get("hasSpiritKey")
private async checkSpiritKeyOwnership() {
try {
const hasSpiritKey = await fetchHasSpiritKey()
const currentSpiritKey = await this.stores.app.get("hasSpiritKey")

if (currentSpiritKey !== hasSpiritKey) {
await this.stores.app.set({ hasSpiritKey, needsSpiritKeyUpdate: true })
await this.updateSpiritKeyOwnership(hasSpiritKey)
}
} catch (err) {
// ignore, don't update app store nor posthog property
log.error("Failed to check Spirit Key ownership", { err })
if (currentSpiritKey !== hasSpiritKey) {
await this.stores.app.set({ hasSpiritKey, needsSpiritKeyUpdate: true })
await this.updateSpiritKeyOwnership(hasSpiritKey)
}
})
} catch (err) {
// ignore, don't update app store nor posthog property
log.error("Failed to check Spirit Key ownership", { err })
}

// in case reporting to posthog fails, set a timer so that every 5 min we will re-attempt
setInterval(async () => {
Expand All @@ -243,6 +242,18 @@ export default class Extension extends ExtensionHandler {
await this.stores.app.set({ needsSpiritKeyUpdate: false })
}

private async checkGhostsOfThePastOwnership() {
try {
const hasGhosts = await hasGhostsOfThePast()
const hasGhostsNft = Object.values(hasGhosts).some((g) => g)
await talismanAnalytics.capture("Ghosts of the past ownership", {
$set: { hasGhostsOfThePast: hasGhostsNft },
})
} catch (err) {
log.error("Failed to check Ghosts of the Past ownership", { err })
}
}

public async handle<TMessageType extends MessageTypes>(
id: string,
type: TMessageType,
Expand Down
14 changes: 9 additions & 5 deletions apps/extension/src/core/libs/Analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ class TalismanAnalytics {
// accounts
const accounts = keyring.getAccounts()
if (accounts.length > 0) {
posthog.capture("accounts count", { count: keyring.getAccounts().length })
// account type breakdown
const accountBreakdown = accounts.reduce(
(result, account) => {
Expand Down Expand Up @@ -145,10 +144,6 @@ class TalismanAnalytics {
throw error
}

posthog.capture("balances fiat sum", {
total: roundToFirstInteger(balances.sum.fiat("usd").total),
})

// balances top 5 tokens/networks
// get Balance list per chain/evmNetwork and token
const balancesPerChainToken: Record<string, Balance[]> = balances.each
Expand Down Expand Up @@ -180,6 +175,15 @@ class TalismanAnalytics {
.slice(0, 5)

posthog.capture("balances top tokens", topChainTokens)
posthog.capture("balances report", {
$set: {
accountsCount: keyring
.getAccounts()
.filter(({ meta }) => meta.origin !== AccountType.Watched).length,
totalFiatValue: roundToFirstInteger(balances.sum.fiat("usd").total),
topToken: `${topChainTokens[0].chainId}: ${topChainTokens[0].tokenId}`,
},
})
}
}

Expand Down
38 changes: 38 additions & 0 deletions apps/extension/src/core/util/hasErc721Nft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AccountType } from "@core/domains/accounts/types"
import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm"
import keyring from "@polkadot/ui-keyring"
import { EvmNetworkId } from "@talismn/chaindata-provider"
import { parseAbi } from "viem"

import { abiErc721 } from "./abi"

type Address = `0x${string}`

export const hasErc721Nft = async ({
evmNetworkId,
contractAddress,
}: {
evmNetworkId: EvmNetworkId
contractAddress: Address
}): Promise<Record<Address, boolean>> => {
const evmAddresses = keyring
.getPairs()
.filter(({ type, meta }) => type === "ethereum" && meta.origin !== AccountType.Watched)
.map(({ address }) => address as Address)

const client = await chainConnectorEvm.getPublicClientForEvmNetwork(evmNetworkId)
if (!client) throw new Error(`Unable to connect to EVM network: ${evmNetworkId}`)

const data = await Promise.all(
evmAddresses.map((address) =>
client.readContract({
address: contractAddress,
abi: parseAbi(abiErc721),
functionName: "balanceOf",
args: [address],
})
)
)

return Object.fromEntries(evmAddresses.map((address, i) => [address, data[i] > 0n]))
}
10 changes: 10 additions & 0 deletions apps/extension/src/core/util/hasGhostsOfThePast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { hasErc721Nft } from "./hasErc721Nft"

const moonbeamEvmNetworkId = "1284"
const contractAddress = "0xDF67E64DC198E5287a6a625a4733841bD147E584"

export const hasGhostsOfThePast = () =>
hasErc721Nft({
evmNetworkId: moonbeamEvmNetworkId,
contractAddress,
})
6 changes: 6 additions & 0 deletions apps/extension/src/ui/domains/SendFunds/useSendFunds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "@core/domains/ethereum/helpers"
import { AssetTransferMethod } from "@core/domains/transfers/types"
import { log } from "@core/log"
import { roundToFirstInteger } from "@core/util/roundToFirstInteger"
import { HexString } from "@polkadot/util/types"
import { provideContext } from "@talisman/util/provideContext"
import { Address, Balance, BalanceFormatter } from "@talismn/balances"
Expand Down Expand Up @@ -536,6 +537,11 @@ const useSendFundsProvider = () => {

setIsProcessing(true)

api.analyticsCapture({
eventName: "asset transfer fiat value",
options: { value: roundToFirstInteger(value.fiat("usd") ?? 0) ?? "0" },
})

if (token.chain?.id && chain?.genesisHash) {
const { hash } = await api.assetTransfer(
token.chain.id,
Expand Down

0 comments on commit 3e1fdde

Please sign in to comment.