Skip to content

Commit

Permalink
feat: add user to gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
dragosp1011 committed Sep 19, 2024
1 parent 5b5bf32 commit ca6b363
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 38 deletions.
1 change: 0 additions & 1 deletion docker/dev/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ FROM_EMAIL=
SENDGRID_API_KEY=
AUTH_IDENTITY_SERVER_SECRET=
AUTH_COOKIE_KEY=
RATE_API_KEY=
GATEHUB_ACCESS_KEY=
GATEHUB_SECRET_KEY=
GATEHUB_GATEWAY_UUID=
Expand Down
1 change: 0 additions & 1 deletion docker/dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ services:
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
FROM_EMAIL: ${FROM_EMAIL}
SEND_EMAIL: ${SEND_EMAIL:-false}
RATE_API_KEY: ${RATE_API_KEY}
REDIS_URL: redis://redis:6379/0
KRATOS_ADMIN_URL: 'http://kratos:4434/admin'
GATEHUB_ACCESS_KEY: ${GATEHUB_ACCESS_KEY}
Expand Down
1 change: 0 additions & 1 deletion docker/prod/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ WALLET_BACKEND_SENDGRID_API_KEY=
WALLET_BACKEND_FROM_EMAIL=
WALLET_BACKEND_SEND_EMAIL=
WALLET_BACKEND_AUTH_DOMAIN=
WALLET_BACKEND_RATE_API_KEY=
WALLET_BACKEND_GATEHUB_ACCESS_KEY=
WALLET_BACKEND_GATEHUB_SECRET_KEY=
WALLET_BACKEND_GATEHUB_GATEWAY_UUID=
Expand Down
1 change: 0 additions & 1 deletion docker/prod/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ services:
SEND_EMAIL: ${WALLET_BACKEND_SEND_EMAIL}
AUTH_IDENTITY_SERVER_SECRET: ${RAFIKI_AUTH_IDENTITY_SERVER_SECRET}
AUTH_DOMAIN: ${WALLET_BACKEND_AUTH_DOMAIN}
RATE_API_KEY: ${WALLET_BACKEND_RATE_API_KEY}
REDIS_URL: ${WALLET_BACKEND_REDIS_URL}
GATEHUB_ACCESS_KEY: ${WALLET_BACKEND_GATEHUB_ACCESS_KEY}
GATEHUB_SECRET_KEY: ${WALLET_BACKEND_GATEHUB_SECRET_KEY}
Expand Down
23 changes: 16 additions & 7 deletions packages/wallet/backend/src/account/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface IAccountService {
includeWalletKeys?: boolean
) => Promise<Account[]>
getAccountById: (userId: string, accountId: string) => Promise<Account>
getAccountBalance: (userId: string, assetCode: string) => Promise<number>
getAccountBalance: (userId: string, account: Account) => Promise<number>
}

export class AccountService implements IAccountService {
Expand Down Expand Up @@ -77,6 +77,7 @@ export class AccountService implements IAccountService {
gateHubWalletId: result.address
})

// On creation account will have balance 0
account.balance = transformBalance(0, account.assetScale)

