Skip to content

Commit

Permalink
Merge pull request #2794 from JoinColony/feat/2793-liquidation-address
Browse files Browse the repository at this point in the history
Crypto-to-fiat: Resolve edge cases with liquidation addresses
  • Loading branch information
jakubcolony authored Aug 14, 2024
2 parents 5f7afa2 + 297858f commit b225170
Show file tree
Hide file tree
Showing 50 changed files with 496 additions and 1,000 deletions.
38 changes: 38 additions & 0 deletions amplify/backend/api/colonycdapp/schema/bridge.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
enum KYCStatus {
NOT_STARTED
INCOMPLETE
PENDING
UNDER_REVIEW
APPROVED
REJECTED
}

type BridgeCheckKYCReturn {
kycStatus: KYCStatus
kycLink: String
tosLink: String
bankAccount: BridgeBankAccount
liquidationAddress: String
}

type BridgeDrainReceipt {
url: String!
}
Expand All @@ -10,3 +27,24 @@ type BridgeDrain {
createdAt: String!
receipt: BridgeDrainReceipt!
}

type BridgeIbanBankAccount {
id: String!
last4: String!
bic: String!
country: String!
}

type BridgeUsBankAccount {
last4: String!
routingNumber: String!
}

type BridgeBankAccount {
id: String!
currency: String!
bankName: String!
accountOwner: String!
iban: BridgeIbanBankAccount
usAccount: BridgeUsBankAccount
}
70 changes: 18 additions & 52 deletions amplify/backend/api/colonycdapp/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,6 @@ input GetSafeTransactionStatusInput {
chainId: String!
}

input BridgeXYZQueryInput {
path: String!
}

