diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index f099c4863..95d5ee299 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -90,7 +90,7 @@ services: # Rafiki rafiki-auth: container_name: rafiki-auth - image: ghcr.io/interledger/rafiki-auth:v1.0.0-alpha.5 + image: ghcr.io/interledger/rafiki-auth:v1.0.0-alpha.6 restart: always networks: - testnet @@ -98,7 +98,7 @@ services: - '3006:3006' - '3008:3008' environment: - PORT: 3006 + AUTH_PORT: 3006 INTROSPECTION_PORT: 3007 ADMIN_PORT: 3008 NODE_ENV: development @@ -106,13 +106,14 @@ services: AUTH_DATABASE_URL: postgresql://rafiki_auth:rafiki_auth@postgres/rafiki_auth IDENTITY_SERVER_DOMAIN: http://localhost:4003/grant-interactions IDENTITY_SERVER_SECRET: ${AUTH_IDENTITY_SERVER_SECRET} + WAIT_SECONDS: 1 depends_on: - postgres <<: *logging rafiki-backend: container_name: rafiki-backend - image: ghcr.io/interledger/rafiki-backend:v1.0.0-alpha.5 + image: ghcr.io/interledger/rafiki-backend:v1.0.0-alpha.6 restart: always privileged: true volumes: @@ -140,7 +141,6 @@ services: ILP_ADDRESS: test.net STREAM_SECRET: BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU= ADMIN_KEY: admin - PUBLIC_HOST: http://rafiki-backend OPEN_PAYMENTS_URL: http://rafiki-backend REDIS_URL: redis://redis:6379/0 WALLET_ADDRESS_URL: https://rafiki-backend/.well-known/pay @@ -159,7 +159,7 @@ services: rafiki-frontend: container_name: rafiki-frontend - image: ghcr.io/interledger/rafiki-frontend:v1.0.0-alpha.5 + image: ghcr.io/interledger/rafiki-frontend:v1.0.0-alpha.6 depends_on: - rafiki-backend restart: always @@ -175,7 +175,7 @@ services: <<: *logging tigerbeetle: - image: ghcr.io/tigerbeetle/tigerbeetle:0.13.57 + image: ghcr.io/tigerbeetle/tigerbeetle:0.14.176 privileged: true volumes: - tigerbeetle-data:/var/lib/tigerbeetle diff --git a/docker/prod/.env.example b/docker/prod/.env.example index d6467abd0..2598b7505 100644 --- a/docker/prod/.env.example +++ b/docker/prod/.env.example @@ -55,6 +55,7 @@ RAFIKI_AUTH_DATABASE_URL= RAFIKI_AUTH_IDENTITY_SERVER_DOMAIN= RAFIKI_AUTH_IDENTITY_SERVER_SECRET= RAFIKI_AUTH_SERVER_DOMAIN= +RAFIKI_AUTH_WAIT_SECONDS= # RAFIKI BACKEND RAFIKI_BACKEND_LOG_LEVEL= @@ -71,7 +72,6 @@ RAFIKI_BACKEND_AUTH_SERVER_INTROSPECTION_URL= RAFIKI_BACKEND_ILP_ADDRESS= RAFIKI_BACKEND_STREAM_SECRET= RAFIKI_BACKEND_ADMIN_KEY= -RAFIKI_BACKEND_PUBLIC_HOST= RAFIKI_BACKEND_OPEN_PAYMENTS_URL= RAFIKI_BACKEND_REDIS_URL= RAFIKI_BACKEND_WALLET_ADDRESS_URL= diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 50f7fb490..2026fe9d7 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -119,17 +119,18 @@ services: <<: *logging rafiki-auth: - image: ghcr.io/interledger/rafiki-auth:v1.0.0-alpha.5 + image: ghcr.io/interledger/rafiki-auth:v1.0.0-alpha.6 container_name: rafiki-auth environment: NODE_ENV: ${NODE_ENV} - PORT: ${RAFIKI_AUTH_PORT} + AUTH_PORT: ${RAFIKI_AUTH_PORT} ADMIN_PORT: ${RAFIKI_AUTH_ADMIN_PORT} INTROSPECTION_PORT: ${RAFIKI_AUTH_INTROSPECTION_PORT} AUTH_DATABASE_URL: ${RAFIKI_AUTH_DATABASE_URL} IDENTITY_SERVER_SECRET: ${RAFIKI_AUTH_IDENTITY_SERVER_SECRET} IDENTITY_SERVER_DOMAIN: ${RAFIKI_AUTH_IDENTITY_SERVER_DOMAIN} AUTH_SERVER_DOMAIN: ${RAFIKI_AUTH_SERVER_DOMAIN} + WAIT_SECONDS: ${RAFIKI_AUTH_WAIT_SECONDS} TRUST_PROXY: true depends_on: - postgres @@ -142,7 +143,7 @@ services: <<: *logging rafiki-backend: - image: ghcr.io/interledger/rafiki-backend:v1.0.0-alpha.5 + image: ghcr.io/interledger/rafiki-backend:v1.0.0-alpha.6 container_name: rafiki-backend depends_on: - postgres @@ -163,7 +164,6 @@ services: ILP_ADDRESS: ${RAFIKI_BACKEND_ILP_ADDRESS} STREAM_SECRET: ${RAFIKI_BACKEND_STREAM_SECRET} ADMIN_KEY: ${RAFIKI_BACKEND_ADMIN_KEY} - PUBLIC_HOST: ${RAFIKI_BACKEND_PUBLIC_HOST} OPEN_PAYMENTS_URL: ${RAFIKI_BACKEND_OPEN_PAYMENTS_URL} REDIS_URL: ${RAFIKI_BACKEND_REDIS_URL} WALLET_ADDRESS_URL: ${RAFIKI_BACKEND_WALLET_ADDRESS_URL} @@ -190,7 +190,7 @@ services: <<: *logging rafiki-frontend: - image: ghcr.io/interledger/rafiki-frontend:v1.0.0-alpha.5 + image: ghcr.io/interledger/rafiki-frontend:v1.0.0-alpha.6 container_name: rafiki-frontend depends_on: - rafiki-backend @@ -207,7 +207,7 @@ services: <<: *logging tigerbeetle: - image: ghcr.io/tigerbeetle/tigerbeetle:0.13.57 + image: ghcr.io/tigerbeetle/tigerbeetle:0.14.176 privileged: true volumes: - tigerbeetle-data:/var/lib/tigerbeetle diff --git a/packages/boutique/backend/package.json b/packages/boutique/backend/package.json index 907088ce8..a5ce332cc 100644 --- a/packages/boutique/backend/package.json +++ b/packages/boutique/backend/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "@google-cloud/logging-winston": "^6.0.0", - "@interledger/open-payments": "^6.1.1", + "@interledger/open-payments": "^6.6.0", "awilix": "^10.0.1", "axios": "^1.6.7", "cors": "^2.8.5", diff --git a/packages/boutique/backend/src/open-payments/service.ts b/packages/boutique/backend/src/open-payments/service.ts index f29ebc56b..dac3676a5 100644 --- a/packages/boutique/backend/src/open-payments/service.ts +++ b/packages/boutique/backend/src/open-payments/service.ts @@ -11,7 +11,8 @@ import { WalletAddress, PendingGrant, Quote, - isPendingGrant + isPendingGrant, + GrantContinuation } from '@interledger/open-payments' import { randomUUID } from 'crypto' import { Logger } from 'winston' @@ -309,6 +310,12 @@ export class OpenPayments implements IOpenPayments { throw new InternalServerError() }) + if (!this.isGrant(continuation)) { + this.logger.error('Expected grant response.') + this.logger.debug(continuation) + throw new InternalServerError() + } + return { accessToken: continuation.access_token.value, manageUrl: continuation.access_token.manage.replace( @@ -318,6 +325,12 @@ export class OpenPayments implements IOpenPayments { } } + private isGrant( + continuation: GrantContinuation | Grant + ): continuation is Grant { + return (continuation as Grant).access_token !== undefined + } + public async instantBuy( params: InstantBuyParams ): Promise { diff --git a/packages/wallet/backend/src/incomingPayment/service.ts b/packages/wallet/backend/src/incomingPayment/service.ts index 8d60f6489..4dd1bc099 100644 --- a/packages/wallet/backend/src/incomingPayment/service.ts +++ b/packages/wallet/backend/src/incomingPayment/service.ts @@ -3,11 +3,9 @@ import { NotFound } from '@/errors' import { PaymentDetails } from '@/incomingPayment/controller' import { WalletAddress } from '@/walletAddress/model' import { RafikiClient } from '@/rafiki/rafiki-client' -import { Transaction } from '@/transaction/model' -import { extractUuidFromUrl, transformAmount } from '@/utils/helpers' +import { transformAmount } from '@/utils/helpers' import { Amount, Asset } from '@/rafiki/backend/generated/graphql' import { add, Duration } from 'date-fns' -import { Logger } from 'winston' import axios from 'axios' import { Env } from '@/config/env' @@ -50,7 +48,6 @@ export class IncomingPaymentService implements IIncomingPaymentService { constructor( private accountService: AccountService, private rafikiClient: RafikiClient, - private logger: Logger, private env: Env ) {} @@ -96,49 +93,24 @@ export class IncomingPaymentService implements IIncomingPaymentService { return response.id } - // Instead of querying our Transaction model, should we fetch this information from Rafiki? - // Reasoning: - // - An incoming payment can be fulfilled by multiple outgoing payments; - // - If we have an incoming payment that awaits $10 and we send $5 initially, - // inserting the IP URL in the receiver field again, currently shows that the incoming amount - // is still $10. - // - By fetching the IP details from Rafiki, we can calculate how much more is needed - // to fulfill this specific IP (after making an initial outgoing payment of $5). async getPaymentDetailsByUrl(url: string): Promise { - const id = extractUuidFromUrl(url) - - const transaction = await Transaction.query() - .where('paymentId', id) - .where('status', 'PENDING') - .first() - .withGraphFetched({ walletAddress: { account: true } }) - - if (!transaction) { - throw new NotFound( - 'The provided incoming payment URL could not be found.' - ) + const receiver = await this.rafikiClient.getReceiverById(url) + const asset = { + scale: + receiver.incomingAmount?.assetScale ?? + receiver.receivedAmount.assetScale, + code: + receiver.incomingAmount?.assetCode ?? receiver.receivedAmount.assetCode } - const asset = await this.rafikiClient.getAssetById( - transaction.walletAddress?.account.assetId - ) - if (!asset) { - throw new NotFound() - } + const value = receiver.incomingAmount?.value + ? receiver.incomingAmount.value - receiver.receivedAmount.value + : 0n return { - description: transaction.description, - value: parseFloat(transformAmount(transaction.value ?? 0n, asset.scale)), - assetCode: transaction.assetCode - } - } - - public async getReceiver(receiver: string) { - try { - // @TODO: replace with get receiver from rafiki when implemented - return await this.getPaymentDetailsByUrl(receiver) - } catch (_e) { - this.logger.info(`Could not find transaction for ${receiver}`) + description: receiver.metadata.description, + value: parseFloat(transformAmount(value, asset.scale)), + assetCode: asset.code } } diff --git a/packages/wallet/backend/src/outgoingPayment/service.ts b/packages/wallet/backend/src/outgoingPayment/service.ts index 8c6c6d124..432bf342b 100644 --- a/packages/wallet/backend/src/outgoingPayment/service.ts +++ b/packages/wallet/backend/src/outgoingPayment/service.ts @@ -15,9 +15,8 @@ export class OutgoingPaymentService implements IOutgoingPaymentService { const quote = await this.rafikiClient.getQuote(quoteId) const walletAddressId = quote.walletAddressId - const incomingPayment = await this.incomingPaymentService.getReceiver( - quote.receiver - ) + const incomingPayment = + await this.incomingPaymentService.getPaymentDetailsByUrl(quote.receiver) const description = incomingPayment?.description await this.rafikiClient.createOutgoingPayment({ diff --git a/packages/wallet/backend/src/quote/service.ts b/packages/wallet/backend/src/quote/service.ts index 00b6f2983..20774d5d2 100644 --- a/packages/wallet/backend/src/quote/service.ts +++ b/packages/wallet/backend/src/quote/service.ts @@ -125,9 +125,10 @@ export class QuoteService implements IQuoteService { amount: value }) if (isIncomingPayment) { - const payment = await this.incomingPaymentService.getReceiver( - params.receiver - ) + const payment = + await this.incomingPaymentService.getPaymentDetailsByUrl( + params.receiver + ) const amount = payment?.value ? transformBalance(payment?.value, assetDetails.assetScale) diff --git a/packages/wallet/backend/src/rafiki/auth/generated/graphql.ts b/packages/wallet/backend/src/rafiki/auth/generated/graphql.ts index f98fae727..889221277 100644 --- a/packages/wallet/backend/src/rafiki/auth/generated/graphql.ts +++ b/packages/wallet/backend/src/rafiki/auth/generated/graphql.ts @@ -175,6 +175,7 @@ export type QueryGrantsArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + sortOrder?: InputMaybe; }; export type RevokeGrantInput = { @@ -188,6 +189,13 @@ export type RevokeGrantMutationResponse = MutationResponse & { success: Scalars['Boolean']['output']; }; +export enum SortOrder { + /** Choose ascending order for results. */ + Asc = 'ASC', + /** Choose descending order for results. */ + Desc = 'DESC' +} + export type GetGrantsQueryVariables = Exact<{ after?: InputMaybe; before?: InputMaybe; diff --git a/packages/wallet/backend/src/rafiki/backend/generated/graphql.ts b/packages/wallet/backend/src/rafiki/backend/generated/graphql.ts index c37634e4f..b0f26c4e4 100644 --- a/packages/wallet/backend/src/rafiki/backend/generated/graphql.ts +++ b/packages/wallet/backend/src/rafiki/backend/generated/graphql.ts @@ -17,28 +17,6 @@ export type Scalars = { UInt8: { input: number; output: number; } }; -export type AddAssetLiquidityInput = { - /** Amount of liquidity to add. */ - amount: Scalars['BigInt']['input']; - /** The id of the asset to add liquidity. */ - assetId: Scalars['String']['input']; - /** The id of the transfer. */ - id: Scalars['String']['input']; - /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ - idempotencyKey: Scalars['String']['input']; -}; - -export type AddPeerLiquidityInput = { - /** Amount of liquidity to add. */ - amount: Scalars['BigInt']['input']; - /** The id of the transfer. */ - id: Scalars['String']['input']; - /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ - idempotencyKey: Scalars['String']['input']; - /** The id of the peer to add liquidity. */ - peerId: Scalars['String']['input']; -}; - export enum Alg { EdDsa = 'EdDSA' } @@ -158,14 +136,14 @@ export type CreateIncomingPaymentInput = { }; export type CreateOrUpdatePeerByUrlInput = { - /** Initial amount of liquidity to add for peer */ - addedLiquidity?: InputMaybe; /** Asset id of peering relationship */ assetId: Scalars['String']['input']; /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ idempotencyKey?: InputMaybe; /** Account Servicing Entity will be notified via a webhook event if peer liquidity falls below this value */ liquidityThreshold?: InputMaybe; + /** Amount of liquidity to deposit for peer */ + liquidityToDeposit?: InputMaybe; /** Maximum packet amount that the peer accepts */ maxPacketAmount?: InputMaybe; /** Peer's internal name for overriding auto-peer's default naming */ @@ -200,7 +178,7 @@ export type CreatePeerInput = { http: HttpInput; /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ idempotencyKey?: InputMaybe; - /** Initial amount of liquidity to add for peer */ + /** Initial amount of liquidity to deposit for peer */ initialLiquidity?: InputMaybe; /** Account Servicing Entity will be notified via a webhook event if peer liquidity falls below this value */ liquidityThreshold?: InputMaybe; @@ -326,6 +304,17 @@ export type DeletePeerMutationResponse = MutationResponse & { success: Scalars['Boolean']['output']; }; +export type DepositAssetLiquidityInput = { + /** Amount of liquidity to deposit. */ + amount: Scalars['BigInt']['input']; + /** The id of the asset to deposit liquidity. */ + assetId: Scalars['String']['input']; + /** The id of the transfer. */ + id: Scalars['String']['input']; + /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ + idempotencyKey: Scalars['String']['input']; +}; + export type DepositEventLiquidityInput = { /** The id of the event to deposit into. */ eventId: Scalars['String']['input']; @@ -333,6 +322,24 @@ export type DepositEventLiquidityInput = { idempotencyKey: Scalars['String']['input']; }; +export type DepositOutgoingPaymentLiquidityInput = { + /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ + idempotencyKey: Scalars['String']['input']; + /** The id of the outgoing payment to deposit into. */ + outgoingPaymentId: Scalars['String']['input']; +}; + +export type DepositPeerLiquidityInput = { + /** Amount of liquidity to deposit. */ + amount: Scalars['BigInt']['input']; + /** The id of the transfer. */ + id: Scalars['String']['input']; + /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ + idempotencyKey: Scalars['String']['input']; + /** The id of the peer to deposit liquidity. */ + peerId: Scalars['String']['input']; +}; + export type Fee = Model & { __typename?: 'Fee'; /** Asset id associated with the fee */ @@ -422,6 +429,8 @@ export type IncomingPayment = BasePayment & Model & { id: Scalars['ID']['output']; /** The maximum amount that should be paid into the wallet address under this incoming payment. */ incomingAmount?: Maybe; + /** Available liquidity */ + liquidity?: Maybe; /** Additional metadata associated with the incoming payment. */ metadata?: Maybe; /** The total amount that has been paid into the wallet address under this incoming payment. */ @@ -524,10 +533,6 @@ export type Model = { export type Mutation = { __typename?: 'Mutation'; - /** Add asset liquidity */ - addAssetLiquidity?: Maybe; - /** Add peer liquidity */ - addPeerLiquidity?: Maybe; /** Create an asset */ createAsset: AssetMutationResponse; /** Withdraw asset liquidity */ @@ -554,8 +559,17 @@ export type Mutation = { createWalletAddressWithdrawal?: Maybe; /** Delete a peer */ deletePeer: DeletePeerMutationResponse; - /** Deposit webhook event liquidity */ + /** Deposit asset liquidity */ + depositAssetLiquidity?: Maybe; + /** + * Deposit webhook event liquidity + * @deprecated Use `depositOutgoingPaymentLiquidity` + */ depositEventLiquidity?: Maybe; + /** Deposit outgoing payment liquidity */ + depositOutgoingPaymentLiquidity?: Maybe; + /** Deposit peer liquidity */ + depositPeerLiquidity?: Maybe; /** Post liquidity withdrawal. Withdrawals are two-phase commits and are committed via this mutation. */ postLiquidityWithdrawal?: Maybe; /** Revoke a public key associated with a wallet address. Open Payment requests using this key for request signatures will be denied going forward. */ @@ -572,18 +586,15 @@ export type Mutation = { updateWalletAddress: UpdateWalletAddressMutationResponse; /** Void liquidity withdrawal. Withdrawals are two-phase commits and are rolled back via this mutation. */ voidLiquidityWithdrawal?: Maybe; - /** Withdraw webhook event liquidity */ + /** + * Withdraw webhook event liquidity + * @deprecated Use `withdrawOutgoingPaymentLiquidity, withdrawIncomingPaymentLiquidity, or createWalletAddressWithdrawal` + */ withdrawEventLiquidity?: Maybe; -}; - - -export type MutationAddAssetLiquidityArgs = { - input: AddAssetLiquidityInput; -}; - - -export type MutationAddPeerLiquidityArgs = { - input: AddPeerLiquidityInput; + /** Withdraw incoming payment liquidity */ + withdrawIncomingPaymentLiquidity?: Maybe; + /** Withdraw outgoing payment liquidity */ + withdrawOutgoingPaymentLiquidity?: Maybe; }; @@ -652,11 +663,26 @@ export type MutationDeletePeerArgs = { }; +export type MutationDepositAssetLiquidityArgs = { + input: DepositAssetLiquidityInput; +}; + + export type MutationDepositEventLiquidityArgs = { input: DepositEventLiquidityInput; }; +export type MutationDepositOutgoingPaymentLiquidityArgs = { + input: DepositOutgoingPaymentLiquidityInput; +}; + + +export type MutationDepositPeerLiquidityArgs = { + input: DepositPeerLiquidityInput; +}; + + export type MutationPostLiquidityWithdrawalArgs = { input: PostLiquidityWithdrawalInput; }; @@ -701,6 +727,16 @@ export type MutationWithdrawEventLiquidityArgs = { input: WithdrawEventLiquidityInput; }; + +export type MutationWithdrawIncomingPaymentLiquidityArgs = { + input: WithdrawIncomingPaymentLiquidityInput; +}; + + +export type MutationWithdrawOutgoingPaymentLiquidityArgs = { + input: WithdrawOutgoingPaymentLiquidityInput; +}; + export type MutationResponse = { code: Scalars['String']['output']; message: Scalars['String']['output']; @@ -716,6 +752,8 @@ export type OutgoingPayment = BasePayment & Model & { error?: Maybe; /** Outgoing payment id */ id: Scalars['ID']['output']; + /** Available liquidity */ + liquidity?: Maybe; /** Additional metadata associated with the outgoing payment. */ metadata?: Maybe; /** Quote for this outgoing payment */ @@ -782,6 +820,8 @@ export type Payment = BasePayment & Model & { createdAt: Scalars['String']['output']; /** Payment id */ id: Scalars['ID']['output']; + /** Available liquidity */ + liquidity?: Maybe; /** Additional metadata associated with the payment. */ metadata?: Maybe; /** Either the IncomingPaymentState or OutgoingPaymentState according to type */ @@ -873,6 +913,8 @@ export type Query = { peers: PeersConnection; /** Fetch an Open Payments quote */ quote?: Maybe; + /** Get an local or remote Open Payments Incoming Payment. The receiver has a wallet address on either this or another Open Payments resource server. */ + receiver?: Maybe; /** Fetch a wallet address */ walletAddress?: Maybe; /** Fetch a page of wallet addresses. */ @@ -935,6 +977,11 @@ export type QueryQuoteArgs = { }; +export type QueryReceiverArgs = { + id: Scalars['String']['input']; +}; + + export type QueryWalletAddressArgs = { id: Scalars['String']['input']; }; @@ -1162,6 +1209,8 @@ export type WalletAddress = Model & { id: Scalars['ID']['output']; /** List of incoming payments received by this wallet address */ incomingPayments?: Maybe; + /** Available liquidity */ + liquidity?: Maybe; /** List of outgoing payments sent from this wallet address */ outgoingPayments?: Maybe; /** Public name associated with the wallet address */ @@ -1288,6 +1337,20 @@ export type WithdrawEventLiquidityInput = { idempotencyKey: Scalars['String']['input']; }; +export type WithdrawIncomingPaymentLiquidityInput = { + /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ + idempotencyKey: Scalars['String']['input']; + /** The id of the incoming payment to withdraw from. */ + incomingPaymentId: Scalars['String']['input']; +}; + +export type WithdrawOutgoingPaymentLiquidityInput = { + /** Unique key to ensure duplicate or retried requests are processed only once. See [idempotence](https://en.wikipedia.org/wiki/Idempotence) */ + idempotencyKey: Scalars['String']['input']; + /** The id of the outgoing payment to withdraw from. */ + outgoingPaymentId: Scalars['String']['input']; +}; + export type CreateAssetMutationVariables = Exact<{ input: CreateAssetInput; }>; @@ -1363,6 +1426,13 @@ export type CreateReceiverMutationVariables = Exact<{ export type CreateReceiverMutation = { __typename?: 'Mutation', createReceiver: { __typename?: 'CreateReceiverResponse', code: string, message?: string | null, success: boolean, receiver?: { __typename?: 'Receiver', createdAt: string, metadata?: any | null, expiresAt?: string | null, id: string, walletAddressUrl: string, incomingAmount?: { __typename?: 'Amount', assetCode: string, assetScale: number, value: bigint } | null, receivedAmount: { __typename?: 'Amount', assetCode: string, assetScale: number, value: bigint } } | null } }; +export type GetReceiverQueryVariables = Exact<{ + id: Scalars['String']['input']; +}>; + + +export type GetReceiverQuery = { __typename?: 'Query', receiver?: { __typename?: 'Receiver', completed: boolean, createdAt: string, expiresAt?: string | null, metadata?: any | null, id: string, walletAddressUrl: string, updatedAt: string, incomingAmount?: { __typename?: 'Amount', assetCode: string, assetScale: number, value: bigint } | null, receivedAmount: { __typename?: 'Amount', assetCode: string, assetScale: number, value: bigint } } | null }; + export type CreateWalletAddressKeyMutationVariables = Exact<{ input: CreateWalletAddressKeyInput; }>; diff --git a/packages/wallet/backend/src/rafiki/backend/request/receiver.request.ts b/packages/wallet/backend/src/rafiki/backend/request/receiver.request.ts index ee5279c7e..5f7cba664 100644 --- a/packages/wallet/backend/src/rafiki/backend/request/receiver.request.ts +++ b/packages/wallet/backend/src/rafiki/backend/request/receiver.request.ts @@ -26,3 +26,27 @@ export const createReceiverMutation = gql` } } ` + +export const getReceiverQuery = gql` + query GetReceiverQuery($id: String!) { + receiver(id: $id) { + completed + createdAt + expiresAt + metadata + id + incomingAmount { + assetCode + assetScale + value + } + walletAddressUrl + receivedAmount { + assetCode + assetScale + value + } + updatedAt + } + } +` diff --git a/packages/wallet/backend/src/rafiki/rafiki-client.ts b/packages/wallet/backend/src/rafiki/rafiki-client.ts index 9b381ed75..3fdedc844 100644 --- a/packages/wallet/backend/src/rafiki/rafiki-client.ts +++ b/packages/wallet/backend/src/rafiki/rafiki-client.ts @@ -42,7 +42,9 @@ import { UpdateWalletAddressMutation, UpdateWalletAddressMutationVariables, WithdrawLiquidityMutation, - WithdrawLiquidityMutationVariables + WithdrawLiquidityMutationVariables, + GetReceiverQuery, + GetReceiverQueryVariables } from './backend/generated/graphql' import { createAssetMutation, @@ -67,7 +69,10 @@ import { createQuoteMutation, getQuoteQuery } from './backend/request/quote.request' -import { createReceiverMutation } from '@/rafiki/backend/request/receiver.request' +import { + createReceiverMutation, + getReceiverQuery +} from '@/rafiki/backend/request/receiver.request' interface IRafikiClient { createAsset(code: string, scale: number): Promise @@ -194,6 +199,15 @@ export class RafikiClient implements IRafikiClient { return paymentResponse.receiver as Receiver } + public async getReceiverById(id: string): Promise { + const response = await this.gqlClient.request< + GetReceiverQuery, + GetReceiverQueryVariables + >(getReceiverQuery, { id }) + + return response.receiver as Receiver + } + public async withdrawLiqudity(eventId: string) { const response = await this.gqlClient.request< WithdrawLiquidityMutation, diff --git a/packages/wallet/backend/tests/incomingPayment/service.test.ts b/packages/wallet/backend/tests/incomingPayment/service.test.ts index ff12ce339..2d8141e6f 100644 --- a/packages/wallet/backend/tests/incomingPayment/service.test.ts +++ b/packages/wallet/backend/tests/incomingPayment/service.test.ts @@ -8,16 +8,12 @@ import { loginUser } from '@/tests/utils' import { truncateTables } from '@/tests/tables' import { Account } from '@/account/model' import { faker } from '@faker-js/faker' -import { - generateMockedTransaction, - mockedListAssets, - mockExternalPayment -} from '@/tests/mocks' +import { mockedListAssets, mockExternalPayment } from '@/tests/mocks' import { WalletAddress } from '@/walletAddress/model' import { env } from '@/config/env' import { NotFound } from '@/errors' -import { Transaction } from '@/transaction/model' import axios from 'axios' +import { Receiver } from '@/rafiki/backend/generated/graphql' describe('Incoming Payment Service', () => { let bindings: AwilixContainer @@ -62,6 +58,26 @@ describe('Incoming Payment Service', () => { id: receiverID || faker.string.uuid(), walletAddressUrl: faker.internet.url(), completed: true + }), + getReceiverById: (): Receiver => ({ + id: receiverID || faker.string.uuid(), + walletAddressUrl: faker.internet.url(), + completed: true, + incomingAmount: { + value: 1000n, + assetCode: 'USD', + assetScale: 2 + }, + receivedAmount: { + value: 500n, + assetCode: 'USD', + assetScale: 2 + }, + createdAt: faker.date.recent().toISOString(), + updatedAt: faker.date.recent().toISOString(), + metadata: { + description: 'Fake receiver' + } }) } } @@ -128,40 +144,15 @@ describe('Incoming Payment Service', () => { describe('Get PaymentDetails By Url', () => { it('should get payment details successfully', async () => { - const { walletAddress, account } = - await prepareIncomePaymentDependencies() const paymentId = faker.string.uuid() - const transaction = await Transaction.query().insert( - generateMockedTransaction({ - walletAddressId: walletAddress.id, - accountId: account.id, - paymentId - }) - ) const url = `${faker.internet.url()}/${paymentId}` const result = await incopmPaymentService.getPaymentDetailsByUrl(url) expect(result).toMatchObject({ - description: transaction.description, - assetCode: transaction.assetCode + description: 'Fake receiver', + assetCode: 'USD', + value: 5 }) }) - - it('should throw error with wrong payment id', async () => { - const { walletAddress, account } = - await prepareIncomePaymentDependencies() - await Transaction.query().insert( - generateMockedTransaction({ - walletAddressId: walletAddress.id, - accountId: account.id - }) - ) - const url = `${faker.internet.url()}/${faker.string.uuid()}` - await expect( - incopmPaymentService.getPaymentDetailsByUrl(url) - ).rejects.toThrowError( - /The provided incoming payment URL could not be found./ - ) - }) }) describe('getExternalPayment', () => { beforeAll(async (): Promise => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dcadf21c6..5b9cd4098 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: ^6.0.0 version: 6.0.0(winston@3.11.0) '@interledger/open-payments': - specifier: ^6.1.1 - version: 6.1.1 + specifier: ^6.6.0 + version: 6.7.0 awilix: specifier: ^10.0.1 version: 10.0.1 @@ -101,7 +101,7 @@ importers: version: 1.14.1 ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3) + version: 29.1.2(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.3.3) tsc-alias: specifier: ^1.8.8 version: 1.8.8 @@ -2683,19 +2683,19 @@ packages: dev: false optional: true - /@interledger/http-signature-utils@2.0.0: - resolution: {integrity: sha512-zJTuTzkb9Y8ZJe8L0KcYnRtha8KyrYVpdM0FLL3HEOscWXRNrYC75IkJLzeTZnjzog77q9DT9PmxLitHuPl8iQ==} + /@interledger/http-signature-utils@2.0.2: + resolution: {integrity: sha512-gTAPFMt7xwG1zv1rbrltcZTAqOw/ZJwkVdnNjQzu5G9JTfvJQzS6pcpiVUej1cblpewotbOzosgdX00f93q9zA==} dependencies: - http-message-signatures: 0.1.2 + http-message-signatures: 1.0.4 httpbis-digest-headers: 1.0.0 jose: 4.15.1 uuid: 9.0.1 dev: false - /@interledger/open-payments@6.1.1: - resolution: {integrity: sha512-eS2zEGCuqb+WMAOzFvjCeKSRhpsIYQpFSslu4ET+jpleDn8NZJCwhW0q16f43UJJNargLeSGIg9CsKRcYRceZA==} + /@interledger/open-payments@6.7.0: + resolution: {integrity: sha512-ibIpNHNZknhYWCy/SqUNWybwDkkhaM/H9lIXruEKsBogkh5l/ouXw78DrOYuzTTIb+3T4x6LlTJk/PGBht8S8Q==} dependencies: - '@interledger/http-signature-utils': 2.0.0 + '@interledger/http-signature-utils': 2.0.2 '@interledger/openapi': 1.2.1 axios: 1.6.7 base64url: 3.0.1 @@ -7271,6 +7271,12 @@ packages: resolution: {integrity: sha512-gjJYDgFBy+xnlAs2G0gIWpiorCv9Xi7pIlOnnd91zHAK7BtkLxonmm/JAtd5e6CakOuW03IwEuJzj2YMy8lfWQ==} dev: false + /http-message-signatures@1.0.4: + resolution: {integrity: sha512-gavCQWnxHFg0BVlKs6CmYK7hNSH1o0x0mHTC68yBAHYOYuTVXPv52mEE7QuT5TenfiagTdOa/zPJzen4lEX7Rg==} + dependencies: + structured-headers: 1.0.1 + dev: false + /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} @@ -10649,6 +10655,11 @@ packages: resolution: {integrity: sha512-oLnmXSsjhud+LxRJpvokwP8ImEB2wTg8sg30buwfVViKMuluTv3BlOJHUX9VW9pJ2nQOxmx87Z0kB86O4cphag==} dev: false + /structured-headers@1.0.1: + resolution: {integrity: sha512-QYBxdBtA4Tl5rFPuqmbmdrS9kbtren74RTJTcs0VSQNVV5iRhJD4QlYTLD0+81SBwUQctjEQzjTRI3WG4DzICA==} + engines: {node: '>= 14', npm: '>=6'} + dev: false + /stubs@3.0.0: resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} dev: false @@ -10967,6 +10978,40 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + /ts-jest@29.1.2(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.3.3): + resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.0 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@18.18.3) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.3.3 + yargs-parser: 21.1.1 + dev: true + /ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0}