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

Crypto-to-fiat: Payments to liquidation address should show relevant member #2892

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,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be made into an env variable once they're set up in lambdas

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
Loading