Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ST-618: Remove desmos queries and reduce account page messages count … #23

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web-stratos/codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ generates:
./src/graphql/types/general_types.ts:
documents:
- 'src/graphql/general/*'
schema: https://hasura-dev.thestratos.org/v1/graphql
schema: https://hasura.thestratos.org/v1/graphql
config:
# omitOperationSuffix: true
skipTypeNameForRoot: true
Expand Down
9 changes: 9 additions & 0 deletions apps/web-stratos/src/recoil/profiles/atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { atomFamily } from 'recoil';
import type { AtomState } from '@/recoil/profiles/types';

const initialState: AtomState = null;

export const atomFamilyState = atomFamily<AtomState, string>({
key: 'profile',
default: initialState,
});
53 changes: 53 additions & 0 deletions apps/web-stratos/src/recoil/profiles/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useRecoilValue } from 'recoil';
import chainConfig from '@/chainConfig';
import useShallowMemo from '@/hooks/useShallowMemo';
import { useDesmosProfile } from '@/hooks/use_desmos_profile';
import {
readDelegatorAddress,
readDelegatorAddresses,
readProfile,
readProfiles,
} from '@/recoil/profiles/selectors';

const { extra } = chainConfig();

/**
* Accepts a delegator address and returns the appropriate profile
* @param address
*/
export const useProfileRecoil = (address: string): AvatarName => {
const profiles = useRecoilValue(readProfile(address));
const delegatorAddress = useRecoilValue(readDelegatorAddress(address));

// ==========================
// Desmos Profile
// ==========================
useDesmosProfile({
addresses: delegatorAddress ? [delegatorAddress] : [],
skip: true,
});

return profiles;
};

/**
* Accepts a list of addresses and returns the appropriate profiles
* @param address
*/
export const useProfilesRecoil = (
addresses: string[]
): { profiles: AvatarName[]; loading: boolean; error: unknown } => {
const profiles = useRecoilValue(readProfiles(addresses));
const delegatorAddresses = useRecoilValue(readDelegatorAddresses(addresses));
const delegatorAddressMemo = useShallowMemo(delegatorAddresses);

// ==========================
// Desmos Profile
// ==========================
const { loading, error } = useDesmosProfile({
addresses: delegatorAddressMemo,
skip: true,
});

return { profiles, loading, error };
};
12 changes: 12 additions & 0 deletions apps/web-stratos/src/recoil/profiles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export { atomFamilyState } from '@/recoil/profiles/atom';
export { useProfileRecoil, useProfilesRecoil } from '@/recoil/profiles/hooks';
export {
readDelegatorAddress,
readDelegatorAddresses,
readProfile,
readProfileExist,
readProfiles,
readProfilesExist,
validatorToDelegatorAddress,
writeProfile,
} from '@/recoil/profiles/selectors';
228 changes: 228 additions & 0 deletions apps/web-stratos/src/recoil/profiles/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { bech32 } from 'bech32';
import { GetRecoilValue, selectorFamily } from 'recoil';
import chainConfig from '@/chainConfig';
import { atomFamilyState } from '@/recoil/profiles/atom';
import type { AtomState as ProfileAtomState } from '@/recoil/profiles/types';
import { readValidator } from '@/recoil/validators';

const { prefix } = chainConfig();
const consensusRegex = new RegExp(`^(${prefix.consensus})`);
const validatorRegex = new RegExp(`^(${prefix.validator})`);
const delegatorRegex = new RegExp(`^(${prefix.account})`);

/**
* It takes an address and returns the delegator address
* @param - `address` - consensus address or validator address or delegator address
* @returns The address of the delegator.
*/
const getDelegatorAddress = ({
address,
get,
}: {
address: string;
get: GetRecoilValue;
}): string => {
let selectedAddress = '';
if (consensusRegex.test(address)) {
// address given is a consensus
const validator = get(readValidator(address));
if (validator) {
selectedAddress = validator.delegator;
}
} else if (validatorRegex.test(address)) {
// address given is a validator
const decode = bech32.decode(address).words;
selectedAddress = bech32.encode(prefix.account, decode);
} else if (delegatorRegex.test(address)) {
// address given is a delegator
selectedAddress = address;
}
return selectedAddress;
};

/**
* It takes a validator address and returns the delegator address
* @param {string} address - The address of the validator to be converted to the delegator address.
* @returns The address of the validator
*/
export const validatorToDelegatorAddress = (address: string) => {
const decode = bech32.decode(address).words;
return bech32.encode(prefix.account, decode);
};

