Skip to content

Commit

Permalink
governance analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
vsubhuman committed Nov 2, 2024
1 parent 5960d10 commit 157166b
Show file tree
Hide file tree
Showing 14 changed files with 103 additions and 22 deletions.
2 changes: 2 additions & 0 deletions packages/yoroi-extension/app/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import PortfolioDetailPage from './UI/pages/portfolio/PortfolioDetailPage';
// $FlowIgnore: suppressing this error
import PortfolioPage from './UI/pages/portfolio/PortfolioPage';
import BuySellDialog from './components/buySell/BuySellDialog';
import { ampli } from '../ampli/index';
// $FlowIgnore: suppressing this error

// PAGES
Expand Down Expand Up @@ -572,6 +573,7 @@ export function wrapGovernance(governanceProps: StoresAndActionsProps, children:
txDelegationError={delegationTxError}
tokenInfo={governanceProps.stores.tokenInfoStore.tokenInfo}
triggerBuySellAdaDialog={() => governanceProps.actions.dialogs.open.trigger({ dialog: BuySellDialog })}
ampli={ampli}
>
<Suspense fallback={null}>{children}</Suspense>;
</GovernanceContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ type ChooseDRepModallProps = {
export const ChooseDRepModal = ({ onSubmit }: ChooseDRepModallProps) => {
const [drepId, setDrepId] = React.useState('');
const [error, setError] = React.useState(false);
const { dRepIdChanged, governanceVoteChanged } = useGovernance();
const { dRepIdChanged, governanceVoteChanged, ampli } = useGovernance();
const { isLoading } = useModal();
const strings = useStrings();

React.useEffect(() => {
// ON MOUNT
ampli?.governanceChooseDrepPageViewed();
}, []);

React.useEffect(() => {
setError(false);
}, [drepId]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Props = {
loading: boolean;
};

const StyledCard: any = styled(Stack)(({ theme, selected, pending, isDrepSelected }: any) => ({
const StyledCard: any = styled(Stack)(({ theme, selected, pending, is_drep_selected }: any) => ({
position: 'relative',
display: 'flex',
flexDirection: 'column',
Expand All @@ -37,7 +37,7 @@ const StyledCard: any = styled(Stack)(({ theme, selected, pending, isDrepSelecte
backgroundImage: !pending && theme.palette.ds.bg_gradient_2,
border: '2px solid transparent',
backgroundOrigin: 'border-box',
pointerEvents: !isDrepSelected && 'none',
pointerEvents: is_drep_selected !== 'true' && 'none',
}),
cursor: 'pointer',
...(pending && {
Expand Down Expand Up @@ -85,9 +85,9 @@ export const GovernanceVoteingCard = ({
<div onMouseOver={() => onHover(true)} onMouseLeave={() => onHover(false)}>
<StyledCard
onClick={pending ? undefined : onClick}
pending={pending === true ? 'true' : undefined}
pending={pending ? 'true' : undefined}
selected={selected}
isDrepSelected={governanceStatus.status === 'delegate'}
is_drep_selected={String(governanceStatus.status === 'delegate')}
>
{loading && (
<SpinnerBox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import {hexToBytes} from "../../../tsCoreUtils";

type drepDelegation = { status: string | null; drep: string | null };

type GovernanceAnalytics = {
governanceChooseDrepPageViewed: () => void,
governanceConfirmTransactionPageViewed: () => void,
governanceTransactionSuccessPageViewed: () => void,
};

const initialGovernanceProvider = {
...defaultGovernanceState,
...defaultGovernanceActions,
Expand All @@ -32,6 +38,7 @@ const initialGovernanceProvider = {
triggerBuySellAdaDialog: null,
recentTransactions: [],
submitedTransactions: ([] as Array<{ isDrepDelegation: Boolean }>),
ampli: (null as GovernanceAnalytics | null),
};

const GovernanceContext = React.createContext(initialGovernanceProvider);
Expand All @@ -45,6 +52,7 @@ type GovernanceProviderProps = {
signDelegationTransaction: (params: any) => Promise<void>;
tokenInfo: any;
triggerBuySellAdaDialog: any;
ampli: GovernanceAnalytics;
};

export const GovernanceContextProvider = ({
Expand All @@ -56,6 +64,7 @@ export const GovernanceContextProvider = ({
signDelegationTransaction,
tokenInfo,
triggerBuySellAdaDialog,
ampli,
}: GovernanceProviderProps) => {
if (!currentWallet?.selectedWallet) throw new Error(`requires a wallet to be selected`);
const [state, dispatch] = React.useReducer(GovernanceReducer, {
Expand Down Expand Up @@ -162,6 +171,7 @@ export const GovernanceContextProvider = ({
triggerBuySellAdaDialog,
recentTransactions,
submitedTransactions,
ampli,
};

return <GovernanceContext.Provider value={context}>{children}</GovernanceContext.Provider>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { DelagationForm } from '../../features/governace/useCases/DelagationForm/DelagationForm';
import GovernanceLayout from './layout';
import {useGovernance} from "../../features/governace/module/GovernanceContextProvider";

type Props = {
stores: any;
Expand All @@ -9,6 +10,13 @@ type Props = {
};

const GovernanceDelegationFormPage = (props: Props): any => {

const { ampli } = useGovernance();
React.useEffect(() => {
// ON MOUNT
ampli?.governanceConfirmTransactionPageViewed();
}, []);

return (
<GovernanceLayout {...props}>
<DelagationForm />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ROUTES } from '../../../routes-config';
import { TransactionSubmitted } from '../../components/TransactionSubmitted/TransactionSubmitted';
import { useStrings } from '../../features/governace/common/useStrings';
import GovernanceLayout from './layout';
import {useGovernance} from "../../features/governace/module/GovernanceContextProvider";

type Props = {
stores: any;
Expand All @@ -22,6 +23,13 @@ const GovernanceTransactionSubmittedPage = (props: Props): any => {
const TransactionSubmittedWrapper = () => {
const history = useHistory();
const strings = useStrings();

const { ampli } = useGovernance();
React.useEffect(() => {
// ON MOUNT
ampli?.governanceTransactionSuccessPageViewed();
}, []);

return (
<TransactionSubmitted
title={strings.thanksForParticipation}
Expand Down
15 changes: 14 additions & 1 deletion packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// @flow

import { RustModule } from './rustLoader';
import { bytesToHex, hexToBytes } from '../../../../coreUtils';
import { bytesToHex, hexToBytes, maybe } from '../../../../coreUtils';
import { base32ToHex } from '../storage/bridge/utils';

export function v4PublicToV2(
v4Key: RustModule.WalletV4.Bip32PublicKey
Expand Down Expand Up @@ -52,16 +53,28 @@ export function transactionHexReplaceWitnessSet(txHex: string, witnessSetHex: st
}

export function dRepToMaybeCredentialHex(s: string): ?string {
const isPotentiallyValidHex = /^(22|23)[0-9a-fA-F]{56}$/.test(s);
return RustModule.WasmScope(Module => {
try {
if (s.startsWith('drep1')) {
if (s.length === 58) {
return maybe(base32ToHex(s), dRepToMaybeCredentialHex);
}
return Module.WalletV4.Credential
.from_keyhash(Module.WalletV4.Ed25519KeyHash.from_bech32(s)).to_hex();
}
if (s.startsWith('drep_script1')) {
return Module.WalletV4.Credential
.from_scripthash(Module.WalletV4.ScriptHash.from_bech32(s)).to_hex();
}
if (isPotentiallyValidHex && s.startsWith('22')) {
return Module.WalletV4.Credential
.from_keyhash(Module.WalletV4.Ed25519KeyHash.from_hex(s.substr(2))).to_hex();
}
if (isPotentiallyValidHex && s.startsWith('23')) {
return Module.WalletV4.Credential
.from_scripthash(Module.WalletV4.ScriptHash.from_hex(s.substr(2))).to_hex();
}
} catch {} // eslint-disable-line no-empty
return null;
})
Expand Down
22 changes: 11 additions & 11 deletions packages/yoroi-extension/app/api/ada/lib/storage/bridge/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { $npm$ReactIntl$MessageDescriptor } from 'react-intl';
import { defineMessages } from 'react-intl';
import { bech32 as bech32Module } from 'bech32';
import typeof { CertificateKind } from '@emurgo/cardano-serialization-lib-browser/cardano_serialization_lib';
import { bytesToHex, fail, hexToBytes } from '../../../../../coreUtils';
import { bytesToHex, fail, hexToBytes, maybe } from '../../../../../coreUtils';

export function tryAddressToKind(
address: string,
Expand Down Expand Up @@ -156,11 +156,18 @@ export function normalizeToBase58(addr: string): void | string {
return undefined;
}

// this implementation was copied from the convert function of the bech32 package.
const convertBase32ToHex = (data: any[]) => {
/**
* this implementation was copied from the convert function of the bech32 package.
*/
const convertBase32ToHex = (data: any[]): string => {
return bytesToHex(bech32Module.fromWords(data));
};

export const base32ToHex = (base32: string): ?string => {
const base32Words = bech32Module.decodeUnsafe(base32, base32.length);
return maybe(base32Words?.words, convertBase32ToHex);
};

/* eslint-disable */
const bigIntToBase58 = n => {
const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
Expand Down Expand Up @@ -255,14 +262,7 @@ export function normalizeToAddress(addr: string): void | RustModule.WalletV4.Add
}

// 3) Try decoding bech32...
const bech32Decoded = bech32Module.decodeUnsafe(addr, addr.length);
if (bech32Decoded) {
// 3.1) if successfull, convert the decoded bech32 to base16 and try parsing the hex
const hex = convertBase32ToHex(bech32Decoded.words);
return parseHexAddress(hex);
}

return undefined;
return maybe(base32ToHex(addr), parseHexAddress) ?? undefined;
}

export function toEnterprise(address: string): void | RustModule.WalletV4.EnterpriseAddress {
Expand Down
5 changes: 3 additions & 2 deletions packages/yoroi-extension/app/api/thunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import type {
import { Logger, stringifyError } from '../utils/logging';
import LocalizableError from '../i18n/LocalizableError';
import { WrongPassphraseError } from './ada/lib/cardanoCrypto/cryptoErrors';
import { sanitizeForLog } from '../coreUtils';

export type { CreateHardwareWalletRequest } from '../../chrome/extension/background/handlers/yoroi/wallet';

Expand Down Expand Up @@ -111,7 +112,7 @@ export function callBackground<T, R>(message: T): Promise<R> {
window.chrome.runtime.sendMessage(message, response => {
if (window.chrome.runtime.lastError) {
// eslint-disable-next-line prefer-promise-reject-errors
reject(`Error ${window.chrome.runtime.lastError} when calling the background with: ${JSON.stringify(message) ?? 'undefined'}`);
reject(`Error ${window.chrome.runtime.lastError} when calling the background with: ${JSON.stringify(sanitizeForLog(message)) ?? 'undefined'}`);
return;
}
resolve(response);
Expand Down Expand Up @@ -409,7 +410,7 @@ const callbacks = Object.freeze({
});
chrome.runtime.onMessage.addListener(async (message, _sender, _sendResponse) => {
//fixme: verify sender.id/origin
Logger.debug('get message from background:', JSON.stringify(message, null, 2));
Logger.debug('get message from background:', JSON.stringify(sanitizeForLog(message)));

if (message.type === 'wallet-state-update') {
if (message.params.newTxs) {
Expand Down
18 changes: 18 additions & 0 deletions packages/yoroi-extension/app/coreUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,24 @@ export function timeCached<R>(fun: () => R, ttl: number): () => R {
}
}

/**
* In case the value is an object, this creates a copy and hides some potentially sensitive fields from it.
*
* @param v - any value
* @return same value or a copy in case the value is an object
*/
export function sanitizeForLog(v: any): any {
if (v != null && typeof v === 'object') {
let r = Object.keys(v).reduce((o, k) => ({ ...o, [k]: sanitizeForLog(v[k]) }) , {})
// $FlowIgnore[incompatible-use]
if (r.password != null) {
r = { ...r, password: '[sanitized]' };
}
return r;
}
return v;
}

/**
* Makes sure the result is an array.
* Either returns the passed array or wraps the item into an array.
Expand Down
14 changes: 13 additions & 1 deletion packages/yoroi-extension/app/coreUtils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
delay, ensureArray,
hexToBytes, hexToUtf,
iterateLenGet,
iterateLenGetMap,
iterateLenGetMap, sanitizeForLog,
timeCached, utfToBytes,
zipGenerators
} from './coreUtils';
Expand Down Expand Up @@ -34,6 +34,18 @@ describe('utils', () => {
expect(ensureArray([undefined])).toEqual([undefined]);
});

test('sanitizeForLog', () => {
expect(sanitizeForLog(null)).toEqual(null);
expect(sanitizeForLog('qwe')).toEqual('qwe');
expect(sanitizeForLog(42)).toEqual(42);
expect(sanitizeForLog(true)).toEqual(true);
expect(sanitizeForLog({})).toEqual({});
expect(sanitizeForLog({ a: 12, b: 22 })).toEqual({ a: 12, b: 22 });
expect(sanitizeForLog({ a: 12, b: 22, password: 'qwe' })).toEqual({ a: 12, b: 22, password: '[sanitized]' });
expect(sanitizeForLog({ a: 12, b: 22, c: { password: 'qwe' } })).toEqual({ a: 12, b: 22, c: { password: '[sanitized]' } });
expect(sanitizeForLog({ a: 12, b: 22, c: { x: false, y: 5.5, z: { password: 'qwe' } } })).toEqual({ a: 12, b: 22, c: { x: false, y: 5.5, z: { password: '[sanitized]' } } });
});

});

describe('timeCached', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/yoroi-extension/app/routes-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

export const PAGE_ERROR_SUBROUTE: string = '/page-error';

export const ROUTES: { [string]: any } = {
export const ROUTES = {
ROOT: '/',
PAGE_ERROR: PAGE_ERROR_SUBROUTE,
NIGHTLY_INFO: '/nightly',
Expand Down Expand Up @@ -78,6 +78,7 @@ export const ROUTES: { [string]: any } = {
},
SWAP: {
ROOT: '/swap',
// $FlowIgnore
ERROR: '/swap' + PAGE_ERROR_SUBROUTE,
ORDERS: '/swap/orders',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { startPoll } from './coinPrice';
import { environment } from '../../../app/environment';
import axios from 'axios';
import fetchAdapter from '@vespaiach/axios-fetch-adapter';
import { sanitizeForLog } from '../../../app/coreUtils';

axios.defaults.adapter = fetchAdapter;

Expand All @@ -31,7 +32,7 @@ if (chrome.action) {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
//fixme: verify sender.id === extension id
if (environment.isDev()) {
console.debug(`get message ${JSON.stringify(message)} from ${sender.tab.id}`);
console.debug(`get message ${JSON.stringify(sanitizeForLog(message))} from ${sender.tab.id}`);
}
const handler = getHandler(message.type);
if (handler) {
Expand Down
2 changes: 2 additions & 0 deletions packages/yoroi-extension/chrome/extension/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ const initializeYoroi: void => Promise<void> = async () => {
ampli.stakingCenterPageViewed();
} else if (pathname === ROUTES.WALLETS.ROOT) {
ampli.walletPageViewed();
} else if (pathname === ROUTES.Governance.ROOT) {
ampli.governanceDashboardPageViewed();
}
});
};
Expand Down

0 comments on commit 157166b

Please sign in to comment.