return account
Expand Down Expand Up @@ -108,8 +109,9 @@ export class AccountService implements IAccountService {
const accounts = await query

if (!includeWalletAddress) {
accounts.forEach((acc) => {
acc.balance = transformBalance(0, acc.assetScale) // TODO: implement GateHub balance
accounts.forEach(async (acc) => {
const balance = await this.getAccountBalance(userId, acc)
acc.balance = transformBalance(balance, acc.assetScale)
})
}

Expand All @@ -130,7 +132,7 @@ export class AccountService implements IAccountService {
}

account.balance = transformBalance(
await this.getAccountBalance(userId, account.assetCode),
await this.getAccountBalance(userId, account),
account.assetScale
)

Expand All @@ -150,21 +152,28 @@ export class AccountService implements IAccountService {
}

account.balance = transformBalance(
await this.getAccountBalance(userId, account.assetCode),
await this.getAccountBalance(userId, account),
account.assetScale
)

return account
}

async getAccountBalance(userId: string, _assetCode: string): Promise<number> {
async getAccountBalance(userId: string, account: Account): Promise<number> {
const user = await User.query().findById(userId)

if (!user || !user.gateHubUserId) {
throw new NotFound()
}

return 0 // TODO: implement GateHub balance
const balances = await this.gateHubClient.getWalletBalance(
account.gateHubWalletId,
userId
)
return Number(
balances.find((balance) => balance.vault.assetCode === account.assetCode)
?.total ?? 0
)
}

public findAccountById = async (
Expand Down
2 changes: 0 additions & 2 deletions packages/wallet/backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ const envSchema = z.object({
AUTH_DOMAIN: z.string().url().default('http://rafiki-auth:3006'),
AUTH_IDENTITY_SERVER_SECRET: z.string().default('replace-me'),
OPEN_PAYMENTS_HOST: z.string().url().default('https://backend:80'),
RAPYD_SETTLEMENT_EWALLET: z.string().default('default_ewallet'),
RAFIKI_MONEY_FRONTEND_HOST: z.string().default('localhost'),
SENDGRID_API_KEY: z.string().default('SG.API_KEY'),
RATE_API_KEY: z.string().default('SG.API_KEY'),
FROM_EMAIL: z.string().default('[email protected]'),
SEND_EMAIL: z
.enum(['true', 'false'])
Expand Down
83 changes: 77 additions & 6 deletions packages/wallet/backend/src/gatehub/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { createHmac } from 'crypto'
import {
HTTP_METHODS,
IApproveUserToGatewayRequest,
IApproveUserToGatewayResponse,
IConnectUserToGatewayResponse,
ICreateManagedUserRequest,
ICreateManagedUserResponse,
ICreateTransactionRequest,
Expand All @@ -9,8 +12,10 @@ import {
ICreateWalletResponse,
IGetVaultsResponse,
IGetWalletResponse,
IRatesResponse,
ITokenRequest,
ITokenResponse
ITokenResponse,
IWalletBalance
} from '@/gatehub/types'
import { Env } from '@/config/env'
import {
Expand All @@ -19,7 +24,8 @@ import {
ONBOARDING_APP_SCOPE,
PAYMENT_TYPE,
PRODUCTION_CLIENT_IDS,
SANDBOX_CLIENT_IDS
SANDBOX_CLIENT_IDS,
SUPPORTED_ASSET_CODES
} from '@/gatehub/consts'
import axios, { AxiosError } from 'axios'
import { Logger } from 'winston'
Expand All @@ -43,12 +49,16 @@ export class GateHubClient {
private env: Env,
private logger: Logger
) {
if (this.env.NODE_ENV === 'production') {
if (this.isSandbox) {
this.clientIds = PRODUCTION_CLIENT_IDS
this.mainUrl = 'gatehub.net'
}
}

get isSandbox() {
return this.env.NODE_ENV !== 'production'
}

get apiUrl() {
return `https://api.${this.mainUrl}`
}
Expand Down Expand Up @@ -167,11 +177,37 @@ export class GateHubClient {
async connectUserToGateway(
userUuid: string,
gatewayUuid: string
): Promise<ICreateManagedUserResponse> {
): Promise<IConnectUserToGatewayResponse> {
const url = `${this.apiUrl}/id/v1/users/${userUuid}/hubs/${gatewayUuid}`

const response: ICreateManagedUserResponse =
await this.request<ICreateManagedUserResponse>('POST', url)
const response: IConnectUserToGatewayResponse =
await this.request<IConnectUserToGatewayResponse>('POST', url)

if (this.isSandbox) {
// Auto approve user to gateway in sandbox environment
await this.approveUserToGateway(userUuid, gatewayUuid)
}

return response
}

private async approveUserToGateway(
userUuid: string,
gatewayUuid: string
): Promise<IApproveUserToGatewayResponse> {
const url = `${this.apiUrl}/id/v1/hubs/${gatewayUuid}/users/${userUuid}`
const body: IApproveUserToGatewayRequest = {
verified: 1,
reasons: [],
customMessage: false
}

const response: IApproveUserToGatewayResponse =
await this.request<IApproveUserToGatewayResponse>(
'PUT',
url,
JSON.stringify(body)
)

return response
}
Expand Down Expand Up @@ -210,6 +246,22 @@ export class GateHubClient {
return response
}

async getWalletBalance(
userUuid: string,
walletId: string
): Promise<IWalletBalance[]> {
const url = `${this.apiUrl}/core/v1/wallets/${walletId}/balances`

const response: IWalletBalance[] = await this.request<IWalletBalance[]>(
'GET',
url,
undefined,
userUuid
)

return response
}

async createTransaction(
body: ICreateTransactionRequest
): Promise<ICreateTransactionResponse> {
Expand All @@ -234,6 +286,25 @@ export class GateHubClient {
return response
}

async getRates(base: string): Promise<Record<string, number>> {
const url = `${this.apiUrl}/rates/v1/rates/current?counter=${base}&amount=1&useAll=true`

const response: IRatesResponse = await this.request<IRatesResponse>(
'GET',
url
)

const flatRates: Record<string, number> = {}
for (const code of SUPPORTED_ASSET_CODES) {
const rateObj = response[code]
if (rateObj && typeof rateObj !== 'string') {
flatRates[code] = +rateObj.rate
}
}

return flatRates
}

private async request<T>(
method: HTTP_METHODS,
url: string,
Expand Down
2 changes: 2 additions & 0 deletions packages/wallet/backend/src/gatehub/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ export enum PAYMENT_TYPE {

export const HOSTED_WALLET_TYPE = 0
export const HOSTED_TRANSACTION_TYPE = 2

export const SUPPORTED_ASSET_CODES = ['USD', 'EUR']
35 changes: 35 additions & 0 deletions packages/wallet/backend/src/gatehub/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,38 @@ export interface ICreateTransactionRequest {
export interface ICreateTransactionResponse {}

export interface IGetVaultsResponse {}

export interface IRatesResponse {
counter: string
[key: string]: string | IRate
}

interface IRate {
type: string
rate: string | number
amount: string
change: string
}

export interface IWalletBalance {
available: string
pending: string
total: string
vault: IVault
}

interface IVault {
uuid: string
name: string
assetCode: string
createdAt: string
updatedAt: string
}

export interface IConnectUserToGatewayResponse {}
export interface IApproveUserToGatewayRequest {
verified: number
reasons: string[]
customMessage: boolean
}
export interface IApproveUserToGatewayResponse {}
6 changes: 4 additions & 2 deletions packages/wallet/backend/src/quote/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@ export class QuoteService implements IQuoteService {
existingWalletAddress.accountId,
params.userId
)
const balance = await this.accountService.getAccountBalance(

const account = await this.accountService.getAccountById(
params.userId,
assetCode
existingWalletAddress.accountId
)
const balance = account.balance

if (Number(balance) < params.amount) {
throw new BadRequest('Not enough funds in account')
Expand Down
20 changes: 4 additions & 16 deletions packages/wallet/backend/src/rates/service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import axios from 'axios'
import NodeCache from 'node-cache'
import { Env } from '@/config/env'
import { RatesResponse } from '@wallet/shared'
import { GateHubClient } from '@/gatehub/client'

export interface IRatesService {
getRates: (base: string) => Promise<RatesResponse>
Expand All @@ -10,8 +9,8 @@ export interface IRatesService {
export class RatesService implements IRatesService {
cache: NodeCache

constructor(private env: Env) {
this.cache = new NodeCache({ stdTTL: 60 * 60 * 12 })
constructor(private gateHubClient: GateHubClient) {
this.cache = new NodeCache({ stdTTL: 60 })
}

public async getRates(base: string): Promise<RatesResponse> {
Expand All @@ -22,23 +21,12 @@ export class RatesService implements IRatesService {
}
}

const result = await this.getApiRates(base)
const result = await this.gateHubClient.getRates(base)
this.cache.set(base, result)

return {
base,
rates: result ? result : {}
}
}

private async getApiRates(base: string): Promise<Record<string, number>> {
const response = await axios.get(
'https://api.freecurrencyapi.com/v1/latest',
{
params: { apikey: this.env.RATE_API_KEY, base_currency: base }
}
)

return response.data.data
}
}
9 changes: 8 additions & 1 deletion packages/wallet/backend/src/user/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getRandomToken, hashToken } from '@/utils/helpers'
import { Logger } from 'winston'
import { BadRequest, Conflict } from '@shared/backend'
import { GateHubClient } from '@/gatehub/client'
import { Env } from '@/config/env'

interface CreateUserArgs {
email: string
Expand All @@ -30,7 +31,8 @@ export class UserService implements IUserService {
constructor(
private emailService: EmailService,
private gateHubClient: GateHubClient,
private logger: Logger
private logger: Logger,
private env: Env
) {}

public async create(args: CreateUserArgs): Promise<User> {
Expand Down Expand Up @@ -131,6 +133,11 @@ export class UserService implements IUserService {
verifyEmailToken: null,
gateHubUserId: gateHubUser.id
})

await this.gateHubClient.connectUserToGateway(
gateHubUser.id,
this.env.GATEHUB_GATEWAY_UUID
)
}

public async resetVerifyEmailToken(args: VerifyEmailArgs): Promise<void> {
Expand Down

0 comments on commit ca6b363

Please sign in to comment.