From 8fba2f3512acf84068e87af3d74e9a598bbb06e7 Mon Sep 17 00:00:00 2001 From: Larry the Cucumber Date: Tue, 17 Oct 2023 18:48:21 -0700 Subject: [PATCH 1/2] chore: add more mixpanel tracking --- .../chain-selector/ChainSelector.tsx | 9 ++- src/views/bridge/components/ConfirmBridge.tsx | 12 +++- .../issuance/components/zap/state/ui-atoms.ts | 61 ++++++++++++++----- 3 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/components/chain-selector/ChainSelector.tsx b/src/components/chain-selector/ChainSelector.tsx index 4f3e457ae..b22079c33 100644 --- a/src/components/chain-selector/ChainSelector.tsx +++ b/src/components/chain-selector/ChainSelector.tsx @@ -2,6 +2,7 @@ import Button from 'components/button' import Base from 'components/icons/logos/Base' import Ethereum from 'components/icons/logos/Ethereum' import Popup from 'components/popup' +import mixpanel from 'mixpanel-browser' import { transition } from 'theme' import { useAtom, useAtomValue } from 'jotai' import { useState } from 'react' @@ -80,7 +81,13 @@ const ChainList = ({ onSelect }: { onSelect(chain: number): void }) => { ) })} - diff --git a/src/views/bridge/components/ConfirmBridge.tsx b/src/views/bridge/components/ConfirmBridge.tsx index ddad8b0b6..dee4b9df5 100644 --- a/src/views/bridge/components/ConfirmBridge.tsx +++ b/src/views/bridge/components/ConfirmBridge.tsx @@ -3,7 +3,7 @@ import TransactionButton from 'components/button/TransactionButton' import useContractWrite from 'hooks/useContractWrite' import useHasAllowance from 'hooks/useHasAllowance' import useWatchTransaction from 'hooks/useWatchTransaction' -import { atom, useAtomValue, useSetAtom } from 'jotai' +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { useCallback, useEffect, useState } from 'react' import { safeParseEther } from 'utils' import { Address } from 'viem' @@ -16,6 +16,7 @@ import { } from '../atoms' import { Modal } from 'components' import { Box, Divider, Text } from 'theme-ui' +import mixpanel from 'mixpanel-browser' const btnLabelAtom = atom((get) => { const token = get(selectedTokenAtom) @@ -78,7 +79,9 @@ const ApproveBtn = () => { const ConfirmBridgeBtn = ({ onSuccess }: { onSuccess(): void }) => { const bridgeTransaction = useAtomValue(bridgeTxAtom) - const setAmount = useSetAtom(bridgeAmountAtom) + const bridgeToken = useAtomValue(selectedTokenAtom) + const isWrapping = useAtomValue(isBridgeWrappingAtom) + const [amount, setAmount] = useAtom(bridgeAmountAtom) const { isReady, gas, @@ -96,6 +99,11 @@ const ConfirmBridgeBtn = ({ onSuccess }: { onSuccess(): void }) => { useEffect(() => { if (status === 'success') { + mixpanel.track('Bridge Success', { + Token: bridgeToken.symbol, + Amount: amount, + Destination: isWrapping ? 'Base' : 'Ethereum', + }) setAmount('') reset() onSuccess() diff --git a/src/views/issuance/components/zap/state/ui-atoms.ts b/src/views/issuance/components/zap/state/ui-atoms.ts index f7de63d0b..7c95bdebb 100644 --- a/src/views/issuance/components/zap/state/ui-atoms.ts +++ b/src/views/issuance/components/zap/state/ui-atoms.ts @@ -2,7 +2,13 @@ import { Token, TokenQuantity } from '@reserve-protocol/token-zapper' import { atom, Getter, SetStateAction, Setter } from 'jotai' import { atomWithStorage } from 'jotai/utils' import { Atom } from 'jotai/vanilla' -import { ethPriceAtom, gasFeeAtom, gasPriceAtom, isWalletModalVisibleAtom, rTokenAtom } from 'state/atoms' +import { + ethPriceAtom, + gasFeeAtom, + gasPriceAtom, + isWalletModalVisibleAtom, + rTokenAtom, +} from 'state/atoms' import { onlyNonNullAtom } from 'utils/atoms/utils' import { notifyError, notifySuccess } from 'hooks/useNotification' @@ -99,7 +105,11 @@ export const zapTxFeeAtom = atom((get) => { const gasPrice = get(gasFeeAtom) const gasUsdPrice = get(ethPriceAtom) return tx?.transaction?.gasEstimate - ? Number(tx.result.universe.nativeToken.from(tx.transaction.feeEstimate(gasPrice ?? 1n)).format()) * gasUsdPrice + ? Number( + tx.result.universe.nativeToken + .from(tx.transaction.feeEstimate(gasPrice ?? 1n)) + .format() + ) * gasUsdPrice : 0 }) @@ -134,7 +144,9 @@ export const zapDust = atom((get) => { const rTokenOut = quote.outputToken - const dust = quote.swaps.outputs.filter(i => i.token !== rTokenOut && i.amount !== 0n) + const dust = quote.swaps.outputs.filter( + (i) => i.token !== rTokenOut && i.amount !== 0n + ) return dust }) @@ -148,10 +160,12 @@ export const zapDustValue = atom(async (get) => { if (quote == null) { return null } - const dustUSD = await Promise.all(dust.map(async d => ({ - dustQuantity: d, - usdValueOfDust: await quote.universe.fairPrice(d) - }))) + const dustUSD = await Promise.all( + dust.map(async (d) => ({ + dustQuantity: d, + usdValueOfDust: await quote.universe.fairPrice(d), + })) + ) let total = 0n for (const d of dustUSD) { @@ -159,7 +173,7 @@ export const zapDustValue = atom(async (get) => { } return { dust: dustUSD, - total: quote.universe.usd.from(total) + total: quote.universe.usd.from(total), } }) @@ -172,7 +186,6 @@ export const zapOutputValue = onlyNonNullAtom((get) => { return qty.format() }, '0') - const state = atom((get) => { const quotePromise = get(zapQuotePromise) const approvePromise = get(approvalNeededAtom) @@ -243,7 +256,10 @@ const loadingStates = new Set([ // 'approval_sent_loading', 'signature_loading', ]) -const buttonEnabled = atom((get) => buttonEnabledStates.has(get(state)) || get(previousZapTransaction) != null) +const buttonEnabled = atom( + (get) => + buttonEnabledStates.has(get(state)) || get(previousZapTransaction) != null +) const buttonIsLoading = atom((get) => loadingStates.has(get(state))) const buttonLabel = atom((get) => { if (get(zapSender) == null) { @@ -300,7 +316,7 @@ const zapEnabledForRTokens = new Set([ '0x9b451beb49a03586e6995e5a93b9c745d068581e', '0xfc0b1eef20e4c68b3dcf36c4537cfa7ce46ca70b', '0x50249c768a6d3cb4b6565c0a2bfbdb62be94915c', - '0xcc7ff230365bd730ee4b352cc2492cedac49383e' + '0xcc7ff230365bd730ee4b352cc2492cedac49383e', ]) export const zapEnabledAtom = atomWithStorage('zap-enabled', false) @@ -375,7 +391,7 @@ export const ui = { button: atom( (get) => ({ loadingLabel: get(buttonLoadingLabel), - enabled: (get(zapSender) == null || get(buttonEnabled)), + enabled: get(zapSender) == null || get(buttonEnabled), loading: get(previousZapTransaction) == null && get(buttonIsLoading), label: get(buttonLabel), }), @@ -391,10 +407,9 @@ export const ui = { } if (flowState === 'tx_loading') { errors = 0 - } - else if (flowState === 'tx_error') { + } else if (flowState === 'tx_error') { if (errors < 5) { - console.log("Requoting..") + console.log('Requoting..') set(redoQuote, Math.random()) errors += 1 } @@ -539,7 +554,7 @@ const signAndSendTx: ZapperAction = async ( const sendTx: ZapperAction = async ( get, set, - { inputQuantity, rToken, signer } + { inputToken, rToken, signer } ) => { const zapTx = get(resolvedZapTransaction) const gasLimit = get(resolvedZapTransactionGasEstimateUnits) @@ -558,10 +573,24 @@ const sendTx: ZapperAction = async ( set(permitSignature, null) set(zapInputString, '') set(addTransactionAtom, [resp.hash, `Easy mint ${rToken.symbol}`]) + + mixpanel.track('Zap Success', { + RToken: rToken.address.toString().toLowerCase() ?? '', + inputToken: inputToken.symbol, + }) } catch (e: any) { if (e.code === 'ACTION_REJECTED') { + mixpanel.track('User Rejected Zap', { + RToken: rToken.address.toString().toLowerCase() ?? '', + inputToken: inputToken.symbol, + }) notifyError('Zap failed', 'User rejected') } else { + mixpanel.track('Zapping Error', { + RToken: rToken.address.toString().toLowerCase() ?? '', + inputToken: inputToken.symbol, + error: e, + }) notifyError('Zap failed', 'Unknown error ' + e.code) } } finally { From bd70acaa4e3b6ca7133580874dacf71cdf39b41e Mon Sep 17 00:00:00 2001 From: Larry the Cucumber Date: Tue, 17 Oct 2023 19:12:01 -0700 Subject: [PATCH 2/2] chore: add mixpanel tracking to more places --- .../issuance/components/zap/state/atoms.ts | 14 ++++++-- .../state/createProxiedOneInchAggregator.ts | 9 +++-- .../issuance/components/zap/state/ui-atoms.ts | 17 +++++++-- .../issuance/components/zap/state/zapper.ts | 35 ++++++++++++------- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/views/issuance/components/zap/state/atoms.ts b/src/views/issuance/components/zap/state/atoms.ts index 4aa4f5f93..dc10edfc8 100644 --- a/src/views/issuance/components/zap/state/atoms.ts +++ b/src/views/issuance/components/zap/state/atoms.ts @@ -28,6 +28,7 @@ import { import { resolvedZapState, zappableTokens } from './zapper' import { type SearcherResult } from '@reserve-protocol/token-zapper/types/searcher/SearcherResult' import { type ZapTransaction } from '@reserve-protocol/token-zapper/types/searcher/ZapTransaction' +import mixpanel from 'mixpanel-browser' /** * I've tried to keep react effects to a minimum so most async code is triggered via some signal @@ -44,10 +45,10 @@ import { type ZapTransaction } from '@reserve-protocol/token-zapper/types/search */ export const previousZapTransaction = atom<{ - result: SearcherResult, - transaction: ZapTransaction, + result: SearcherResult + transaction: ZapTransaction permit2?: { - permit: PermitTransferFrom, + permit: PermitTransferFrom signature: string } } | null>(null) @@ -331,6 +332,8 @@ export const resolvedZapTransaction = simplifyLoadable(zapTransaction) export const zapTransactionGasEstimateUnits = loadable( onlyNonNullAtom(async (get) => { + const selectedZapToken = get(selectedZapTokenAtom) + const rToken = get(rTokenAtom) const tx = get(resolvedZapTransaction) const needsApproval = get(resolvedApprovalNeeded) if ( @@ -357,6 +360,11 @@ export const zapTransactionGasEstimateUnits = loadable( continue } } + + mixpanel.track('Failed to estimate gas for zapping', { + RToken: rToken.address.toString().toLowerCase() ?? '', + inputToken: selectedZapToken.symbol, + }) throw new Error('Failed to estimate gas') }) ) diff --git a/src/views/issuance/components/zap/state/createProxiedOneInchAggregator.ts b/src/views/issuance/components/zap/state/createProxiedOneInchAggregator.ts index 10988453b..a01ea0a3b 100644 --- a/src/views/issuance/components/zap/state/createProxiedOneInchAggregator.ts +++ b/src/views/issuance/components/zap/state/createProxiedOneInchAggregator.ts @@ -1,8 +1,9 @@ import { createOneInchDexAggregator, DexAggregator, - Universe + Universe, } from '@reserve-protocol/token-zapper' +import mixpanel from 'mixpanel-browser' /** * Creates a 1inch aggregator that will use a list of proxies to make requests @@ -42,7 +43,7 @@ export const createProxiedOneInchAggregator = ( const instances = [...aggregatorInstances] for (let i = instances.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) - ;[instances[i], instances[j]] = [instances[j], instances[i]] + ;[instances[i], instances[j]] = [instances[j], instances[i]] } return instances } @@ -64,6 +65,10 @@ export const createProxiedOneInchAggregator = ( continue } } + mixpanel.track('All 1inch aggregators failed', { + RToken: output.address.toString().toLowerCase() ?? '', + inputToken: input.token.symbol, + }) throw new Error('All aggregators failed') } ) diff --git a/src/views/issuance/components/zap/state/ui-atoms.ts b/src/views/issuance/components/zap/state/ui-atoms.ts index 7c95bdebb..742cd35bd 100644 --- a/src/views/issuance/components/zap/state/ui-atoms.ts +++ b/src/views/issuance/components/zap/state/ui-atoms.ts @@ -502,7 +502,7 @@ const resetTxAtoms = (set: Setter) => { const signAndSendTx: ZapperAction = async ( get, set, - { signer, provider, rToken, quote } + { signer, provider, rToken, quote, inputToken } ) => { try { const permit = get(permit2ToSignAtom) @@ -541,10 +541,23 @@ const signAndSendTx: ZapperAction = async ( set(zapTxHash, resp.hash) set(zapInputString, '') set(addTransactionAtom, [resp.hash, `Easy mint ${rToken.symbol}`]) + mixpanel.track('Zap Success', { + RToken: rToken.address.toString().toLowerCase() ?? '', + inputToken: inputToken.symbol, + }) } catch (e: any) { if (e.code === 'ACTION_REJECTED') { + mixpanel.track('User Rejected Zap', { + RToken: rToken.address.toString().toLowerCase() ?? '', + inputToken: inputToken.symbol, + }) notifyError('Zap failed', 'User rejected signature request') } else { + mixpanel.track('Zap Execution Error', { + RToken: rToken.address.toString().toLowerCase() ?? '', + inputToken: inputToken.symbol, + error: e, + }) notifyError('Zap failed', 'Unknown error ' + e.code) } } finally { @@ -586,7 +599,7 @@ const sendTx: ZapperAction = async ( }) notifyError('Zap failed', 'User rejected') } else { - mixpanel.track('Zapping Error', { + mixpanel.track('Zap Execution Error', { RToken: rToken.address.toString().toLowerCase() ?? '', inputToken: inputToken.symbol, error: e, diff --git a/src/views/issuance/components/zap/state/zapper.ts b/src/views/issuance/components/zap/state/zapper.ts index f846f6ff1..a8263dfc3 100644 --- a/src/views/issuance/components/zap/state/zapper.ts +++ b/src/views/issuance/components/zap/state/zapper.ts @@ -1,11 +1,18 @@ -import { setupEthereumZapper, ethereumConfig, Universe, baseConfig, setupBaseZapper } from '@reserve-protocol/token-zapper' +import { + setupEthereumZapper, + ethereumConfig, + Universe, + baseConfig, + setupBaseZapper, +} from '@reserve-protocol/token-zapper' import { atom } from 'jotai' import { loadable } from 'jotai/utils' import { onlyNonNullAtom, simplifyLoadable } from 'utils/atoms/utils' import { createProxiedOneInchAggregator } from './createProxiedOneInchAggregator' import { clientAtom } from 'state/atoms' -import { Web3Provider } from "@ethersproject/providers" +import { Web3Provider } from '@ethersproject/providers' import { PublicClient } from 'viem' +import mixpanel from 'mixpanel-browser' export function publicClientToProvider(publicClient: PublicClient) { const { chain } = publicClient @@ -17,12 +24,12 @@ export function publicClientToProvider(publicClient: PublicClient) { return new Web3Provider(async (method, params) => { return publicClient.request({ method, - params + params, } as any) }, network) } -const providerAtom = atom(get => { +const providerAtom = atom((get) => { const cli = get(clientAtom) if (cli == null) { return null @@ -30,7 +37,6 @@ const providerAtom = atom(get => { return publicClientToProvider(cli as any) }) - // TODO: Convert provider viem -> ethers export const connectionName = onlyNonNullAtom((get) => { return get(providerAtom).connection.url @@ -61,18 +67,20 @@ export const zapperState = loadable( } try { - - const chainIdToConfig: Record) => Promise}> = { + const chainIdToConfig: Record< + number, + { config: any; setup: (uni: Universe) => Promise } + > = { 1: { config: ethereumConfig, - setup: setupEthereumZapper + setup: setupEthereumZapper, }, 8453: { config: baseConfig, - setup: setupBaseZapper - } + setup: setupBaseZapper, + }, } - + const universe = await Universe.createWithConfig( provider, chainIdToConfig[provider.network.chainId].config, @@ -90,6 +98,9 @@ export const zapperState = loadable( return universe } catch (e) { + mixpanel.track('Failed zapper set up', { + ChainId: provider.network.chainId, + }) console.log(e) throw e } @@ -106,7 +117,7 @@ export const zapperLoaded = atom(async (get) => { return true }) -export const zappableTokens = atom(async(get) => { +export const zappableTokens = atom(async (get) => { const uni = get(resolvedZapState) if (uni == null) { return []