input BridgeXYZMutationAddressInput {
street_line_1: String!
street_line_2: String
Expand Down Expand Up @@ -335,50 +331,16 @@ type BridgeUpdateBankAccountReturn {
success: Boolean
}

type BridgeIbanBankAccount {
id: String!
last4: String!
bic: String!
country: String!
}

type BridgeUsBankAccount {
last4: String!
routingNumber: String!
}

type BridgeXYZBankAccount {
id: String!
currency: String!
bankName: String!
accountOwner: String!
iban: BridgeIbanBankAccount
usAccount: BridgeUsBankAccount
}

type BridgeXYZMutationReturn {
#
# KYC Links
#
tos_link: String
kyc_link: String

#
# KYC Status
#
kycStatus: KYCStatus
country: String
# Should support multiple accounts at some point
bankAccount: BridgeXYZBankAccount

success: Boolean
}

type BridgeXYZQueryReturn {
success: Boolean
transactionFee: String
}

"""
Return type for tokens gotten from DB or from chain
"""
Expand Down Expand Up @@ -918,15 +880,6 @@ enum ContributorType {
GENERAL
}

enum KYCStatus {
NOT_STARTED
INCOMPLETE
PENDING
UNDER_REVIEW
APPROVED
REJECTED
}

"""
Root query type
"""
Expand Down Expand Up @@ -975,13 +928,22 @@ type Query {
@function(name: "getSafeTransactionStatus-${env}")

"""
Fetch from the Bridge XYZ API
Get drains history for the current user
"""
bridgeXYZQuery(input: BridgeXYZQueryInput!): BridgeXYZQueryReturn
@function(name: "bridgeXYZQuery-${env}")

bridgeGetDrainsHistory: [BridgeDrain!]
@function(name: "bridgeXYZQuery-${env}")
@function(name: "bridgeXYZMutation-${env}")

"""
Check bridge KYC status for the current user
"""
bridgeCheckKYC: BridgeCheckKYCReturn
@function(name: "bridgeXYZMutation-${env}")

"""
Get liquidation address of a given user
"""
bridgeGetUserLiquidationAddress(userAddress: String!): String
@function(name: "bridgeXYZMutation-${env}")
}

"""
Expand Down Expand Up @@ -1895,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
6 changes: 0 additions & 6 deletions amplify/backend/backend-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@
"providerPlugin": "awscloudformation",
"service": "Lambda"
},
"bridgeXYZQuery": {
"build": true,
"dependsOn": [],
"providerPlugin": "awscloudformation",
"service": "Lambda"
},
"colonycdappSSMAccess": {
"build": true,
"providerPlugin": "awscloudformation",
Expand Down
75 changes: 41 additions & 34 deletions amplify/backend/function/bridgeXYZMutation/src/handlers/checkKyc.js
Original file line number Diff line number Diff line change
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 @@ -117,42 +114,48 @@ const checkKYCHandler = async (
const response = await externalAccountRes.json();

const externalAccounts = response.data;

// TODO: Support multiple accounts
const firstAccount = externalAccounts?.[0];

const mappedAccount = firstAccount
? {
id: firstAccount.id,
currency: firstAccount.currency,
bankName: firstAccount.bank_name,
accountOwner: firstAccount.account_owner_name,
iban: firstAccount.iban
? {
last4: firstAccount.iban.last_4,
bic: firstAccount.iban.bic,
country: firstAccount.iban.country,
}
: null,
usAccount: firstAccount.account
? {
last4: firstAccount.account.last_4,
routingNumber: firstAccount.account.routing_number,
}
: null,
}
: null;
if (!firstAccount) {
return {
kycStatus,
kycLink,
bankAccount: null,
};
}

const mappedAccount = {
id: firstAccount.id,
currency: firstAccount.currency,
bankName: firstAccount.bank_name,
accountOwner: firstAccount.account_owner_name,
iban: firstAccount.iban
? {
last4: firstAccount.iban.last_4,
bic: firstAccount.iban.bic,
country: firstAccount.iban.country,
}
: null,
usAccount: firstAccount.account
? {
last4: firstAccount.account.last_4,
routingNumber: firstAccount.account.routing_number,
}
: null,
};

const liquidationAddresses = await getLiquidationAddresses(
apiUrl,
apiKey,
bridgeCustomerId,
);
const hasLiquidationAddress = liquidationAddresses.length > 0;

if (firstAccount && !hasLiquidationAddress) {
// They have external accounts. Create a liquidation address
console.log('Bank account exists, creating liquidation address');
let externalAccountLiquidationAddress = liquidationAddresses.find(
(address) => address.external_account_id === firstAccount.id,
)?.address;

if (!externalAccountLiquidationAddress) {
console.log('No liquidation address found for account, creating one');
const liquidationAddressCreation = await fetch(
`${apiUrl}/v0/customers/${bridgeCustomerId}/liquidation_addresses`,
{
Expand All @@ -177,13 +180,16 @@ const checkKYCHandler = async (
await liquidationAddressCreation.json();

if (liquidationAddressCreation.status === 201) {
const liquidationAddress = liquidationAddressCreationRes.address;
externalAccountLiquidationAddress =
liquidationAddressCreationRes.address;

// create liquidation address entry in the database
await graphqlRequest(
createLiquidationAddress,
{
input: {
chainId: 42161,
liquidationAddress,
liquidationAddress: externalAccountLiquidationAddress,
userAddress: checksummedWalletAddress,
},
},
Expand All @@ -192,16 +198,17 @@ const checkKYCHandler = async (
);
} else {
console.error(
`Failed to create liquidation address: `,
'Failed to create liquidation address: ',
liquidationAddressCreationRes,
);
}
}

return {
kycStatus,
kyc_link: kycLink,
kycLink,
bankAccount: mappedAccount,
liquidationAddress: externalAccountLiquidationAddress,
};
} catch (e) {
console.error(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const fetch = require('cross-fetch');
const { graphqlRequest } = require('../utils');
const { getLiquidationAddresses } = require('./utils');

const { getUser } = require('../graphql');

const getUserLiquidationAddressHandler = async (
event,
{ appSyncApiKey, apiKey, apiUrl, graphqlURL },
) => {
const userAddress = event.arguments.userAddress;

const { data: graphQlData } = await graphqlRequest(
getUser,
{
id: userAddress,
},
graphqlURL,
appSyncApiKey,
);
const colonyUser = graphQlData?.getUser;

const bridgeCustomerId = colonyUser?.bridgeCustomerId;
if (!bridgeCustomerId) {
return null;
}

const externalAccountRes = await fetch(
`${apiUrl}/v0/customers/${bridgeCustomerId}/external_accounts`,
{
headers: {
'Content-Type': 'application/json',
'Api-Key': apiKey,
},
},
);

const response = await externalAccountRes.json();

const externalAccounts = response.data;
const firstAccount = externalAccounts?.[0];

if (!firstAccount) {
return null;
}

const liquidationAddresses = await getLiquidationAddresses(
apiUrl,
apiKey,
bridgeCustomerId,
);

const relevantLiquidationAddress = liquidationAddresses.find(
(address) => address.external_account_id === firstAccount.id,
)?.address;

return relevantLiquidationAddress;
};

module.exports = {
getUserLiquidationAddressHandler,
};
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
Loading

0 comments on commit b225170

Please sign in to comment.