From 0801ed4533a36975b2677542eeb6a0aad33f0e1b Mon Sep 17 00:00:00 2001 From: Oana Lolea Date: Tue, 29 Oct 2024 12:52:23 +0200 Subject: [PATCH 1/4] Added checks if assets, peers, and wallet addresses exist before creating them --- .../generated/graphql.ts | 26 ++++ packages/auth/src/graphql/schema.graphql | 4 +- packages/backend/src/asset/service.ts | 13 ++ .../src/graphql/generated/graphql.schema.json | 119 ++++++++++++++++++ .../backend/src/graphql/generated/graphql.ts | 26 ++++ .../src/graphql/resolvers/asset.test.ts | 64 ++++++++++ .../backend/src/graphql/resolvers/asset.ts | 7 ++ .../backend/src/graphql/resolvers/index.ts | 18 ++- .../src/graphql/resolvers/peer.test.ts | 99 +++++++++++++++ .../backend/src/graphql/resolvers/peer.ts | 10 ++ .../graphql/resolvers/wallet_address.test.ts | 83 ++++++++++++ .../src/graphql/resolvers/wallet_address.ts | 11 ++ packages/backend/src/graphql/schema.graphql | 19 +++ packages/frontend/app/generated/graphql.ts | 26 ++++ .../src/generated/graphql.ts | 26 ++++ .../src/requesters.ts | 116 +++++++++++++++++ test/integration/lib/generated/graphql.ts | 26 ++++ 17 files changed, 688 insertions(+), 5 deletions(-) diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 131de7eb44..9437d6ec24 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -1124,6 +1124,8 @@ export type Query = { accountingTransfers: AccountingTransferConnection; /** Fetch an asset by its ID. */ asset?: Maybe; + /** Get an asset based on its currency code and scale if it exists. */ + assetByCodeAndScale?: Maybe; /** Fetch a paginated list of assets. */ assets: AssetsConnection; /** Fetch an Open Payments incoming payment by its ID. */ @@ -1136,6 +1138,8 @@ export type Query = { payments: PaymentConnection; /** Fetch a peer by its ID. */ peer?: Maybe; + /** Get a peer based on its ILP address and asset ID if it exists. */ + peerByAddressAndAsset?: Maybe; /** Fetch a paginated list of peers. */ peers: PeersConnection; /** Fetch an Open Payments quote by its ID. */ @@ -1144,6 +1148,8 @@ export type Query = { receiver?: Maybe; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; + /** Get a wallet address by its url if it exists */ + walletAddressByUrl?: Maybe; /** Fetch a paginated list of wallet addresses. */ walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ @@ -1162,6 +1168,12 @@ export type QueryAssetArgs = { }; +export type QueryAssetByCodeAndScaleArgs = { + code: Scalars['String']['input']; + scale: Scalars['UInt8']['input']; +}; + + export type QueryAssetsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1206,6 +1218,12 @@ export type QueryPeerArgs = { }; +export type QueryPeerByAddressAndAssetArgs = { + assetId: Scalars['String']['input']; + staticIlpAddress: Scalars['String']['input']; +}; + + export type QueryPeersArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1230,6 +1248,11 @@ export type QueryWalletAddressArgs = { }; +export type QueryWalletAddressByUrlArgs = { + url: Scalars['String']['input']; +}; + + export type QueryWalletAddressesArgs = { after?: InputMaybe; before?: InputMaybe; @@ -2275,16 +2298,19 @@ export type PeersConnectionResolvers = { accountingTransfers?: Resolver>; asset?: Resolver, ParentType, ContextType, RequireFields>; + assetByCodeAndScale?: Resolver, ParentType, ContextType, RequireFields>; assets?: Resolver>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; + peerByAddressAndAsset?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; + walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; }; diff --git a/packages/auth/src/graphql/schema.graphql b/packages/auth/src/graphql/schema.graphql index a80d651089..d4a78717c9 100644 --- a/packages/auth/src/graphql/schema.graphql +++ b/packages/auth/src/graphql/schema.graphql @@ -15,8 +15,8 @@ type Query { sortOrder: SortOrder ): GrantsConnection! - "Fetch a grant" - grant(id: ID!): Grant! + "Fetch a specific grant by its ID." + grant("Unique identifier of the grant." id: ID!): Grant! } type Mutation { diff --git a/packages/backend/src/asset/service.ts b/packages/backend/src/asset/service.ts index 139006e926..8967fc5d32 100644 --- a/packages/backend/src/asset/service.ts +++ b/packages/backend/src/asset/service.ts @@ -33,6 +33,7 @@ export interface AssetService { update(options: UpdateOptions): Promise delete(options: DeleteOptions): Promise get(id: string): Promise + getByCodeAndScale(code: string, scale: number): Promise getPage(pagination?: Pagination, sortOrder?: SortOrder): Promise getAll(): Promise } @@ -59,6 +60,8 @@ export async function createAssetService({ update: (options) => updateAsset(deps, options), delete: (options) => deleteAsset(deps, options), get: (id) => getAsset(deps, id), + getByCodeAndScale: (code, scale) => + getAssetByCodeAndScale(deps, code, scale), getPage: (pagination?, sortOrder?) => getAssetsPage(deps, pagination, sortOrder), getAll: () => getAll(deps) @@ -171,6 +174,16 @@ async function getAsset( return await Asset.query(deps.knex).whereNull('deletedAt').findById(id) } +async function getAssetByCodeAndScale( + deps: ServiceDependencies, + code: string, + scale: number +): Promise { + return await Asset.query(deps.knex) + .where({ code: code, scale: scale }) + .first() +} + async function getAssetsPage( deps: ServiceDependencies, pagination?: Pagination, diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index 3f5f20485e..f0a9d3f643 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -6215,6 +6215,51 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assetByCodeAndScale", + "description": "Get an asset based on its currency code and scale if it exists.", + "args": [ + { + "name": "code", + "description": "ISO 4217 currency code.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "scale", + "description": "Difference in order of magnitude between the standard unit of an asset and its corresponding fractional unit.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "UInt8", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Asset", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assets", "description": "Fetch a paginated list of assets.", @@ -6557,6 +6602,51 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "peerByAddressAndAsset", + "description": "Get a peer based on its ILP address and asset ID if it exists.", + "args": [ + { + "name": "assetId", + "description": "Asset ID of peering relationship.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "staticIlpAddress", + "description": "ILP address of the peer.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Peer", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "peers", "description": "Fetch a paginated list of peers.", @@ -6721,6 +6811,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "walletAddressByUrl", + "description": "Get a wallet address by its url if it exists", + "args": [ + { + "name": "url", + "description": "Wallet Address URL.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "WalletAddress", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "walletAddresses", "description": "Fetch a paginated list of wallet addresses.", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 131de7eb44..9437d6ec24 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -1124,6 +1124,8 @@ export type Query = { accountingTransfers: AccountingTransferConnection; /** Fetch an asset by its ID. */ asset?: Maybe; + /** Get an asset based on its currency code and scale if it exists. */ + assetByCodeAndScale?: Maybe; /** Fetch a paginated list of assets. */ assets: AssetsConnection; /** Fetch an Open Payments incoming payment by its ID. */ @@ -1136,6 +1138,8 @@ export type Query = { payments: PaymentConnection; /** Fetch a peer by its ID. */ peer?: Maybe; + /** Get a peer based on its ILP address and asset ID if it exists. */ + peerByAddressAndAsset?: Maybe; /** Fetch a paginated list of peers. */ peers: PeersConnection; /** Fetch an Open Payments quote by its ID. */ @@ -1144,6 +1148,8 @@ export type Query = { receiver?: Maybe; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; + /** Get a wallet address by its url if it exists */ + walletAddressByUrl?: Maybe; /** Fetch a paginated list of wallet addresses. */ walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ @@ -1162,6 +1168,12 @@ export type QueryAssetArgs = { }; +export type QueryAssetByCodeAndScaleArgs = { + code: Scalars['String']['input']; + scale: Scalars['UInt8']['input']; +}; + + export type QueryAssetsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1206,6 +1218,12 @@ export type QueryPeerArgs = { }; +export type QueryPeerByAddressAndAssetArgs = { + assetId: Scalars['String']['input']; + staticIlpAddress: Scalars['String']['input']; +}; + + export type QueryPeersArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1230,6 +1248,11 @@ export type QueryWalletAddressArgs = { }; +export type QueryWalletAddressByUrlArgs = { + url: Scalars['String']['input']; +}; + + export type QueryWalletAddressesArgs = { after?: InputMaybe; before?: InputMaybe; @@ -2275,16 +2298,19 @@ export type PeersConnectionResolvers = { accountingTransfers?: Resolver>; asset?: Resolver, ParentType, ContextType, RequireFields>; + assetByCodeAndScale?: Resolver, ParentType, ContextType, RequireFields>; assets?: Resolver>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; + peerByAddressAndAsset?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; + walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; }; diff --git a/packages/backend/src/graphql/resolvers/asset.test.ts b/packages/backend/src/graphql/resolvers/asset.test.ts index d174e32a2b..7968de3f98 100644 --- a/packages/backend/src/graphql/resolvers/asset.test.ts +++ b/packages/backend/src/graphql/resolvers/asset.test.ts @@ -280,6 +280,70 @@ describe('Asset Resolvers', (): void => { }) }) + test('Can get an asset by code and scale', async (): Promise => { + const asset = await assetService.create({ + ...randomAsset(), + withdrawalThreshold: BigInt(10), + liquidityThreshold: BigInt(100) + }) + assert.ok(!isAssetError(asset)) + assert.ok(asset.withdrawalThreshold) + const args = { code: asset.code, scale: asset.scale } + const query = async () => + await appContainer.apolloClient + .query({ + query: gql` + query GetAssetByCodeAndScale($code: String!, $scale: UInt8!) { + assetByCodeAndScale(code: $code, scale: $scale) { + id + code + scale + liquidity + withdrawalThreshold + liquidityThreshold + createdAt + } + } + `, + variables: args + }) + .then((query): Asset => { + if (query.data) { + return query.data.assetByCodeAndScale + } else { + throw new Error('Data was empty') + } + }) + + await expect(query()).resolves.toEqual({ + __typename: 'Asset', + id: asset.id, + code: asset.code, + scale: asset.scale, + liquidity: '0', + withdrawalThreshold: asset.withdrawalThreshold.toString(), + liquidityThreshold: asset.liquidityThreshold?.toString(), + createdAt: new Date(+asset.createdAt).toISOString() + }) + + await accountingService.createDeposit({ + id: uuid(), + account: asset, + amount: BigInt(100) + }) + + await expect(query()).resolves.toEqual({ + __typename: 'Asset', + id: asset.id, + code: asset.code, + scale: asset.scale, + liquidity: '100', + withdrawalThreshold: asset.withdrawalThreshold.toString(), + liquidityThreshold: asset.liquidityThreshold?.toString(), + createdAt: new Date(+asset.createdAt).toISOString() + }) + }) + test.each([ undefined, { fixed: BigInt(100), basisPoints: 1000, type: FeeType.Sending }, diff --git a/packages/backend/src/graphql/resolvers/asset.ts b/packages/backend/src/graphql/resolvers/asset.ts index 52a18a2b30..50638d721c 100644 --- a/packages/backend/src/graphql/resolvers/asset.ts +++ b/packages/backend/src/graphql/resolvers/asset.ts @@ -56,6 +56,13 @@ export const getAsset: QueryResolvers['asset'] = async ( return assetToGraphql(asset) } +export const getAssetByCodeAndScale: QueryResolvers['assetByCodeAndScale'] = + async (parent, args, ctx): Promise => { + const assetService = await ctx.container.use('assetService') + const asset = await assetService.getByCodeAndScale(args.code, args.scale) + return asset ? assetToGraphql(asset) : null + } + export const createAsset: MutationResolvers['createAsset'] = async ( parent, diff --git a/packages/backend/src/graphql/resolvers/index.ts b/packages/backend/src/graphql/resolvers/index.ts index d71a771a97..2c191b30e8 100644 --- a/packages/backend/src/graphql/resolvers/index.ts +++ b/packages/backend/src/graphql/resolvers/index.ts @@ -4,7 +4,8 @@ import { getWalletAddresses, createWalletAddress, updateWalletAddress, - triggerWalletAddressEvents + triggerWalletAddressEvents, + getWalletAddressByUrl } from './wallet_address' import { getAsset, @@ -14,7 +15,8 @@ import { deleteAsset, getAssetReceivingFee, getAssetSendingFee, - getFees + getFees, + getAssetByCodeAndScale } from './asset' import { getWalletAddressIncomingPayments, @@ -33,7 +35,14 @@ import { createOutgoingPaymentFromIncomingPayment, cancelOutgoingPayment } from './outgoing_payment' -import { getPeer, getPeers, createPeer, updatePeer, deletePeer } from './peer' +import { + getPeer, + getPeers, + createPeer, + updatePeer, + deletePeer, + getPeerByAddressAndAsset +} from './peer' import { getAssetLiquidity, getPeerLiquidity, @@ -84,13 +93,16 @@ export const resolvers: Resolvers = { }, Query: { walletAddress: getWalletAddress, + walletAddressByUrl: getWalletAddressByUrl, walletAddresses: getWalletAddresses, asset: getAsset, assets: getAssets, + assetByCodeAndScale: getAssetByCodeAndScale, outgoingPayment: getOutgoingPayment, outgoingPayments: getOutgoingPayments, incomingPayment: getIncomingPayment, peer: getPeer, + peerByAddressAndAsset: getPeerByAddressAndAsset, peers: getPeers, quote: getQuote, webhookEvents: getWebhookEvents, diff --git a/packages/backend/src/graphql/resolvers/peer.test.ts b/packages/backend/src/graphql/resolvers/peer.test.ts index 8fc24012d2..4aec23a2e8 100644 --- a/packages/backend/src/graphql/resolvers/peer.test.ts +++ b/packages/backend/src/graphql/resolvers/peer.test.ts @@ -334,6 +334,105 @@ describe('Peer Resolvers', (): void => { }) }) + test('Can get a peer by address and asset id', async (): Promise => { + const peer = await createPeer(deps, randomPeer()) + const args = { + staticIlpAddress: peer.staticIlpAddress, + assetId: peer.assetId + } + + const query = async () => + await appContainer.apolloClient + .query({ + query: gql` + query getPeerByAddressAndAsset( + $staticIlpAddress: String! + $assetId: String! + ) { + peerByAddressAndAsset( + staticIlpAddress: $staticIlpAddress + assetId: $assetId + ) { + id + asset { + code + scale + } + maxPacketAmount + http { + outgoing { + authToken + endpoint + } + } + staticIlpAddress + liquidity + name + liquidityThreshold + } + } + `, + variables: args + }) + .then((query): GraphQLPeer => { + if (query.data) { + return query.data.peerByAddressAndAsset + } else { + throw new Error('Data was empty') + } + }) + + await expect(query()).resolves.toEqual({ + __typename: 'Peer', + id: peer.id, + asset: { + __typename: 'Asset', + code: peer.asset.code, + scale: peer.asset.scale + }, + http: { + __typename: 'Http', + outgoing: { + __typename: 'HttpOutgoing', + ...peer.http.outgoing + } + }, + staticIlpAddress: peer.staticIlpAddress, + maxPacketAmount: peer.maxPacketAmount?.toString(), + liquidity: '0', + name: peer.name, + liquidityThreshold: '100' + }) + + await accountingService.createDeposit({ + id: uuid(), + account: peer, + amount: BigInt(100) + }) + + await expect(query()).resolves.toEqual({ + __typename: 'Peer', + id: peer.id, + asset: { + __typename: 'Asset', + code: peer.asset.code, + scale: peer.asset.scale + }, + http: { + __typename: 'Http', + outgoing: { + __typename: 'HttpOutgoing', + ...peer.http.outgoing + } + }, + staticIlpAddress: peer.staticIlpAddress, + maxPacketAmount: peer.maxPacketAmount?.toString(), + liquidity: '100', + name: peer.name, + liquidityThreshold: '100' + }) + }) + test('Returns error for unknown peer', async (): Promise => { expect.assertions(2) try { diff --git a/packages/backend/src/graphql/resolvers/peer.ts b/packages/backend/src/graphql/resolvers/peer.ts index b2296b7a89..d14b73603d 100644 --- a/packages/backend/src/graphql/resolvers/peer.ts +++ b/packages/backend/src/graphql/resolvers/peer.ts @@ -58,6 +58,16 @@ export const getPeer: QueryResolvers['peer'] = async ( return peerToGraphql(peer) } +export const getPeerByAddressAndAsset: QueryResolvers['peerByAddressAndAsset'] = + async (parent, args, ctx): Promise => { + const peerService = await ctx.container.use('peerService') + const peer = await peerService.getByDestinationAddress( + args.staticIlpAddress, + args.assetId + ) + return peer ? peerToGraphql(peer) : null + } + export const createPeer: MutationResolvers['createPeer'] = async ( parent, diff --git a/packages/backend/src/graphql/resolvers/wallet_address.test.ts b/packages/backend/src/graphql/resolvers/wallet_address.test.ts index ff51dd0d1f..c2bcaa8627 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.test.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.test.ts @@ -718,6 +718,89 @@ describe('Wallet Address Resolvers', (): void => { } ) + test.each` + publicName + ${'Alice'} + ${undefined} + `( + 'Can get a wallet address by its url (publicName: $publicName)', + async ({ publicName }): Promise => { + const walletProp01 = new WalletAddressAdditionalProperty() + walletProp01.fieldKey = 'key-test-query-one' + walletProp01.fieldValue = 'value-test-query' + walletProp01.visibleInOpenPayments = true + const walletProp02 = new WalletAddressAdditionalProperty() + walletProp02.fieldKey = 'key-test-query-two' + walletProp02.fieldValue = 'value-test-query' + walletProp02.visibleInOpenPayments = false + const additionalProperties = [walletProp01, walletProp02] + + const walletAddress = await createWalletAddress(deps, { + publicName, + createLiquidityAccount: true, + additionalProperties + }) + const args = { url: walletAddress.url } + const query = await appContainer.apolloClient + .query({ + query: gql` + query getWalletAddressByUrl($url: String!) { + walletAddressByUrl(url: $url) { + id + liquidity + asset { + code + scale + } + url + publicName + additionalProperties { + key + value + visibleInOpenPayments + } + } + } + `, + variables: args + }) + .then((query): WalletAddress => { + if (query.data) { + return query.data.walletAddressByUrl + } else { + throw new Error('Data was empty') + } + }) + + expect(query).toEqual({ + __typename: 'WalletAddress', + id: walletAddress.id, + liquidity: '0', + asset: { + __typename: 'Asset', + code: walletAddress.asset.code, + scale: walletAddress.asset.scale + }, + url: walletAddress.url, + publicName: publicName ?? null, + additionalProperties: [ + { + __typename: 'AdditionalProperty', + key: walletProp01.fieldKey, + value: walletProp01.fieldValue, + visibleInOpenPayments: walletProp01.visibleInOpenPayments + }, + { + __typename: 'AdditionalProperty', + key: walletProp02.fieldKey, + value: walletProp02.fieldValue, + visibleInOpenPayments: walletProp02.visibleInOpenPayments + } + ] + }) + } + ) + test('Returns error for unknown wallet address', async (): Promise => { expect.assertions(2) try { diff --git a/packages/backend/src/graphql/resolvers/wallet_address.ts b/packages/backend/src/graphql/resolvers/wallet_address.ts index 1731699fc9..d1f7172dab 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.ts @@ -69,6 +69,17 @@ export const getWalletAddress: QueryResolvers['walletAddress'] = return walletAddressToGraphql(walletAddress) } +export const getWalletAddressByUrl: QueryResolvers['walletAddressByUrl'] = + async ( + parent, + args, + ctx + ): Promise => { + const walletAddressService = await ctx.container.use('walletAddressService') + const walletAddress = await walletAddressService.getByUrl(args.url) + return walletAddress ? walletAddressToGraphql(walletAddress) : null + } + export const createWalletAddress: MutationResolvers['createWalletAddress'] = async ( parent, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index b6a57d5523..a41dcd85ac 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -2,6 +2,14 @@ type Query { "Fetch an asset by its ID." asset("Unique identifier of the asset." id: String!): Asset + "Get an asset based on its currency code and scale if it exists." + assetByCodeAndScale( + "ISO 4217 currency code." + code: String! + "Difference in order of magnitude between the standard unit of an asset and its corresponding fractional unit." + scale: UInt8! + ): Asset + "Fetch a paginated list of assets." assets( "Forward pagination: Cursor (asset ID) to start retrieving assets after this point." @@ -19,6 +27,14 @@ type Query { "Fetch a peer by its ID." peer("Unique identifier of the peer." id: String!): Peer + "Get a peer based on its ILP address and asset ID if it exists." + peerByAddressAndAsset( + "ILP address of the peer." + staticIlpAddress: String! + "Asset ID of peering relationship." + assetId: String! + ): Peer + "Fetch a paginated list of peers." peers( "Forward pagination: Cursor (peer ID) to start retrieving peers after this point." @@ -39,6 +55,9 @@ type Query { id: String! ): WalletAddress + "Get a wallet address by its url if it exists" + walletAddressByUrl("Wallet Address URL." url: String!): WalletAddress + "Fetch a paginated list of wallet addresses." walletAddresses( "Forward pagination: Cursor (wallet address ID) to start retrieving wallet addresses after this point." diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 2d35119c43..4bc2279d6f 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -1124,6 +1124,8 @@ export type Query = { accountingTransfers: AccountingTransferConnection; /** Fetch an asset by its ID. */ asset?: Maybe; + /** Get an asset based on its currency code and scale if it exists. */ + assetByCodeAndScale?: Maybe; /** Fetch a paginated list of assets. */ assets: AssetsConnection; /** Fetch an Open Payments incoming payment by its ID. */ @@ -1136,6 +1138,8 @@ export type Query = { payments: PaymentConnection; /** Fetch a peer by its ID. */ peer?: Maybe; + /** Get a peer based on its ILP address and asset ID if it exists. */ + peerByAddressAndAsset?: Maybe; /** Fetch a paginated list of peers. */ peers: PeersConnection; /** Fetch an Open Payments quote by its ID. */ @@ -1144,6 +1148,8 @@ export type Query = { receiver?: Maybe; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; + /** Get a wallet address by its url if it exists */ + walletAddressByUrl?: Maybe; /** Fetch a paginated list of wallet addresses. */ walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ @@ -1162,6 +1168,12 @@ export type QueryAssetArgs = { }; +export type QueryAssetByCodeAndScaleArgs = { + code: Scalars['String']['input']; + scale: Scalars['UInt8']['input']; +}; + + export type QueryAssetsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1206,6 +1218,12 @@ export type QueryPeerArgs = { }; +export type QueryPeerByAddressAndAssetArgs = { + assetId: Scalars['String']['input']; + staticIlpAddress: Scalars['String']['input']; +}; + + export type QueryPeersArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1230,6 +1248,11 @@ export type QueryWalletAddressArgs = { }; +export type QueryWalletAddressByUrlArgs = { + url: Scalars['String']['input']; +}; + + export type QueryWalletAddressesArgs = { after?: InputMaybe; before?: InputMaybe; @@ -2275,16 +2298,19 @@ export type PeersConnectionResolvers = { accountingTransfers?: Resolver>; asset?: Resolver, ParentType, ContextType, RequireFields>; + assetByCodeAndScale?: Resolver, ParentType, ContextType, RequireFields>; assets?: Resolver>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; + peerByAddressAndAsset?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; + walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; }; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 131de7eb44..9437d6ec24 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -1124,6 +1124,8 @@ export type Query = { accountingTransfers: AccountingTransferConnection; /** Fetch an asset by its ID. */ asset?: Maybe; + /** Get an asset based on its currency code and scale if it exists. */ + assetByCodeAndScale?: Maybe; /** Fetch a paginated list of assets. */ assets: AssetsConnection; /** Fetch an Open Payments incoming payment by its ID. */ @@ -1136,6 +1138,8 @@ export type Query = { payments: PaymentConnection; /** Fetch a peer by its ID. */ peer?: Maybe; + /** Get a peer based on its ILP address and asset ID if it exists. */ + peerByAddressAndAsset?: Maybe; /** Fetch a paginated list of peers. */ peers: PeersConnection; /** Fetch an Open Payments quote by its ID. */ @@ -1144,6 +1148,8 @@ export type Query = { receiver?: Maybe; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; + /** Get a wallet address by its url if it exists */ + walletAddressByUrl?: Maybe; /** Fetch a paginated list of wallet addresses. */ walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ @@ -1162,6 +1168,12 @@ export type QueryAssetArgs = { }; +export type QueryAssetByCodeAndScaleArgs = { + code: Scalars['String']['input']; + scale: Scalars['UInt8']['input']; +}; + + export type QueryAssetsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1206,6 +1218,12 @@ export type QueryPeerArgs = { }; +export type QueryPeerByAddressAndAssetArgs = { + assetId: Scalars['String']['input']; + staticIlpAddress: Scalars['String']['input']; +}; + + export type QueryPeersArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1230,6 +1248,11 @@ export type QueryWalletAddressArgs = { }; +export type QueryWalletAddressByUrlArgs = { + url: Scalars['String']['input']; +}; + + export type QueryWalletAddressesArgs = { after?: InputMaybe; before?: InputMaybe; @@ -2275,16 +2298,19 @@ export type PeersConnectionResolvers = { accountingTransfers?: Resolver>; asset?: Resolver, ParentType, ContextType, RequireFields>; + assetByCodeAndScale?: Resolver, ParentType, ContextType, RequireFields>; assets?: Resolver>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; + peerByAddressAndAsset?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; + walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; }; diff --git a/packages/mock-account-service-lib/src/requesters.ts b/packages/mock-account-service-lib/src/requesters.ts index e6edd93bb9..b1fe2423b9 100644 --- a/packages/mock-account-service-lib/src/requesters.ts +++ b/packages/mock-account-service-lib/src/requesters.ts @@ -114,6 +114,12 @@ export async function createAsset( scale: number, liquidityThreshold: number ): Promise { + const existingAsset = await getAssetByCodeAndScale(apolloClient, code, scale) + + if (existingAsset) { + return existingAsset + } + const createAssetMutation = gql` mutation CreateAsset($input: CreateAssetInput!) { createAsset(input: $input) { @@ -146,6 +152,33 @@ export async function createAsset( }) } +async function getAssetByCodeAndScale( + apolloClient: ApolloClient, + code: string, + scale: number +) { + const getAssetQuery = gql` + query GetAssetByCodeAndScale($code: String!, $scale: UInt8!) { + assetByCodeAndScale(code: $code, scale: $scale) { + id + code + scale + liquidityThreshold + } + } + ` + const args = { code: code, scale: scale } + const { data } = await apolloClient.query({ + query: getAssetQuery, + variables: args + }) + + if (data.assetByCodeAndScale) { + return { asset: data.assetByCodeAndScale } + } + return null +} + export async function createPeer( apolloClient: ApolloClient, logger: Logger, @@ -156,6 +189,15 @@ export async function createPeer( name: string, liquidityThreshold: number ): Promise { + const existingPeer = await getPeerByAddressAndAsset( + apolloClient, + staticIlpAddress, + assetId + ) + if (existingPeer) { + return existingPeer + } + const createPeerMutation = gql` mutation CreatePeer($input: CreatePeerInput!) { createPeer(input: $input) { @@ -191,6 +233,44 @@ export async function createPeer( }) } +async function getPeerByAddressAndAsset( + apolloClient: ApolloClient, + staticIlpAddress: string, + assetId: string +) { + const getPeerByAddressAndAssetQuery = gql` + query getPeerByAddressAndAsset( + $staticIlpAddress: String! + $assetId: String! + ) { + peerByAddressAndAsset( + staticIlpAddress: $staticIlpAddress + assetId: $assetId + ) { + id + name + asset { + id + scale + code + withdrawalThreshold + } + } + } + ` + const args = { staticIlpAddress: staticIlpAddress, assetId: assetId } + + const { data } = await apolloClient.query({ + query: getPeerByAddressAndAssetQuery, + variables: args + }) + + if (data.peerByAddressAndAsset) { + return { peer: data.peerByAddressAndAsset } + } + return null +} + export async function createAutoPeer( apolloClient: ApolloClient, logger: Logger, @@ -315,6 +395,14 @@ export async function createWalletAddress( accountUrl: string, assetId: string ): Promise { + const existingWalletAddress = await getWalletAddressByURL( + apolloClient, + accountUrl + ) + if (existingWalletAddress) { + return existingWalletAddress + } + const createWalletAddressMutation = gql` mutation CreateWalletAddress($input: CreateWalletAddressInput!) { createWalletAddress(input: $input) { @@ -351,6 +439,34 @@ export async function createWalletAddress( }) } +async function getWalletAddressByURL( + apolloClient: ApolloClient, + url: string +) { + const query = gql` + query getWalletAddressByUrl($url: String!) { + walletAddressByUrl(url: $url) { + id + liquidity + url + publicName + asset { + id + scale + code + withdrawalThreshold + } + } + } + ` + const { data } = await apolloClient.query({ + query: query, + variables: { url: url } + }) + + return data.walletAddressByUrl ?? null +} + export async function createWalletAddressKey( apolloClient: ApolloClient, logger: Logger, diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 131de7eb44..9437d6ec24 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -1124,6 +1124,8 @@ export type Query = { accountingTransfers: AccountingTransferConnection; /** Fetch an asset by its ID. */ asset?: Maybe; + /** Get an asset based on its currency code and scale if it exists. */ + assetByCodeAndScale?: Maybe; /** Fetch a paginated list of assets. */ assets: AssetsConnection; /** Fetch an Open Payments incoming payment by its ID. */ @@ -1136,6 +1138,8 @@ export type Query = { payments: PaymentConnection; /** Fetch a peer by its ID. */ peer?: Maybe; + /** Get a peer based on its ILP address and asset ID if it exists. */ + peerByAddressAndAsset?: Maybe; /** Fetch a paginated list of peers. */ peers: PeersConnection; /** Fetch an Open Payments quote by its ID. */ @@ -1144,6 +1148,8 @@ export type Query = { receiver?: Maybe; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; + /** Get a wallet address by its url if it exists */ + walletAddressByUrl?: Maybe; /** Fetch a paginated list of wallet addresses. */ walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ @@ -1162,6 +1168,12 @@ export type QueryAssetArgs = { }; +export type QueryAssetByCodeAndScaleArgs = { + code: Scalars['String']['input']; + scale: Scalars['UInt8']['input']; +}; + + export type QueryAssetsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1206,6 +1218,12 @@ export type QueryPeerArgs = { }; +export type QueryPeerByAddressAndAssetArgs = { + assetId: Scalars['String']['input']; + staticIlpAddress: Scalars['String']['input']; +}; + + export type QueryPeersArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1230,6 +1248,11 @@ export type QueryWalletAddressArgs = { }; +export type QueryWalletAddressByUrlArgs = { + url: Scalars['String']['input']; +}; + + export type QueryWalletAddressesArgs = { after?: InputMaybe; before?: InputMaybe; @@ -2275,16 +2298,19 @@ export type PeersConnectionResolvers = { accountingTransfers?: Resolver>; asset?: Resolver, ParentType, ContextType, RequireFields>; + assetByCodeAndScale?: Resolver, ParentType, ContextType, RequireFields>; assets?: Resolver>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; + peerByAddressAndAsset?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; + walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; }; From d5ecf053ec45b19a206f23be4456fe320f62dbee Mon Sep 17 00:00:00 2001 From: Oana Lolea Date: Tue, 29 Oct 2024 17:34:01 +0200 Subject: [PATCH 2/4] Fix on requesters functions --- .../src/requesters.ts | 186 +++++++++--------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/packages/mock-account-service-lib/src/requesters.ts b/packages/mock-account-service-lib/src/requesters.ts index b1fe2423b9..7541ccf281 100644 --- a/packages/mock-account-service-lib/src/requesters.ts +++ b/packages/mock-account-service-lib/src/requesters.ts @@ -152,33 +152,6 @@ export async function createAsset( }) } -async function getAssetByCodeAndScale( - apolloClient: ApolloClient, - code: string, - scale: number -) { - const getAssetQuery = gql` - query GetAssetByCodeAndScale($code: String!, $scale: UInt8!) { - assetByCodeAndScale(code: $code, scale: $scale) { - id - code - scale - liquidityThreshold - } - } - ` - const args = { code: code, scale: scale } - const { data } = await apolloClient.query({ - query: getAssetQuery, - variables: args - }) - - if (data.assetByCodeAndScale) { - return { asset: data.assetByCodeAndScale } - } - return null -} - export async function createPeer( apolloClient: ApolloClient, logger: Logger, @@ -233,44 +206,6 @@ export async function createPeer( }) } -async function getPeerByAddressAndAsset( - apolloClient: ApolloClient, - staticIlpAddress: string, - assetId: string -) { - const getPeerByAddressAndAssetQuery = gql` - query getPeerByAddressAndAsset( - $staticIlpAddress: String! - $assetId: String! - ) { - peerByAddressAndAsset( - staticIlpAddress: $staticIlpAddress - assetId: $assetId - ) { - id - name - asset { - id - scale - code - withdrawalThreshold - } - } - } - ` - const args = { staticIlpAddress: staticIlpAddress, assetId: assetId } - - const { data } = await apolloClient.query({ - query: getPeerByAddressAndAssetQuery, - variables: args - }) - - if (data.peerByAddressAndAsset) { - return { peer: data.peerByAddressAndAsset } - } - return null -} - export async function createAutoPeer( apolloClient: ApolloClient, logger: Logger, @@ -439,34 +374,6 @@ export async function createWalletAddress( }) } -async function getWalletAddressByURL( - apolloClient: ApolloClient, - url: string -) { - const query = gql` - query getWalletAddressByUrl($url: String!) { - walletAddressByUrl(url: $url) { - id - liquidity - url - publicName - asset { - id - scale - code - withdrawalThreshold - } - } - } - ` - const { data } = await apolloClient.query({ - query: query, - variables: { url: url } - }) - - return data.walletAddressByUrl ?? null -} - export async function createWalletAddressKey( apolloClient: ApolloClient, logger: Logger, @@ -554,3 +461,96 @@ export async function setFee( return data.setFee }) } + +async function getAssetByCodeAndScale( + apolloClient: ApolloClient, + code: string, + scale: number +) { + const getAssetQuery = gql` + query GetAssetByCodeAndScale($code: String!, $scale: UInt8!) { + assetByCodeAndScale(code: $code, scale: $scale) { + id + code + scale + liquidityThreshold + } + } + ` + const args = { code: code, scale: scale } + const { data } = await apolloClient.query({ + query: getAssetQuery, + variables: args + }) + + if (data.assetByCodeAndScale) { + return { asset: data.assetByCodeAndScale } + } + return null +} + +async function getWalletAddressByURL( + apolloClient: ApolloClient, + url: string +) { + const query = gql` + query getWalletAddressByUrl($url: String!) { + walletAddressByUrl(url: $url) { + id + liquidity + url + publicName + asset { + id + scale + code + withdrawalThreshold + } + } + } + ` + const { data } = await apolloClient.query({ + query: query, + variables: { url: url } + }) + + return data.walletAddressByUrl ?? null +} + +async function getPeerByAddressAndAsset( + apolloClient: ApolloClient, + staticIlpAddress: string, + assetId: string +) { + const getPeerByAddressAndAssetQuery = gql` + query getPeerByAddressAndAsset( + $staticIlpAddress: String! + $assetId: String! + ) { + peerByAddressAndAsset( + staticIlpAddress: $staticIlpAddress + assetId: $assetId + ) { + id + name + asset { + id + scale + code + withdrawalThreshold + } + } + } + ` + const args = { staticIlpAddress: staticIlpAddress, assetId: assetId } + + const { data } = await apolloClient.query({ + query: getPeerByAddressAndAssetQuery, + variables: args + }) + + if (data.peerByAddressAndAsset) { + return { peer: data.peerByAddressAndAsset } + } + return null +} From 24c55c4da09a5c5f1c840b69ea344b4e732d2221 Mon Sep 17 00:00:00 2001 From: Oana Lolea Date: Tue, 5 Nov 2024 11:34:36 +0200 Subject: [PATCH 3/4] Removed unnecessary properties from wallet address test file --- .../graphql/resolvers/wallet_address.test.ts | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/packages/backend/src/graphql/resolvers/wallet_address.test.ts b/packages/backend/src/graphql/resolvers/wallet_address.test.ts index c2bcaa8627..44a37b0545 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.test.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.test.ts @@ -725,20 +725,9 @@ describe('Wallet Address Resolvers', (): void => { `( 'Can get a wallet address by its url (publicName: $publicName)', async ({ publicName }): Promise => { - const walletProp01 = new WalletAddressAdditionalProperty() - walletProp01.fieldKey = 'key-test-query-one' - walletProp01.fieldValue = 'value-test-query' - walletProp01.visibleInOpenPayments = true - const walletProp02 = new WalletAddressAdditionalProperty() - walletProp02.fieldKey = 'key-test-query-two' - walletProp02.fieldValue = 'value-test-query' - walletProp02.visibleInOpenPayments = false - const additionalProperties = [walletProp01, walletProp02] - const walletAddress = await createWalletAddress(deps, { publicName, - createLiquidityAccount: true, - additionalProperties + createLiquidityAccount: true }) const args = { url: walletAddress.url } const query = await appContainer.apolloClient @@ -782,21 +771,7 @@ describe('Wallet Address Resolvers', (): void => { scale: walletAddress.asset.scale }, url: walletAddress.url, - publicName: publicName ?? null, - additionalProperties: [ - { - __typename: 'AdditionalProperty', - key: walletProp01.fieldKey, - value: walletProp01.fieldValue, - visibleInOpenPayments: walletProp01.visibleInOpenPayments - }, - { - __typename: 'AdditionalProperty', - key: walletProp02.fieldKey, - value: walletProp02.fieldValue, - visibleInOpenPayments: walletProp02.visibleInOpenPayments - } - ] + publicName: publicName ?? null }) } ) From 0582c3a090d742d30c28d89e3cdf70c8aab26234 Mon Sep 17 00:00:00 2001 From: Oana Lolea Date: Tue, 5 Nov 2024 11:54:30 +0200 Subject: [PATCH 4/4] Fix test --- packages/backend/src/graphql/resolvers/wallet_address.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/graphql/resolvers/wallet_address.test.ts b/packages/backend/src/graphql/resolvers/wallet_address.test.ts index 44a37b0545..12e55044ca 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.test.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.test.ts @@ -771,7 +771,8 @@ describe('Wallet Address Resolvers', (): void => { scale: walletAddress.asset.scale }, url: walletAddress.url, - publicName: publicName ?? null + publicName: publicName ?? null, + additionalProperties: [] }) } )