Skip to content

Commit

Permalink
Merge pull request #2892 from JoinColony/fix/2733-liquidation-address…
Browse files Browse the repository at this point in the history
…-recipient

Crypto-to-fiat: Payments to liquidation address should show relevant member
  • Loading branch information
jakubcolony authored Aug 13, 2024
2 parents 68d3eaf + 78aec0b commit c36a424
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 134 deletions.
4 changes: 4 additions & 0 deletions amplify/backend/api/colonycdapp/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,10 @@ type User @model {
@hasMany(indexName: "byUserAddress", fields: ["id"])
}

"""
NOTE: This model should only be used to lookup users by liquidation address
To get the liquidation address of a user, use the `bridgeGetUserLiquidationAddress` query instead
"""
type LiquidationAddress @model {
"""
Unique identifier for the liquidation address entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { getLiquidationAddresses } = require('./utils');
* So that we can always ensure it follows the latest schema
* (currently it's just saved statically)
*/
const { getUser } = require('../graphql');
const { getUser, createLiquidationAddress } = require('../graphql');

const KYC_STATUS = {
NOT_STARTED: 'NOT_STARTED',
Expand Down Expand Up @@ -75,14 +75,11 @@ const checkKYCHandler = async (

// Is it an existing KYC link or a new one?
let kycLink;
let kycLinkId;
let kycStatus;
if (data.existing_kyc_link) {
kycLinkId = data.existing_kyc_link.id;
kycLink = data.existing_kyc_link.kyc_link;
kycStatus = data.existing_kyc_link.kyc_status;
} else {
kycLinkId = data.id;
kycLink = data.kyc_link;
kycStatus = data.kyc_status;
}
Expand Down Expand Up @@ -182,13 +179,29 @@ const checkKYCHandler = async (
const liquidationAddressCreationRes =
await liquidationAddressCreation.json();

if (liquidationAddressCreation.status !== 201) {
throw new Error(
`Failed to create liquidation address: ${liquidationAddressCreationRes}`,
if (liquidationAddressCreation.status === 201) {
externalAccountLiquidationAddress =
liquidationAddressCreationRes.address;

// create liquidation address entry in the database
await graphqlRequest(
createLiquidationAddress,
{
input: {
chainId: 42161,
liquidationAddress: externalAccountLiquidationAddress,
userAddress: checksummedWalletAddress,
},
},
graphqlURL,
appSyncApiKey,
);
} else {
console.error(
'Failed to create liquidation address: ',
liquidationAddressCreationRes,
);
}

externalAccountLiquidationAddress = liquidationAddressCreationRes.address;
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,16 @@ const kycLinksHandler = async (
*/
const tosLink = data?.tos_link ?? data?.existing_kyc_link?.tos_link;
if (!tosLink) {
throw new Error('TOS link missing from Bridge XYZ response');
throw new Error(
`TOS link missing from Bridge XYZ response: ${data.toString()}`,
);
}

const customerId = extractCustomerId(tosLink);
if (!customerId) {
throw new Error('Could not extract customer ID from TOS link');
throw new Error(
`Could not extract customer ID from TOS link: ${tosLink}`,
);
}

const userByCustomerIdQuery = await graphqlRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const updateExternalAccountHandler = async (
},
);

// Exit if deleting account fails to avoid creating multiple accounts
if (deleteAccountRes.status !== 200) {
throw Error(
`Error deleting external account: ${await deleteAccountRes.text()}`,
Expand All @@ -57,6 +58,8 @@ const updateExternalAccountHandler = async (

/**
* Update liquidation addresses associated with the deleted account
* Only if the currency is the same as the new account
* Otherwise, creating new liquidation address is handled elsewhere
*/
const liquidationAddresses = await getLiquidationAddresses(
apiUrl,
Expand Down Expand Up @@ -90,32 +93,6 @@ const updateExternalAccountHandler = async (
`Error updating liquidation address: ${await updateAddressRes.text()}`,
);
}
} else {
const createAddressRes = await fetch(
`${apiUrl}/v0/customers/${bridgeCustomerId}/liquidation_addresses`,
{
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': newAccount.id,
'Api-Key': apiKey,
},
method: 'POST',
body: JSON.stringify({
chain: 'arbitrum',
currency: 'usdc',
external_account_id: newAccount.id,
destination_payment_rail:
newAccount.currency === 'usd' ? 'ach' : 'sepa',
destination_currency: newAccount.currency,
}),
},
);

if (createAddressRes.status !== 201) {
throw Error(
`Error creating liquidation address: ${await createAddressRes.text()}`,
);
}
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineMessages } from 'react-intl';
import { toast } from 'react-toastify';

import {
CheckKycStatusDocument,
SupportedCurrencies,
useCreateBankAccountMutation,
useUpdateBankAccountMutation,
Expand Down Expand Up @@ -42,8 +43,12 @@ export const useBankDetailsFields = ({
redirectToSecondTab,
data,
}: UseBankDetailsParams) => {
const [createBankAccount] = useCreateBankAccountMutation();
const [updateBankAccount] = useUpdateBankAccountMutation();
const [createBankAccount] = useCreateBankAccountMutation({
refetchQueries: [CheckKycStatusDocument],
});
const [updateBankAccount] = useUpdateBankAccountMutation({
refetchQueries: [CheckKycStatusDocument],
});

const [bankDetailsFields, setBankDetailsFields] =
useState<BankDetailsFormValues>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,12 @@ const useGetPaymentBuilderColumns = ({
paymentBuilderColumnHelper.accessor('recipient', {
enableSorting: false,
header: formatText({ id: 'table.row.recipient' }),
cell: ({ row }) =>
isLoading ? (
<div className="flex w-full items-center">
<div className="h-4 w-full overflow-hidden rounded skeleton" />
</div>
) : (
<RecipientField address={row.original.recipient} />
),
cell: ({ row }) => (
<RecipientField
isLoading={!!isLoading}
address={row.original.recipient}
/>
),
footer: hasMoreThanOneToken
? () => (
<span className="flex min-h-[1.875rem] items-center text-xs text-gray-400">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import React, { type FC } from 'react';

import useUserByAddress from '~hooks/useUserByAddress.ts';
import UserPopover from '~v5/shared/UserPopover/UserPopover.tsx';

import { type RecipientFieldProps } from './types.ts';

const RecipientField: FC<RecipientFieldProps> = ({ address }) => (
<UserPopover size={18} walletAddress={address} withVerifiedBadge />
);
const RecipientField: FC<RecipientFieldProps> = ({ address, isLoading }) => {
const { user, loading: isUserLoading } = useUserByAddress(address, true);

if (isLoading || isUserLoading) {
return (
<div className="flex w-full items-center">
<div className="h-4 w-full overflow-hidden rounded skeleton" />
</div>
);
}

return (
<UserPopover
size={18}
walletAddress={user?.walletAddress ?? address}
withVerifiedBadge
/>
);
};

export default RecipientField;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface RecipientFieldProps {
address: string;
isLoading: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const SimplePayment = ({ action }: SimplePaymentProps) => {
getTokenDecimalsWithFallback(token?.decimals),
);

const { user } = useUserByAddress(actionRecipientAddress as string, true);
const { user } = useUserByAddress(actionRecipientAddress ?? '', true);
const recipientAddress = user?.walletAddress ?? actionRecipientAddress;
const recipientUser = user ?? actionRecipientUser;

Expand Down
62 changes: 11 additions & 51 deletions src/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,10 @@ export enum KycStatus {
UnderReview = 'UNDER_REVIEW'
}

/**
* NOTE: This model should only be used to lookup users by liquidation address
* To get the liquidation address of a user, use the `bridgeGetUserLiquidationAddress` query instead
*/
export type LiquidationAddress = {
__typename?: 'LiquidationAddress';
/** The chain id the colony is on */
Expand Down Expand Up @@ -9415,18 +9419,11 @@ export type GetExtensionInstallationsCountQuery = { __typename?: 'Query', getExt

export type GetUserByUserOrLiquidationAddressQueryVariables = Exact<{
userOrLiquidationAddress: Scalars['ID'];
}>;


export type GetUserByUserOrLiquidationAddressQuery = { __typename?: 'Query', getUserByAddress?: { __typename?: 'ModelUserConnection', items: Array<{ __typename?: 'User', bridgeCustomerId?: string | null, walletAddress: string, profile?: { __typename?: 'Profile', avatar?: string | null, bio?: string | null, displayName?: string | null, displayNameChanged?: string | null, email?: string | null, location?: string | null, thumbnail?: string | null, website?: string | null, preferredCurrency?: SupportedCurrencies | null, isAutoOfframpEnabled?: boolean | null, meta?: { __typename?: 'ProfileMetadata', metatransactionsEnabled?: boolean | null, decentralizedModeEnabled?: boolean | null, customRpc?: string | null } | null } | null, privateBetaInviteCode?: { __typename?: 'PrivateBetaInviteCode', id: string, shareableInvites?: number | null } | null } | null> } | null, getUserByLiquidationAddress?: { __typename?: 'ModelLiquidationAddressConnection', items: Array<{ __typename?: 'LiquidationAddress', id: string, chainId: number, userAddress: string, liquidationAddress: string, user?: { __typename?: 'User', bridgeCustomerId?: string | null, walletAddress: string, profile?: { __typename?: 'Profile', avatar?: string | null, bio?: string | null, displayName?: string | null, displayNameChanged?: string | null, email?: string | null, location?: string | null, thumbnail?: string | null, website?: string | null, preferredCurrency?: SupportedCurrencies | null, isAutoOfframpEnabled?: boolean | null, meta?: { __typename?: 'ProfileMetadata', metatransactionsEnabled?: boolean | null, decentralizedModeEnabled?: boolean | null, customRpc?: string | null } | null } | null, privateBetaInviteCode?: { __typename?: 'PrivateBetaInviteCode', id: string, shareableInvites?: number | null } | null } | null } | null> } | null };

export type GetUserLiquidationAddressesQueryVariables = Exact<{
userAddress: Scalars['ID'];
chainId: Scalars['Int'];
}>;


export type GetUserLiquidationAddressesQuery = { __typename?: 'Query', getLiquidationAddressesByUserAddress?: { __typename?: 'ModelLiquidationAddressConnection', items: Array<{ __typename?: 'LiquidationAddress', id: string, chainId: number, userAddress: string, liquidationAddress: string, user?: { __typename?: 'User', bridgeCustomerId?: string | null, walletAddress: string, profile?: { __typename?: 'Profile', avatar?: string | null, bio?: string | null, displayName?: string | null, displayNameChanged?: string | null, email?: string | null, location?: string | null, thumbnail?: string | null, website?: string | null, preferredCurrency?: SupportedCurrencies | null, isAutoOfframpEnabled?: boolean | null, meta?: { __typename?: 'ProfileMetadata', metatransactionsEnabled?: boolean | null, decentralizedModeEnabled?: boolean | null, customRpc?: string | null } | null } | null, privateBetaInviteCode?: { __typename?: 'PrivateBetaInviteCode', id: string, shareableInvites?: number | null } | null } | null } | null> } | null };
export type GetUserByUserOrLiquidationAddressQuery = { __typename?: 'Query', getUserByAddress?: { __typename?: 'ModelUserConnection', items: Array<{ __typename?: 'User', bridgeCustomerId?: string | null, walletAddress: string, profile?: { __typename?: 'Profile', avatar?: string | null, bio?: string | null, displayName?: string | null, displayNameChanged?: string | null, email?: string | null, location?: string | null, thumbnail?: string | null, website?: string | null, preferredCurrency?: SupportedCurrencies | null, isAutoOfframpEnabled?: boolean | null, meta?: { __typename?: 'ProfileMetadata', metatransactionsEnabled?: boolean | null, decentralizedModeEnabled?: boolean | null, customRpc?: string | null } | null } | null, privateBetaInviteCode?: { __typename?: 'PrivateBetaInviteCode', id: string, shareableInvites?: number | null } | null } | null> } | null, getUserByLiquidationAddress?: { __typename?: 'ModelLiquidationAddressConnection', items: Array<{ __typename?: 'LiquidationAddress', id: string, chainId: number, userAddress: string, liquidationAddress: string, user?: { __typename?: 'User', bridgeCustomerId?: string | null, walletAddress: string, profile?: { __typename?: 'Profile', avatar?: string | null, bio?: string | null, displayName?: string | null, displayNameChanged?: string | null, email?: string | null, location?: string | null, thumbnail?: string | null, website?: string | null, preferredCurrency?: SupportedCurrencies | null, isAutoOfframpEnabled?: boolean | null, meta?: { __typename?: 'ProfileMetadata', metatransactionsEnabled?: boolean | null, decentralizedModeEnabled?: boolean | null, customRpc?: string | null } | null } | null, privateBetaInviteCode?: { __typename?: 'PrivateBetaInviteCode', id: string, shareableInvites?: number | null } | null } | null } | null> } | null };

export type GetReputationMiningCycleMetadataQueryVariables = Exact<{ [key: string]: never; }>;

Expand Down Expand Up @@ -12703,13 +12700,16 @@ export type GetExtensionInstallationsCountQueryHookResult = ReturnType<typeof us
export type GetExtensionInstallationsCountLazyQueryHookResult = ReturnType<typeof useGetExtensionInstallationsCountLazyQuery>;
export type GetExtensionInstallationsCountQueryResult = Apollo.QueryResult<GetExtensionInstallationsCountQuery, GetExtensionInstallationsCountQueryVariables>;
export const GetUserByUserOrLiquidationAddressDocument = gql`
query GetUserByUserOrLiquidationAddress($userOrLiquidationAddress: ID!) {
query GetUserByUserOrLiquidationAddress($userOrLiquidationAddress: ID!, $chainId: Int!) {
getUserByAddress(id: $userOrLiquidationAddress) {
items {
...User
}
}
getUserByLiquidationAddress(liquidationAddress: $userOrLiquidationAddress) {
getUserByLiquidationAddress(
liquidationAddress: $userOrLiquidationAddress
filter: {chainId: {eq: $chainId}}
) {
items {
...LiquidationAddress
}
Expand All @@ -12731,6 +12731,7 @@ ${LiquidationAddressFragmentDoc}`;
* const { data, loading, error } = useGetUserByUserOrLiquidationAddressQuery({
* variables: {
* userOrLiquidationAddress: // value for 'userOrLiquidationAddress'
* chainId: // value for 'chainId'
* },
* });
*/
Expand All @@ -12745,47 +12746,6 @@ export function useGetUserByUserOrLiquidationAddressLazyQuery(baseOptions?: Apol
export type GetUserByUserOrLiquidationAddressQueryHookResult = ReturnType<typeof useGetUserByUserOrLiquidationAddressQuery>;
export type GetUserByUserOrLiquidationAddressLazyQueryHookResult = ReturnType<typeof useGetUserByUserOrLiquidationAddressLazyQuery>;
export type GetUserByUserOrLiquidationAddressQueryResult = Apollo.QueryResult<GetUserByUserOrLiquidationAddressQuery, GetUserByUserOrLiquidationAddressQueryVariables>;
export const GetUserLiquidationAddressesDocument = gql`
query GetUserLiquidationAddresses($userAddress: ID!, $chainId: Int!) {
getLiquidationAddressesByUserAddress(
userAddress: $userAddress
filter: {chainId: {eq: $chainId}}
) {
items {
...LiquidationAddress
}
}
}
${LiquidationAddressFragmentDoc}`;

/**
* __useGetUserLiquidationAddressesQuery__
*
* To run a query within a React component, call `useGetUserLiquidationAddressesQuery` and pass it any options that fit your needs.
* When your component renders, `useGetUserLiquidationAddressesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetUserLiquidationAddressesQuery({
* variables: {
* userAddress: // value for 'userAddress'
* chainId: // value for 'chainId'
* },
* });
*/
export function useGetUserLiquidationAddressesQuery(baseOptions: Apollo.QueryHookOptions<GetUserLiquidationAddressesQuery, GetUserLiquidationAddressesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetUserLiquidationAddressesQuery, GetUserLiquidationAddressesQueryVariables>(GetUserLiquidationAddressesDocument, options);
}
export function useGetUserLiquidationAddressesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetUserLiquidationAddressesQuery, GetUserLiquidationAddressesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetUserLiquidationAddressesQuery, GetUserLiquidationAddressesQueryVariables>(GetUserLiquidationAddressesDocument, options);
}
export type GetUserLiquidationAddressesQueryHookResult = ReturnType<typeof useGetUserLiquidationAddressesQuery>;
export type GetUserLiquidationAddressesLazyQueryHookResult = ReturnType<typeof useGetUserLiquidationAddressesLazyQuery>;
export type GetUserLiquidationAddressesQueryResult = Apollo.QueryResult<GetUserLiquidationAddressesQuery, GetUserLiquidationAddressesQueryVariables>;
export const GetReputationMiningCycleMetadataDocument = gql`
query GetReputationMiningCycleMetadata {
getReputationMiningCycleMetadata(id: "REPUTATION_MINING_CYCLE_METADATA") {
Expand Down
17 changes: 6 additions & 11 deletions src/graphql/queries/liquidationAddress.graphql
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
query GetUserByUserOrLiquidationAddress($userOrLiquidationAddress: ID!) {
query GetUserByUserOrLiquidationAddress(
$userOrLiquidationAddress: ID!
$chainId: Int!
) {
getUserByAddress(id: $userOrLiquidationAddress) {
items {
...User
}
}
getUserByLiquidationAddress(liquidationAddress: $userOrLiquidationAddress) {
items {
...LiquidationAddress
}
}
}

query GetUserLiquidationAddresses($userAddress: ID!, $chainId: Int!) {
getLiquidationAddressesByUserAddress(
userAddress: $userAddress
getUserByLiquidationAddress(
liquidationAddress: $userOrLiquidationAddress
filter: { chainId: { eq: $chainId } }
) {
items {
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/useUserByAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
useGetUserByUserOrLiquidationAddressQuery,
} from '~gql';
import { type Address } from '~types/index.ts';
import { getChainId } from '~utils/chainId.ts';

const useUserByAddress = (
address: Address,
Expand All @@ -20,6 +21,7 @@ const useUserByAddress = (
useGetUserByUserOrLiquidationAddressQuery({
variables: {
userOrLiquidationAddress: address,
chainId: Number(getChainId()),
},
fetchPolicy: 'cache-and-network',
skip: !tryLiquidationAddress || !address,
Expand Down
2 changes: 1 addition & 1 deletion src/redux/sagas/transactions/getMetatransactionPromise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import {
ExtendedClientType,
} from '~types/transactions.ts';
import { isFullWallet } from '~types/wallet.ts';
import { getChainId } from '~utils/chainId.ts';
import {
generateMetatransactionErrorMessage,
generateMetamaskTypedDataSignatureErrorMessage,
} from '~utils/web3/index.ts';

import { type TransactionType } from '../../immutable/index.ts';
import {
getChainId,
generateEIP2612TypedData,
generateMetatransactionMessage,
broadcastMetatransaction,
Expand Down
Loading

0 comments on commit c36a424

Please sign in to comment.