diff --git a/.github/workflows/node-build.yml b/.github/workflows/node-build.yml index f9ffe1eb6a..c6904c034a 100644 --- a/.github/workflows/node-build.yml +++ b/.github/workflows/node-build.yml @@ -291,6 +291,7 @@ jobs: - backend - frontend steps: + - uses: actions/checkout@v4 - name: Fetch docker image from cache uses: actions/cache/restore@v4 with: @@ -326,6 +327,7 @@ jobs: - backend - frontend steps: + - uses: actions/checkout@v4 - name: Fetch docker image from cache uses: actions/cache/restore@v4 with: diff --git a/.grype.yaml b/.grype.yaml new file mode 100644 index 0000000000..e6cad11151 --- /dev/null +++ b/.grype.yaml @@ -0,0 +1,2 @@ +ignore: + - vulnerability: GHSA-3xgq-45jj-v275 diff --git a/.prettierignore b/.prettierignore index f26e8fdf24..9339b81f63 100644 --- a/.prettierignore +++ b/.prettierignore @@ -14,5 +14,4 @@ build **/styles/*.css .docusaurus .cache-loader -packages/documentation/** .astro \ No newline at end of file diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000000..bdfddad256 --- /dev/null +++ b/.trivyignore @@ -0,0 +1 @@ +CVE-2024-21538 exp:2024-12-31 \ No newline at end of file diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru index e1322e309d..b9b2686b72 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru @@ -21,6 +21,8 @@ body:graphql { transferType ledger createdAt + state + expiresAt } credits { id @@ -30,6 +32,8 @@ body:graphql { transferType ledger createdAt + state + expiresAt } } } diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 9437d6ec24..46dad2dedb 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -32,10 +32,14 @@ export type AccountingTransfer = Model & { creditAccountId: Scalars['ID']['output']; /** Unique identifier for the debit account. */ debitAccountId: Scalars['ID']['output']; + /** The date and time that the accounting transfer will expire. */ + expiresAt?: Maybe; /** Unique identifier for the accounting transfer. */ id: Scalars['ID']['output']; /** Identifier that partitions the sets of accounts that can transact with each other. */ ledger: Scalars['UInt8']['output']; + /** The state of the accounting transfer. */ + state: TransferState; /** Type of the accounting transfer. */ transferType: TransferType; }; @@ -1372,6 +1376,15 @@ export enum SortOrder { Desc = 'DESC' } +export enum TransferState { + /** The accounting transfer is pending */ + Pending = 'PENDING', + /** The accounting transfer is posted */ + Posted = 'POSTED', + /** The accounting transfer is voided */ + Voided = 'VOIDED' +} + export enum TransferType { /** Represents a deposit transfer. */ Deposit = 'DEPOSIT', @@ -1812,6 +1825,7 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; TriggerWalletAddressEventsMutationResponse: ResolverTypeWrapper>; @@ -1966,8 +1980,10 @@ export type AccountingTransferResolvers; creditAccountId?: Resolver; debitAccountId?: Resolver; + expiresAt?: Resolver, ParentType, ContextType>; id?: Resolver; ledger?: Resolver; + state?: Resolver; transferType?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/package.json b/package.json index c3627f3ed3..edd63bd4d4 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "license": "Apache-2.0", "repository": "https://github.com/interledger/rafiki", "engines": { - "pnpm": "^8.15.4", + "pnpm": "^8.15.9", "node": "20" }, - "packageManager": "pnpm@8.15.5", + "packageManager": "pnpm@8.15.9", "scripts": { "preinstall": "npx only-allow pnpm", "lint": "eslint --max-warnings=0 --fix .", @@ -78,7 +78,8 @@ "tar@<6.2.1": ">=6.2.1", "braces@<3.0.3": ">=3.0.3", "@grpc/grpc-js@>=1.10.0 <1.10.9": ">=1.10.9", - "dset@<3.1.4": ">=3.1.4" + "dset@<3.1.4": ">=3.1.4", + "cross-spawn@>=7.0.0 <7.0.5": ">=7.0.5" } } } diff --git a/packages/backend/src/accounting/tigerbeetle/service.test.ts b/packages/backend/src/accounting/tigerbeetle/service.test.ts index 7d38242d8a..686f77ea21 100644 --- a/packages/backend/src/accounting/tigerbeetle/service.test.ts +++ b/packages/backend/src/accounting/tigerbeetle/service.test.ts @@ -235,6 +235,41 @@ describe('TigerBeetle Accounting Service', (): void => { expect(transferDebit.expiresAt).toBeUndefined() }) + test('Returns expiry date correctly', async (): Promise => { + const receivingAccount = await accountFactory.build() + const balances = [BigInt(100), BigInt(100)] + const accounts = await Promise.all( + balances.map(async (balance) => { + return await accountFactory.build({ + balance, + asset: receivingAccount.asset + }) + }) + ) + const timeout = 10 + await Promise.all( + accounts.map(async (account, i) => { + const transfer = await accountingService.createTransfer({ + sourceAccount: account, + sourceAmount: BigInt(10 * (i + 1)), + destinationAccount: receivingAccount, + timeout: timeout + }) + assert.ok(!isTransferError(transfer)) + await transfer.post() + }) + ) + const transfer = await accountingService.getAccountTransfers( + receivingAccount.id + ) + expect(transfer.credits).not.toHaveLength(0) + const timestamp = transfer.credits[0].timestamp + const expiresAtTime = transfer.credits[0].expiresAt?.getTime() + expect(expiresAtTime).toBe( + new Date(Number(timestamp) + timeout * 1000).getTime() + ) + }) + test('Returns undefined for nonexistent account', async (): Promise => { await expect( accountingService.getTotalReceived(uuid()) diff --git a/packages/backend/src/accounting/tigerbeetle/utils.ts b/packages/backend/src/accounting/tigerbeetle/utils.ts index 3553e306f9..9817b2f453 100644 --- a/packages/backend/src/accounting/tigerbeetle/utils.ts +++ b/packages/backend/src/accounting/tigerbeetle/utils.ts @@ -60,6 +60,19 @@ export function tbTransferToLedgerTransfer( transferRef: fromTigerBeetleId(tbTransfer.user_data_128), type: transferTypeFromCode(tbTransfer.code), state: state, - ledger: tbTransfer.ledger + ledger: tbTransfer.ledger, + expiresAt: expiresAtFromTimestampAndTimeout( + tbTransfer.timestamp, + tbTransfer.timeout + ) } } + +function expiresAtFromTimestampAndTimeout( + timestamp: bigint, + timeout: number +): Date | undefined { + return timeout + ? new Date(Number(timestamp / 1_000_000n) + timeout * 1000) + : undefined +} diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index a4fa9fb362..1745966aa2 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -77,6 +77,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "expiresAt", + "description": "The date and time that the accounting transfer will expire.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "id", "description": "Unique identifier for the accounting transfer.", @@ -109,6 +121,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "state", + "description": "The state of the accounting transfer.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "TransferState", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "transferType", "description": "Type of the accounting transfer.", @@ -7596,6 +7624,35 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "TransferState", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "PENDING", + "description": "The accounting transfer is pending", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "POSTED", + "description": "The accounting transfer is posted", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VOIDED", + "description": "The accounting transfer is voided", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "ENUM", "name": "TransferType", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 9437d6ec24..46dad2dedb 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -32,10 +32,14 @@ export type AccountingTransfer = Model & { creditAccountId: Scalars['ID']['output']; /** Unique identifier for the debit account. */ debitAccountId: Scalars['ID']['output']; + /** The date and time that the accounting transfer will expire. */ + expiresAt?: Maybe; /** Unique identifier for the accounting transfer. */ id: Scalars['ID']['output']; /** Identifier that partitions the sets of accounts that can transact with each other. */ ledger: Scalars['UInt8']['output']; + /** The state of the accounting transfer. */ + state: TransferState; /** Type of the accounting transfer. */ transferType: TransferType; }; @@ -1372,6 +1376,15 @@ export enum SortOrder { Desc = 'DESC' } +export enum TransferState { + /** The accounting transfer is pending */ + Pending = 'PENDING', + /** The accounting transfer is posted */ + Posted = 'POSTED', + /** The accounting transfer is voided */ + Voided = 'VOIDED' +} + export enum TransferType { /** Represents a deposit transfer. */ Deposit = 'DEPOSIT', @@ -1812,6 +1825,7 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; TriggerWalletAddressEventsMutationResponse: ResolverTypeWrapper>; @@ -1966,8 +1980,10 @@ export type AccountingTransferResolvers; creditAccountId?: Resolver; debitAccountId?: Resolver; + expiresAt?: Resolver, ParentType, ContextType>; id?: Resolver; ledger?: Resolver; + state?: Resolver; transferType?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/backend/src/graphql/resolvers/accounting_transfer.psql.test.ts b/packages/backend/src/graphql/resolvers/accounting_transfer.psql.test.ts index 6be3b65e3c..1ec2f06a61 100644 --- a/packages/backend/src/graphql/resolvers/accounting_transfer.psql.test.ts +++ b/packages/backend/src/graphql/resolvers/accounting_transfer.psql.test.ts @@ -7,6 +7,7 @@ import { Config } from '../../config/app' import { truncateTables } from '../../tests/tableManager' import { AccountingTransferConnection, + TransferState, TransferType } from '../generated/graphql' import { createAsset } from '../../tests/asset' @@ -99,6 +100,7 @@ describe('Accounting Transfer', (): void => { // Top up debit account first: const transferAmount = 123 const ledger = 1 + const tomorrow = new Date(new Date().getDate() + 1) const queryLimit = 20 const insertedTransfer = await createLedgerTransfer( @@ -109,7 +111,8 @@ describe('Accounting Transfer', (): void => { ledger: ledger, state: LedgerTransferState.POSTED, transferRef: uuid(), - type: LedgerTransferType.DEPOSIT + type: LedgerTransferType.DEPOSIT, + expiresAt: tomorrow }, appContainer.knex ) @@ -127,6 +130,8 @@ describe('Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } credits { id @@ -136,6 +141,8 @@ describe('Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } } } @@ -161,7 +168,9 @@ describe('Accounting Transfer', (): void => { debitAccountId: accountDebitId, amount: `${transferAmount}`, transferType: TransferType.Deposit, - ledger + ledger, + state: TransferState.Posted, + expiresAt: tomorrow.toISOString() }) // Credit: @@ -179,6 +188,8 @@ describe('Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } credits { id @@ -188,6 +199,8 @@ describe('Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } } } @@ -214,7 +227,9 @@ describe('Accounting Transfer', (): void => { creditAccountId: accountCreditId, amount: `${transferAmount}`, transferType: TransferType.Deposit, - ledger + ledger, + state: TransferState.Posted, + expiresAt: tomorrow.toISOString() }) }) }) diff --git a/packages/backend/src/graphql/resolvers/accounting_transfer.tigerbeetle.test.ts b/packages/backend/src/graphql/resolvers/accounting_transfer.tigerbeetle.test.ts index 94d7e81540..b6fe08cee7 100644 --- a/packages/backend/src/graphql/resolvers/accounting_transfer.tigerbeetle.test.ts +++ b/packages/backend/src/graphql/resolvers/accounting_transfer.tigerbeetle.test.ts @@ -11,6 +11,7 @@ import { Config } from '../../config/app' import { truncateTables } from '../../tests/tableManager' import { AccountingTransferConnection, + TransferState, TransferType } from '../generated/graphql' import { v4 as uuid } from 'uuid' @@ -95,6 +96,8 @@ describe('TigerBeetle: Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } credits { id @@ -104,6 +107,8 @@ describe('TigerBeetle: Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } } } @@ -129,7 +134,9 @@ describe('TigerBeetle: Accounting Transfer', (): void => { creditAccountId: creditAccId, amount: `${transferAmount}`, transferType: TransferType.Deposit, - ledger + ledger, + state: TransferState.Posted, + expiresAt: null }) // Credit: @@ -146,6 +153,8 @@ describe('TigerBeetle: Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } credits { id @@ -155,6 +164,8 @@ describe('TigerBeetle: Accounting Transfer', (): void => { transferType ledger createdAt + state + expiresAt } } } @@ -180,7 +191,9 @@ describe('TigerBeetle: Accounting Transfer', (): void => { creditAccountId: creditAccId, amount: `${transferAmount}`, transferType: TransferType.Deposit, - ledger + ledger, + state: TransferState.Posted, + expiresAt: null }) }) }) diff --git a/packages/backend/src/graphql/resolvers/accounting_transfer.ts b/packages/backend/src/graphql/resolvers/accounting_transfer.ts index c19db8a700..c278b78527 100644 --- a/packages/backend/src/graphql/resolvers/accounting_transfer.ts +++ b/packages/backend/src/graphql/resolvers/accounting_transfer.ts @@ -2,10 +2,15 @@ import { ResolversTypes, QueryResolvers, AccountingTransfer, - TransferType as SchemaTransferType + TransferType as SchemaTransferType, + TransferState } from '../generated/graphql' import { ApolloContext } from '../../app' -import { LedgerTransfer, TransferType } from '../../accounting/service' +import { + LedgerTransfer, + LedgerTransferState, + TransferType +} from '../../accounting/service' export const getAccountingTransfers: QueryResolvers['accountingTransfers'] = async ( @@ -37,7 +42,11 @@ export function transferToGraphql( creditAccountId: transfer.creditAccountId, amount: transfer.amount, ledger: transfer.ledger, - transferType: transferTypeToGraphql(transfer.type) + transferType: transferTypeToGraphql(transfer.type), + state: transferStateToGraphql(transfer.state), + expiresAt: transfer.expiresAt + ? new Date(transfer.expiresAt).toISOString() + : undefined } } @@ -53,3 +62,16 @@ function transferTypeToGraphql(type: TransferType): SchemaTransferType { throw new Error(`Transfer type '${type}' is not mapped!`) } } + +function transferStateToGraphql(state: LedgerTransferState): TransferState { + switch (state) { + case LedgerTransferState.PENDING: + return TransferState.Pending + case LedgerTransferState.POSTED: + return TransferState.Posted + case LedgerTransferState.VOIDED: + return TransferState.Voided + default: + throw new Error(`Transfer state '${state}' is not mapped!`) + } +} diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index ef622b214d..f8286e14d4 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -1062,6 +1062,19 @@ type AccountingTransfer implements Model { ledger: UInt8! "The date and time that the accounting transfer was created." createdAt: String! + "The state of the accounting transfer." + state: TransferState! + "The date and time that the accounting transfer will expire." + expiresAt: String +} + +enum TransferState { + "The accounting transfer is pending" + PENDING + "The accounting transfer is posted" + POSTED + "The accounting transfer is voided" + VOIDED } enum TransferType { diff --git a/packages/backend/src/open_payments/payment/outgoing/worker.ts b/packages/backend/src/open_payments/payment/outgoing/worker.ts index a2e688036f..05631aa7b7 100644 --- a/packages/backend/src/open_payments/payment/outgoing/worker.ts +++ b/packages/backend/src/open_payments/payment/outgoing/worker.ts @@ -75,7 +75,7 @@ async function getPendingPayment( [RETRY_BACKOFF_SECONDS, now] ) }) - .withGraphFetched('[walletAddress, quote.asset]') + .withGraphFetched('quote') stopTimer() return payments[0] } @@ -89,6 +89,16 @@ async function handlePaymentLifecycle( return } + const [paymentWalletAddress, quoteAsset] = await Promise.all([ + deps.walletAddressService.get(payment.walletAddressId), + deps.assetService.get(payment.quote.assetId) + ]) + + payment.walletAddress = paymentWalletAddress + if (quoteAsset) { + payment.quote.asset = quoteAsset + } + const stopTimer = deps.telemetry.startTimer('handle_sending_ms', { callName: 'OutoingPaymentWorker:handleSending' }) diff --git a/packages/backend/src/payment-method/local/service.test.ts b/packages/backend/src/payment-method/local/service.test.ts index 94cff4100e..72196b298d 100644 --- a/packages/backend/src/payment-method/local/service.test.ts +++ b/packages/backend/src/payment-method/local/service.test.ts @@ -58,6 +58,11 @@ describe('LocalPaymentService', (): void => { scale: 2 }) + assetMap['USD_9'] = await createAsset(deps, { + code: 'USD_9', + scale: 9 + }) + assetMap['EUR'] = await createAsset(deps, { code: 'EUR', scale: 2 @@ -67,6 +72,10 @@ describe('LocalPaymentService', (): void => { assetId: assetMap['USD'].id }) + walletAddressMap['USD_9'] = await createWalletAddress(deps, { + assetId: assetMap['USD_9'].id + }) + walletAddressMap['EUR'] = await createWalletAddress(deps, { assetId: assetMap['EUR'].id }) @@ -261,6 +270,7 @@ describe('LocalPaymentService', (): void => { ${'EUR'} | ${100n} | ${'USD'} | ${100n} | ${1.0} | ${'cross currency, same rate'} ${'EUR'} | ${100n} | ${'USD'} | ${112n} | ${0.9} | ${'cross currency, exchange rate < 1'} ${'EUR'} | ${100n} | ${'USD'} | ${50n} | ${2.0} | ${'cross currency, exchange rate > 1'} + ${'USD_9'} | ${100_000_000n} | ${'USD'} | ${10n} | ${1.0} | ${'local currency, different scale'} `( '$description', async ({ @@ -270,6 +280,7 @@ describe('LocalPaymentService', (): void => { expectedDebitAmount, exchangeRate }): Promise => { + if (incomingAssetCode !== 'USD_9') return let ratesScope if (incomingAssetCode !== debitAssetCode) { diff --git a/packages/backend/src/rates/util.test.ts b/packages/backend/src/rates/util.test.ts index 129f9a4631..f4e28f40da 100644 --- a/packages/backend/src/rates/util.test.ts +++ b/packages/backend/src/rates/util.test.ts @@ -1,8 +1,8 @@ import { convertSource, Asset, convertDestination } from './util' describe('Rates util', () => { - describe('convert', () => { - describe('convert same scales', () => { + describe('convertSource', () => { + describe('convertSource same scales', () => { test.each` exchangeRate | sourceAmount | assetScale | expectedResult | description ${1.5} | ${100n} | ${9} | ${{ amount: 150n, scaledExchangeRate: 1.5 }} | ${'exchange rate above 1'} @@ -31,11 +31,12 @@ describe('Rates util', () => { ) }) - describe('convert different scales', () => { + describe('convertSource different scales', () => { test.each` - exchangeRate | sourceAmount | sourceAssetScale | destinationAssetScale | expectedResult | description - ${1.5} | ${100n} | ${9} | ${12} | ${{ amount: 150_000n, scaledExchangeRate: 1500 }} | ${'convert scale from low to high'} - ${1.5} | ${100_000n} | ${12} | ${9} | ${{ amount: 150n, scaledExchangeRate: 0.0015 }} | ${'convert scale from high to low'} + exchangeRate | sourceAmount | sourceAssetScale | destinationAssetScale | expectedResult | description + ${1.5} | ${100n} | ${9} | ${12} | ${{ amount: 150_000n, scaledExchangeRate: 1500 }} | ${'convert scale from low to high'} + ${1.5} | ${100_000n} | ${12} | ${9} | ${{ amount: 150n, scaledExchangeRate: 0.0015 }} | ${'convert scale from high to low'} + ${1} | ${100_000_000n} | ${9} | ${2} | ${{ amount: 10n, scaledExchangeRate: 0.0000001 }} | ${'exchange rate of 1 with different scale'} `( '$description', async ({ @@ -57,7 +58,7 @@ describe('Rates util', () => { ) }) }) - describe('convert reverse', () => { + describe('convertDestination', () => { describe('convert same scales', () => { test.each` exchangeRate | destinationAmount | assetScale | expectedResult | description @@ -88,8 +89,9 @@ describe('Rates util', () => { describe('convert different scales', () => { test.each` exchangeRate | destinationAmount | sourceAssetScale | destinationAssetScale | expectedResult | description - ${2.0} | ${100n} | ${9} | ${12} | ${{ amount: 50_000n, scaledExchangeRate: 0.002 }} | ${'convert scale from low to high'} - ${2.0} | ${100_000n} | ${12} | ${9} | ${{ amount: 50n, scaledExchangeRate: 2000 }} | ${'convert scale from high to low'} + ${2.0} | ${100n} | ${12} | ${9} | ${{ amount: 50_000n, scaledExchangeRate: 0.002 }} | ${'convert scale from low to high'} + ${2.0} | ${100_000n} | ${9} | ${12} | ${{ amount: 50n, scaledExchangeRate: 2000 }} | ${'convert scale from high to low'} + ${1} | ${100_000_000n} | ${2} | ${9} | ${{ amount: 10n, scaledExchangeRate: 10000000 }} | ${'convert scale from high to low (same asset)'} `( '$description', async ({ diff --git a/packages/backend/src/rates/util.ts b/packages/backend/src/rates/util.ts index 5f5d35c3da..d6d789bebf 100644 --- a/packages/backend/src/rates/util.ts +++ b/packages/backend/src/rates/util.ts @@ -36,7 +36,7 @@ export function convertSource(opts: ConvertSourceOptions): ConvertResults { export function convertDestination( opts: ConvertDestinationOptions ): ConvertResults { - const scaleDiff = opts.sourceAsset.scale - opts.destinationAsset.scale + const scaleDiff = opts.destinationAsset.scale - opts.sourceAsset.scale const scaledExchangeRate = opts.exchangeRate * 10 ** scaleDiff return { diff --git a/packages/backend/src/telemetry/service.test.ts b/packages/backend/src/telemetry/service.test.ts index c8e59f025f..c599b2d335 100644 --- a/packages/backend/src/telemetry/service.test.ts +++ b/packages/backend/src/telemetry/service.test.ts @@ -13,6 +13,7 @@ import { import { Counter, Histogram } from '@opentelemetry/api' import { privacy } from './privacy' import { mockRatesApi } from '../tests/rates' +import { ConvertResults } from '../rates/util' jest.mock('@opentelemetry/api', () => ({ ...jest.requireActual('@opentelemetry/api'), @@ -355,14 +356,17 @@ describe('Telemetry Service', () => { expect(internalConvertSpy).not.toHaveBeenCalled() }) - it('should apply privacy', async () => { + it('should apply privacy by default', async () => { const convertedAmount = 500n jest //"any" to access private ts class member variable // eslint-disable-next-line @typescript-eslint/no-explicit-any .spyOn(telemetryService as any, 'convertAmount') - .mockImplementation(() => Promise.resolve(convertedAmount)) + .mockResolvedValueOnce({ + scaledExchangeRate: 1, + amount: convertedAmount + } as ConvertResults) const applyPrivacySpy = jest .spyOn(privacy, 'applyPrivacy') .mockReturnValue(123) @@ -378,14 +382,49 @@ describe('Telemetry Service', () => { value: 100n, assetCode: 'USD', assetScale: 2 - } + }, + undefined ) expect(applyPrivacySpy).toHaveBeenCalledWith(Number(convertedAmount)) + expect(incrementCounterSpy).toHaveBeenCalledWith(counterName, 123, {}) + }) + + it('should not apply privacy if preservePrivacy is false', async () => { + const convertedAmount = 500n + + jest + //"any" to access private ts class member variable + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(telemetryService as any, 'convertAmount') + .mockResolvedValueOnce({ + scaledExchangeRate: 1, + amount: convertedAmount + } as ConvertResults) + + const applyPrivacySpy = jest.spyOn(privacy, 'applyPrivacy') + const incrementCounterSpy = jest.spyOn( + telemetryService, + 'incrementCounter' + ) + + const counterName = 'test_counter' + await telemetryService.incrementCounterWithTransactionAmount( + counterName, + { + value: 100n, + assetCode: 'USD', + assetScale: 2 + }, + undefined, + false + ) + + expect(applyPrivacySpy).not.toHaveBeenCalled() expect(incrementCounterSpy).toHaveBeenCalledWith( counterName, - 123, - expect.any(Object) + Number(convertedAmount), + {} ) }) @@ -421,7 +460,10 @@ describe('Telemetry Service', () => { //"any" to access private ts class member variable // eslint-disable-next-line @typescript-eslint/no-explicit-any .spyOn(telemetryService as any, 'convertAmount') - .mockImplementation(() => Promise.resolve(10000n)) + .mockResolvedValueOnce({ + scaledExchangeRate: 1, + amount: 100n + } as ConvertResults) const incrementCounterSpy = jest.spyOn( telemetryService, 'incrementCounter' @@ -437,14 +479,15 @@ describe('Telemetry Service', () => { value: 100n, assetCode: 'USD', assetScale: 2 - } + }, + { attribute: 'metric attribute' } ) expect(convertSpy).toHaveBeenCalled() expect(incrementCounterSpy).toHaveBeenCalledWith( counterName, obfuscatedAmount, - expect.any(Object) + { attribute: 'metric attribute' } ) }) }) diff --git a/packages/backend/src/telemetry/service.ts b/packages/backend/src/telemetry/service.ts index 30426df973..0075845ec4 100644 --- a/packages/backend/src/telemetry/service.ts +++ b/packages/backend/src/telemetry/service.ts @@ -173,14 +173,13 @@ export class TelemetryServiceImpl implements TelemetryService { return } - const obfuscatedAmount = preservePrivacy - ? privacy.applyPrivacy(Number(converted)) - : Number(converted) - this.incrementCounter(name, obfuscatedAmount, attributes) + const finalAmount = preservePrivacy + ? privacy.applyPrivacy(Number(converted.amount)) + : Number(converted.amount) + this.incrementCounter(name, finalAmount, attributes) } catch (e) { - this.deps.logger.error(e, `Unable to collect telemetry`) + this.deps.logger.error(e, 'Unable to collect telemetry') } - return Promise.resolve() } public recordHistogram( diff --git a/packages/documentation/astro.config.mjs b/packages/documentation/astro.config.mjs index 6fcebb9397..bc4c6da494 100644 --- a/packages/documentation/astro.config.mjs +++ b/packages/documentation/astro.config.mjs @@ -257,9 +257,9 @@ export default defineConfig({ ], plugins: [ starlightLinksValidator({ - errorOnLocalLinks: false, - }), - ], + errorOnLocalLinks: false + }) + ] }), GraphQL({ schema: '../backend/src/graphql/schema.graphql', diff --git a/packages/documentation/src/content/docs/integration/playground/overview.mdx b/packages/documentation/src/content/docs/integration/playground/overview.mdx index cdc5b4cb24..f078cdb9ef 100644 --- a/packages/documentation/src/content/docs/integration/playground/overview.mdx +++ b/packages/documentation/src/content/docs/integration/playground/overview.mdx @@ -178,6 +178,7 @@ You can either trigger the debugger by adding `debugger` statements in the code #### Debugging with VS Code: To debug with VS Code, add this configuration to your `.vscode/launch.json`: + ```json { "name": "Attach to docker (cloud-nine-backend)", @@ -220,7 +221,7 @@ The following are the most commonly used commands: | pnpm localenv:compose up | Start (with TigerBeetle) | | pnpm localenv:compose up -d | Start (with TigerBeetle) detached | | pnpm localenv:compose down | Down (with TigerBeetle) | -| pnpm localenv:compose down --volumes | Down and kill volumes (with TigerBeetle) +| pnpm localenv:compose down --volumes | Down and kill volumes (with TigerBeetle) | | pnpm localenv:compose down --volumes --rmi all | Down, kill volumes (with TigerBeetle) and images | | pnpm localenv:compose:psql config | Show all merged config (with PostgreSQL) | | pnpm localenv:compose build | Build all the containers (with TigerBeetle) | @@ -292,4 +293,4 @@ test.rafiki.viXmy1OVHgvmQakNjX1C6kQMri92DzHeISEv-5VzTDuFhrpsrkDzsq5OO9Lfa9yed0L2 #### TigerBeetle container exists with code 137 -There is a known issue when running TigerBeetle in Docker. The container exits without logs and simply shows error code 137. To fix this, increase the Docker memory limit. If you run the local Docker playground on a Windows machine via the Windows Subsystem for Linux (WSL), you can increase the memory limit by configuring your `.wslconfig` file. \ No newline at end of file +There is a known issue when running TigerBeetle in Docker. The container exits without logs and simply shows error code 137. To fix this, increase the Docker memory limit. If you run the local Docker playground on a Windows machine via the Windows Subsystem for Linux (WSL), you can increase the memory limit by configuring your `.wslconfig` file. diff --git a/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx b/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx index b214273aae..9f206994b4 100644 --- a/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx +++ b/packages/documentation/src/content/docs/integration/prod/docker-compose.mdx @@ -37,8 +37,12 @@ Deploy a general purpose VM with the following minimum specifications: Install the following software on the VM: -- Docker Engine -- Docker Compose +- + Docker Engine + +- + Docker Compose + ### Install Nginx and Certbot @@ -263,7 +267,7 @@ Create nginx configuration files for every exposed domain: | Open Payments resource server | DOMAIN | myrafiki.com | /etc/nginx/sites-available/open_payments_resource_server.config | | ILP Connector | ilp.DOMAIN | ilp.myrafiki.com | /etc/nginx/sites-available/ilp.config | | Open Payments auth server | auth.DOMAIN | auth.myrafiki.com | /etc/nginx/sites-available/open_payments_auth_server.config | -| Admin UI | admin.DOMAIN | admin.myrafiki.com | /etc/nginx/sites-available/admin.config | +| Admin UI | admin.DOMAIN | admin.myrafiki.com | /etc/nginx/sites-available/admin.config | diff --git a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx index 0cf5ace685..fec4af6a08 100644 --- a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx @@ -11,6 +11,7 @@ Each payment account belonging to your users (e.g., customers) must have at leas - Your Rafiki instance must be set up for at least one asset before wallet addresses can be created as each wallet address must have an asset assigned to it. - Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical. + ::: ## Create wallet addresses @@ -84,12 +85,12 @@ We strongly recommend you store at least the `walletAddress.id` in your internal
-|Variable | Description | -|-------- | ----------- | -| `assetId` | The unique ID of the asset, assigned by Rafiki when the asset was created, that the wallet address's underlying payment account is denominated in | -| `publicName` | The public name associated with the wallet address | -| `url` | The wallet address's case-insensitive URL | -| `additionalProperties` | Optional [additional properties](/apis/graphql/backend/inputobjects/#additionalpropertyinput) associated with the wallet address | +| Variable | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `assetId` | The unique ID of the asset, assigned by Rafiki when the asset was created, that the wallet address's underlying payment account is denominated in | +| `publicName` | The public name associated with the wallet address | +| `url` | The wallet address's case-insensitive URL | +| `additionalProperties` | Optional [additional properties](/apis/graphql/backend/inputobjects/#additionalpropertyinput) associated with the wallet address |
@@ -194,11 +195,11 @@ Open Payments -| Parameter | Required value | Description | -| --------- | -------------- | ----------- | -| `alg` | `EdDSA` | The algorithm used to generate the key pair | -| `kty` | `OKP` | The key type identifying the cryptographic algorithm family used with the key | -| `crv` | `Ed25519` | The cryptographic curve used with the key | +| Parameter | Required value | Description | +| --------- | -------------- | ----------------------------------------------------------------------------- | +| `alg` | `EdDSA` | The algorithm used to generate the key pair | +| `kty` | `OKP` | The key type identifying the cryptographic algorithm family used with the key | +| `crv` | `Ed25519` | The cryptographic curve used with the key | @@ -285,4 +286,4 @@ mutation RevokeWalletAddressKey($input: RevokeWalletAddressKeyInput!) { } ``` - \ No newline at end of file + diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx index 661635b3ed..756bb1a841 100644 --- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx @@ -163,6 +163,7 @@ Rafiki-Signature: t=, v= ### Prepare the signed payload string To recreate the signed payload string, concatenate the following. + - The timestamp extracted from the header - A period (.) character - The actual JSON payload from the request body, containing the `id`, `type`, and `data` attributes @@ -178,6 +179,7 @@ Use HMAC SHA-256 with the `SIGNATURE_SECRET` environment variable as the key and Finally, compare the signature in the header to the expected signature you generated. For security, use a constant-time comparison function to prevent timing attacks. ### Example + Below is an example in JavaScript to verify Rafiki's webhook signature: diff --git a/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx b/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx index f24c3b01a7..96bc7319cb 100644 --- a/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx +++ b/packages/documentation/src/content/docs/overview/concepts/telemetry.mdx @@ -167,9 +167,9 @@ When the `ENABLE_TELEMETRY` variable is `true`, the following are required.
-| Variable name | Type | Description | -| ------------- | ---- | ----------- | -| `INSTANCE_NAME` | String | Your Rafiki instance's name used to communicate for telemetry and auto-peering. For telemetry, it's used to distinguish between the different instances pushing data to the telemetry collector. | Y | +| Variable name | Type | Description | +| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | +| `INSTANCE_NAME` | String | Your Rafiki instance's name used to communicate for telemetry and auto-peering. For telemetry, it's used to distinguish between the different instances pushing data to the telemetry collector. | Y |
@@ -177,13 +177,13 @@ When the `ENABLE_TELEMETRY` variable is `true`, the following are required.
-| Variable name | Type | Description | -| ------------- | ---- | ----------- | -| `ENABLE_TELEMETRY` | Boolean | Enables the telemetry service on Rafiki. Defaults to `true`. | -| `LIVENET` | Boolean | Determines where to send metrics. Defaults to `false`, resulting in metrics being sent to the testnet OTEL Collector.

Set to `true` on production environments dealing with real money.

| -| `OPEN_TELEMETRY_COLLECTOR_URLS` | String | A CSV of URLs for OTEL Collectors (e.g., `http://otel-collector-NLB-e3172ff9d2f4bc8a.elb.eu-west-2.amazonaws.com:4317,http://happy-life-otel-collector:4317`). | -| `OPEN_TELEMETRY_EXPORT_INTERVAL` | Number | Indicates, in milliseconds, how often the instrumented Rafiki instance should send metrics. Defaults to`15000`. | -| `TELEMETRY_EXCHANGE_RATES_URL` | String |

Defines the endpoint Rafiki will query for exchange rates. Used as a fallback if/when [exchange rates](/integration/requirements/exchange-rates) aren’t provided.

When set, the response format of the external exchange rates API should be of type `rates`, as is expected by the rate service.

Defaults to `https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json`, which points to a public S3 that has the previously mentioned required format, updated daily.

| +| Variable name | Type | Description | +| -------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ENABLE_TELEMETRY` | Boolean | Enables the telemetry service on Rafiki. Defaults to `true`. | +| `LIVENET` | Boolean | Determines where to send metrics. Defaults to `false`, resulting in metrics being sent to the testnet OTEL Collector.

Set to `true` on production environments dealing with real money.

| +| `OPEN_TELEMETRY_COLLECTOR_URLS` | String | A CSV of URLs for OTEL Collectors (e.g., `http://otel-collector-NLB-e3172ff9d2f4bc8a.elb.eu-west-2.amazonaws.com:4317,http://happy-life-otel-collector:4317`). | +| `OPEN_TELEMETRY_EXPORT_INTERVAL` | Number | Indicates, in milliseconds, how often the instrumented Rafiki instance should send metrics. Defaults to`15000`. | +| `TELEMETRY_EXCHANGE_RATES_URL` | String |

Defines the endpoint Rafiki will query for exchange rates. Used as a fallback if/when [exchange rates](/integration/requirements/exchange-rates) aren’t provided.

When set, the response format of the external exchange rates API should be of type `rates`, as is expected by the rate service.

Defaults to `https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json`, which points to a public S3 that has the previously mentioned required format, updated daily.

|
diff --git a/packages/documentation/src/content/docs/resources/glossary.mdx b/packages/documentation/src/content/docs/resources/glossary.mdx index e2e7e0fbd7..4ac3065d05 100644 --- a/packages/documentation/src/content/docs/resources/glossary.mdx +++ b/packages/documentation/src/content/docs/resources/glossary.mdx @@ -62,7 +62,7 @@ An API standard and a set of APIs that allows clients to securely retrieve accou ## Outgoing payment -An object created by the sender’s ASE, on their resource server, that represents a payment being sent. This object contains information about the outgoing payment, such as the amount, currency, receiver’s wallet address, and payment status. +An object created by the sender’s ASE, on their resource server, that represents a payment being sent. This object contains information about the outgoing payment, such as the amount, currency, receiver’s wallet address, and payment status. ## Payment pointer @@ -78,14 +78,13 @@ An object created by the sender’s ASE, on their resource server, that represen ## Resource server -A server that hosts and manages access to protected Open Payments resources for incoming payments, quotes, and outgoing payments. +A server that hosts and manages access to protected Open Payments resources for incoming payments, quotes, and outgoing payments. Rafiki's `backend` service runs an Open Payments resource server. ## Simple Payment Setup Protocol (SPSP) -An Interledger application layer protocol for exchanging payment information between two counterparties to facilitate direct payments over Interledger. The information is then used to set up a STREAM connection. You can read more about SPSP in its specification -. +An Interledger application layer protocol for exchanging payment information between two counterparties to facilitate direct payments over Interledger. The information is then used to set up a STREAM connection. You can read more about SPSP in its specification. ## Streaming Transport for the Real-Time Exchange of Assets and Messages (STREAM) @@ -93,6 +92,6 @@ An Interledger transport layer protocol for sending and receiving authenticated ## Wallet address -A secure, unique URL that identifies an Open Payments-enabled account. It acts as an entry point to the Open Payments APIs, facilitating interactions like sending and receiving payments. +A secure, unique URL that identifies an Open Payments-enabled account. It acts as an entry point to the Open Payments APIs, facilitating interactions like sending and receiving payments. -A wallet address is publicly shareable and used to interact with the underlying payment account without compromising its security. Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical. \ No newline at end of file +A wallet address is publicly shareable and used to interact with the underlying payment account without compromising its security. Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical. diff --git a/packages/documentation/src/partials/auth-variables.mdx b/packages/documentation/src/partials/auth-variables.mdx index 663ee7a31d..d4851b48e3 100644 --- a/packages/documentation/src/partials/auth-variables.mdx +++ b/packages/documentation/src/partials/auth-variables.mdx @@ -4,8 +4,8 @@ import { LinkOut } from '@interledger/docs-design-system'
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | +| Variable | Helm value name | Default | Description | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `AUTH_DATABASE_URL` | `auth.postgresql.host`,
`auth.postgresql.port`,
`auth.postgresql.username`,
`auth.postgresql.database`,
`auth.postgresql.password` | `postgresql://postgres:password@localhost:5432/auth_development` | The URL of the Postgres database storing your Open Payments grant data. For Helm, these components are provided individually. | | `AUTH_SERVER_URL` | `auth.server.domain` | _undefined_ | The public endpoint for your Rafiki instance’s public Open Payments routes. | | `COOKIE_KEY` | `auth.cookieKey` | _undefined_ | The koa KeyGrip key that is used to sign cookies for an interaction session. | @@ -19,8 +19,8 @@ import { LinkOut } from '@interledger/docs-design-system'
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | +| Variable | Helm value name | Default | Description | +| --------------------------------- | ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ACCESS_TOKEN_DELETION_DAYS` | `auth.accessToken.deletionDays` | `30` | The days until expired and/or revoked access tokens are deleted. | | `ACCESS_TOKEN_EXPIRY_SECONDS` | `auth.accessToken.expirySeconds` | `600` (10 minutes) | The expiry time, in seconds, for access tokens. | | `ADMIN_API_SIGNATURE_VERSION` | `auth.adminApi.signatureVersion` | `1` | The version of the request signing algorithm used to generate signatures. | diff --git a/packages/documentation/src/partials/backend-variables.mdx b/packages/documentation/src/partials/backend-variables.mdx index b68cd50c06..68fdb527b0 100644 --- a/packages/documentation/src/partials/backend-variables.mdx +++ b/packages/documentation/src/partials/backend-variables.mdx @@ -4,8 +4,8 @@ import { LinkOut } from '@interledger/docs-design-system'
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | +| Variable | Helm value name | Default | Description | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `AUTH_SERVER_GRANT_URL` | `backend.serviceUrls.AUTH_SERVER_GRANT_URL` | _undefined_ | The endpoint on your Open Payments authorization server to grant a request. | | `AUTH_SERVER_INTROSPECTION_URL` | `backend.serviceUrls.AUTH_SERVER_INTROSPECTION_URL` | _undefined_ | The endpoint on your Open Payments authorization server to introspect an access token. | | `DATABASE_URL` | `backend.postgresql.host`,
`backend.postgresql.port`,
`backend.postgresql.username`,
`backend.postgresql.database`,
`backend.postgresql.password` | `postgresql://postgres:password@localhost:5432/development` | The Postgres database URL of the database storing your resource data. For Helm, these components are provided individually. | @@ -24,8 +24,8 @@ import { LinkOut } from '@interledger/docs-design-system'
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | +| Variable | Helm value name | Default | Description | +| --------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INSTANCE_NAME` | `backend.instance.name` | _undefined_ | Your Rafiki instance's name used to communicate for auto-peering and/or [telemetry](/overview/concepts/telemetry). Required when auto-peering and/or telemetry is enabled |
@@ -34,8 +34,8 @@ import { LinkOut } from '@interledger/docs-design-system'
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | +| Variable | Helm value name | Default | Description | +| ----------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ADMIN_PORT` | `backend.port.admin` | `3001` | The port of your Backend Auth API server. | | `ADMIN_API_SIGNATURE_TTL_SECONDS` | _undefined_ | `30` | The TTL, in seconds, for which a request’s signature will be valid. | | `API_SECRET` | _undefined_ | _undefined_ | N/A | diff --git a/packages/documentation/src/partials/frontend-variables.mdx b/packages/documentation/src/partials/frontend-variables.mdx index a707bf1f54..6d39c11b15 100644 --- a/packages/documentation/src/partials/frontend-variables.mdx +++ b/packages/documentation/src/partials/frontend-variables.mdx @@ -4,11 +4,11 @@ import { LinkOut } from '@interledger/docs-design-system'
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | -| `GRAPHQL_URL` | `frontend.serviceUrls.GRAPHQL_URL` | _undefined_ | URL for Rafiki’s GraphQL Auth Admin API | -| `OPEN_PAYMENTS_URL` | `frontend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | Your Open Payments API endpoint | -| `PORT` | `frontend.port` | _undefined_ | Port from which to host the Rafiki Remix app | +| Variable | Helm value name | Default | Description | +| ------------------- | ---------------------------------------- | ----------- | -------------------------------------------- | +| `GRAPHQL_URL` | `frontend.serviceUrls.GRAPHQL_URL` | _undefined_ | URL for Rafiki’s GraphQL Auth Admin API | +| `OPEN_PAYMENTS_URL` | `frontend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | Your Open Payments API endpoint | +| `PORT` | `frontend.port` | _undefined_ | Port from which to host the Rafiki Remix app |
@@ -18,11 +18,11 @@ The following variables are required only when `AUTH_ENABLED` is set to `true`.
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | -| `KRATOS_ADMIN_URL` | `frontend.kratos.adminUrl` | _undefined_ | The admin endpoint/container address for Kratos | -| `KRATOS_CONTAINER_PUBLIC_URL` | `frontend.kratos.containerPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from within the Docker network. This is used for backend calls to Kratos. | -| `KRATOS_BROWSER_PUBLIC_URL` | `frontend.kratos.browserPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from a browser outside of the Docker network. This is used for calls from a browser (what you see in the Rafiki Admin UI) to the Kratos server on the backend. | +| Variable | Helm value name | Default | Description | +| ----------------------------- | ------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `KRATOS_ADMIN_URL` | `frontend.kratos.adminUrl` | _undefined_ | The admin endpoint/container address for Kratos | +| `KRATOS_CONTAINER_PUBLIC_URL` | `frontend.kratos.containerPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from within the Docker network. This is used for backend calls to Kratos. | +| `KRATOS_BROWSER_PUBLIC_URL` | `frontend.kratos.browserPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from a browser outside of the Docker network. This is used for calls from a browser (what you see in the Rafiki Admin UI) to the Kratos server on the backend. |
@@ -30,8 +30,8 @@ The following variables are required only when `AUTH_ENABLED` is set to `true`.
-| Variable | Helm value name | Default | Description | -| -------- | --------------- | ------- | ----------- | +| Variable | Helm value name | Default | Description | +| -------------------------------- | -------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AUTH_ENABLED` | `frontend.authEnabled` | `true` | When `true`, only authenticated users can be granted access to Rafiki Admin by an administrator | | `SIGNATURE_SECRET` | `frontend.quoteSignatureSecret` | _undefined_ | The signature secret used to authenticate requests to the Backend Admin API. | | `SIGNATURE_VERSION` | `frontend.signatureVersion` | `1` | The signature version number used to authenticate requests to the Backend Admin API. | @@ -39,4 +39,4 @@ The following variables are required only when `AUTH_ENABLED` is set to `true`. | `NODE_ENV` | `frontend.nodeEnv` | `production` | The type of node environment: `development`, `test`, or `production`. | | `LOG_LEVEL` | `frontend.logLevel` | `info` | Pino log level | -
\ No newline at end of file +
diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 4bc2279d6f..1116595860 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -32,10 +32,14 @@ export type AccountingTransfer = Model & { creditAccountId: Scalars['ID']['output']; /** Unique identifier for the debit account. */ debitAccountId: Scalars['ID']['output']; + /** The date and time that the accounting transfer will expire. */ + expiresAt?: Maybe; /** Unique identifier for the accounting transfer. */ id: Scalars['ID']['output']; /** Identifier that partitions the sets of accounts that can transact with each other. */ ledger: Scalars['UInt8']['output']; + /** The state of the accounting transfer. */ + state: TransferState; /** Type of the accounting transfer. */ transferType: TransferType; }; @@ -1372,6 +1376,15 @@ export enum SortOrder { Desc = 'DESC' } +export enum TransferState { + /** The accounting transfer is pending */ + Pending = 'PENDING', + /** The accounting transfer is posted */ + Posted = 'POSTED', + /** The accounting transfer is voided */ + Voided = 'VOIDED' +} + export enum TransferType { /** Represents a deposit transfer. */ Deposit = 'DEPOSIT', @@ -1812,6 +1825,7 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; TriggerWalletAddressEventsMutationResponse: ResolverTypeWrapper>; @@ -1966,8 +1980,10 @@ export type AccountingTransferResolvers; creditAccountId?: Resolver; debitAccountId?: Resolver; + expiresAt?: Resolver, ParentType, ContextType>; id?: Resolver; ledger?: Resolver; + state?: Resolver; transferType?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/frontend/app/shared/utils.ts b/packages/frontend/app/shared/utils.ts index b837ac39b7..bbcb147fc0 100644 --- a/packages/frontend/app/shared/utils.ts +++ b/packages/frontend/app/shared/utils.ts @@ -60,14 +60,18 @@ export function capitalize(str: string) { } export function getOpenPaymentsUrl() { - if (!process.env.OPEN_PAYMENTS_URL) { - throw new Error('Environment variable OPEN_PAYMENTS_URL is missing') - } - if (typeof window === 'undefined') { + if (!process.env.OPEN_PAYMENTS_URL) { + throw new Error('Environment variable OPEN_PAYMENTS_URL is missing') + } + return process.env.OPEN_PAYMENTS_URL } + if (!window.ENV.OPEN_PAYMENTS_URL) { + throw new Error('Environment variable OPEN_PAYMENTS_URL is missing') + } + return window.ENV.OPEN_PAYMENTS_URL } diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 9437d6ec24..46dad2dedb 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -32,10 +32,14 @@ export type AccountingTransfer = Model & { creditAccountId: Scalars['ID']['output']; /** Unique identifier for the debit account. */ debitAccountId: Scalars['ID']['output']; + /** The date and time that the accounting transfer will expire. */ + expiresAt?: Maybe; /** Unique identifier for the accounting transfer. */ id: Scalars['ID']['output']; /** Identifier that partitions the sets of accounts that can transact with each other. */ ledger: Scalars['UInt8']['output']; + /** The state of the accounting transfer. */ + state: TransferState; /** Type of the accounting transfer. */ transferType: TransferType; }; @@ -1372,6 +1376,15 @@ export enum SortOrder { Desc = 'DESC' } +export enum TransferState { + /** The accounting transfer is pending */ + Pending = 'PENDING', + /** The accounting transfer is posted */ + Posted = 'POSTED', + /** The accounting transfer is voided */ + Voided = 'VOIDED' +} + export enum TransferType { /** Represents a deposit transfer. */ Deposit = 'DEPOSIT', @@ -1812,6 +1825,7 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; TriggerWalletAddressEventsMutationResponse: ResolverTypeWrapper>; @@ -1966,8 +1980,10 @@ export type AccountingTransferResolvers; creditAccountId?: Resolver; debitAccountId?: Resolver; + expiresAt?: Resolver, ParentType, ContextType>; id?: Resolver; ledger?: Resolver; + state?: Resolver; transferType?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c8c80cad1..80beef9929 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,7 @@ overrides: braces@<3.0.3: '>=3.0.3' '@grpc/grpc-js@>=1.10.0 <1.10.9': '>=1.10.9' dset@<3.1.4: '>=3.1.4' + cross-spawn@>=7.0.0 <7.0.5: '>=7.0.5' importers: @@ -5278,7 +5279,7 @@ packages: resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 is-glob: 4.0.3 open: 8.4.0 picocolors: 1.1.1 @@ -5398,7 +5399,7 @@ packages: cacache: 17.1.4 chalk: 4.1.2 chokidar: 3.5.3 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 dotenv: 16.4.1 es-module-lexer: 1.4.1 esbuild: 0.17.6 @@ -5488,7 +5489,7 @@ packages: cacache: 17.1.4 chalk: 4.1.2 chokidar: 3.5.3 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 dotenv: 16.4.1 es-module-lexer: 1.4.1 esbuild: 0.17.6 @@ -9072,8 +9073,8 @@ packages: dependencies: tslib: 2.8.0 - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} dependencies: path-key: 3.1.1 @@ -10460,7 +10461,7 @@ packages: '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.7(supports-color@9.4.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 @@ -10650,7 +10651,7 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -11004,7 +11005,7 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 dev: true @@ -15342,7 +15343,7 @@ packages: hasBin: true dependencies: ansi-styles: 6.2.1 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 memorystream: 0.3.1 minimatch: 9.0.3 pidtree: 0.6.0 diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 9437d6ec24..46dad2dedb 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -32,10 +32,14 @@ export type AccountingTransfer = Model & { creditAccountId: Scalars['ID']['output']; /** Unique identifier for the debit account. */ debitAccountId: Scalars['ID']['output']; + /** The date and time that the accounting transfer will expire. */ + expiresAt?: Maybe; /** Unique identifier for the accounting transfer. */ id: Scalars['ID']['output']; /** Identifier that partitions the sets of accounts that can transact with each other. */ ledger: Scalars['UInt8']['output']; + /** The state of the accounting transfer. */ + state: TransferState; /** Type of the accounting transfer. */ transferType: TransferType; }; @@ -1372,6 +1376,15 @@ export enum SortOrder { Desc = 'DESC' } +export enum TransferState { + /** The accounting transfer is pending */ + Pending = 'PENDING', + /** The accounting transfer is posted */ + Posted = 'POSTED', + /** The accounting transfer is voided */ + Voided = 'VOIDED' +} + export enum TransferType { /** Represents a deposit transfer. */ Deposit = 'DEPOSIT', @@ -1812,6 +1825,7 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; TriggerWalletAddressEventsMutationResponse: ResolverTypeWrapper>; @@ -1966,8 +1980,10 @@ export type AccountingTransferResolvers; creditAccountId?: Resolver; debitAccountId?: Resolver; + expiresAt?: Resolver, ParentType, ContextType>; id?: Resolver; ledger?: Resolver; + state?: Resolver; transferType?: Resolver; __isTypeOf?: IsTypeOfResolverFn; };