diff --git a/packages/huma-shared/src/services/CampaignService.ts b/packages/huma-shared/src/services/CampaignService.ts index 5637a30a..609112e0 100644 --- a/packages/huma-shared/src/services/CampaignService.ts +++ b/packages/huma-shared/src/services/CampaignService.ts @@ -1,8 +1,9 @@ import { gql } from 'graphql-request' import { SolanaChainEnum, SolanaPoolInfo } from '../solana' -import { ChainEnum } from '../utils/chain' +import { ChainEnum, NETWORK_TYPE } from '../utils/chain' import { configUtil } from '../utils/config' +import { COMMON_ERROR_MESSAGE } from '../utils/const' import { requestPost } from '../utils/request' import { PoolInfoV2 } from '../v2/utils/pool' @@ -53,30 +54,6 @@ export type SolanaCampaignGroup = { partners: CampaignPartner[] } -type Wallet = { - id: string - address: string - referralCode: string - referrer: { - id: string - address: string - } - createdAt: string -} - -type WalletPoint = { - id: string - rank: number - wallet: Wallet - totalPoints: number - numberOfReferred: number -} - -type WalletRank = { - totalCount: number - walletPoints: WalletPoint[] -} - type CampaignPoints = { campaignId: string juniorTranchePoints: number @@ -84,12 +61,33 @@ type CampaignPoints = { lockupPeriodMonths: number } +export type LeaderboardItem = { + accountId: string + accountName: string + points: number + ranking: number + referredCount: number +} + +export type HumaAccountPoints = { + accountId: string + totalPoints: number + basePoints: number + liquidityPoints: number + liquidityPointsList: { + address: string + points: number + }[] + referralPoints: number + bonusPoints: number +} + function checkWalletOwnership( wallet: string, + networkType: NETWORK_TYPE, isDev: boolean, - pointsTestnetExperience: boolean, ): Promise { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) + const url = configUtil.getCampaignAPIUrlV2(networkType, isDev) const query = gql` query { @@ -99,211 +97,188 @@ function checkWalletOwnership( return requestPost<{ data?: { - walletOwnership: boolean + walletOwnership: boolean & { errMessage: string } } errors?: unknown }>(url, JSON.stringify({ query })) .then((res) => { if (res.errors) { - console.error(res.errors) - return undefined + console.log(res.errors) + throw new Error(COMMON_ERROR_MESSAGE) + } + const errMessage = res.data?.walletOwnership?.errMessage + if (errMessage) { + console.error(errMessage) + throw new Error(errMessage) } return res.data?.walletOwnership }) .catch((err) => { console.error(err) - return undefined + throw new Error(COMMON_ERROR_MESSAGE) }) } -function getWalletInfo( - wallet: string, +function getLeaderboard( + seasonId: string, + networkType: NETWORK_TYPE, isDev: boolean, - pointsTestnetExperience: boolean, -): Promise<{ wallet: Wallet; walletPoint: WalletPoint } | undefined> { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) +): Promise { + const url = configUtil.getCampaignAPIUrlV2(networkType, isDev) const query = gql` query { - wallet(address:"${wallet}") { - id - address - referralCode + leaderboard(seasonId: "${seasonId}") { + ... on LeaderboardResult { + data { + accountId + accountName + points + referredCount + ranking + } } - walletPoint(address:"${wallet}") { - rank - numberOfReferred - totalPoints + ... on PointServiceError { + errMessage } } + } ` return requestPost<{ - data?: { wallet: Wallet; walletPoint: WalletPoint } + data?: { leaderboard: { data: LeaderboardItem[] & { errMessage: string } } } errors?: unknown }>(url, JSON.stringify({ query })) .then((res) => { if (res.errors) { - console.error(res.errors) - return undefined + console.log(res.errors) + throw new Error(COMMON_ERROR_MESSAGE) } - return res.data + const errMessage = res.data?.leaderboard?.data?.errMessage + if (errMessage) { + console.error(errMessage) + throw new Error(errMessage) + } + return res.data?.leaderboard?.data }) .catch((err) => { console.error(err) - return undefined + throw new Error(COMMON_ERROR_MESSAGE) }) } -function getWalletRankList( +function getHumaAccountRanking( seasonId: string, - first: number, - skip: number, + networkType: NETWORK_TYPE, isDev: boolean, - pointsTestnetExperience: boolean, -): Promise { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) +): Promise { + const url = configUtil.getCampaignAPIUrlV2(networkType, isDev) const query = gql` query { - walletPoints( - seasonId: "${seasonId}", - first: ${first}, - skip: ${skip} - ){ - totalCount - walletPoints { - id - rank - wallet { - id - address - } - totalPoints - numberOfReferred - } + myRankingEntry(seasonId: "${seasonId}") { + ... on LeaderboardItem { + accountId + accountName + points + referredCount + ranking + } + ... on PointServiceError { + errMessage } } + } ` return requestPost<{ - data?: { walletPoints: WalletRank } + data?: { myRankingEntry: LeaderboardItem & { errMessage: string } } errors?: unknown }>(url, JSON.stringify({ query })) .then((res) => { if (res.errors) { - console.error(res.errors) - return undefined + console.log(res.errors) + throw new Error(COMMON_ERROR_MESSAGE) + } + const errMessage = res.data?.myRankingEntry?.errMessage + if (errMessage) { + console.error(errMessage) + throw new Error(errMessage) } - return res.data?.walletPoints + + return res.data?.myRankingEntry }) .catch((err) => { console.error(err) - return undefined + throw new Error(COMMON_ERROR_MESSAGE) }) } -function getRecentJoins( +function getHumaAccountPoints( + networkType: NETWORK_TYPE, isDev: boolean, - pointsTestnetExperience: boolean, -): Promise { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) +): Promise { + const url = configUtil.getCampaignAPIUrlV2(networkType, isDev) + const query = gql` query { - wallets(first: 5, skip: 0, orderBy: "createdAt", orderDirection: "desc") { - wallets { - id - address - referrer { - id + accountPoints { + ... on AccountPointsResult { + accountId + basePoints + liquidityPoints + liquidityPointsList { address + points } - createdAt + referralPoints + bonusPoints } - } - } - ` - - return ( - requestPost(url, JSON.stringify({ query })) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .then((res: any) => { - if (res.errors) { - console.error(res.errors) - return undefined - } - return res.data?.wallets?.wallets - }) - .catch((err) => { - console.error(err) - return undefined - }) - ) -} - -function getActiveSeasonAndCampaignGroups( - isDev: boolean, - pointsTestnetExperience: boolean, -): Promise<{ - activeSeason?: CampaignSeason - campaignGroups?: CampaignGroup[] -}> { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) - - const query = gql` - query { - activeSeason { - id - estimatedTotalPoints - name - } - campaignGroups { - id - name - campaigns { - id - name - chainId - juniorMultiplier - lockupPeriodMonths - seniorMultiplier - poolAddress - } - partners { - id - name + ... on PointServiceError { + errMessage } } } ` return requestPost<{ - data?: { - activeSeason: CampaignSeason - campaignGroups: CampaignGroup[] - } + data?: { accountPoints: HumaAccountPoints & { errMessage: string } } errors?: unknown }>(url, JSON.stringify({ query })) .then((res) => { if (res.errors) { - console.error(res.errors) - return {} + console.log(res.errors) + throw new Error(COMMON_ERROR_MESSAGE) + } + const errMessage = res.data?.accountPoints?.errMessage + if (errMessage) { + console.error(errMessage) + throw new Error(errMessage) } - return res.data ?? {} + + const accountPoints = res.data?.accountPoints + if (accountPoints) { + accountPoints.totalPoints = + accountPoints.basePoints + + accountPoints.liquidityPoints + + accountPoints.referralPoints + + accountPoints.bonusPoints + } + return accountPoints }) .catch((err) => { console.error(err) - return {} + throw new Error(COMMON_ERROR_MESSAGE) }) } function getEstimatedPoints( campaignGroupId: string, principal: string, + networkType: NETWORK_TYPE, isDev: boolean, - pointsTestnetExperience: boolean, ): Promise { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) + const url = configUtil.getCampaignAPIUrlV2(networkType, isDev) const query = gql` query { @@ -322,98 +297,52 @@ function getEstimatedPoints( data?: { calculateEstimatedPoints?: { campaignPointsEstimations?: CampaignPoints[] - } + } & { errMessage: string } } errors?: unknown }>(url, JSON.stringify({ query })) .then((res) => { if (res.errors) { console.error(res.errors) - return [] + throw new Error(COMMON_ERROR_MESSAGE) } - return res.data?.calculateEstimatedPoints?.campaignPointsEstimations ?? [] - }) - .catch((err) => { - console.error(err) - return [] - }) -} - -function createNewWallet( - account: string, - referralCode: string | null | undefined, - isDev: boolean, - pointsTestnetExperience: boolean, -): Promise<{ wallet: string } | undefined> { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) - - const query = gql` - mutation { - createWallet( - input: { - walletAddress: "${account}" - referralCode: "${referralCode}" - } - ) { - ... on CreateWalletResult { - wallet { - address - } - } - ... on WalletExistsError { - message - } - ... on UnauthorizedError { - message - } + const errMessage = res.data?.calculateEstimatedPoints?.errMessage + if (errMessage) { + console.error(errMessage) + throw new Error(errMessage) } - } - ` - return requestPost<{ - data?: { - createWallet?: { - wallet: string - } - } - errors?: unknown - }>(url, JSON.stringify({ query })) - .then((res) => { - if (res.errors) { - console.error(res.errors) - return undefined - } - return res.data?.createWallet + return res.data?.calculateEstimatedPoints?.campaignPointsEstimations ?? [] }) .catch((err) => { console.error(err) - return undefined + throw new Error(COMMON_ERROR_MESSAGE) }) } -function updateWalletPoints( - account: string, - hash: string, +function updateHumaAccountPoints( + walletAddress: string, + transactionHash: string, chainId: ChainEnum | SolanaChainEnum, + networkType: NETWORK_TYPE, isDev: boolean, - pointsTestnetExperience: boolean, ): Promise<{ pointsAccumulated?: number }> { - const url = configUtil.getCampaignAPIUrl(isDev, pointsTestnetExperience) + const url = configUtil.getCampaignAPIUrlV2(networkType, isDev) const query = gql` mutation { - updateWalletPoints( + updateAccountPoints( input: { chainId: ${chainId}, - walletAddress: "${account}", - transactionHash: "${hash}" + walletAddress: "${walletAddress}", + transactionHash: "${transactionHash}" } ) { - ... on UpdateWalletPointsResult { + ... on UpdateAccountPointsResult { pointsAccumulated } - ... on UnauthorizedError { - message + ... on PointServiceError { + errMessage } } } @@ -421,53 +350,40 @@ function updateWalletPoints( return requestPost<{ data?: { - updateWalletPoints?: { pointsAccumulated?: number } + updateAccountPoints?: { pointsAccumulated?: number } & { + errMessage: string + } } errors?: unknown }>(url, JSON.stringify({ query })) .then((res) => { if (res.errors) { - console.error(res.errors) - return {} + console.log(res.errors) + throw new Error(COMMON_ERROR_MESSAGE) + } + const errMessage = res.data?.updateAccountPoints?.errMessage + if (errMessage) { + console.error(errMessage) + throw new Error(errMessage) } - return res.data?.updateWalletPoints ?? {} + + return res.data?.updateAccountPoints ?? {} }) .catch((err) => { console.error(err) - return {} + throw new Error(COMMON_ERROR_MESSAGE) }) } -async function checkAndCreateWallet( - account: string, - referralCode: string | null | undefined, - isDev: boolean, - pointsTestnetExperience: boolean, -): Promise<{ wallet: string } | undefined> { - const result = await getWalletInfo(account, isDev, pointsTestnetExperience) - if (!result?.wallet) { - return createNewWallet( - account, - referralCode, - isDev, - pointsTestnetExperience, - ) - } - return undefined -} - /** * An object that contains functions to interact with Huma's campaign service. * @namespace CampaignService */ export const CampaignService = { checkWalletOwnership, - getWalletInfo, - getWalletRankList, - getRecentJoins, - getActiveSeasonAndCampaignGroups, getEstimatedPoints, - createNewWallet, - updateWalletPoints, - checkAndCreateWallet, + getLeaderboard, + getHumaAccountRanking, + getHumaAccountPoints, + updateHumaAccountPoints, } diff --git a/packages/huma-shared/src/services/IdentityServiceV2.ts b/packages/huma-shared/src/services/IdentityServiceV2.ts index 1578ceb7..9482d7b5 100644 --- a/packages/huma-shared/src/services/IdentityServiceV2.ts +++ b/packages/huma-shared/src/services/IdentityServiceV2.ts @@ -1,5 +1,6 @@ +import { NETWORK_TYPE } from '../utils/chain' import { configUtil } from '../utils/config' -import { requestGet, requestPost } from '../utils/request' +import { requestGet, requestPatch, requestPost } from '../utils/request' /** * Enum representing the identity status V2. @@ -63,32 +64,51 @@ export type ResumeVerificationResultV2 = { /** * Object representing the Huma account. * @typedef {Object} HumaAccount - * @property {string} accountId The account id. + * @property {string} id The account id. + * @property {string} name The account name. + * @property {Wallet[]} wallets The account wallets. + * @property {string} referralCode The account referral code. + * @property {number} numReferrals The number of referrals. + * @property {Referrer} referrer The account referrer. + * @property {string} createdAt The account created time. */ export type HumaAccount = { - accountId: string + id: string + name: string + wallets: { + address: string + chainId: string + }[] + referralCode: string + numReferrals: number + referrer: { + id: string + name: string + } + createdAt: string } /** - * Get wallet's identity verification status. - * - * @param {string} walletAddress The wallet address. - * @param {number} chainId Chain ID. - * @param {boolean} isDev Is dev environment or not. - * @returns {Promise} Promise that returns the verification status result. + * Object representing the Huma account login result. + * @typedef {Object} HumaAccountLoginResult + * @property {HumaAccount} account The Huma account. + * @property {boolean} isNewAccount Is new account or not. */ -const getVerificationStatusV2 = async ( - walletAddress: string, - chainId: number, - isDev = false, -): Promise => - requestGet( - `${configUtil.getIdentityAPIUrl( - chainId, - isDev, - )}/wallets/${walletAddress}/verification-status?&chainId=${chainId}`, - ) +export type HumaAccountLoginResult = HumaAccount & { + account: HumaAccount + isNewAccount: boolean +} +/** + * Object representing the accreditation answers. + * @typedef {Object} AccreditationAnswers + * @property {string} question1 The question 1. + * @property {boolean} question1Answer The question 1 answer. + * @property {string} question2 The question 2. + * @property {boolean} question2Answer The question 2 answer. + * @property {string} question3 The question 3. + * @property {boolean} question3Answer The question 3 answer. + */ export type AccreditationAnswers = { question1: string question1Answer: boolean @@ -99,93 +119,116 @@ export type AccreditationAnswers = { } /** - * Start wallet's accreditation process. + * Object representing the account name validity. + * @typedef {Object} AccountNameValidity + * @property {string} name The account name. + * @property {string} invalidReason The invalid reason. + */ +export type AccountNameValidity = { + name: string + invalidReason: 'already_taken' | 'inappropriate_language' +} + +/** + * Get account's identity verification status. * - * @param {string} walletAddress The wallet address. - * @param {number} chainId Chain ID. + * @param {string} networkType Network type. + * @param {boolean} isDev Is dev environment or not. + * @returns {Promise} Promise that returns the verification status result. + */ +const getVerificationStatusV2 = async ( + networkType: NETWORK_TYPE, + isDev = false, +): Promise => + requestGet( + `${configUtil.getIdentityAPIUrlV2( + networkType, + isDev, + )}/account/verification-status`, + ) + +/** + * Start account's accreditation process. + * + * @param {NETWORK_TYPE} networkType Network type. * @param {AccreditationAnswers} answers accreditation answer. * @param {boolean} isDev Is dev environment or not. - * @returns {Promise} Promise that returns the start verification result. + * @returns {Promise} Promise that returns the accreditation result. */ const accredit = async ( - walletAddress: string, - chainId: number, + networkType: NETWORK_TYPE, answers: AccreditationAnswers, isDev = false, ): Promise => requestPost( - `${configUtil.getIdentityAPIUrl( - chainId, + `${configUtil.getIdentityAPIUrlV2( + networkType, isDev, - )}/wallets/${walletAddress}/accredit?chainId=${chainId}`, + )}/account/accreditation`, { answers }, ) /** - * Start wallet's verification process. + * Start account's verification process. * - * @param {string} walletAddress The wallet address. - * @param {number} chainId Chain ID. + * @param {NETWORK_TYPE} networkType Network type. * @param {boolean} isDev Is dev environment or not. * @returns {Promise} Promise that returns the start verification result. */ const startVerification = async ( - walletAddress: string, - chainId: number, + networkType: NETWORK_TYPE, isDev = false, ): Promise => requestPost( - `${configUtil.getIdentityAPIUrl( - chainId, + `${configUtil.getIdentityAPIUrlV2( + networkType, isDev, - )}/wallets/${walletAddress}/start-verification?chainId=${chainId}`, + )}/account/verification`, ) /** - * Resume wallet's verification process. + * Resume account's verification process. * - * @param {string} walletAddress The wallet address. - * @param {number} chainId Chain ID. + * @param {NETWORK_TYPE} networkType Network type. * @param {boolean} isDev Is dev environment or not. - * @returns {Promise} Promise that returns the start verification result. + * @returns {Promise} Promise that returns the resume verification result. */ const resumeVerification = async ( - walletAddress: string, - chainId: number, + networkType: NETWORK_TYPE, isDev = false, ): Promise => - requestPost( - `${configUtil.getIdentityAPIUrl( - chainId, + requestPatch( + `${configUtil.getIdentityAPIUrlV2( + networkType, isDev, - )}/wallets/${walletAddress}/resume-verification?chainId=${chainId}`, + )}/account/verification`, ) /** * Consent to subscription. * - * @param {string} walletAddress The wallet address. - * @param {number} chainId Chain ID. + * @param {NETWORK_TYPE} networkType Network type. * @param {string} documentHash The subscription file hash. * @param {boolean} isDev Is dev environment or not. * @returns {Promise} Promise that returns void. */ const consentToSubscription = async ( - walletAddress: string, - chainId: number, + networkType: NETWORK_TYPE, documentHash: string, isDev = false, ): Promise => requestPost( - `${configUtil.getIdentityAPIUrl( - chainId, + `${configUtil.getIdentityAPIUrlV2( + networkType, isDev, - )}/wallets/${walletAddress}/consent-to-subscription?chainId=${chainId}&documentHash=${documentHash}`, + )}/account/purchase-consent`, + { documentHash }, ) /** * Approve wallet as lender. * + * @param {NETWORK_TYPE} networkType Network type. * @param {string} walletAddress The wallet address. * @param {number} chainId Chain ID. * @param {string} contractAddress The tranche vault contract address. @@ -194,60 +237,179 @@ const consentToSubscription = async ( * @returns {Promise} Promise that returns void. */ const approveLender = async ( + networkType: NETWORK_TYPE, walletAddress: string, chainId: number, contractAddress: string, isDev = false, chainSpecificData?: Record, ): Promise => - requestPost( - `${configUtil.getIdentityAPIUrl( - chainId, + requestPatch( + `${configUtil.getIdentityAPIUrlV2( + networkType, isDev, - )}/wallets/${walletAddress}/approve-lender?chainId=${chainId}&contractAddress=${contractAddress}`, - { chain_specific_data: chainSpecificData }, + )}/account/wallets/${chainId}/${walletAddress}`, + { trancheAddress: contractAddress, chainSpecificData }, ) /** - * Authenticate wallet account. + * Get Huma account. * + * @param {string} networkType Network type. + * @param {boolean} isDev Is dev environment or not. + * @returns {Promise} Promise that returns huma account. + */ +const getHumaAccount = async ( + networkType: NETWORK_TYPE, + isDev = false, +): Promise => { + const { account } = await requestGet<{ account: HumaAccount }>( + `${configUtil.getIdentityAPIUrlV2(networkType, isDev)}/account`, + ) + return account +} + +/** + * Huma account login by wallet address and chain. + * + * @param {string} networkType Network type. * @param {string} walletAddress The wallet address. * @param {number} chainId Chain ID. * @param {boolean} isDev Is dev environment or not. - * @returns {Promise} Promise that returns void. + * @returns {Promise} Promise that returns HumaAccountLoginResult. */ -const authenticate = async ( +const humaAccountLogin = async ( + networkType: NETWORK_TYPE, walletAddress: string, chainId: number, isDev = false, +): Promise => + requestPost( + `${configUtil.getIdentityAPIUrlV2(networkType, isDev)}/auth/login`, + { + walletAddress, + chainId: String(chainId), + }, + ) + +/** + * Huma account logout. + * + * @param {string} networkType Network type. + * @param {boolean} isDev Is dev environment or not. + * @returns {Promise} Promise that returns void. + */ +const humaAccountLogout = async ( + networkType: NETWORK_TYPE, + isDev = false, ): Promise => - requestPost( - `${configUtil.getIdentityAPIUrl( - chainId, - isDev, - )}/wallets/${walletAddress}/authenticate?chainId=${chainId}`, + requestPost( + `${configUtil.getIdentityAPIUrlV2(networkType, isDev)}/auth/logout`, ) /** - * Get Huma account. + * Update huma account. + * + * @param {string} networkType Network type. + * @param {boolean} isDev Is dev environment or not. + * @param {HumaAccount} humaAccount The Huma account. + * @returns {Promise} Promise that returns void. + */ +const humaAccountUpdate = async ( + networkType: NETWORK_TYPE, + humaAccount: { name: string }, + isDev = false, +): Promise => + requestPatch( + `${configUtil.getIdentityAPIUrlV2(networkType, isDev)}/account`, + humaAccount, + ) + +/** + * Update huma account referral code. + * + * @param {string} networkType Network type. + * @param {string} referralCode The referral code. + * @param {boolean} isDev Is dev environment or not. + * @param {HumaAccount} humaAccount The Huma account. + * @returns {Promise} Promise that returns void. + */ +const humaAccountUpdateReferral = async ( + networkType: NETWORK_TYPE, + referralCode: string, + isDev = false, +): Promise => + requestPost( + `${configUtil.getIdentityAPIUrlV2(networkType, isDev)}/account/referral`, + { referralCode }, + ) + +/** + * Huma account adds wallet. * + * @param {string} networkType Network type. * @param {string} walletAddress The wallet address. * @param {number} chainId Chain ID. * @param {boolean} isDev Is dev environment or not. - * @returns {Promise} Promise that returns void. + * @returns {Promise} Promise that returns huma account. */ -const getHumaAccount = async ( +const humaAccountAddWallet = async ( + networkType: NETWORK_TYPE, walletAddress: string, chainId: number, isDev = false, ): Promise => - requestGet( - `${configUtil.getIdentityAPIUrl( - chainId, + requestPost( + `${configUtil.getIdentityAPIUrlV2(networkType, isDev)}/account/wallets`, + { + walletAddress, + chainId: String(chainId), + }, + ) + +/** + * Huma account name validity. + * + * @param {string} networkType Network type. + * @param {name} name Name to check validity. + * @param {boolean} isDev Is dev environment or not. + * @returns {Promise} Promise that returns huma account. + */ +const humaAccountNameValidity = async ( + networkType: NETWORK_TYPE, + name: string, + isDev = false, +): Promise => + requestGet( + `${configUtil.getIdentityAPIUrlV2( + networkType, isDev, - )}/wallets/${walletAddress}/account?chainId=${chainId}`, + )}/account/name-validity?name=${name}`, ) +/** + * Get recently joined accounts. + * + * @param {string} networkType Network type. + * @param {boolean} isDev Is dev environment or not. + * @param {number} limit The limit of the number of accounts to return. + * @returns {Promise} Promise that returns recently joined huma accounts. + */ +const getRecentlyJoinedHumaAccounts = async ( + networkType: NETWORK_TYPE, + isDev = false, + limit = 10, +): Promise => { + const result = await requestGet<{ accounts: HumaAccount[] }>( + `${configUtil.getIdentityAPIUrlV2( + networkType, + isDev, + )}/accounts/recently-joined?limit=${limit}`, + ) + + return result.accounts +} + export const IdentityServiceV2 = { getVerificationStatusV2, accredit, @@ -255,6 +417,12 @@ export const IdentityServiceV2 = { resumeVerification, consentToSubscription, approveLender, - authenticate, getHumaAccount, + humaAccountLogin, + humaAccountLogout, + humaAccountUpdate, + humaAccountAddWallet, + humaAccountUpdateReferral, + humaAccountNameValidity, + getRecentlyJoinedHumaAccounts, } diff --git a/packages/huma-shared/src/solana/chain.ts b/packages/huma-shared/src/solana/chain.ts index f3f719e5..d1ad6ea6 100644 --- a/packages/huma-shared/src/solana/chain.ts +++ b/packages/huma-shared/src/solana/chain.ts @@ -1,3 +1,5 @@ +import { NETWORK_TYPE } from '../utils' + export enum SolanaChainEnum { SolanaDevnet = 901, SolanaMainnet = 900, @@ -20,6 +22,10 @@ export function isSolanaTestnet(chainId: SolanaChainEnum): boolean { return chainId !== SolanaChainEnum.SolanaMainnet } +export function getSolanaNetworkType(chainId: SolanaChainEnum): NETWORK_TYPE { + return isSolanaTestnet(chainId) ? NETWORK_TYPE.testnet : NETWORK_TYPE.mainnet +} + export function getSolanaExplorerUrl( chainId: SolanaChainEnum, signature: string, diff --git a/packages/huma-shared/src/utils/chain.ts b/packages/huma-shared/src/utils/chain.ts index a7e42959..c6904022 100644 --- a/packages/huma-shared/src/utils/chain.ts +++ b/packages/huma-shared/src/utils/chain.ts @@ -1,6 +1,11 @@ import type { AddEthereumChainParameter } from '@web3-react/types' import { ethers } from 'ethers' +export enum NETWORK_TYPE { + testnet = 'testnet', + mainnet = 'mainnet', +} + export enum CHAIN_TYPE { EVM = 'evm', SOLANA = 'solana', @@ -178,6 +183,10 @@ export function isTestnet(chainId: number): boolean { return CHAINS[chainId].isTestnet ?? false } +export function getEvmNetworkType(chainId: ChainEnum): NETWORK_TYPE { + return isTestnet(chainId) ? NETWORK_TYPE.testnet : NETWORK_TYPE.mainnet +} + export function isChainEnum( chainId: number | string | undefined, ): chainId is keyof typeof ChainEnum { @@ -187,7 +196,7 @@ export function isChainEnum( function isExtendedChainInformation( chainInformation: BasicChainInformation | ExtendedChainInformation, ): chainInformation is ExtendedChainInformation { - return !!(chainInformation as ExtendedChainInformation).nativeCurrency + return !!(chainInformation as ExtendedChainInformation)?.nativeCurrency } export function getAddChainParameters( @@ -217,12 +226,12 @@ export const URLS: { [chainId: number]: string[] } = Object.keys( return accumulator }, {}) -export const getWalletAddressAbbr = (address: string) => { +export const getWalletAddressAbbr = (address: string, startNum = 6) => { if (!address) { return address } const { length } = address - return `${address.slice(0, 6)}...${address.slice(length - 4, length)}` + return `${address.slice(0, startNum)}...${address.slice(length - 4, length)}` } /** diff --git a/packages/huma-shared/src/utils/config.ts b/packages/huma-shared/src/utils/config.ts index 629a21ed..c642bc4e 100644 --- a/packages/huma-shared/src/utils/config.ts +++ b/packages/huma-shared/src/utils/config.ts @@ -1,5 +1,5 @@ import { isSolanaTestnet, SolanaChainEnum } from '../solana/chain' -import { CHAINS } from './chain' +import { CHAINS, NETWORK_TYPE } from './chain' const getDevPrefix = (isDev = false) => (isDev ? 'dev.' : '') @@ -44,6 +44,11 @@ const getIdentityAPIUrl = (chainId: number, isDev = false) => isDev, )}.identity-verification.huma.finance` +const getIdentityAPIUrlV2 = (networkType: NETWORK_TYPE, isDev = false) => + `https://${getDevPrefix( + isDev, + )}${networkType}.identity-verification.huma.finance` + const getAuthServiceUrl = (chainId: number, isDev = false) => `https://${getNetworkAgnosticServiceUrlPrefix( chainId, @@ -67,6 +72,11 @@ const getCampaignAPIUrl = (isDev: boolean, pointsTestnetExperience: boolean) => pointsTestnetExperience ? 'testnet.' : 'mainnet.' }campaign-points.huma.finance/graphql` +const getCampaignAPIUrlV2 = (networkType: NETWORK_TYPE, isDev: boolean) => + `https://${getDevPrefix( + isDev, + )}${networkType}.campaign-points.huma.finance/graphql` + const getSolanaGraphAPIUrl = ( isDev: boolean, pointsTestnetExperience: boolean, @@ -108,9 +118,11 @@ export const configUtil = { getEABaseUrlV1, getRequestAPIUrl, getIdentityAPIUrl, + getIdentityAPIUrlV2, getAuthServiceUrl, getKYCProviderBaseUrl, getCampaignAPIUrl, + getCampaignAPIUrlV2, getSolanaGraphAPIUrl, DEFAULT_CHAIN_ID, } diff --git a/packages/huma-shared/src/utils/const.ts b/packages/huma-shared/src/utils/const.ts index fe5b20ba..2e003b79 100644 --- a/packages/huma-shared/src/utils/const.ts +++ b/packages/huma-shared/src/utils/const.ts @@ -3,6 +3,9 @@ export const EARejectReason = 'Your wallet does not meet qualifications' export const EARejectMessage = 'Based on your wallet transaction history your application was not approved.' +export const COMMON_ERROR_MESSAGE = + 'Sorry, there was an error. Please try again.' + export enum CURRENCY_CODE { USD = 840, } @@ -10,3 +13,10 @@ export enum CURRENCY_CODE { export const CAMPAIGN_REFERENCE_CODE = 'CAMPAIGN_REFERENCE_CODE' export const BP_FACTOR_NUMBER = 10000 + +export enum HUMA_ACCOUNT_EXCEPTION { + AccountTokenNotFoundException = 'AccountTokenNotFoundException', + InvalidAccountTokenException = 'InvalidAccountTokenException', + WalletNotSignedInException = 'WalletNotSignedInException', + WalletNotCreatedException = 'WalletNotCreatedException', +} diff --git a/packages/huma-shared/src/utils/request.ts b/packages/huma-shared/src/utils/request.ts index 2a0a7565..3bd98374 100644 --- a/packages/huma-shared/src/utils/request.ts +++ b/packages/huma-shared/src/utils/request.ts @@ -2,21 +2,21 @@ import axios, { AxiosRequestConfig } from 'axios' axios.defaults.withCredentials = true +const getConfig = (customConfig: AxiosRequestConfig = {}) => ({ + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, + ...customConfig, +}) + export const requestGet = async ( url: string, customConfig: AxiosRequestConfig = {}, ): Promise => { - const config = { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - withCredentials: true, - ...customConfig, - } - + const config = getConfig(customConfig) // @ts-ignore - return axios.get(url, {}, config).then((response) => response.data as T) + return axios.get(url, config).then((response) => response.data as T) } export const requestPost = async ( @@ -25,15 +25,7 @@ export const requestPost = async ( payload?: any, customConfig: AxiosRequestConfig = {}, ): Promise => { - const config = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - withCredentials: true, - ...customConfig, - } - + const config = getConfig(customConfig) return ( axios .post(url, payload, config) @@ -48,15 +40,7 @@ export const requestPut = async ( payload?: any, customConfig: AxiosRequestConfig = {}, ): Promise => { - const config = { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - withCredentials: true, - ...customConfig, - } - + const config = getConfig(customConfig) return ( axios .put(url, payload, config) @@ -64,3 +48,18 @@ export const requestPut = async ( .then((response) => response.data as T) ) } + +export const requestPatch = async ( + url: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + payload?: any, + customConfig: AxiosRequestConfig = {}, +): Promise => { + const config = getConfig(customConfig) + return ( + axios + .patch(url, payload, config) + // @ts-ignore + .then((response) => response.data as T) + ) +} diff --git a/packages/huma-shared/tests/utils/request.test.ts b/packages/huma-shared/tests/utils/request.test.ts index cfbf2cd9..d9b99ee4 100644 --- a/packages/huma-shared/tests/utils/request.test.ts +++ b/packages/huma-shared/tests/utils/request.test.ts @@ -13,15 +13,10 @@ describe('requestGet', () => { const result = await requestGet(url) expect(result).toEqual(responseData) - expect(axios.get).toHaveBeenCalledWith( - url, - {}, - { - headers: { 'Content-Type': 'application/json' }, - method: 'GET', - withCredentials: true, - }, - ) + expect(axios.get).toHaveBeenCalledWith(url, { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + }) }) it('throws an error if the GET request fails', async () => { @@ -31,15 +26,10 @@ describe('requestGet', () => { const url = 'https://example.com/api' await expect(requestGet(url)).rejects.toThrow(errorMessage) - expect(axios.get).toHaveBeenCalledWith( - url, - {}, - { - headers: { 'Content-Type': 'application/json' }, - method: 'GET', - withCredentials: true, - }, - ) + expect(axios.get).toHaveBeenCalledWith(url, { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + }) }) }) diff --git a/packages/huma-web-shared/src/hooks/useAuthErrorHandling/index.ts b/packages/huma-web-shared/src/hooks/useAuthErrorHandling/index.ts index 18d513a7..2f971b9e 100644 --- a/packages/huma-web-shared/src/hooks/useAuthErrorHandling/index.ts +++ b/packages/huma-web-shared/src/hooks/useAuthErrorHandling/index.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { CHAIN_TYPE } from '@huma-finance/shared' +import { CHAIN_TYPE, HUMA_ACCOUNT_EXCEPTION } from '@huma-finance/shared' import axios, { HttpStatusCode } from 'axios' import { useCallback, useState } from 'react' import { useAuthErrorHandlingEvm } from './useAuthErrorHandlingEvm' @@ -35,13 +35,14 @@ export const useAuthErrorHandling = ( axios.isAxiosError(error) && error.response?.status === HttpStatusCode.Unauthorized && [ - 'IdTokenNotFoundException', - 'InvalidIdTokenException', - 'WalletMismatchException', + HUMA_ACCOUNT_EXCEPTION.AccountTokenNotFoundException, + HUMA_ACCOUNT_EXCEPTION.InvalidAccountTokenException, ].includes(error.response?.data?.detail?.type) - const isWalletNotCreatedError = error === 'WalletNotCreatedException' - const isWalletNotSignInError = error === 'WalletNotSignedInException' + const isWalletNotCreatedError = + error === HUMA_ACCOUNT_EXCEPTION.WalletNotCreatedException + const isWalletNotSignInError = + error === HUMA_ACCOUNT_EXCEPTION.WalletNotSignedInException return { isUnauthorizedError, diff --git a/packages/huma-web-shared/src/hooks/useAuthErrorHandling/useAuthErrorHandlingEvm.ts b/packages/huma-web-shared/src/hooks/useAuthErrorHandling/useAuthErrorHandlingEvm.ts index 34fa082b..661f9888 100644 --- a/packages/huma-web-shared/src/hooks/useAuthErrorHandling/useAuthErrorHandlingEvm.ts +++ b/packages/huma-web-shared/src/hooks/useAuthErrorHandling/useAuthErrorHandlingEvm.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { JsonRpcProvider } from '@ethersproject/providers' -import { AuthService, CHAIN_TYPE } from '@huma-finance/shared' +import { AuthService, CHAIN_TYPE, CHAINS } from '@huma-finance/shared' import { useWeb3React } from '@web3-react/core' import { useEffect } from 'react' import { SiweMessage } from 'siwe' @@ -68,6 +68,14 @@ export const useAuthErrorHandlingEvm = ( return } + const isChainSupported = Object.values(CHAINS).some( + (chain) => chain.id === chainId, + ) + + if (!isChainSupported) { + return + } + const { isUnauthorizedError, isWalletNotCreatedError, diff --git a/packages/huma-web-shared/src/hooks/useChainInfo.ts b/packages/huma-web-shared/src/hooks/useChainInfo.ts index 51488489..2a2ccf2c 100644 --- a/packages/huma-web-shared/src/hooks/useChainInfo.ts +++ b/packages/huma-web-shared/src/hooks/useChainInfo.ts @@ -41,3 +41,38 @@ export const useChainInfo = ( provider, } } + +export const useChainsInfo = (isDev: boolean) => { + const { account: evmAccount, chainId: evmChainId } = useChainInfo( + isDev, + CHAIN_TYPE.EVM, + ) + const { account: solanaAccount, chainId: solanaChainId } = useChainInfo( + isDev, + CHAIN_TYPE.SOLANA, + ) + + return { + evmAccount, + evmChainId, + solanaAccount, + solanaChainId, + } +} + +export const useActiveChainInfo = ( + isDev: boolean, + activeNetwork: CHAIN_TYPE, +) => { + const evmChainInfo = useChainInfo(isDev, CHAIN_TYPE.EVM) + const solanaChainInfo = useChainInfo(isDev, CHAIN_TYPE.SOLANA) + + switch (activeNetwork) { + case CHAIN_TYPE.EVM: + return evmChainInfo + case CHAIN_TYPE.SOLANA: + return solanaChainInfo + default: + return null + } +} diff --git a/packages/huma-web-shared/src/hooks/useDebouncedValue.ts b/packages/huma-web-shared/src/hooks/useDebouncedValue.ts index 1ed1480c..dc64ceac 100644 --- a/packages/huma-web-shared/src/hooks/useDebouncedValue.ts +++ b/packages/huma-web-shared/src/hooks/useDebouncedValue.ts @@ -1,9 +1,9 @@ import { useEffect, useState } from 'react' -export const useDebouncedValue = ( - value: number | string = '', +export const useDebouncedValue = ( + value: T, delay = 500, -): string | number => { +): T => { const [debouncedValue, setDebouncedValue] = useState(value) useEffect(() => { diff --git a/packages/huma-widget/API.md b/packages/huma-widget/API.md index f3616637..4a95cde6 100644 --- a/packages/huma-widget/API.md +++ b/packages/huma-widget/API.md @@ -702,7 +702,6 @@ To be used when re-enabling autopay and other pool actions that require allowanc | --- | --- | --- | | poolInfo | SolanaPoolInfo |

The metadata of the pool.

| | poolState | SolanaPoolState |

The current state config of the pool.

| -| pointsTestnetExperience | boolean |

If the user is in the testnet experience.

| | handleClose | function |

Function to notify to close the widget modal when user clicks the 'x' close button.

| | handleSuccess | function |

Optional function to notify that the lending pool supply action is successful.

| @@ -747,7 +746,6 @@ To be used when re-enabling autopay and other pool actions that require allowanc | Name | Type | Description | | --- | --- | --- | | poolName | POOL\_NAME |

The name of the pool.

| -| pointsTestnetExperience | boolean |

If the user is in the testnet experience.

| | campaign | Campaign |

The campaign info.

| | handleClose | function |

Function to notify to close the widget modal when user clicks the 'x' close button.

| | handleSuccess | function |

Optional function to notify that the lending pool supply action is successful.

| diff --git a/packages/huma-widget/src/components/Lend/components/ApproveLenderBase.tsx b/packages/huma-widget/src/components/Lend/components/ApproveLenderBase.tsx index 52990970..c9eb2cb2 100644 --- a/packages/huma-widget/src/components/Lend/components/ApproveLenderBase.tsx +++ b/packages/huma-widget/src/components/Lend/components/ApproveLenderBase.tsx @@ -3,6 +3,7 @@ import { CHAIN_TYPE, checkIsDev, IdentityServiceV2, + NETWORK_TYPE, timeUtil, TrancheType, } from '@huma-finance/shared' @@ -18,6 +19,7 @@ type Props = { juniorTrancheVault: string seniorTrancheVault: string chainType: CHAIN_TYPE + networkType: NETWORK_TYPE isUniTranche: boolean chainSpecificData?: Record changeTranche: (tranche: TrancheType) => void @@ -28,6 +30,7 @@ export function ApproveLenderBase({ seniorTrancheVault, isUniTranche, chainType, + networkType, chainSpecificData, changeTranche, }: Props): React.ReactElement | null { @@ -43,6 +46,7 @@ export function ApproveLenderBase({ try { tryAttempts -= 1 await IdentityServiceV2.approveLender( + networkType, account!, chainId!, trancheVault, @@ -64,7 +68,7 @@ export function ApproveLenderBase({ } } }, - [account, chainId, chainSpecificData, dispatch, isDev], + [account, chainId, chainSpecificData, dispatch, isDev, networkType], ) useEffect(() => { diff --git a/packages/huma-widget/src/components/Lend/components/PersonaEvaluation.tsx b/packages/huma-widget/src/components/Lend/components/PersonaEvaluation.tsx index 835a03f1..1ff532a7 100644 --- a/packages/huma-widget/src/components/Lend/components/PersonaEvaluation.tsx +++ b/packages/huma-widget/src/components/Lend/components/PersonaEvaluation.tsx @@ -1,6 +1,4 @@ import { - CAMPAIGN_REFERENCE_CODE, - CampaignService, CHAIN_TYPE, checkIsDev, CloseModalOptions, @@ -8,9 +6,10 @@ import { IdentityVerificationStatusV2, KYCCopy, KYCType, + NETWORK_TYPE, VerificationStatusResultV2, } from '@huma-finance/shared' -import { useAuthErrorHandling, useChainInfo } from '@huma-finance/web-shared' +import { useAuthErrorHandling } from '@huma-finance/web-shared' import { Box, css, useTheme } from '@mui/material' import Persona, { Client } from 'persona' import React, { useCallback, useEffect, useRef, useState } from 'react' @@ -32,8 +31,8 @@ type Props = { juniorTrancheVault: string seniorTrancheVault: string } + networkType: NETWORK_TYPE chainType: CHAIN_TYPE - pointsTestnetExperience: boolean campaign?: Campaign handleClose: (options?: CloseModalOptions) => void } @@ -41,14 +40,13 @@ type Props = { export function PersonaEvaluation({ poolInfo, campaign, - pointsTestnetExperience, + networkType, chainType, handleClose, }: Props): React.ReactElement | null { const theme = useTheme() const isDev = checkIsDev() const dispatch = useAppDispatch() - const { account, chainId } = useChainInfo(isDev, chainType) const { errorType, setError: setAuthError, @@ -68,143 +66,109 @@ export function PersonaEvaluation({ const isActionOngoingRef = useRef(false) const isKYCResumedRef = useRef(false) - useEffect(() => { - const createNewWallet = async () => { - if (isWalletOwnershipVerified && campaign && account) { - await CampaignService.createNewWallet( - account, - localStorage.getItem(CAMPAIGN_REFERENCE_CODE) ?? undefined, - isDev, - pointsTestnetExperience, - ) - } - } - createNewWallet() - }, [ - account, - campaign, - isDev, - isWalletOwnershipVerified, - pointsTestnetExperience, - ]) - const checkVerificationStatus = useCallback(async () => { if (isActionOngoingRef.current || isKYCResumedRef.current) { return } try { - if (account && chainId) { - isActionOngoingRef.current = true - setLoadingType('verificationStatus') - const verificationStatus = - await IdentityServiceV2.getVerificationStatusV2( - account, - chainId, - isDev, - ) - setVerificationStatus(verificationStatus) - setInquiryId(verificationStatus.personaInquiryId) - - switch (verificationStatus.status) { - case IdentityVerificationStatusV2.ACCREDITED: { - const startVerificationResult = - await IdentityServiceV2.startVerification(account, chainId, isDev) - setInquiryId(startVerificationResult.personaInquiryId) - setKYCCopy(KYCCopies.verifyIdentity) - setLoadingType(undefined) - if ( - startVerificationResult.status === - IdentityVerificationStatusV2.BYPASSED - ) { - setVerificationStatus(startVerificationResult) - setKYCCopy(KYCCopies.verificationBypassed) - } - - break - } + isActionOngoingRef.current = true + setLoadingType('verificationStatus') + const verificationStatus = + await IdentityServiceV2.getVerificationStatusV2(networkType, isDev) + setVerificationStatus(verificationStatus) + setInquiryId(verificationStatus.personaInquiryId) - case IdentityVerificationStatusV2.CREATED: { - setKYCCopy(KYCCopies.verifyIdentity) - setLoadingType(undefined) - break + switch (verificationStatus.status) { + case IdentityVerificationStatusV2.ACCREDITED: { + const startVerificationResult = + await IdentityServiceV2.startVerification(networkType, isDev) + setInquiryId(startVerificationResult.personaInquiryId) + setKYCCopy(KYCCopies.verifyIdentity) + setLoadingType(undefined) + if ( + startVerificationResult.status === + IdentityVerificationStatusV2.BYPASSED + ) { + setVerificationStatus(startVerificationResult) + setKYCCopy(KYCCopies.verificationBypassed) } - case IdentityVerificationStatusV2.PENDING: { - if (!isKYCCompletedRef.current) { - const resumeVerificationResult = - await IdentityServiceV2.resumeVerification( - account, - chainId, - isDev, - ) - verificationStatus.status = resumeVerificationResult.status - setVerificationStatus(verificationStatus) - setSessionToken(resumeVerificationResult.sessionToken) - setLoadingType(undefined) - isKYCResumedRef.current = true - } else { - setLoadingType('verificationStatus') - } - setKYCCopy(KYCCopies.verifyIdentity) - break - } + break + } + + case IdentityVerificationStatusV2.CREATED: { + setKYCCopy(KYCCopies.verifyIdentity) + setLoadingType(undefined) + break + } - case IdentityVerificationStatusV2.EXPIRED: { + case IdentityVerificationStatusV2.PENDING: { + if (!isKYCCompletedRef.current) { const resumeVerificationResult = - await IdentityServiceV2.resumeVerification( - account, - chainId, - isDev, - ) + await IdentityServiceV2.resumeVerification(networkType, isDev) verificationStatus.status = resumeVerificationResult.status setVerificationStatus(verificationStatus) setSessionToken(resumeVerificationResult.sessionToken) - setKYCCopy(KYCCopies.verifyIdentity) setLoadingType(undefined) isKYCResumedRef.current = true - break + } else { + setLoadingType('verificationStatus') } + setKYCCopy(KYCCopies.verifyIdentity) + break + } - case IdentityVerificationStatusV2.BYPASSED: { - setKYCCopy(KYCCopies.verificationBypassed) - setLoadingType(undefined) + case IdentityVerificationStatusV2.EXPIRED: { + const resumeVerificationResult = + await IdentityServiceV2.resumeVerification(networkType, isDev) + verificationStatus.status = resumeVerificationResult.status + setVerificationStatus(verificationStatus) + setSessionToken(resumeVerificationResult.sessionToken) + setKYCCopy(KYCCopies.verifyIdentity) + setLoadingType(undefined) + isKYCResumedRef.current = true + break + } - break - } + case IdentityVerificationStatusV2.BYPASSED: { + setKYCCopy(KYCCopies.verificationBypassed) + setLoadingType(undefined) - case IdentityVerificationStatusV2.APPROVED: { - setKYCCopy(KYCCopies.verificationApproved) - setLoadingType(undefined) - break - } + break + } - case IdentityVerificationStatusV2.DECLINED: { - setKYCCopy(KYCCopies.verificationDeclined) - setLoadingType(undefined) - break - } + case IdentityVerificationStatusV2.APPROVED: { + setKYCCopy(KYCCopies.verificationApproved) + setLoadingType(undefined) + break + } - case IdentityVerificationStatusV2.NEEDS_REVIEW: { - setKYCCopy(KYCCopies.verificationNeedsReview) - setLoadingType(undefined) - break - } + case IdentityVerificationStatusV2.DECLINED: { + setKYCCopy(KYCCopies.verificationDeclined) + setLoadingType(undefined) + break + } - case IdentityVerificationStatusV2.CONSENTED_TO_SUBSCRIPTION: { - dispatch(setStep(WIDGET_STEP.ApproveLender)) - break - } + case IdentityVerificationStatusV2.NEEDS_REVIEW: { + setKYCCopy(KYCCopies.verificationNeedsReview) + setLoadingType(undefined) + break + } - default: - break + case IdentityVerificationStatusV2.CONSENTED_TO_SUBSCRIPTION: { + dispatch(setStep(WIDGET_STEP.ApproveLender)) + break } + + default: + break } } catch (e: unknown) { setAuthError(e) } finally { isActionOngoingRef.current = false } - }, [KYCCopies, account, chainId, dispatch, isDev, setAuthError]) + }, [KYCCopies, dispatch, isDev, networkType, setAuthError]) useEffect(() => { checkVerificationStatus() @@ -316,7 +280,7 @@ export function PersonaEvaluation({ } }, [KYCAutoStarted, startKYC, verificationStatus]) - const handleAction = () => { + const handleAction = useCallback(() => { if (verificationStatus) { switch (verificationStatus.status) { case IdentityVerificationStatusV2.ACCREDITED: @@ -340,7 +304,7 @@ export function PersonaEvaluation({ break } } - } + }, [handleClose, startKYC, verificationStatus]) const styles = { iconWrapper: css` diff --git a/packages/huma-widget/src/components/Lend/solanaSupply/1-Evaluation.tsx b/packages/huma-widget/src/components/Lend/solanaSupply/1-Evaluation.tsx index 67f7a4ed..34bbc7e6 100644 --- a/packages/huma-widget/src/components/Lend/solanaSupply/1-Evaluation.tsx +++ b/packages/huma-widget/src/components/Lend/solanaSupply/1-Evaluation.tsx @@ -1,4 +1,4 @@ -import { CHAIN_TYPE, SolanaPoolInfo } from '@huma-finance/shared' +import { CHAIN_TYPE, NETWORK_TYPE, SolanaPoolInfo } from '@huma-finance/shared' import React from 'react' import { PersonaEvaluation } from '../components/PersonaEvaluation' @@ -6,14 +6,14 @@ import { Campaign } from '../supplyV2' type Props = { poolInfo: SolanaPoolInfo - pointsTestnetExperience: boolean + networkType: NETWORK_TYPE campaign?: Campaign handleClose: () => void } export function Evaluation({ poolInfo, - pointsTestnetExperience, + networkType, campaign, handleClose, }: Props): React.ReactElement | null { @@ -26,9 +26,9 @@ export function Evaluation({ seniorTrancheVault: poolInfo.seniorTrancheMint, }} handleClose={handleClose} - pointsTestnetExperience={pointsTestnetExperience} - campaign={campaign} + networkType={networkType} chainType={CHAIN_TYPE.SOLANA} + campaign={campaign} /> ) } diff --git a/packages/huma-widget/src/components/Lend/solanaSupply/2-ApproveLender.tsx b/packages/huma-widget/src/components/Lend/solanaSupply/2-ApproveLender.tsx index ad0368b0..ac3aae93 100644 --- a/packages/huma-widget/src/components/Lend/solanaSupply/2-ApproveLender.tsx +++ b/packages/huma-widget/src/components/Lend/solanaSupply/2-ApproveLender.tsx @@ -1,5 +1,6 @@ import { CHAIN_TYPE, + getSolanaNetworkType, SOLANA_CHAIN_INFO, SolanaPoolInfo, TrancheType, @@ -27,6 +28,7 @@ export function ApproveLender({ seniorTrancheVault={poolInfo.seniorTrancheMint} isUniTranche={isUniTranche} chainType={CHAIN_TYPE.SOLANA} + networkType={getSolanaNetworkType(poolInfo.chainId)} chainSpecificData={{ huma_program_id: solanaChainInfo.poolProgram, pool_id: poolInfo.poolId, diff --git a/packages/huma-widget/src/components/Lend/solanaSupply/6-Transfer.tsx b/packages/huma-widget/src/components/Lend/solanaSupply/6-Transfer.tsx index d271893d..4803ed92 100644 --- a/packages/huma-widget/src/components/Lend/solanaSupply/6-Transfer.tsx +++ b/packages/huma-widget/src/components/Lend/solanaSupply/6-Transfer.tsx @@ -3,12 +3,11 @@ import { checkIsDev, convertToShares, getTokenAccounts, + NETWORK_TYPE, SolanaPoolInfo, SolanaTokenUtils, TrancheType, } from '@huma-finance/shared' -import React, { useCallback, useEffect, useState } from 'react' - import { SolanaPoolState, useHumaProgram, @@ -22,6 +21,7 @@ import { } from '@solana/spl-token' import { useWallet } from '@solana/wallet-adapter-react' import { PublicKey, Transaction } from '@solana/web3.js' +import React, { useCallback, useEffect, useState } from 'react' import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' import { setPointsAccumulated, setStep } from '../../../store/widgets.reducers' import { selectWidgetState } from '../../../store/widgets.selectors' @@ -33,14 +33,14 @@ type Props = { poolInfo: SolanaPoolInfo poolState: SolanaPoolState selectedTranche: TrancheType - pointsTestnetExperience: boolean + networkType: NETWORK_TYPE } export function Transfer({ poolInfo, poolState, selectedTranche, - pointsTestnetExperience, + networkType, }: Props): React.ReactElement | null { const isDev = checkIsDev() const dispatch = useAppDispatch() @@ -72,12 +72,12 @@ export function Transfer({ async (options?: { signature: string }) => { if (publicKey && poolState.campaign && options?.signature) { try { - const result = await CampaignService.updateWalletPoints( + const result = await CampaignService.updateHumaAccountPoints( publicKey.toString(), options.signature, poolInfo.chainId, + networkType, isDev, - pointsTestnetExperience, ) dispatch(setPointsAccumulated(result.pointsAccumulated)) } catch (error) { @@ -89,7 +89,7 @@ export function Transfer({ [ dispatch, isDev, - pointsTestnetExperience, + networkType, poolInfo.chainId, poolState.campaign, publicKey, diff --git a/packages/huma-widget/src/components/Lend/solanaSupply/8-PointsEarned.tsx b/packages/huma-widget/src/components/Lend/solanaSupply/8-PointsEarned.tsx index 9ffb68d6..4971959d 100644 --- a/packages/huma-widget/src/components/Lend/solanaSupply/8-PointsEarned.tsx +++ b/packages/huma-widget/src/components/Lend/solanaSupply/8-PointsEarned.tsx @@ -5,19 +5,15 @@ import { CloseModalOptions, formatNumber, isEmpty, + NETWORK_TYPE, } from '@huma-finance/shared' -import { - SolanaPoolState, - txAtom, - useAuthErrorHandling, - useChainInfo, -} from '@huma-finance/web-shared' +import { SolanaPoolState, txAtom, useChainInfo } from '@huma-finance/web-shared' import { Box, css, useTheme } from '@mui/material' import { useResetAtom } from 'jotai/utils' import React, { useCallback, useEffect, useState } from 'react' import { useDispatch } from 'react-redux' -import { resetState, setError } from '../../../store/widgets.reducers' +import { resetState } from '../../../store/widgets.reducers' import { BottomButton } from '../../BottomButton' import { CongratulationsIcon, HumaPointsIcon, RibbonIcon } from '../../icons' import { LoadingModal } from '../../LoadingModal' @@ -29,20 +25,17 @@ enum STATE { Congrats = 'Congrats', } -const ERROR_MESSAGE = - 'Failed to update wallet points. Be assured that your points will be added later.' - type Props = { transactionHash: string poolState: SolanaPoolState - pointsTestnetExperience: boolean + networkType: NETWORK_TYPE handleAction: (options?: CloseModalOptions) => void } export function PointsEarned({ transactionHash, poolState, - pointsTestnetExperience, + networkType, handleAction, }: Props): React.ReactElement { const theme = useTheme() @@ -60,91 +53,26 @@ export function PointsEarned({ ) const monthText = lockupMonths > 1 ? `${lockupMonths} months` : `${lockupMonths} month` - - const { - errorType, - setError: setAuthError, - isWalletOwnershipVerified, - isWalletOwnershipVerificationRequired, - } = useAuthErrorHandling(isDev) - const [walletOwnership, setWalletOwnership] = useState() const [state, setState] = useState(STATE.Loading) useEffect(() => { - if (isWalletOwnershipVerificationRequired) { - setState(STATE.Loading) - } - }, [isWalletOwnershipVerificationRequired]) - - useEffect(() => { - if (isWalletOwnershipVerified) { - setWalletOwnership(true) - } - }, [isWalletOwnershipVerified]) - - useEffect(() => { - const checkWalletOwnership = async () => { - if (account) { - const ownership = await CampaignService.checkWalletOwnership( - account, + const updateWalletPoints = async () => { + try { + const result = await CampaignService.updateHumaAccountPoints( + account!, + transactionHash, + chainId!, + networkType, isDev, - pointsTestnetExperience, ) - setWalletOwnership(ownership) - if (!ownership) { - setAuthError('WalletNotSignedInException') - } - } - } - checkWalletOwnership() - }, [account, isDev, pointsTestnetExperience, setAuthError]) - - useEffect(() => { - if (errorType === 'NotSignedIn') { - setState(STATE.SignIn) - } else if (errorType === 'UserRejected') { - dispatch( - setError({ - errorMessage: 'User has rejected the transaction.', - }), - ) - } else if (errorType === 'Other') { - dispatch( - setError({ - errorMessage: ERROR_MESSAGE, - }), - ) - } - }, [dispatch, errorType]) - - useEffect(() => { - const updateWalletPoints = async () => { - if (walletOwnership) { - try { - const result = await CampaignService.updateWalletPoints( - account!, - transactionHash, - chainId!, - isDev, - pointsTestnetExperience, - ) - setPointsAccumulated(result.pointsAccumulated) - setState(STATE.Congrats) - } catch (error) { - console.error('Failed to update wallet points', error) - } + setPointsAccumulated(result.pointsAccumulated) + setState(STATE.Congrats) + } catch (error) { + console.error('Failed to update wallet points', error) } } updateWalletPoints() - }, [ - account, - chainId, - dispatch, - isDev, - pointsTestnetExperience, - transactionHash, - walletOwnership, - ]) + }, [account, chainId, isDev, networkType, transactionHash]) const handleCloseModal = useCallback(() => { reset() diff --git a/packages/huma-widget/src/components/Lend/solanaSupply/index.tsx b/packages/huma-widget/src/components/Lend/solanaSupply/index.tsx index 69387f30..b9a9572e 100644 --- a/packages/huma-widget/src/components/Lend/solanaSupply/index.tsx +++ b/packages/huma-widget/src/components/Lend/solanaSupply/index.tsx @@ -1,5 +1,6 @@ import { CloseModalOptions, + getSolanaNetworkType, SolanaPoolInfo, TrancheType, } from '@huma-finance/shared' @@ -36,14 +37,12 @@ export interface Campaign { * @typedef {Object} SolanaLendSupplyProps * @property {SolanaPoolInfo} poolInfo The metadata of the pool. * @property {SolanaPoolState} poolState The current state config of the pool. - * @property {boolean} pointsTestnetExperience If the user is in the testnet experience. * @property {function((CloseModalOptions|undefined)):void} handleClose Function to notify to close the widget modal when user clicks the 'x' close button. * @property {function():void|undefined} handleSuccess Optional function to notify that the lending pool supply action is successful. */ export interface SolanaLendSupplyProps { poolInfo: SolanaPoolInfo poolState: SolanaPoolState - pointsTestnetExperience: boolean handleClose: (options?: CloseModalOptions) => void handleSuccess?: () => void } @@ -51,7 +50,6 @@ export interface SolanaLendSupplyProps { export function SolanaLendSupply({ poolInfo, poolState, - pointsTestnetExperience, handleClose, handleSuccess, }: SolanaLendSupplyProps): React.ReactElement | null { @@ -128,8 +126,8 @@ export function SolanaLendSupply({ {step === WIDGET_STEP.Evaluation && ( )} @@ -164,7 +162,7 @@ export function SolanaLendSupply({ poolInfo={poolInfo} poolState={poolState} selectedTranche={selectedTranche} - pointsTestnetExperience={pointsTestnetExperience} + networkType={getSolanaNetworkType(poolInfo.chainId)} /> )} {step === WIDGET_STEP.Done && ( diff --git a/packages/huma-widget/src/components/Lend/supplyV2/2-Evaluation.tsx b/packages/huma-widget/src/components/Lend/supplyV2/2-Evaluation.tsx index 83175966..c0f592e2 100644 --- a/packages/huma-widget/src/components/Lend/supplyV2/2-Evaluation.tsx +++ b/packages/huma-widget/src/components/Lend/supplyV2/2-Evaluation.tsx @@ -1,4 +1,9 @@ -import { CHAIN_TYPE, CloseModalOptions, PoolInfoV2 } from '@huma-finance/shared' +import { + CHAIN_TYPE, + CloseModalOptions, + NETWORK_TYPE, + PoolInfoV2, +} from '@huma-finance/shared' import React from 'react' import { Campaign } from '.' @@ -7,7 +12,7 @@ import { SecuritizeEvaluation } from '../components/SecuritizeEvaluation' type Props = { poolInfo: PoolInfoV2 - pointsTestnetExperience: boolean + networkType: NETWORK_TYPE campaign?: Campaign handleClose: (options?: CloseModalOptions) => void } @@ -15,7 +20,7 @@ type Props = { export function Evaluation({ poolInfo, campaign, - pointsTestnetExperience, + networkType, handleClose, }: Props): React.ReactElement | null { if (poolInfo.KYC?.Securitize) { @@ -28,8 +33,8 @@ export function Evaluation({ ) diff --git a/packages/huma-widget/src/components/Lend/supplyV2/3-ApproveLender.tsx b/packages/huma-widget/src/components/Lend/supplyV2/3-ApproveLender.tsx index e1b496f7..a1d4b213 100644 --- a/packages/huma-widget/src/components/Lend/supplyV2/3-ApproveLender.tsx +++ b/packages/huma-widget/src/components/Lend/supplyV2/3-ApproveLender.tsx @@ -1,4 +1,9 @@ -import { CHAIN_TYPE, PoolInfoV2, TrancheType } from '@huma-finance/shared' +import { + CHAIN_TYPE, + getEvmNetworkType, + PoolInfoV2, + TrancheType, +} from '@huma-finance/shared' import React from 'react' import { ApproveLenderBase } from '../components/ApproveLenderBase' @@ -20,6 +25,7 @@ export function ApproveLender({ seniorTrancheVault={poolInfo.seniorTrancheVault} isUniTranche={isUniTranche} chainType={CHAIN_TYPE.EVM} + networkType={getEvmNetworkType(poolInfo.chainId)} changeTranche={changeTranche} /> ) diff --git a/packages/huma-widget/src/components/Lend/supplyV2/6-Transfer.tsx b/packages/huma-widget/src/components/Lend/supplyV2/6-Transfer.tsx index 72264893..d7cefa75 100644 --- a/packages/huma-widget/src/components/Lend/supplyV2/6-Transfer.tsx +++ b/packages/huma-widget/src/components/Lend/supplyV2/6-Transfer.tsx @@ -1,6 +1,7 @@ import { CampaignService, checkIsDev, + NETWORK_TYPE, PoolInfoV2, TrancheType, } from '@huma-finance/shared' @@ -19,14 +20,14 @@ import { TxSendModalV2 } from '../../TxSendModalV2' type Props = { poolInfo: PoolInfoV2 trancheType: TrancheType - pointsTestnetExperience: boolean + networkType: NETWORK_TYPE campaign?: Campaign } export function Transfer({ poolInfo, trancheType, - pointsTestnetExperience, + networkType, campaign, }: Props): React.ReactElement | null { const isDev = checkIsDev() @@ -49,12 +50,12 @@ export function Transfer({ async (options?: { txHash: string }) => { if (campaign && options?.txHash) { try { - const result = await CampaignService.updateWalletPoints( + const result = await CampaignService.updateHumaAccountPoints( account!, options.txHash, chainId!, + networkType, isDev, - pointsTestnetExperience, ) dispatch(setPointsAccumulated(result.pointsAccumulated)) } catch (error) { @@ -63,7 +64,7 @@ export function Transfer({ } dispatch(setStep(WIDGET_STEP.Done)) }, - [account, campaign, chainId, dispatch, isDev, pointsTestnetExperience], + [account, campaign, chainId, dispatch, isDev, networkType], ) if (!trancheVaultContract || !account) { diff --git a/packages/huma-widget/src/components/Lend/supplyV2/index.tsx b/packages/huma-widget/src/components/Lend/supplyV2/index.tsx index 05f49ac4..c51e5e55 100644 --- a/packages/huma-widget/src/components/Lend/supplyV2/index.tsx +++ b/packages/huma-widget/src/components/Lend/supplyV2/index.tsx @@ -1,5 +1,6 @@ import { CloseModalOptions, + getEvmNetworkType, openInNewTab, POOL_NAME, TrancheType, @@ -39,14 +40,12 @@ export interface Campaign { * Lend pool supply props * @typedef {Object} LendSupplyPropsV2 * @property {POOL_NAME} poolName The name of the pool. - * @property {boolean} pointsTestnetExperience If the user is in the testnet experience. * @property {Campaign} campaign The campaign info. * @property {function((CloseModalOptions|undefined)):void} handleClose Function to notify to close the widget modal when user clicks the 'x' close button. * @property {function((number|undefined)):void|undefined} handleSuccess Optional function to notify that the lending pool supply action is successful. */ export interface LendSupplyPropsV2 { poolName: keyof typeof POOL_NAME - pointsTestnetExperience: boolean campaign?: Campaign handleClose: (options?: CloseModalOptions) => void handleSuccess?: (blockNumber?: number) => void @@ -54,7 +53,6 @@ export interface LendSupplyPropsV2 { export function LendSupplyV2({ poolName: poolNameStr, - pointsTestnetExperience, campaign, handleClose, handleSuccess, @@ -171,8 +169,8 @@ export function LendSupplyV2({ )} {step === WIDGET_STEP.ApproveLender && ( @@ -199,8 +197,8 @@ export function LendSupplyV2({ )} {step === WIDGET_STEP.Done && (