/**
* Returns a validator address if the given address is a consensus address.
* Returns address otherwise
*/
const getReturnAddress = ({ address, get }: { address: string; get: GetRecoilValue }): string => {
let selectedAddress = address;
if (consensusRegex.test(address)) {
// address given is a consensus
const validator = get(readValidator(address));
if (validator) {
selectedAddress = validator.validator;
}
}
return selectedAddress;
};

/**
* Takes a address and returns the profile
* Returns null if no record found
* ex - cosmosvalcon1... returns cosmosvaloper1...
* @param address string
* @returns string | null
*/
const getProfile =
(address: string) =>
({ get }: { get: GetRecoilValue }): AvatarName => {
const returnAddress = getReturnAddress({
address,
get,
});
const delegatorAddress = getDelegatorAddress({
address,
get,
});
const state = get(atomFamilyState(delegatorAddress));
const name = state && state !== true ? (state.moniker ?? address) : address;
const imageUrl = state && state !== true ? state.imageUrl || undefined : undefined;
return {
address: returnAddress,
name: name || address || '',
imageUrl,
};
};

/**
* It takes an array of addresses and returns an array of AvatarName objects
* @param {string[]} addresses - string[] - an array of addresses to get the profile for
* @returns An array of objects with the following properties:
* address: string
* name: string
* imageUrl: string
*/
const getProfiles =
(addresses: string[]) =>
({ get }: { get: GetRecoilValue }): AvatarName[] => {
const profiles = addresses.map((x) => {
const returnAddress = getReturnAddress({
address: x,
get,
});
const delegatorAddress = getDelegatorAddress({
address: x,
get,
});
const state = get(atomFamilyState(delegatorAddress));
const name = state && state !== true ? (state?.moniker ?? x) : x;
const imageUrl = state && state !== true ? state?.imageUrl || undefined : undefined;
return {
address: returnAddress,
name: name || x || '',
imageUrl,
};
});
return profiles;
};

/* A selector family that takes an address and returns a profile. */
export const writeProfile = selectorFamily<AvatarName | null, string>({
key: 'profile.write.profile',
get: getProfile,
set:
(address: string) =>
({ set, get }, profile) => {
const delegatorAddress = getDelegatorAddress({
address,
get,
});
if (delegatorAddress) {
if (!isAvatarName(profile)) {
set(atomFamilyState(delegatorAddress), false);
} else {
set(atomFamilyState(delegatorAddress), {
moniker: profile.name,
imageUrl: profile.imageUrl || undefined,
});
}
}

function isAvatarName(x: typeof profile): x is AvatarName {
if (!x) return false;
return 'name' in x && 'imageUrl' in x;
}
},
});

/* Creating a selector family that takes an address and returns a profile. */
export const readProfile = selectorFamily({
key: 'profile.read.profile',
get: getProfile,
});

/* Creating a selector family that takes an array of addresses and returns an array of AvatarName
objects. */
export const readProfiles = selectorFamily({
key: 'profile.read.profiles',
get: getProfiles,
});

/* A selector family that takes an address and returns a delegator address. */
export const readDelegatorAddress = selectorFamily({
key: 'profile.read.delegatorAddress',
get:
(address: string) =>
({ get }): string =>
getDelegatorAddress({
address,
get,
}),
});

/* A selector family that takes an array of addresses and returns an array of delegator addresses. */
export const readDelegatorAddresses = selectorFamily({
key: 'profile.read.delegatorAddresses',
get:
(addresses: string[]) =>
({ get }): string[] =>
addresses.map((x) =>
getDelegatorAddress({
address: x,
get,
})
),
});

/* A selector family that takes an address and returns a profile. */
export const readProfileExist = selectorFamily({
key: 'profile.read.profileExist',
get:
(address: string) =>
({ get }): ProfileAtomState => {
const delegatorAddress = getDelegatorAddress({
address,
get,
});
const state = get(atomFamilyState(delegatorAddress));
return state;
},
});

/* A selector family that takes an array of addresses and returns an array of profile states. */
export const readProfilesExist = selectorFamily({
key: 'profile.read.profilesExist',
get:
(addresses: string[]) =>
({ get }) => {
const profiles: ProfileAtomState[] = addresses.map((x) => {
const delegatorAddress = getDelegatorAddress({
address: x,
get,
});
const state = get(atomFamilyState(delegatorAddress));
return state;
});
return profiles;
},
});
12 changes: 12 additions & 0 deletions apps/web-stratos/src/recoil/profiles/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type AtomState =
| {
moniker: string;
imageUrl?: string;
}
| null
| boolean;

export interface Profile {
moniker: string;
imageUrl?: string;
}
Loading
Loading