From 5496612bd37b052ac766556ca801dc2603a74c00 Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Tue, 20 Aug 2024 10:43:18 +0200 Subject: [PATCH 1/3] feat(tests): initial performance test (#2828) * feat(tests): initial performance test * feat: add test shell scripts, run inside docker network * feat: run script in docker container * chore: formatting * chore: remove unused script * fix: update test script parameters * fix: ping wallet address hosts instead of wallet addresses * fix README * feat: move wallet address retrieval into setup function * feat: add another error condition * formatting * fix: typechecks * chore: remove comments * chore: remove empty metadata object --- pnpm-lock.yaml | 79 ++++----- pnpm-workspace.yaml | 2 +- test/performance/README.md | 30 ++++ test/performance/package.json | 18 ++ .../scripts/create-outgoing-payments.js | 154 ++++++++++++++++++ test/performance/scripts/run-tests.sh | 57 +++++++ 6 files changed, 301 insertions(+), 39 deletions(-) create mode 100644 test/performance/README.md create mode 100644 test/performance/package.json create mode 100644 test/performance/scripts/create-outgoing-payments.js create mode 100755 test/performance/scripts/run-tests.sh diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7f897338c..ba7cf22bc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -784,6 +784,12 @@ importers: specifier: ^2.5.0 version: 2.5.0 + test/performance: + dependencies: + hostile: + specifier: ^1.4.0 + version: 1.4.0 + packages: /@aashutoshrathi/word-wrap@1.2.6: @@ -870,7 +876,6 @@ packages: zen-observable-ts: 1.2.5 transitivePeerDependencies: - '@types/react' - dev: true /@apollo/client@3.9.9(@types/react@18.2.73)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-/sMecU/M0WK9knrguts1lSLV8xFKzIgOMVb4mi6MOxgJXjliDB8PvOtmXhTqh2cVMMR4TzXgOnb+af/690zlQw==} @@ -5058,7 +5063,6 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 - dev: true /@mdx-js/mdx@2.3.0: resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} @@ -5227,7 +5231,7 @@ packages: resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.2 + semver: 7.6.0 dev: true /@npmcli/git@4.1.0: @@ -5240,7 +5244,7 @@ packages: proc-log: 3.0.0 promise-inflight: 1.0.1 promise-retry: 2.0.1 - semver: 7.6.2 + semver: 7.6.0 which: 3.0.1 transitivePeerDependencies: - bluebird @@ -5256,7 +5260,7 @@ packages: json-parse-even-better-errors: 3.0.1 normalize-package-data: 5.0.0 proc-log: 3.0.0 - semver: 7.6.2 + semver: 7.6.0 transitivePeerDependencies: - bluebird dev: true @@ -5452,7 +5456,7 @@ packages: '@opentelemetry/api': ^1.7.0 dependencies: '@opentelemetry/api': 1.8.0 - '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.8.0) + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.8.0) transitivePeerDependencies: - supports-color @@ -5467,8 +5471,8 @@ packages: '@opentelemetry/api': 1.8.0 '@opentelemetry/api-logs': 0.52.1 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.9.0 - require-in-the-middle: 7.3.0 + import-in-the-middle: 1.11.0 + require-in-the-middle: 7.4.0 semver: 7.6.2 shimmer: 1.2.1 transitivePeerDependencies: @@ -5724,7 +5728,7 @@ packages: '@opentelemetry/api': ^1.1.0 dependencies: '@opentelemetry/api': 1.8.0 - '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.8.0) + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) dev: false /@ory/client@1.9.0: @@ -6717,7 +6721,7 @@ packages: resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} dependencies: '@types/node': 20.14.15 - '@types/qs': 6.9.12 + '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -6726,7 +6730,7 @@ packages: dependencies: '@types/body-parser': 1.19.5 '@types/express-serve-static-core': 4.17.43 - '@types/qs': 6.9.12 + '@types/qs': 6.9.14 '@types/serve-static': 1.15.5 /@types/graceful-fs@4.1.5: @@ -6839,7 +6843,7 @@ packages: '@types/http-errors': 2.0.4 '@types/keygrip': 1.0.2 '@types/koa-compose': 3.2.5 - '@types/node': 20.12.7 + '@types/node': 20.14.15 dev: true /@types/koa__cors@5.0.0: @@ -6961,12 +6965,8 @@ packages: /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - /@types/qs@6.9.12: - resolution: {integrity: sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==} - /@types/qs@6.9.14: resolution: {integrity: sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==} - dev: true /@types/range-parser@1.2.7: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -7116,7 +7116,7 @@ packages: grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.6.2 + semver: 7.6.0 tsutils: 3.21.0(typescript@5.4.3) typescript: 5.4.3 transitivePeerDependencies: @@ -7229,7 +7229,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.60.1(typescript@5.4.3) '@typescript-eslint/utils': 5.60.1(eslint@8.57.0)(typescript@5.4.3) - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 eslint: 8.57.0 tsutils: 3.21.0(typescript@5.4.3) typescript: 5.4.3 @@ -7283,7 +7283,7 @@ packages: dependencies: '@typescript-eslint/types': 5.60.1 '@typescript-eslint/visitor-keys': 5.60.1 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 @@ -7329,7 +7329,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.2 + semver: 7.6.0 ts-api-utils: 1.0.1(typescript@5.4.3) typescript: 5.4.3 transitivePeerDependencies: @@ -7389,7 +7389,7 @@ packages: '@typescript-eslint/types': 7.5.0 '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) eslint: 8.57.0 - semver: 7.6.2 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -7668,7 +7668,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -8630,7 +8630,6 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} @@ -9779,7 +9778,7 @@ packages: resolution: {integrity: sha512-h0Ow21gclbYsZ3mkHDfsYNDqtRhXS8fXr51bU0qr1dxgTMJj0XufbzX+jhNOvA8KuEEzn6JbvLVhXyv+fny9Uw==} engines: {node: '>= 8.0'} dependencies: - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 readable-stream: 3.6.0 split-ca: 1.0.1 ssh2: 1.11.0 @@ -11834,7 +11833,6 @@ packages: minimist: 1.2.8 once: 1.4.0 split: 1.0.1 - dev: true /html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} @@ -11903,7 +11901,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -11926,7 +11924,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -12035,8 +12033,8 @@ packages: engines: {node: '>=12.2'} dev: true - /import-in-the-middle@1.9.0: - resolution: {integrity: sha512-Ng1SJINJDBzyUEkx9Mj32XD8G0TQCUb5TMoL9V91CTn6F3wYZLygLuhNFrv0cNMBZaeptnL1zecV6XrIdHJ+xQ==} + /import-in-the-middle@1.11.0: + resolution: {integrity: sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q==} dependencies: acorn: 8.12.0 acorn-import-attributes: 1.9.5(acorn@8.12.0) @@ -12629,7 +12627,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -13430,7 +13428,7 @@ packages: content-disposition: 0.5.4 content-type: 1.0.5 cookies: 0.9.1 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 delegates: 1.0.0 depd: 2.0.0 destroy: 1.2.0 @@ -14741,7 +14739,7 @@ packages: resolution: {integrity: sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==} dependencies: '@types/debug': 4.1.7 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.0.6 micromark-factory-space: 1.0.0 @@ -15148,7 +15146,7 @@ packages: dependencies: hosted-git-info: 6.1.1 is-core-module: 2.13.0 - semver: 7.6.2 + semver: 7.6.0 validate-npm-package-license: 3.0.4 dev: true @@ -16616,7 +16614,6 @@ packages: optional: true react: optional: true - dev: true /rehype-expressive-code@0.35.6: resolution: {integrity: sha512-pPdE+pRcRw01kxMOwHQjuRxgwlblZt5+wAc3w2aPGgmcnn57wYjn07iKO7zaznDxYVxMYVvYlnL+R3vWFQS4Gw==} @@ -16889,8 +16886,8 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - /require-in-the-middle@7.3.0: - resolution: {integrity: sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==} + /require-in-the-middle@7.4.0: + resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} engines: {node: '>=8.6.0'} dependencies: debug: 4.3.5 @@ -17217,6 +17214,14 @@ packages: lru-cache: 6.0.0 dev: true + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + /semver@7.6.2: resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} engines: {node: '>=10'} @@ -17526,7 +17531,6 @@ packages: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} dependencies: through: 2.3.8 - dev: true /sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} @@ -18089,7 +18093,6 @@ packages: /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true /tigerbeetle-node@0.15.4: resolution: {integrity: sha512-HJRZ9OQzv56cE+EqqkzLk2VMkPRroFlv1/HGLN7wjcdElOAz78GX5fBfi7/IsJWacIrpIdgSkZnvKDOWQtJXyQ==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7d4d7b52cc..9566f88d3c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,5 @@ packages: - 'packages/*' - 'localenv/mock-account-servicing-entity' - - 'test/integration' + - 'test/*' - 'bruno/collections/Rafiki' diff --git a/test/performance/README.md b/test/performance/README.md new file mode 100644 index 0000000000..7ae13794e5 --- /dev/null +++ b/test/performance/README.md @@ -0,0 +1,30 @@ +# Performance Tests + +This package contains a script that will determine the performance of Rafiki by repeatedly making a series of requests to a Rafiki instance to create several kinds of resources (receivers, quotes, outgoing payments). + +## Prerequisites + +- [Grafana k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) + + - [Grafana k6](https://grafana.com/docs/k6/latest/) is used to run performance test scripts against Rafiki. + +- [Running local playground for Rafiki](../../localenv/README.md) + - It is recommended to start the local playground with Telemetry running in order to see the impact of a performance test. + +If the local environment isn't running it may be started by using the command `pnpm localenv:compose:telemetry:up`. + +## Run tests + +To run the performance tests (of which there is currently only one): + +``` +pnpm --filter performance run-tests +``` + +The test makes a few checks to verify the local playground is running, then runs the k6 binary on the [create-outgoing-payments.js](./scripts/create-outgoing-payments.js) script. + +The test can also be run inside of a Docker container on the same Docker network as the Local Playground: + +``` +pnpm --filter performance run-tests-docker +``` diff --git a/test/performance/package.json b/test/performance/package.json new file mode 100644 index 0000000000..64414cd095 --- /dev/null +++ b/test/performance/package.json @@ -0,0 +1,18 @@ +{ + "name": "performance", + "version": "1.0.0", + "description": "", + "scripts": { + "test": "k6 run ./scripts/create-outgoing-payments.js", + "test-docker": "docker run --rm --network=rafiki_rafiki -v ./scripts:/scripts -i grafana/k6 run /scripts/create-outgoing-payments.js", + "run-tests": "./scripts/run-tests.sh", + "run-tests-docker": "./scripts/run-tests.sh --docker", + "hostile": "hostile" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "hostile": "^1.4.0" + } +} diff --git a/test/performance/scripts/create-outgoing-payments.js b/test/performance/scripts/create-outgoing-payments.js new file mode 100644 index 0000000000..1b3b883206 --- /dev/null +++ b/test/performance/scripts/create-outgoing-payments.js @@ -0,0 +1,154 @@ +import http from 'k6/http' +import { fail } from 'k6' +export const options = { + // A number specifying the number of VUs to run concurrently. + vus: 1, + // A string specifying the total duration of the test run. + duration: '600s' +} + +const HEADERS = { + 'Content-Type': 'application/json' +} + +const CLOUD_NINE_GQL_ENDPOINT = 'http://cloud-nine-wallet-backend:3001/graphql' +const CLOUD_NINE_WALLET_ADDRESS = + 'https://cloud-nine-wallet-backend/accounts/gfranklin' +const HAPPY_LIFE_BANK_WALLET_ADDRESS = + 'https://happy-life-bank-backend/accounts/pfry' + +export function setup() { + const c9WalletAddressesRes = http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + query GetWalletAddresses { + walletAddresses { + edges { + node { + id + url + } + } + } + } + ` + }), + { headers: HEADERS } + ) + + if (c9WalletAddressesRes.status !== 200) { + fail(`GraphQL Request failed to find ${CLOUD_NINE_WALLET_ADDRESS}`) + } + const c9WalletAddresses = JSON.parse(c9WalletAddressesRes.body).data + .walletAddresses.edges + const c9WalletAddress = c9WalletAddresses.find( + (edge) => edge.node.url === CLOUD_NINE_WALLET_ADDRESS + ).node + if (!c9WalletAddress) { + fail(`could not find wallet address: ${CLOUD_NINE_WALLET_ADDRESS}`) + } + + return { data: { c9WalletAddress } } +} + +// The function that defines VU logic. +// +// See https://grafana.com/docs/k6/latest/examples/get-started-with-k6/ to learn more +// about authoring k6 scripts. +// +export default function (data) { + const { + data: { c9WalletAddress } + } = data + + const createReceiverResponse = http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + mutation CreateReceiver($input: CreateReceiverInput!) { + createReceiver(input: $input) { + receiver { + id + } + } + } + `, + variables: { + input: { + expiresAt: null, + metadata: { + description: 'Hello my friend', + externalRef: null + }, + incomingAmount: { + assetCode: 'USD', + assetScale: 2, + value: 1002 + }, + walletAddressUrl: HAPPY_LIFE_BANK_WALLET_ADDRESS + } + } + }), + { + headers: HEADERS + } + ) + + const receiver = JSON.parse(createReceiverResponse.body).data.createReceiver + .receiver + + const createQuoteResponse = http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + mutation CreateQuote($input: CreateQuoteInput!) { + createQuote(input: $input) { + quote { + id + } + } + } + `, + variables: { + input: { + walletAddressId: c9WalletAddress.id, + receiveAmount: null, + receiver: receiver.id, + debitAmount: { + assetCode: 'USD', + assetScale: 2, + value: 500 + } + } + } + }), + { + headers: HEADERS + } + ) + + const quote = JSON.parse(createQuoteResponse.body).data.createQuote.quote + + http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + mutation CreateOutgoingPayment($input: CreateOutgoingPaymentInput!) { + createOutgoingPayment(input: $input) { + payment { + id + } + } + } + `, + variables: { + input: { + walletAddressId: c9WalletAddress.id, + quoteId: quote.id + } + } + }), + { headers: HEADERS } + ) +} diff --git a/test/performance/scripts/run-tests.sh b/test/performance/scripts/run-tests.sh new file mode 100755 index 0000000000..17b23bc059 --- /dev/null +++ b/test/performance/scripts/run-tests.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +c9_gql_url="http://localhost:3001/graphql" +c9_wallet_address="http://localhost:3000/" +hlb_wallet_address="http://localhost:4000/" + +# Verify that the localenv backend is live +if curl -s --head --request GET "$c9_gql_url" | grep "HTTP/1.[01]" > /dev/null; then + echo "Localenv is up: $c9_gql_url" +else + echo "Localenv is down: $c9_gql_url" + exit 1 +fi + +# Verify that cloud nine mock ase is live +if curl -s --head --request GET "$c9_wallet_address" | grep "HTTP/1.[01]" > /dev/null; then + echo "Cloud Nine Wallet Address is up: $c9_wallet_address" +else + echo "Cloud Nine Wallet Address is down: $c9_wallet_address" + exit 1 +fi + +# Verify that happy life bank mock ase is live +if curl -s --head --request GET "$hlb_wallet_address" | grep "HTTP/1.[01]" > /dev/null; then + echo "Happy Life Bank Address is up: $hlb_wallet_address" +else + echo "Happy Life Bank Address is down: $hlb_wallet_address" + exit 1 +fi + +# setup hosts +addHost() { + local hostname="$1" + + # check first to avoid sudo prompt if host is already set + if pnpm --filter performance hostile list | grep -q "127.0.0.1 $hostname"; then + echo "$hostname already set" + else + sudo pnpm --filter performance hostile set 127.0.0.1 "$hostname" + if [ $? -ne 0 ]; then + echo "Error: Failed to write hosts to hostfile." + exit 1 + fi + fi +} +addHost "cloud-nine-wallet-backend" +addHost "cloud-nine-wallet-auth" +addHost "happy-life-bank-backend" +addHost "happy-life-bank-auth" + +# run tests +if [[ $* == *--docker* ]]; then + pnpm --filter performance test-docker +else + pnpm --filter performance test +fi +exit $? From 3fd3b902f939501e57d2be4ed1c28519acc04039 Mon Sep 17 00:00:00 2001 From: Sarah Jones Date: Tue, 20 Aug 2024 12:38:17 +0200 Subject: [PATCH 2/3] docs: updated READMEs and added Rafiki Admin docs (#2867) * docs: updated READMEs and added Rafiki Admin docs --- localenv/README.md | 12 ++---- packages/documentation/astro.config.mjs | 14 +++++++ .../documentation/public/img/rafiki-admin.png | Bin 0 -> 25631 bytes .../documentation/public/img/rafiki-auth.png | Bin 0 -> 26981 bytes .../src/content/docs/introduction/overview.md | 2 +- .../src/content/docs/playground/overview.md | 10 ++--- .../src/content/docs/rafikiadmin/auth.md | 35 ++++++++++++++++ .../src/content/docs/rafikiadmin/overview.md | 11 +++++ packages/frontend/README.md | 38 +++++++++++++----- 9 files changed, 98 insertions(+), 24 deletions(-) create mode 100644 packages/documentation/public/img/rafiki-admin.png create mode 100644 packages/documentation/public/img/rafiki-auth.png create mode 100644 packages/documentation/src/content/docs/rafikiadmin/auth.md create mode 100644 packages/documentation/src/content/docs/rafikiadmin/overview.md diff --git a/localenv/README.md b/localenv/README.md index b6a13bab4c..98860cb907 100644 --- a/localenv/README.md +++ b/localenv/README.md @@ -9,7 +9,7 @@ These packages include: - `mock-account-servicing-entity` (mocks an [Account Servicing Entity](https://rafiki.dev/concepts/account-servicing-entity/) - `frontend` (Remix app to expose a UI for Rafiki Admin management via interaction with the `backend` Admin APIs) - `kratos` (An identity and user management solution for the `frontend`) -- `mailslurper` (A SMTP mail server to catch account recovery emails) +- `mailslurper` (A SMTP mail server to catch account recovery emails for the `frontend`) These packages depend on the following databases: @@ -255,15 +255,11 @@ Note that you have to go through an interaction flow by clicking on the `redirec #### Admin UI -In order to manage, and view information about the Rafiki instance(s) using a UI, you can navigate to [`localhost:3010`](http://localhost:3010) (Cloud Nine Wallet) or [`localhost:4010`](http://localhost:4010) (Happy Life Bank). This is the `frontend` project which runs a Remix app for querying info and executing mutations against the Rafiki [Admin APIs](#admin-apis). +In order to manage, and view information about the Rafiki instance(s) you can use the [Rafiki Admin](../packages/frontend/README.md) UI. We have secured access to Rafiki Admin using [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro). Since access to the UI is on an invitation-only basis the registration flow is not publicly available. As such, in order to access Rafiki Admin you can click the registration link provided in the logs during `localenv` startup or you can manually add a new user with the invite-user script. Run `docker exec -it npm run invite-user -- example@mail.com` and it will output a link to the terminal. Copy and paste this link in your browser and you will automatically be logged in and directed to the account settings page. The next step is changing your password. We're using a simple email and password authentication method. -We have secured access to the Admin UI using [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro), a secure and fully open-source identity and user management solution. Check it out on [GitHub](https://github.com/ory/kratos). Since access to the UI is on an invitation-only basis the registration flow is not publicly available. As such, in order to access the Admin UI you can click the registration link provided in the logs during `localenv` startup or you can manually add a new user with the invite-user script. Run `docker exec -it npm run invite-user -- example@mail.com` and it will output recovery link to the terminal. The recovery link doubles as the invitation method. Copy and paste this link in your browser and you will automatically be logged in and directed to the account settings page. The next step is changing your password. We're using a simple email and password authentication method. +Note that a separate registration is required for Cloud Nine Wallet's Rafiki Admin and Happy Life Bank's Rafiki Admin, since they are each designed to run as separate mock account servicing entities. Once you've registered, you can always come back to your Rafiki Admin account by navigating to [`localhost:3010`](http://localhost:3010) (Cloud Nine Wallet) or [`localhost:4010`](http://localhost:4010) (Happy Life Bank) and logging in. -There is a password recovery flow. On the login page if you clkick the `forgot password` link and enter an email for a registered user then you can open [Mail Slurper](http://localhost:4436) to access the recovery link for your account. - -We've also included a script to remove users: `docker exec -it npm run delete-user -- example@mail.com`. - -See the `frontend` [README](../packages/frontend/README.md) for more information. +You can test the account recovery flow by clicking "Forgot pasword?" on the login page and by navigating to [`localhost:4436`](http://localhost:4436) (Mailslurper interface). #### Admin APIs diff --git a/packages/documentation/astro.config.mjs b/packages/documentation/astro.config.mjs index 786693173d..7b9a64ec5e 100644 --- a/packages/documentation/astro.config.mjs +++ b/packages/documentation/astro.config.mjs @@ -170,6 +170,20 @@ export default defineConfig({ } ] }, + { + label: 'Rafiki Admin', + collapsed: true, + items: [ + { + label: 'Overview', + link: 'rafikiadmin/overview' + }, + { + label: 'Rafiki Admin Auth', + link: 'rafikiadmin/auth' + } + ] + }, { label: 'Reference', collapsed: true, diff --git a/packages/documentation/public/img/rafiki-admin.png b/packages/documentation/public/img/rafiki-admin.png new file mode 100644 index 0000000000000000000000000000000000000000..001099bd38fafba3001d279d98cf7ba6ceb941ad GIT binary patch literal 25631 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Ӛu`KKkELuKSjWw-KM2OgBzy;GP_ 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 literal 0 HcmV?d00001 diff --git a/packages/documentation/public/img/rafiki-auth.png b/packages/documentation/public/img/rafiki-auth.png new file mode 100644 index 0000000000000000000000000000000000000000..d84849bb09c289d715cbeba6f3256ffbb8dabfa4 GIT binary patch literal 26981 zcmbsQbyQVR_W+9S6ExDPbTNQd#<_WnmbraL-`)=eOv$l?x{Rc&;bAp5CC9ruwiHj z-6#GJ=mwyrrl)vyesXtrcM_f8Z{XgzG|>?<-s^BSXK86Ewd>Et$>G7y z=HA-U&CSj6{!VXS|Jl+0%EIjI=GM%7SctY;g)z#VI-r3oiL2WgwkoNW2484dA z@{RA>`3aqn;r03c_4(Gx`N{F|v7V^*Oo9WGxZm~34_0L1_31Y4bC`UMR74@ck2EyRf)qlD&Gp+1$Eul37soP+V{0^i1R1 zw7063xnoTDTfeU(r@Om*!XJOmr@J+tUi$q$eiYPCFCF?sSlj$V@!vnm<<5q-(pnjF zr@RlX&uc?jWh~Mxb&3Lf{?4_$2x=&=ZoL>O_x&7uUFRRzxNLl&n-3?@h z?zJbYxu%%E2}ZsNSg8LhfsA+=^|QPrceyvm-odx~v!!d(R#sNNb@Ar2wCS2KU%JO1 z>pjh)(kj9_O5XV=Z8gSA8-w}pRg9pymrn}nn(}gu$!<_A{ z)b);fa?(?i+UE!VtR2~SMc>`7mQ4IPFZk;9;gh$c@81N=;{FvY=g{L=9|sfUm$r@O zXuqrFh;?0;NURS)bqT5FT>U>qbut?$nth@_#`*=>_v3#V zopX?)p6yB1_s*8Yb03eB!tR8|>BQ>3#*DZaq}OTVM0azoQ+w#&yehHuFt?vS4m7mR ztX1|c;?04xL;!fnprRnF=P|W2!>9ujAOjbB6SFFXdv{|0jEdoh$U6}}s-V#B(+-hC ziUBQs1fOTE=1;B&4mH=BSvCnkzM*33xbtQcwVW@J1i10&7A8aqyp#1Q#VG@rQHj+7Oh{yn>fy+_(@eJOu}G;bje*9H@5I8;ryR z_=WB5S0GUgy9|c7u6QL{N`(0q=<@Lau(HLa3ab^KKQ$UsV$>$E&-$!jGV#ti+DVQH zYl(^E)4Qm5lSfgZ?Oc)mtv?5sS$CzchVo{EQd-h_XuQ??HYIiaTeY8nv==_Gg{Vi+ z9R^^51gZu?Y?x4RDv%Ky0m1wp43Hv$K!$$+wtqM(zkh(le>f!e|5xHAdbmdk@usE4ZyvAH z^saDddxE58W|!-=hkfz9~SWrt9rXFVltO#b@I zp(%0!Osp(P9JOT9oUAckM>6|D&b>n;aQNrwg> z_l)%&KhU;$PuNSBJ7eY<_p7D#+g$hNz?0nqhNP#8wSSO>W*Dj&V_fFQrdN>r3S4Z; zF4Pml0%o6^uBIbJLxX|U1|TTHlp%g!K`JM zJ>0rNZG(qS<>GuX$DKcA9XZ_eKR)@=F#0Hlo_Rq#%1JL0jDO!e*WBy^r#U3TRj$tp zaK}fcl0NMj|F)hqu@?uqzM)BbFFR2-hBIe@zU=F)pQg&MA9xDlAYhgPpKca zHci}1h?kHc`amGy zfjz@XE#rIJU*X576Owj>2)iVdjywd!i|-+N>8b9wh zV0-DInPiR$rQ9$MJPgv=fors4#u=}(SIjU>Sgo{;qn}HLprewrmBL{~nsU9;AVF&@ zsR+BysvKXus^rA@j^dV_&p0Nu8={Qo@vwo+l){bT0F|1+FUGL0t`#_XYEx!5(a}I& zQKZ3u%zIZ+^V;RIOFU-njlpa$AqC%zKs6X@Hv4i}(Z&qVXw_L`2KS$2)YdR&+U^uT zy|Y%-E@!65i^8G8QL7fQwu1}^GCWtUaN;g9EPGD0{JR%4c1?I-j+)pK5NlVm zWPB%df6BW6ad>2!h}J;mUKC?rR}nI>TxhZY#+P@Ie_e!BA%T?A*k6v1K|2Yg?!7C4 zF)=N495NxR7H{H;RaH@m)yJ!19F#a#>c-Gc76v!2<_sALf#IE*ZvA@pD4)bfEh72PXa zT%QHX^@*zU{L12%)_zhV0)k~NeoQQv#ZZ>d5>RwSv3}T54m*ZYRTiP)YxU4_P( z!-!o`qglPAY0pFAK3ZUeeOO9F-m0~$Cbb3_fdN0jb^E%E*>&j!0}6U;+?!`+seh4Ml>oD zGhTy&B(tm~V8^|gNq=vmvWB7L&v)^s{|qwD1^$SW@em$X#-AMU7_TWQ9K^HZwVO6X zeU^?4W-TIv+&()Uu?rvoC*IY~STDh`KT;@Y5f%2ozel<*WFJagCU)1xd;00qE55kK zEUYd_(173L)=Eh(_%BIQ2M?#UfISjJlneiSiL>q9VVv@qjTkH1om@r5fk`Y3B_L;w zvz|5{S!6hPp90*!Hh3L+BC1PARB4^(OIQ<=< z7QS|UiyzH=?FY`!ydSN4Z8@ByyhqEU!i6*@ETAsH)gI=SAI?pWz4rogM{xpK6DZPu z6!_wu{|UzR?sAvpm*M*l9z-O+w&Bp=Lz3AJ8k*bxpk53-xd=D&FpqhPdjCLwrWbmv z{*-P}JLwhu5l#$#v``lBg6M#ZU+wHbF*J)tp{FmL%lO?NG$Z*w_e(rl!MfPFK+$7)=Kvpfj9auXj(gMyi{Fm3#@p3 zbwZym)3~%$Im>ln$|@1Zg6+RCE&B_E9xTCU!xU5$dOe7rSotep{A3Lc#@SNKnQq}} zag_*1tV)5pf zBWaud>Vx@x)N2)0(dxb85ClJM30l|sqkvHZX9euk zov->+o_UiwBK=Oc{&GVpXxaCw&-YrsL!A)wlI1J!YReVPd9_D?z zAK}sar|&lmZgTXD5IIh(h)rPaxuD>&l(oOMcE>o(?a{FA{H86MP7O zT|Jst~-A>(1H^$^mQKeSk|Wa9rHJ~r(Y2W&3)Y11GO6lq`M$tHHq&4T@k^Ne9W@# zMwH@qO!ygjocY62l7PImBr9)H2mU}*DE~d&>p<_nXVZ?kb5E&I7`6BDwAL^Y56r&2 z>f;q=oN;t?4yVKa_T@xCsNYfJZ(=zAqWJp%opVv>B zcZm3)bEfv!I8qTa^fD{jqmztIu;Z4?Vs$DaO_f?=LtsDhZMN278*$U7mgvEW0fg#D zQM4A=4OG*(w_Nt2|Wz2f* zjFYNexo&J$j_BZ5{-gLLAsa|+V>9V>*ay)=J3(j(N#GnkVfTu|rNe7N^Q)@^d=o-pHx{zh=l*iEM-+fVmrN{iK( z_bL>R)QW!U5^*7KWQsdXuE$MH%`6Cy|fUTVnnx~rUF|k zkVO}+Af1AVqbnypkNlkA%>GG~>mBpc;z_)q>;ntK(`X43ep;mq1DYT2J zqmOzyeVgYmKAG7U@(!mU_@nD3?x#}iJN^--=D(1{Vkh_{;z%X^XwmGsERbn^PAI2J zPB6tB>Her3nA@D;y-8oiNW*ud?b#DCxRhiv!@6gTJ?JXC+@+Iw_^WG?f82Kyp=#uu z%R_uSWKQl(3+r$qLTS0yt;bMlhPrDRM_X4uZUvAD%L88OXL`G=TBG6<$K08@iFvK0 z&zkDR@AC+yxsXgqkp=HmLyc?`6Yx){byDx+@1Kvpw=&M=!*13GZgJm0U1>T9!1m0UI=4=G^UvG@T0U@;ZU8Pw-bEEO#0{XhNw@l#sw(Px9i{l`Y zmjpIVVtqNd0`gdZPYKze9X4J?&;9Bzv*IutVzpvxGO4mh*ZB_TtYro(zK&723q3!~ zWfwX^b6e7&J_9tU>s%pj&=;W$jYwBx1?#~_JG>_g<3W+pey6`G$x)|kA;7?!qMcap z%&JHHK08KXoLhQ{qx`+;)FEHfUdTu5D`k>fzYHYi?jOG`YfU(AwwCs;{uld{9-IAc9f8T3P6W z)sZDrWoljD`K!-Spon6KR{k{)sI)3>8}O_xahAi^*zs{h?enh30yOZl`^ws!w>~A+ z&mPM+eKZnuKT(kwRCUGb+2EXMN1Sw3<5NT{u)j~U#c)bz7>2nkz2Z+-&Z)+)mfhgZ zWzZTQBe%HBdv^I_sh1$+TTA_vR))73KmQ<*(~O^rvmi%~S@W1!@f;|V6&)ditLj8I z!gtomelBmjO&|RVi787Ll6h%o0Z&f>C@#Niwt<2P3SBtJR4S0m^Og)O`{ z6b

Z`26OvOKVCVM6yTl0?L14J+{^TP+BVIA;79HOT~SiV=9oeHx6DflgRFg7BOY zr|OXn%RT#QW^g+ut8SwN$m6CSwS|!ZR2AnsVW8&S$yoJz2ZmE~!iWMN31->#vwnrM zsw_H`DBybJVD5T7A-K_p z@{#@iFl{&i;9uo@0hK9u8%9m_vQ{l}`lt`c-_Lk<(k2jOebRQ%KyOz9qt9L^e)>gO zK(a9fmdXGYUowNdousHE`L^hg4OcZh zt9WvqTEh@EwL8-+>7B$*ciD13o);&`<67!3H^NN(^ERoq z7hn-+;ZCRqeD(v1fQ9H8`U%#5E< zM&Zwy0yz)d025`@vl>opPdZDW=)qq#W@HKhbfOn)z4{dvBA21)GCJ+~S1NGRl?Ti; ztEq9VXocW>fh|m5H!kzEWz(SYMj7G03B)KoEfP#bj;2(?mF$rlv;5NK{uq*evlB@nW!^3p*&MBCbA#scRiL}d`je~-!0?V(D)iZ26uuih>m+VdaVc&ly zMPqWHHI<|o@O`h}b8AO~2;~Cn;a+WEt5WEPSeEJM^;H8Cru;7Ev{ewQ+duDumA=^i z38pY^%24tOmootN@m&f_-ZY2%T8JTHp9wI7?jml}ya@A!7wa|Srb!abvnM(hA%m5D z5Pr+D1kD^Q6SX?$rfw(_-q=A?ycls0W#FhUqE-zkvG1xgU;{Y7!jmSL{;_#n-O72Ws5Fe|CD9k-^l+Jzr8i1Nubow zdFtW|i8Y$yYm;30A#^MW|yvORT}hKTbuGO^Nwv}d4?wr-+vU)tWm_S z$rS+VNt+7w;Y?#+IIWz!q6NJS`q-4MFO$#EKX#rp>hKeMTGS@jK#uCBel8@;nD0e%|*v7NZ+SCX2C8)>{{0RUOi$Xdk%`7}{4v z2@$5R%&V9xdH*LY`KOhOlR}8VF{4z;DJ`MTQp!ElOyJAkXky<#~V zbyf3F2U?{M&mt|-gb*$q8*|6^YK+5E5sgD1jwL^8x_@-eqH5)zobt6Uu4g#~3`}g& zaPpg5CyURY9@GCYDbJ2vUx6EL3n@XKihhh`rT_a!V(IsTV@YHB8IyyMiJm%RQ65SH zm^|~wZC6dnud5P$g0`(k6oBeSEwU_&Cb&e}sASA)F8mw0? zeri8lL|#${)s(SW5{9@KpfF_YYZ7`oL!!K6JiL+u@F7Ll>8p}wl#m0v&|r5%afk45{*y zgVfCvA5r*m-!yZScXDC`{C9}b?n&XP%ZrIJ6=^5EgcWnxVBj_Py|1Vn_gG0FB~W6CW!WhKfFJ`?nCh9o$wGzCdjjq*?z;+)F#_`Z8cFDW2rrTEh1 z&%Kn(!D5_KLD~sS-gQ-d>8viny-Z~~BSAcn@ZrqCoSd|?4{IXLtAL~Ga-YqJ4(#~5Ziww(nYlTOOFo->6OZ~P{04Q;- z_;o1cw6x<*HNX>Lov%?McN@VY+W-9hOrvMuW8Q<(aU2pJ_=|%m#^jR3u8P0~mxSwT=mcL6_Yz>VbDQw2{|&JJ`C3|z#Gd%cs@e>ny`YTEBt5|<4%9#e8 z^y!)3nN_tTeSy9qblg`;hvTZ3scf*`tIDQeA-2ub!|n_TH7a*woI^ZN$<&9rZDwHS z*i3zj|HQ;NlERRoRpyW-cj)Jn@xaIL)moOYfIN{z{^rWZ?+-(*t^|9%oC&7<>>y(^ zTe15E(t*Qubi(0~z4&$4NYhnAd7NBeKH;sjxGV_q@JoWEMi}hf%thB3XY%ee{>bcy zoUNZ~r#y-qCmF{QV{N4tJ;g=PP*6;9uw6P+=ue`vF604;k)g z3z@(>dpS`;SJrsptCQOR*GnJMmNx(h?6aWK7c+Q_52Vb^y&gO-wT+;_N^uwf zpZEgwl=v_&rl47INW7LrvH$IqkkqXRtr?%%!$jjh0n_=hpYEy)tFJG&07w3tgt!}X zDNlDm0{j54!+<@C$*YI*?MX5i?DQo_5j)5O45&U_C#TIePDxY)%h*hd9%4G`qh&e) z|BeQP0o$n%!>AL<^9v`K+5c#9eWf&Dbi zv-ds&VmL;q#{nP)elnZ~$smVK)xjM2sDt<$n}?x4Btxg#ZaaVgP0Po&vPg>?$V$09YOw zgaV3>B0zV=nn-rSMwobnp8;KT1ol_gJeeVdZUG`TJn!Lp<-~%Av!xAp!wx2myz3Tp%FHGfKQ*Q5ZE{E&g`Y9KTfW zkQbxQ(w(iCYbI$%I^q4z;j-54cd7sYiyRMW#JpoeBg+za^z1IwtA=c$!%@(6D5~+B zJ$2ZXM5P50ltwL_6bDlCS*!tSS_7pcbUH?^ZQ8T)qSppl9SHzTMr`n$ChbQ+Jk9@rt1;@z=Ukg>t{cAy22U7{n|ay{9J0=t{i_~;7Kx+q zOvk?zj6X5%rnPx$##8Js6oG@TF2F831Nal!cjZ-^_&D9gKD;aH`DZe44=yNA`SITK z*S9VObDk%UNzR{-(ATGuM^@fWO_(%Si@1K^LGhX!6gR(^JC20;0qtCHBVBO3(Nnel zOU@kI{rI}>nUCIJbB+2N!du*fL}T|i%|7?wRQeIXHnvm7e%^iAyqV`E(cd1_j(re) zFxA?ywX(B;y`%|JmY$!97B$hD31~hBwRf=(?yx*Yo_ye`{W93IRpvT5^Klg-U1qz8 z8A)2bMGL1I4F~LSoTk6H-xG|R=G}WvTs`L6*bOVnA!Fstyv>vm!$uUzfx=-7LrdIv zWumXabvKF_RY~}NdT!U_tY6u|wbzy1D~Nw4>T{lD5F>-})n1YOlbw>y2g-s9zwkO?YrYD1f#V z^bMD9lc6CLGNIH=b{aW$v?gUFq31_P{gY&T1hSCfwwx4gp>tD%5 z2P-BR)2Ou1kji0`;Naj8w~=<8&s*vmcRanlxoV=Xnk~H%5sHh^2WkkC)c)3wW%716O7dV3Q_}p9^Pq%did=-PD0I?`sd?&w9 zuR~e8oH63k5gG)>`es$ffd>8^0 zNd7|)MZP_WCJu1^t|Q(qmh)DL-fRoIWmD;QAZG}PKY$2NXMBqZ`mgNs0~9LhFHv!0 z%z`|{vF&ClF72z97aUV0^eFY>fUgr!l75mI6&fgbSN)^#0(I7GXED3RMagaT&#O2iaPB1!ZMPj=x1gYm6u%B!vm;&QYHcZ zm+%4)>>zCk!&2b_XtJLAK*~NtQBQOr3u&-x30xqQ69{9`7X!jmGH0h3UJdN`JnUxT zO=|^=^dLx<{5FVFH*#0g=Y_HqC)5Y*+I4(E22dFO0OC}|(+P&ZkxeXgmt*&D$&Z)%)8t~z$6r9E_rch9QabB}d3#U2IN zRZm&-e(Z^ioHhq|YM{{TUon&E;wn{Zw1Vmp*eX>Bz_iX=`)1g`S8#I4xvS15rB#nRmD z70nFuiatI{K#-0}i%n{Ea;%Rvc^}MF#6eNyJ&`(SQCRu{BBLz9Sy5tGc2fTB^S)} zu4;^QfZ^ijK5sE%)y%_Yv*MOJnvW}>p^*Ka1t`@+>`Vs*LMuQZdIg5kDeeYx4-S4v z9(*b8Nm|IRzh{fXLnx>zEzq6$W|~*L@}j?zhSM1d@N@%+C_Q~l4|bpHH6^#|H@4c( zTq51HcaobnlC~$8$yLPPKT{N;rVr)l@7`K{-bp|OK>}fIaHpGLaS{5TUie0y*JjOs zz2<7`iYR4j*XRB&HAM?9A`iBj?B-Unaez=kv;mQ$?F&%W0jSVMhW;e~*%Uc|iHalx zl%j9}7}_^n13i)xn}kuW-w~ML-8g^%!n4{4oid53Pdvp_t166*gD$>#0%2%GoUso* zvL`htxZu*?zGMN=w70ERgQKtkA5!3!{_i%xZ~Fb&DW9{M>=rHe4GX$YnK^Lbv*8FU zdj6+FP3}{JizJ{V0|))#;9Za$%>J2xZgo$mv)`)Hc^SCIQoyEGE|?$x2`flrlYPZl zrDf?|!Pdd^LM-IGqJsQ%a&CU^Nub%<5MBEdsnIov$6pPhBj_567#A76Q6Hay7OT|FJq zzcUoYvu|F)9T9wtCANy)s{L*7P~jM9S-%Fz)Ss?)zGLN&v+IW;(Vn#|02jw8m!jQl z#L8YYO4PQ}(mpX%%;0TcWS~etLl<<8q;Q#Z2bytBLJ`&-Kkr^LJPQfH>W=V7W}lF{q7>8Ddfg>W(sY=&+Ju!)6X(5TB?Z3YwMlxng>k zEgVDB=_6j5#GLc|w3Ld5!;HR1l;Lo2GA!u-rWYXjj%>U=BrS zGmW2#g?x3j2t~zGTAx!f)oso~JRuH)5gy;mO{k6w@IUx~-|SkfhvV_a_+ML6q^_ta z`QiP~nhlnxi>M@Ok@lky&(_N#P6qX5yv9gv%UD=XC5$Egc2VasWp0dhSc zapz;EdR{aaHw0ooe6a9AVze>q@UoyDy?1?27{ls9Kg{YMg>gR`25Yi1r4e4To>vTe zDQax-Yqv3Hc1>7n=@%ig3o24;0Uu1fD@(5K@vIdfHQY*q{120QcsOO&{x4#GyGY85 zX|3Hl73=x13so*CgO IEZCv+Dyn(Z}1B2TCrkYD3(XB(VasJ0~{;7qV*&%yi&o zgyNY}f%moMin-wIp8i{0=zrMEp5jk|jXfCafBvW@spq5855ZSC-H5yJQ%~@iKUagw znnfp`M&ydm;!W3_@DR1)E7e^~g04CJxQulgD>p`&#JnhI^UBo;$Cyk1QSZY zKX<@0*>)_m*rb73pmiSGEcx;^Gd@i0c5X_pJywo|fzl$G#l?w?CJQ_0p>q0Y)Q%BN8aS{=a?!fIK-=4P<3RX?^;K$430$&@93zR^@t<|Lr^krRf## z?!$l^asKP0f>+kKvSDEK4yQ~99hRyd`G6^MyiT;EGvAebV$t=pMp0EjL@(Bl!@A6o z(q)E(SFj>-^kF9|3qCQ!|R40 zzN{bgwnp<*uK=b$6@kRUaqu?4m_x7 z>%p2mQm}k6l@J_VovTCxO;&|d65ffFUV6hPi6lZIjUXCp;LSyoL+`|xD}Ok_KGePoY`$18`L9U4!h!%*IWQgf zpF)U`d2%358Tyn|fIi6b;Gh-o&j9}aMIXk~N-Di(z+FP58mcp2^S*+K1oEB(bZwFo zMiU;;0T#al+^dK6exDiaTY7jn zU`dcAFs<-D2Hhg~0%S_%_ZTp^lc4$WB}P7E=QN4rK^J;2)L|oxb%$&|DXE}Y3Q`0I z6IUj2t=UmGk z=SlSHVvYQNj?0HO*#E>wjQH!7rdhNIfkfRF})SWsvA|qW=>x zzqweoBe}UYDeZnYejq(Nz37o0-(>)q^Sb%6?M+SwNo9pz_w>*Dix3AwXnaZ;ATron zM7%MgrX~LHx4$yr5-voJPaZ#c8}^4aIJjK?&tu)F$16GnFge*0EixCa`wxY2*pQfo zp@Ss3Hi`iSE@cd{Aw$2{n^l{giA$YUG6(3ZnpYmx(i3XVQ3%DJ0zT_Gd?oon<5pLF zer-*}S&u!R^u33TTW%id+yHwA`%hiGb~(^f5Ki^4r=$0ZnBmD` zznIs3g%*ox)3gtbFWhAULxskm$yv1+H&-!>u7F2BcG>#)Se>^pzfzM`Zt@6ZqLxe+ z6o6B0k4gX!u1ss4Q)_#dxZ2%+?LGH=Aya-9EooTX;&JKHrvvU&Qhi%YvX;9jygZQs z&eW-#XmPYTeE~~s#F*hG@Zi4jdjDh%VcRrNb}wgse+yHt&jM^$oz!LEi0y*tADqcR z!e7St@i3sMpkb%F5~$9adQy4*+>Ci?0q_O>^B}?{?{|xif0*qIiS{rV@Q&@rG+(m$ zSu&RjTD;wEB1U?r^Kw~3fSedNuhmjax`jNf~)WK{`1v<4D1LpqFGbh=Q zSPtM3ZH5y)_HLrTBiY?A8hv97r;i3a62_c@!@opLa4n@vt2#ap>t=>92Lk)WM95wj z1A2Mp63gV9qpSF{O-)yT>ball>9Dvb`jSf;BNFbg4vUX-z&o}GDa$BR{sTEvx4Joq z+Id*Qu+|}#M|)UNtq5Iv0bDe2NG=KkMkc1F{J%834b%>WU+Z`l^d77^KVCgT)JV}^Xjg8Hs zI`3VLH^=))1buYDh?;P?zhK_EoP^zy^dvG%kHGq-EP+?+=@(!iG$!_R8-O*&=Co`3 zJ&WtBzK=qT+=r~$>G3YnTxx%x6l5F}iH&RC!_-&(!Dp0J=$#P+5PD-HUKaeonjpn} zl!wkuUekb|#N93rO0!$p7gpPg-U1V-*fis())MY6L z?sI?0)KFLx+o1;ujim|*GeQX61zHz+{)7k>!6%cl*oi|L8THlV3N%1qKE>x#(zqmE z|7TNXJE1Y4%v)awzU2I%X26Z6<%;J2ih2qL zC<~p`plC1^FKP50q<+Dh7jnsh3LpCo1V@o@XUl3EhZRE;rj}}tbzRPd^n$Gy(%SNa zo)+=$0YzqAF_qW)9X~YW6;#!0su&h=Jk;#T-H1andAr3R`}zNt>FxqcuGR?brzD3_2TVG(H5mwV1XxCqlyO(&WMNl5E&@dP5K7 z0tO8O&YjV2Vis7iY|m6KY&<&O*f>DD9mw815wfUpQrs2ZQl=%`x8H{03MirT*sr?8 zDEyFQpSsR4ure)|CUkWwKR*K-Ut;Z8dw<6B&}*{mkDpNSdbJ4od zcy#>t7EYD+oo%A!xmy&v^#a|>5N(P3H%c`z^3lJ&|0y zl3VQW-MlV;NO4C?wB+T-K&t)cj@L))vv$XPbKWR;7_hDhVOWQ+*MjI@>BRMcU;IB1 zkbiGPzJWIhxn1h^gg25X`g33<3G zJ(VFbck1~H?;uO@c^c6|cG6Aa1v|baj1&QEWcff_gttqv1-{D0Zf#7x!gDhGSwW11 zK`?=o6D*JSUDf%aQEhdd!oq30l9{}rLt8aXzjFn##1e;C8AqDixjveBt}8mu7XnEs z2o5<25))=v~}%wM=ndlfN^#t1Dk0@MrIo9{6X7T1V0m9Q_}miB=;cq zxUp3m{+)4~Z!)&;y^|d7tsBxACZ z>vi^@2Dc`R7eidG5&Yj^1&CD-`@sT2p@xP7l5lSehUtgT&JFhlZS3cAb2|w$wJ1j^ z;KlZ#G1Tz?m4y&KUy*44ylFAM4MhuDq3&tAkLtgfuDj77|6&(;>djn4P4TSJhOT~C z89eWZcRIcwF=R0Ozy#LX+<4_gVRYDjA&-$jQKxJj$>q<80=`Gy0`uP)Yv#yP7AZO+ zbB)GI^MQHy7OVupf*Pug=v))VxMj8BDHhc1ioVIK387l+B5gg$+$Ayccv7t)DDDCS zZ4r0=!&r4L!yl<<)6-AhG;3$!T=ZERd`WsuU`4VpuZb!ow22nQDH}SBu*t}=d{fFF zeLhwToO7Z|j7|Rv(#{cc3Qj=K<4kQ3A}IBB=q|nFM5TXdsS+g27Bsq{huDW0N@!$W z>w=e@+7s3AT}Yv%4Aj&(qS8c*k&;u;0)0(ulO=;v*DUxQH*G^eBXNva)g?M{+;A^u zL2Z%2`JWdab7GGhTs3TaPa9t-hFykmyirY!+$2*NvHh~cf~v}uHQdRK=Ju?tVn@1x z01OoQ3sZZMMfNV}y``b4XAQklCa*+; zEseFKa6Baks`BUD{>oX;dzIUzg|-B->fURC)Fz=A^qmE83x{a1qYY$%r{TjU&Epoc zh7cf)1{nyvB056f#;IZ-(8Ihy;8m~T+1{g?4zcj%i}T1QEL`pMn#}KOZ1rhBCKng( zu05e8&*}Y`=3M+jq-%|))ATK=zTu2ul=xCe(&svPY{qZ}h0jljZr^&PjWHz@XI{LP z*|#z|pZ>egJ@1ufE|NlUo3X(`a2HeQ0g3}W#;z;@8JQA59SbN5L8i9>VHvq%v;ZFk zk^sPqEvX35s?vLg1wsydVl)gD%89dx>!`8hgYLMEl3moW@kSOV7E;vgtL zIH;|M0k#7k+~{gH^a{8DQzjJ6f8S{cQ0qV$0|ZH?4_R;m=5Rp_93V3f7l_A4VEy+! z2;%_F4LY}>s1JzofRs1)z`{3MF`C#w<^&E91VNmO0uHZUiveiScdh_(At11OX@skOSdFs9=02-NyF9U(3lZZG5>6qXZ)k2%poMhnhNOryeNv zjdrc$teKIFH@sooypoP8HMA)WG$LI!xvUB1a(*x)F|lW&yOPvzdo1uz5hjp@d8~x1zeM56GptxsUWK91A=dXw)8Ze2dgS=MC*ewspyW=s0mZ;LcNE96t06m9%L<=1;7Wjdoe)f zGCwCB#B*M&0h67V;Op*g{K<(nwVD~)5Yu@T*j3}6(O*6l4jJ)u%}Dg2>|gf!-;Cda zoQ&0X3)#ND5rg$kP8CjNV+r={JMOfLu#<_!iBu0%msQD>FkGyV0hlO`H7JT0!{Z6| z0`KKddB=7fUB0AzOH1{Yvx*u1dV>(2b5>mG0L$;+rioBfzw3GcDj?Gc4^SpRw)7yq zE~DI0Iip@zYLRxYGz@7RN}CGmI|b(NR-NY_6YW<0qA{1}h9@N2veyNk)k4C}AIi(2 zZ@d@M&ITYkk<+sk%#w~?)xp#S0-lehi<=i>b=UX!n`mU)N1kac4eh%Lk0mol_ZEM1pEn~rB&C|~ch1U%Ic1Dz=!;A8Q{IVxuNpWUi+lL!MmR*JBhqel@x zN-r!8!=IN`;7YO*--h-)zi)P?SXI;GiN_H5-}E)*XJ|cby+9D4qdAH zFtNQuECL|}F7qNFqW*B_jNAkNL7~p;?TP=RvoDW_>Wc%t_s$H1!Hji6_C&QUo5Gvb9ma@G&{XXw~-sgSZKkv;SbI$jB zzU#TnJ?DJC=N#Xo$NtGS!s!^6y3A|T%_nq8MIL&4Y~yxo>4xxk{|=o@JH40PjHsG% zes9m5L)rpoWHqimQRIs7R0~%CM|gB~!2JBkbtd{_MC0Q{=ZZ;PZzi}wM7~v;m-fyn zeAXuFR8c^S!S%JJNqhI$&$|!Swz2o~ztMjAsxv9G|HR4moVl~I`zlm+bw{>pxM`KUWjjCs~cvAKl$s&ExJUE-|*rNstI+uwNi6; zu0Vh0eO((RNOz>vZ)7x*z07H-WQ7;#c(18(w7(I%+FX?V+&AfNP=tq?zfks#cljq0 zR8wbK&-C9`FDz8(p?vkx%homw@o~JZJ>J6kHZ0;vQiTghzp~+jBfa_DX}TxV&ZR27 zd;f{1+-C|euXTx@sS-VK@ZyU8)-0#AVd*3mNVUEf0*Jo)WqUXIscC$}se#_(znbtt z+VZpb%o77vjTztHY<;|`)RDy~T~@$S9QEg0-PMzY0HGJIz$8b=*S=Gkf4^F;E@|_L zaL*Lh`IY2;$Qb;)#)`|biHZg z??6s`78-l7%EvannXIcn<})W?aq;kXj-B!j!WN0U*aC>` zGMv(560-77PhGZqdaRKrK1R?V&uMzN-h@^f`v`yHa@BlTw^y~-XYjfP=M`6h77c>R|nPrN)H|2{EhPuI`~eyJeWD?DEq1o6Ctf>Z=a7tl9hNE zulyY!zKmQ(?AEvtiLxqyfF$qYp;q~PP(J0sDODB^M3sEPQ32bNsz83EJ4c5bmlBG! zMPGY6=|-S!$;m!9juOv_UD(WRXlByBcaTpqFPz7Q06unr%|nO^Xej`4hf(so0>JgB z02w~O3GX|Ks*%GXvbz8_>UD_9C&*$2Dao8L*!vju9^F##|H6Ju5eOE(k;FL!_*kP# zPwqil;+dky9e5BAEFxQ&hf!*F*^OF2LM_N7&?@+jX#=7xK!}Sz!%ert0b0_ik-N81 zcPRZ={r|MrS|8E2mVlN7|AV#{0}_`zpP%j_s9ymDg8yZup$F{OWvT@babtFjqVVl(%Hgbh(Sd@!f22 z$HZUnsc`?q&v8!W&L6|SHnkhqsJ(XW+}*vRd-6jrFw!hI?KwF7S)^~~&?GYRG`MO} zQbf(E&#w4ZcGP^+$%`Ata)Gi?NY#-7e~u=BlM|l`t_4~RomaeFz-tZCJip@U%0p8F zncsctcrV3w??KAOMMk*&>hdoRMw~pCn33?kkU8+j$}QpPT3G%X z!~Ooq&zk!Q8AEz&Yj|nMVdU|fbBlf7yJOq$^)D0)xq!5ML3U3Z1 zf4`9P;6k!APWQUxVX-~QJf_BU(^I%-@?FOcneY7}AERg^b1Q@ZpC0UY-8~ zPH-@?zJgS|{F(f+7H%x z8lAq@D?g>^sr!Z512D}+kWU60JrIigcDfg#N?198k4nx z58I7x9{wdHLS=9g;_oEXWKTY?x`jM7t^b;utAHQu9D02m?jv;%YR=3_u zgpiU^zFn@sB`csw9m=}43(5l0?XZ*`$8u-7OSu~;M6AjC7$|7bqH-&0h7W65``Lz+ zB1i>orIDkg|68HU!*n`k3CrLq$I>y?C33mD5fV0QR|aOV26>6qi2;sG=p(_0qo;%z z1jy#*IIuNy4cp>#MxJUv8yT^lw(*2_&T2A5;W6W9!w{=Cj1ROVhV_2|25cl2IBi&rNyb0@4*pWcaWm0BZun~4g-ULw8Rs^}N(et1 zdNYShv$bAx2e9IBQx@{%=6#cf_1I{ow8O^~`l(O$xAH7=^ zT{V0ujX2miR`t!>Hp zDAz|^!id!M7lBQMzGjEXO_78NrCs@M8fOZcsitzf+c5$!zHNVTfr35lyM0|L6w6Nx zsl4(jCC8XiPa9&qBR;lZ6xjD%oPMZrr7`*9&3U8dx6b+-m0~=?fDC()9I*skB>)&| zFb4U4B9a1orl@bT@Hwe?m3IGjzV(=iYm{{c{~~#({!Zx+tkjt1vR&}O)pz%*6_A%Z zaE9+lfl_LAuCGqz351=Q8N!7;7UBTVY5D8pk3i)gc7Y}a2K&c-ie?4xHZfHYvWX;+ zD)x+rd35_e%HBHw(@w3t`T67g*%ZNB_U8*hmi#3z;NX$J{SN~8f%aPg<)K0s+C|}v zIgOX0=$0(_>f%>G`G-?r)Ibuc@$*i`f)Eu#@PLeP5$MEy!dKDdo6gwCw@*8!@^(I& zPrL)JEd9>iMytJnCg@6_2^Ohh#cVW_g`4F7`^S8p@z>^p@sXHQ_hs9MaSUW-gEBA_ zBl$UZcM_#w^QpYc`{c+4X;wbd`DY@?pKSR`+scpP%3LD-x--_ciZksaII3g$OZN4^ z(#i_Yg;{YrsaJ-hL~|<7WE)FYb`cxB)RrUug!{tdw-t){VY>`Yb-g>Ic3}SZ0LJOy zqQ|e4mJoD(Vzz*XUQqRz%Q^gvh?>14J_tIkM?e;h6|NfknVA9PvZo?Qx#rTFa}gW# zr-1U=O0k_zU5xq*Er~$oZ*$8L+V&j~^7O!0U~aRwSfaPBv zG?ZU&wQb&Dkqcl$X3+&0;jeQoApj*zA4u&N1UAuhEey)_5>1vu^Gm*D-hr5LD?M12 z-%d{zfDz{An?De1;LGzx06jo$IRq6`*IVi)V|AKBXxRVLWM`Ug2X!JY{aB-*@v(uo zmf^cQ>+>$$r;YoLM$H}q@NM7OhkJrN@B;daX?XYtz5rYe+%++u9}w)j3DMhuEUk;q zaKpO6O~y(%4`2h>B<|%x+Vv-&uhmpMzbi3T@xB-Gc~Q0vpY-~mQDHa`h`J=h z4N}g@la%VxKDYtd?93$|V^6@w+ImxK!0-SD0V2KwS+;^e3ADNq#(rNNcya`X6akyT zHJHv5P^EuT`(a)bu4RDVZGa#%vkU2L?1&<;8D7t7&*HGz>Z@`aERU8 zg-)iC$_(|&1J{cxTfjjP+5=_H@UpoJm;%}zCn?jtx}a>zjpAnY#X8<)TzSYzrCZsu zhA#XJRnQr)3?I4pX4^ZpDroHP#c>J;^AId3jO6v>R{!_~1>($u)T_efkVnw^<4VBx z>z>)Edg!X}6p5cv5A0Wl?JxFs#NwpG?^)Tv>`?U$C!oY@s67@p_GY+U#xDe~O=Jjv zz|o3=7OBy_e?uE*;6$kSH4t@?1-}Vx%VVX09+TMjcesn;vect2cG{L&9#NOxl^SB6q| zQ(!KyG+;%j+wm|J5bk`Ar;UuTuXzu3do=AtH+sE%RPL<~t^dXINUh5!+r$}&1bt`g zC7Kli({iiB^h6&xm!KM<_&VY?{*LJKHEz08k`JgnF>T)Vk)G=X-%61Dg>#q{i&?Kd zpot{NtFcuM+MZcgt3IHC@FvJx+e&jxGFK>{02RXF8^ze1_g6=x?tmT`vArJ@Lt{S7 zQ`&$njkv>V`?DFkD$c3*w^7Z+c2pE|Xr)SRqiP{nZ_KF4^-|JZVO=o&Tj{r3O7+G@<$>jw4$zu;nr3Z0p1xBTwMXFUZb@e=$Ay z0FUhgcLm6_g(StT9&8uKzz!lc(cYqi@H9!&-64BlFazd2?O zkNT|GpPhfke2|Q{72hR|(1b!hd(9E++D$dFRK1MDUE};AyfLHP8yU zeux`Pc!^v%5U3;G6ui=@u>gS2lYs`ZUggA{l!n|%tJI}WVEaVfrj^1D&l({xp%jij zxF@zb&-?RUGnb>H{}8jeCf^2w&qdds`Dy~6R-#f{Jc+L_oZIai&%;k8bLn&nP1Nq! ze0Azm7~18fv5WW9K5Sfhu4BLI?H~7y;Xfl@{wiM4EbO+jw*o|P0l6Ie-roEjX4cJz zCD%gY-%4Kn@D*xB$A~@)D%Qfkj=%XG6xl6?C71WszrIphdq6JXA)4dlgR|-7iB3H8 zq(=j7f|Sq54}1-_jOCy>sY1(A+RvBn^`pCv>{H>$W@{~nH_yJXApHjBs0Mb}I8vqm z;S~Cea1qVsrLRNDkG`s7$xq0j-c`(e{k*RQFNvDvik&f^x9`&8KCyyAoMc_^$lUVa zyNlaw;M2RP-yFUBT4qL1<`3X0`l!Z-f-^qLv}bK^qJf4Z$j5!&e>#g^lKf<|{(%)9 zQgs=L^;mKaxVGQ4v_C&Hyh94EFK%Fe^>Hl)QqK`ukp$pI+*<0t% zs-P_CS;niMtjkw(mzD%i5ve+32hT>?io~}hDxick0p=sU=6M@57mL!Uu45$EPmu63_ zr5oD`Z`Xe{&uqpGRaQiNP}9)tc#!9RNH+IWmR-TA{MlN=n|zGGZtQ8cbBejOB*u|e zzB2(aN1Fad1G%mqE7-%?j4wm4(FS#&CK1z{CWFl`;{1%4pa-|qW&Ui#sE}(wE)T^U ze}FAiahe^9*n<>H2?WHXz)Kj=u--Y9!xi12TpcBe@PHm9r6wXc?E=k^s6zFwA+bUY z_m)(^WjrN?x;ufqx|#-5VY4jge8cxG>i?txdN>r|F%!rh!82c`Lf)=GdhDVoxXek( z*C8cPEZhCDtNSdYcc=nZ*PJH?^oWd0Y*|f2PC|4lizP4>QVbg;M0cVJg{e7@H0?iz zn~BMENZ^@AQX#Ie8+QM-k*S=6$NM6+8dbYc%579}tacy)F}X$jTkE>^W9r&e`+rLG=7Q87ow-0O>R9=4$y84{<|NJ#MneT${R&$DjaFE zf0`0+j#37~3d|+HJbXIRkX=&t&T;SH4;3(ohk~o^&wo^H9b9?(gRf8hG6^*-oIMM2 znK>S{_(|lhx8JTVyO6v(Y_A?7uxjV>(bk5?6 z6Ee=FZKgG)8N&9Nd^{Q;MGwEJ_4uQ--8qh3U6llfD_R%v^_B_EwPAPa)`oRcbO<96 zfhwC8$RsowzbwC8_*#b6))#!=qY;B5rzFXhJbD`vjvV8)N9Xj;Sdh1*so~(_5l{SI z8*9N?5B%EZBR`d~rKi7o0ZQAKhyJHf6aM8jaxpxkMKq4GYA~?m?h?CA=(PxbLUWp&X;51m2AJ0b39F4VNOxM3m z?D@hOyPyIs6De##X2&pAjMqO_RD$gim%h%Unw$`&vFzTW_i4zu|M188ad~4&sxatA z73L}QkYV!Wk9cuz4gz2}KFcDTt3TSI^p3iw0E$?{XOVlOYrMYTBWmvG;a)IMS^JSV z;-uJ7GfReUBJWLsR-76XBtT&+w_NLOc5i*U;`rvqasdb}^HWsms*bbRjda@f3O^V& zz_l*UJc;-UfF0xCL-06lW26@HAbEyEQ+|vcGsA@f(s-CeuY!}KXFKtritEa8X-+x7 z64`|0*m%y~KM{GwX)Cu)#j zaj__cC^Afo?;4LWT_bU_tEC`|mo421St=Rbd#T|%)v(?eS%98!sNQqDce$E`cx?7g>N2es_iB?6 zvnusNIUQB#$&p3st%195nh=e?y(6y2M8oR1R*M(&T>1tozlcV%r{vTD8Ab%C4y_5p z2R(_a+S>R@k_I+gj%vvW)P0H*)s0!z-i`ObB*KdLECVMMy)u&=sm%ktF`;qn;MVE+ zbl(BChdQa42g3ucW@liaPmapE3=`!|Cyn@q{6io}l3Aqe{LYHdBiqY_j z=vYHTm7dxG_=IWo?It`WH^=4$eiA#k4eZ8K=11igUPriX6#b&ShCo|!)DR9^Ly4k= zsg%dO*Clol8R&tTVt&~jN#ojAA60T<9VF4-;ab)nH1W|6$+cSZkBGkp6k)SSA#ojE z9ZT%VEkr>)-5h>>Ip}UjYtF*-1KC?rl`-9th#Kg@5zz2qYB_e=^9NU}C;=+wN7doh zrt4luWp3^f5k)h)SQry=aN~e8H7d@DM126LLo0frgx|r=j(1b}D@8ydPKpezT^x)}s@V037I-`A;mJTRWFtjv45r;jb(wm<3jvz-3S?esFOs^zYpa1>7XbDHiZX<>-79{;j?92xlwc zB!jqu9@shOt-QL>Z7P%u1}awxi4b@exRJU!>Cu8F!h%9v6uqS7CH22|b4#H`#lLLe zaV`pur^KjpL~N)p>!6@Ano55Bo62|p980eC*KcuPCKttM?UWxQKtqlAj6_q2Nfhf1BtS@^fKs`)Sw9$ky;Pj zS|^Xsl+I{7bUor`2%d)lF1AWW6Im}Kj*FP0=U}8o1R*6d!!?+gUsLz6?oU;PA<9Xj zz5|5A2rJ(-|5mXDcgKP{=CB~fX%b((BC>IceR{FXG!59#R*AS(Ubkw%G0x0Lh}D-7 zsULt_!Gwy%vz2gW`=>pt1anak4XvEhOS*2=2uVMPy%<^2DT&+xm8Z#mQ&NAc}lK4@k!lbk8u@tS^;`MK_w5QB*L&uA8i*GWT*Yu{;Zzu zVJwisR(XXKG8Iq?6KVAKO`K9tu?U0jIb;+nym9K$vs0zf-=&d4P?^eJy3*_bq|g<$ zh^Md)nn=-z$&9|15TZ8o^MNTBn90q26HL7HzoxrDlb&|qyzKgq7^h!6u0yL_ z-~C_veTKOkIUprbMociV&l7l@1k@YGo3@Odi_mtQ(giyLcP=;3;s9^<-l6GT_YK!f$?&@k4KXzQ4){l0d6ZUPrjp=q+^qf& zQxdV${`7@3l|)=z?ZE^CsCYZ$Tez>F$+AT$jECJA7)VK+@jm0i%~uy^Q^v76Km$Ju zlVM7`u7)B8UG*Vo66b8wx9pLTd6%rhGQAJgZz+~ zD&pAo#;)`AHPJ?DuN3kV^jp6;>rPS*{k@$QBg8)6U?7bmYje-Go08^^mDjQpkmv{v z9};y5*qM7{I#gy*;eC&;3D-M1y5APhJbt~(i|1K?|LF`+N5GI{0S1Km821+Box1ei z^P@^t33z%0tR3}WaA_W0QgBJB2u1+0R+ZRoU?wl~#Gaq= zc1Tjhd9RiX@)sP|k)wk=AB@pacf@=as;!G1^G{iTI0n5C0aB8nW+F#W+s26KeFF@L z0VK}qzAKWld!5p z3*MmeGiGO!lJM|$zq6ke0SR8Z6w@UFuv#>)0k~)yD}WXHYXryc{!q=c z6Y0T-g^$w?HRg|o{yQ4 zWb#WOxNLKN#sC;Z7>iavE64^4G4}4SHGy$DUDrDHY5W6YSzu=ua_=m9O+@s1%rq4} z;~^j8Bm75D0?}jx4aKNMG{~tk0f+kjD)=e1?eLGV$U!C6-mXLWkYalIMlSHkMCH2WnZCkmZQ(|H}QhSSGyh zdPgut%LEQ{(JC@(i)yA;EZP-Gd1>3?&=Sv|WGKNxU{{O<&{qU3qZB#FE`>nZr++N! z;^_c8Q^YfmB?;*|%hE>IP{RtLC4+c!XAA2N3@{bm&yAK-yYI2|E@s+}pz@d(_ZOgC zt2*J$Wo+OkYb+p*kCD7AGIXbv&Jkxr=`cHEtbykcPA1^Nalb(Mi*>?{+Cg4HSRE(z zW2kx+Ky%-sQl3gNW7TkcX#VC)GjOU$zRG>QNznUl;`<4P{g84(ueG~ ztXd$O_DLz^92jMwzY3`i9+W~(gPVGICeL$MJk1Hd#0yo&9?=2QxtIf^?b`-J;(;&DQiVsA4MCMdEbOT`eul+NCss!h8DR&V#~%?HkAbRWB_t)w1W!8-^9CP3t0;iF zbhWrsNS@W_nu~(yZYt{hX*_dRJiw0YB{;zL3RoR^!~=nTRss?E_ITA)@;L8Gyb$mJ0OX z7+0kMd%!0lAduku1dj*;cTWQ$bXG+I3@O1Qz;iA{#scsIdig1^a0)O8(05V+6(NIV YuxL7iE#AT88i0QGS=gFCH>Jk>53?=ORR910 literal 0 HcmV?d00001 diff --git a/packages/documentation/src/content/docs/introduction/overview.md b/packages/documentation/src/content/docs/introduction/overview.md index 8823e319f0..ef7805e625 100644 --- a/packages/documentation/src/content/docs/introduction/overview.md +++ b/packages/documentation/src/content/docs/introduction/overview.md @@ -15,7 +15,7 @@ This includes Rafiki is intended to be run by [Account Servicing Entities](/reference/glossary#account-servicing-entity) only and should not be used in production by non-regulated entities. ::: -Rafiki is made up of several components including an Interledger connector, a high-throughput accounting database called [TigerBeetle](/reference/glossary#tigerbeetle), and several APIs: +Rafiki is made up of several components including an Interledger connector, a high-throughput accounting database called [TigerBeetle](/reference/glossary#tigerbeetle), an internal [admin interface](/rafikiadmin/overview), and several APIs: - the [Admin API](/integration/management) to create [peering relationships](/reference/glossary#peer), add supported [assets](/reference/glossary#asset), and issue [wallet addresses](/reference/glossary#wallet-address) - the [Open Payments](/reference/glossary#open-payments) API to allow third-parties (with the account holder's consent) to initiate payments and to view the transaction history diff --git a/packages/documentation/src/content/docs/playground/overview.md b/packages/documentation/src/content/docs/playground/overview.md index b26bc7aa53..2bc1718f4e 100644 --- a/packages/documentation/src/content/docs/playground/overview.md +++ b/packages/documentation/src/content/docs/playground/overview.md @@ -11,7 +11,7 @@ These packages include: - `mock-account-servicing-entity` (mocks an [Account Servicing Entity](/reference/glossary#account-servicing-entity)) - `frontend` (Remix app to expose a UI for Rafiki Admin management via interaction with the `backend` Admin APIs) - `kratos` (An identity and user management solution for the `frontend`) -- `mailslurper` (A SMTP mail server to catch account recovery emails) +- `mailslurper` (A SMTP mail server to catch account recovery emails for the `frontend`) These packages depend on the following databases: @@ -223,13 +223,11 @@ Note that you have to go through an interaction flow by clicking on the `redirec #### Admin UI -In order to manage, and view information about the Rafiki instance(s) using a UI, you can navigate to [`localhost:3010`](http://localhost:3010) (Cloud Nine Wallet) or [`localhost:4010`](http://localhost:4010) (Happy Life Bank). This is the `frontend` project which runs a Remix app for querying info and executing mutations against the Rafiki [Admin APIs](#admin-apis). +In order to manage, and view information about the Rafiki instance(s) you can use the [Rafiki Admin](/rafikiadmin/overview) UI. We have secured access to Rafiki Admin using [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro). Since access to the UI is on an invitation-only basis the registration flow is not publicly available. As such, in order to access Rafiki Admin you can click the registration link provided in the logs during `localenv` startup or you can manually add a new user with the invite-user script. Run `docker exec -it npm run invite-user -- example@mail.com` and it will output a link to the terminal. Copy and paste this link in your browser and you will automatically be logged in and directed to the account settings page. The next step is changing your password. We're using a simple email and password authentication method. -We have secured access to the Admin UI using [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro), a secure and fully open-source identity and user management solution. Check it out on [GitHub](https://github.com/ory/kratos). Since access to the UI is on an invitation-only basis the registration flow is not publicly available. As such, in order to access the Admin UI you can click the registration link provided in the logs during `localenv` startup or you can manually add a new user with the invite-user script. Run `docker exec -it npm run invite-user -- example@mail.com` and it will output recovery link to the terminal. The recovery link doubles as the invitation method. Copy and paste this link in your browser and you will automatically be logged in and directed to the account settings page. The next step is changing your password. We're using a simple email and password authentication method. +Note that a separate registration is required for Cloud Nine Wallet's Rafiki Admin and Happy Life Bank's Rafiki Admin, since they are each designed to run as separate mock account servicing entities. Once you've registered, you can always come back to your Rafiki Admin account by navigating to [`localhost:3010`](http://localhost:3010) (Cloud Nine Wallet) or [`localhost:4010`](http://localhost:4010) (Happy Life Bank) and logging in. -There is a password recovery flow. On the login page if you clkick the `forgot password` link and enter an email for a registered user then you can open [Mail Slurper](http://localhost:4436) to access the recovery link for your account. - -We've also included a script to remove users: `docker exec -it npm run delete-user -- example@mail.com`. +You can test the account recovery flow by clicking "Forgot pasword?" on the login page and by navigating to [`localhost:4436`](http://localhost:4436) (Mailslurper interface). #### Admin APIs diff --git a/packages/documentation/src/content/docs/rafikiadmin/auth.md b/packages/documentation/src/content/docs/rafikiadmin/auth.md new file mode 100644 index 0000000000..2f33e57ff8 --- /dev/null +++ b/packages/documentation/src/content/docs/rafikiadmin/auth.md @@ -0,0 +1,35 @@ +--- +title: Rafiki Admin Auth +--- + +## Setup + +Rafiki Admin functions as an interface to the Rafiki backend service and relies on the [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro) identity and user management solution. It doesn’t operate independently; all actions performed in the Rafiki Admin interface, such as fetching data or executing commands, are passed to the Rafiki `backend` service. + +Ory Kratos, a secure and open-source identity and user management solution, handles authentication (login) and user management (account creation and password recovery). Check it out on [GitHub](https://github.com/ory/kratos). For an example of how to get these services up and running, see our [local environment](/playground/overview) setup. + +> **Note: Ory Kratos and Rafiki Admin must be hosted on the same top-level domain. Hosting Kratos on a subdomain is generally not recommended by Ory, but if you choose this approach, ensure you follow the guidelines provided in the Kratos documentation.** + +## Login and account management + +Access to Rafiki Admin is restricted to ensure that only authorized users can register. This is achieved by using an invitation-only system, where new users are invited by an administrator. The registration flow is not public, so users cannot sign up on their own. Instead, administrators create accounts using the `invite-user` script. + +An administrator (someone with backend interface system access) can run the invite-user script in one of two ways, either from outside the container, on the host machine where Docker is running, `docker exec -it npm run invite-user -- example@mail.com`, or directly inside the Rafiki Admin Docker container `npm run invite-user -- example@mail.com`. + +After running the invite-user script, it generates a recovery link that also serves as an invitation link. This link is output to the terminal, and the administrator can send it to the user. When the user opens the link in their browser, they are automatically logged in and taken to the account settings page, where they can set a new password. Afterward, they can log in normally via the Rafiki Admin URL. + +![Rafiki account settings screen](/img/rafiki-auth.png) + +> **Note**: The invitation link is single-use for security purposes. Once accessed, it becomes invalid. If sending the link through Slack, ensure you format it as code by placing it inside backticks (\`) to prevent Slack from automatically previewing the link, which would invalidate it. Example: \``http://localhost:4433/self-service/recovery?flow=116250ee-07bd-4b5c-a98e-87406192bb4b&token=miv0yZ7DFKKw8RyBBQvWoOsTRa2TVuZm`\`. + +There is an automated account recovery flow which requires an SMTP mail server for sending recovery links to users. Alternatively, an administrator may generate a recovery link using the same `invite-user` script. + +To remove a user, administrators can use the following script: `docker exec -it npm run delete-user -- example@mail.com`. + +## Why Ory Kratos? + +We chose Kratos for its open-source nature, lightweight design, and robust security features. It eliminates the need to manage password hashing, storage, or account recovery flows ourselves, allowing us to focus on what we do best. + +Kratos also enhances security with features like built-in breach detection, secure session management, and regular security updates. + +Ory Kratos provides frontend components (such as forms and buttons) for identity management flows like login, and account settings. These components are not fixed in design; they are fetched via API calls which allows us to match the identity management components with Rafiki Admin's overall look and feel. diff --git a/packages/documentation/src/content/docs/rafikiadmin/overview.md b/packages/documentation/src/content/docs/rafikiadmin/overview.md new file mode 100644 index 0000000000..7839bf6760 --- /dev/null +++ b/packages/documentation/src/content/docs/rafikiadmin/overview.md @@ -0,0 +1,11 @@ +--- +title: Overview +--- + +Rafiki Admin provides a user-friendly administrative interface for interacting with the `backend` admin API [mutations](/apis/backend/mutations) and [queries](/apis/backend/queries). In this web application, you'll be able to view and manage peering relationships, assets, and wallet addresses, among other settings. + +![Rafiki Admin home screen](/img/rafiki-admin.png) + +In order to run Rafiki Admin you'll need a Rafiki backend instance up and running, an Ory Kratos identity management service, and an SMTP mail server for sending account recovery emails. All of these services, along with Rafiki Admin are available within the local playground for testing and devlopment purposes. See the local environment pages for more information on how to set it up. TLDR: to get the whole environment up, run the command `pnpm localenv:compose up` from the root of your project. + +Access to Rafiki Admin is restricted to ensure that only authorized users can register. This is achieved by using an invitation-only system, where new users are invited by an administrator. The registration flow is not public, so users cannot sign up on their own. Instead, administrators create accounts using the `invite-user` script. See [Rafiki Admin Auth](/rafikiadmin/auth) for more details. diff --git a/packages/frontend/README.md b/packages/frontend/README.md index a4b802be24..a19d312547 100644 --- a/packages/frontend/README.md +++ b/packages/frontend/README.md @@ -2,25 +2,45 @@ ## Setup -This project assumes that you have a local Rafiki backend instance up and running, as well as a Kratos identity service and Mailslurper. Typically, all of these services, along with this project, would be used within the local Rafiki playground. See the local environment [README](../../localenv/README) for more information on how to set it up. TLDR: to get the whole environment up, run the command `pnpm localenv:compose up` from the root of the project. Once all the Docker containers are up, you can interact with the Admin UI either through the local Cloud Nine Wallet instance at http://localhost:3010, or through the Happy Life Bank instance at http://localhost:4010. +Rafiki Admin functions as an interface to the Rafiki backend service and relies on the [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro) identity and user management solution, and an SMTP mail server for sending account recovery emails. It doesn’t operate independently; all actions performed in the Rafiki Admin interface, such as fetching data or executing commands, are passed to the Rafiki `backend` service. -In order to log in to the Admin UI, you can either follow the registration link, which can be found in the Docker container logs on startup, or you can manually generate a registration link by running the `invite-user` script. Run `docker exec -it npm run invite-user -- example@mail.com` and it will output a link to the terminal. Copy and paste this link in your browser and you will automatically be logged in and directed to the account settings page. The next step is saving a new password. +Ory Kratos, a secure and open-source identity and user management solution, handles authentication (login) and user management (account creation and password recovery). Check it out on [GitHub](https://github.com/ory/kratos). For an example of how to get these services up and running, see our [local environment](../../localenv/cloud-nine-wallet/docker-compose.yml) setup. TLDR: to get the whole environment up, run the command `pnpm localenv:compose up` from the root of the project. Once all the Docker containers are up, you can interact with Rafiki Admin either through the local Cloud Nine Wallet instance at http://localhost:3010, or through the Happy Life Bank instance at http://localhost:4010. + +> **Note: Ory Kratos and Rafiki Admin must be hosted on the same top-level domain. Hosting Kratos on a subdomain is generally not recommended by Ory, but if you choose this approach, ensure you follow the guidelines provided in the Kratos documentation.** + +### Login and account management + +Access to Rafiki Admin is restricted to ensure that only authorized users can register. This is achieved by using an invitation-only system, where new users are invited by an administrator. The registration flow is not public, so users cannot sign up on their own. Instead, administrators create accounts using the `invite-user` script. + +An administrator (someone with backend interface system access) can run the invite-user script in one of two ways, either from outside the container, on the host machine where Docker is running, `docker exec -it npm run invite-user -- example@mail.com`, or directly inside the Rafiki Admin Docker container `npm run invite-user -- example@mail.com`. + +After running the invite-user script, it generates a recovery link that also serves as an invitation link. This link is output to the terminal, and the administrator can send it to the user. When the user opens the link in their browser, they are automatically logged in and taken to the account settings page, where they can set a new password. Afterward, they can log in normally via the Rafiki Admin URL. + +> **Note**: The invitation link is single-use for security purposes. Once accessed, it becomes invalid. If sending the link through Slack, ensure you format it as code by placing it inside backticks (\`) to prevent Slack from automatically previewing the link, which would invalidate it. Example: \``http://localhost:4433/self-service/recovery?flow=116250ee-07bd-4b5c-a98e-87406192bb4b&token=miv0yZ7DFKKw8RyBBQvWoOsTRa2TVuZm`\`. + +There is an automated account recovery flow which is triggered by clicking "Forgot pasword?" on the login page. This functionality requires an SMTP mail server for sending recovery links to users. Alternatively, an administrator may generate a recovery link using the same `invite-user` script. + +To remove a user, administrators can use the following script: `docker exec -it npm run delete-user -- example@mail.com`. + +### Why Ory Kratos? + +We chose Kratos for its open-source nature, lightweight design, and robust security features. It eliminates the need to manage password hashing, storage, or account recovery flows ourselves, allowing us to focus on what we do best. Kratos also enhances security with features like built-in breach detection, secure session management, and regular security updates. ## Development -The Admin UI runs in development mode in the local playground, so when you have it running, you can see live code changes by simply refreshing your page. +You can get a local development environment up by running the command `pnpm localenv:compose up` from the root of the project. Once all the Docker containers are up, you can interact with Rafiki Admin either through the local Cloud Nine Wallet instance at http://localhost:3010, or through the Happy Life Bank instance at http://localhost:4010.. For more information see the local environment [README](../../localenv/README). -To add a new typed Apollo request, you will need to add an untyped request and regenerate the GraphQL types. This will generate new types tailored to the specific request being made. The generated type will reflect the request's query or mutation name, variables used, and requested fields. +We've made development smoother by attaching our Docker containers to the current code with a bind mount. This allows for live development changes with a simple page refresh while running locally. This is not suitable for production setups. -## Ory Kratos +Ory Kratos provides frontend components (such as forms and buttons) for identity management flows like login, and account settings. These components are not fixed in design; they are fetched via API calls based on the specific flow (e.g., login, recovery). Kratos then returns the necessary UI elements, which you can organize, place, and style within the Rafiki Admin interface. This flexibility allows you to match the identity management components with Rafiki Admin's overall look and feel. -We have secured access to the Admin UI using [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro), a secure and fully open-source identity and user management solution. Check it out on [GitHub](https://github.com/ory/kratos). We're using a simple email and password authentication method. Since access to the UI is on an invitation-only basis, the registration flow is not publicly available. Kratos allows administrators to generate a recovery link for an account, whether that account already exists in the system or not. As such, for new users, the recovery link doubles as the invitation method. To create an account on the system, you'll need to add a new user with the `invite-user` script, which must be run inside the admin Docker container (running on the same network as the Kratos container) since the Kratos admin port is not exposed: `docker exec -it npm run invite-user -- example@mail.com`. +Kratos uses the [identity schema](./kratos/config/identity.schema.json) to determine which fields (like email, password, etc.) are required for each flow. This schema dictates the structure and content of the forms and other UI components that Kratos provides. -We've also included a script to remove users: `docker exec -it npm run delete-user -- example@mail.com`. +Rafiki Admin is built with Remix, which integrates client and server-side operations seamlessly. Remix's architecture allows us to manage authentication securely on the server side, minimizing the risk of exposing sensitive logic to client-side vulnerabilities. This approach ensures that loaders check Kratos sessions for user login states before any data is sent to the client, providing a robust security framework. -Kratos allows you to render all of the UIs for idenenty management so that they are fully integrated into the look and feel of your environment. To get the UI elements for any particular page you make an API call to Kratos, which returns the UI elements needed for that flow. It's then up to you to group them, place them, and style them as you see fit. Kratos uses the identity schema that was defined during setup to determine which fields are needed for each flow and all the particulars. +In Remix, the architecture does not include middleware in the traditional sense where you can centrally handle requests before they reach route-specific logic. Instead, Remix provides loaders and actions on each route to handle fetching data and processing requests, respectively. This decentralized approach means that checking for authentication and managing session or user state needs to be handled explicitly within each loader. If authentication logic is only handled in root.tsx, client-side navigation may not re-run the root.tsx loader logic fully if not explicitly designed to do so. This can lead to situations where client-side navigations do not properly re-check authentication states, depending on how data is cached or passed around in the application. -> **Note: Ory Kratos and the Rafiki Admin UI must be hosted on the same top-level domain.** +To add a new typed Apollo request, you will need to add an untyped request and regenerate the GraphQL types. This will generate new types tailored to the specific request being made. The generated type will reflect the request's query or mutation name, variables used, and requested fields. ## Structure From 8e96bc38d10547cfa1afcd55024307f7f874ccc8 Mon Sep 17 00:00:00 2001 From: Tadej Golobic Date: Tue, 20 Aug 2024 16:07:52 +0200 Subject: [PATCH 3/3] feat(quote): remove internal fields from schema (#2858) * feat(quote): remote internaly used fields * test(quote): remove removed fields from resolvers response * docs(bruno): remove internal fields * feat(quote): add estimatedExchangeRate field * docs(bruno): add estimatedExchangeRate to quote query * feat(lint): everything * fix(incoming-payment): service test * fix(bruno): remove deprecated internal quote fields --- .../Create Quote.bru | 4 -- .../Peer-to-Peer Payment/Create Quote.bru | 4 -- .../Cancel Outgoing Payment.bru | 5 +- ...Outgoing Payment From Incoming Payment.bru | 5 +- .../Create Outgoing Payment.bru | 5 +- .../Rafiki/Rafiki Admin APIs/Create Quote.bru | 4 -- .../Get Outgoing Payment.bru | 5 +- .../Rafiki/Rafiki Admin APIs/Get Quote.bru | 5 +- .../generated/graphql.ts | 15 +--- .../src/graphql/generated/graphql.schema.json | 68 +++---------------- .../backend/src/graphql/generated/graphql.ts | 15 +--- .../resolvers/outgoing_payment.test.ts | 14 ++-- .../src/graphql/resolvers/quote.test.ts | 8 --- .../backend/src/graphql/resolvers/quote.ts | 7 +- packages/backend/src/graphql/schema.graphql | 10 +-- packages/frontend/app/generated/graphql.ts | 15 +--- .../src/generated/graphql.ts | 15 +--- test/integration/lib/admin-client.ts | 4 -- test/integration/lib/generated/graphql.ts | 15 +--- 19 files changed, 36 insertions(+), 187 deletions(-) diff --git a/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Cross Currency Payment/Create Quote.bru b/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Cross Currency Payment/Create Quote.bru index 406c1548dd..b3c20f6802 100644 --- a/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Cross Currency Payment/Create Quote.bru +++ b/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Cross Currency Payment/Create Quote.bru @@ -16,11 +16,7 @@ body:graphql { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate walletAddressId receiveAmount { assetCode diff --git a/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Payment/Create Quote.bru b/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Payment/Create Quote.bru index 406c1548dd..b3c20f6802 100644 --- a/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Payment/Create Quote.bru +++ b/bruno/collections/Rafiki/Examples/Admin API - only locally/Peer-to-Peer Payment/Create Quote.bru @@ -16,11 +16,7 @@ body:graphql { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate walletAddressId receiveAmount { assetCode diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru index 9d3dc30452..4095780540 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru @@ -22,11 +22,8 @@ body:graphql { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate + estimatedExchangeRate walletAddressId receiveAmount { assetCode diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru index ea46af7d07..bc376464ca 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru @@ -22,12 +22,9 @@ body:graphql { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate walletAddressId + estimatedExchangeRate receiveAmount { assetCode assetScale diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru index ab0cf3c740..ae3229e021 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru @@ -22,11 +22,8 @@ body:graphql { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate + estimatedExchangeRate walletAddressId receiveAmount { assetCode diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru index dc52218dc1..7211afa661 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru @@ -16,11 +16,7 @@ body:graphql { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate walletAddressId receiveAmount { assetCode diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru index ae9cd5ff47..d0b13d0744 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru @@ -23,12 +23,9 @@ body:graphql { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate walletAddressId + estimatedExchangeRate receiveAmount { assetCode assetScale diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru index 2d92319182..3b2ad6b773 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru @@ -14,13 +14,10 @@ body:graphql { query GetQuote($id: String!) { quote(id: $id) { createdAt - highEstimatedExchangeRate expiresAt id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate walletAddressId + estimatedExchangeRate receiveAmount { assetCode assetScale diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index e482374c2f..55a68afa65 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -1113,18 +1113,12 @@ export type Quote = { createdAt: Scalars['String']['output']; /** Amount to send (fixed send) */ debitAmount: Amount; + /** Estimated exchange rate */ + estimatedExchangeRate?: Maybe; /** Date-time of expiration */ expiresAt: Scalars['String']['output']; - /** Upper bound of probed exchange rate */ - highEstimatedExchangeRate: Scalars['Float']['output']; /** Quote id */ id: Scalars['ID']['output']; - /** Lower bound of probed exchange rate */ - lowEstimatedExchangeRate: Scalars['Float']['output']; - /** Maximum value per packet allowed on the possible routes */ - maxPacketAmount: Scalars['UInt64']['output']; - /** Aggregate exchange rate the payment is guaranteed to meet */ - minExchangeRate: Scalars['Float']['output']; /** Amount to receive (fixed receive) */ receiveAmount: Amount; /** Wallet address URL of the receiver */ @@ -2101,12 +2095,9 @@ export type QueryResolvers = { createdAt?: Resolver; debitAmount?: Resolver; + estimatedExchangeRate?: Resolver, ParentType, ContextType>; expiresAt?: Resolver; - highEstimatedExchangeRate?: Resolver; id?: Resolver; - lowEstimatedExchangeRate?: Resolver; - maxPacketAmount?: Resolver; - minExchangeRate?: Resolver; receiveAmount?: Resolver; receiver?: Resolver; walletAddressId?: Resolver; diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index f0b8b64033..58c79a704c 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -6596,31 +6596,27 @@ "deprecationReason": null }, { - "name": "expiresAt", - "description": "Date-time of expiration", + "name": "estimatedExchangeRate", + "description": "Estimated exchange rate", "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "Float", + "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "highEstimatedExchangeRate", - "description": "Upper bound of probed exchange rate", + "name": "expiresAt", + "description": "Date-time of expiration", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "Float", + "name": "String", "ofType": null } }, @@ -6643,54 +6639,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "lowEstimatedExchangeRate", - "description": "Lower bound of probed exchange rate", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Float", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "maxPacketAmount", - "description": "Maximum value per packet allowed on the possible routes", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "UInt64", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "minExchangeRate", - "description": "Aggregate exchange rate the payment is guaranteed to meet", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Float", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "receiveAmount", "description": "Amount to receive (fixed receive)", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index e482374c2f..55a68afa65 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -1113,18 +1113,12 @@ export type Quote = { createdAt: Scalars['String']['output']; /** Amount to send (fixed send) */ debitAmount: Amount; + /** Estimated exchange rate */ + estimatedExchangeRate?: Maybe; /** Date-time of expiration */ expiresAt: Scalars['String']['output']; - /** Upper bound of probed exchange rate */ - highEstimatedExchangeRate: Scalars['Float']['output']; /** Quote id */ id: Scalars['ID']['output']; - /** Lower bound of probed exchange rate */ - lowEstimatedExchangeRate: Scalars['Float']['output']; - /** Maximum value per packet allowed on the possible routes */ - maxPacketAmount: Scalars['UInt64']['output']; - /** Aggregate exchange rate the payment is guaranteed to meet */ - minExchangeRate: Scalars['Float']['output']; /** Amount to receive (fixed receive) */ receiveAmount: Amount; /** Wallet address URL of the receiver */ @@ -2101,12 +2095,9 @@ export type QueryResolvers = { createdAt?: Resolver; debitAmount?: Resolver; + estimatedExchangeRate?: Resolver, ParentType, ContextType>; expiresAt?: Resolver; - highEstimatedExchangeRate?: Resolver; id?: Resolver; - lowEstimatedExchangeRate?: Resolver; - maxPacketAmount?: Resolver; - minExchangeRate?: Resolver; receiveAmount?: Resolver; receiver?: Resolver; walletAddressId?: Resolver; diff --git a/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts b/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts index 71c51affcc..d0729f29c1 100644 --- a/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts +++ b/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts @@ -198,10 +198,7 @@ describe('OutgoingPayment Resolvers', (): void => { metadata quote { id - maxPacketAmount - minExchangeRate - lowEstimatedExchangeRate - highEstimatedExchangeRate + estimatedExchangeRate createdAt expiresAt } @@ -245,14 +242,11 @@ describe('OutgoingPayment Resolvers', (): void => { metadata, quote: { id: payment.quote.id, - maxPacketAmount: payment.quote.maxPacketAmount.toString(), - minExchangeRate: payment.quote.minExchangeRate.valueOf(), - lowEstimatedExchangeRate: - payment.quote.lowEstimatedExchangeRate.valueOf(), - highEstimatedExchangeRate: - payment.quote.highEstimatedExchangeRate.valueOf(), createdAt: payment.quote.createdAt.toISOString(), expiresAt: payment.quote.expiresAt.toISOString(), + estimatedExchangeRate: payment.quote.estimatedExchangeRate + ? parseFloat(payment.quote.estimatedExchangeRate?.toString()) + : undefined, __typename: 'Quote' }, createdAt: payment.createdAt.toISOString(), diff --git a/packages/backend/src/graphql/resolvers/quote.test.ts b/packages/backend/src/graphql/resolvers/quote.test.ts index 39efdac43c..dcb703edc6 100644 --- a/packages/backend/src/graphql/resolvers/quote.test.ts +++ b/packages/backend/src/graphql/resolvers/quote.test.ts @@ -93,10 +93,6 @@ describe('Quote Resolvers', (): void => { assetCode assetScale } - maxPacketAmount - minExchangeRate - lowEstimatedExchangeRate - highEstimatedExchangeRate createdAt expiresAt } @@ -124,10 +120,6 @@ describe('Quote Resolvers', (): void => { assetScale: quote.receiveAmount.assetScale, __typename: 'Amount' }, - maxPacketAmount: quote.maxPacketAmount.toString(), - minExchangeRate: quote.minExchangeRate.valueOf(), - lowEstimatedExchangeRate: quote.lowEstimatedExchangeRate.valueOf(), - highEstimatedExchangeRate: quote.highEstimatedExchangeRate.valueOf(), createdAt: quote.createdAt.toISOString(), expiresAt: quote.expiresAt.toISOString(), __typename: 'Quote' diff --git a/packages/backend/src/graphql/resolvers/quote.ts b/packages/backend/src/graphql/resolvers/quote.ts index 638c0fecde..16bd2863e1 100644 --- a/packages/backend/src/graphql/resolvers/quote.ts +++ b/packages/backend/src/graphql/resolvers/quote.ts @@ -104,11 +104,8 @@ export function quoteToGraphql(quote: Quote): SchemaQuote { receiver: quote.receiver, debitAmount: quote.debitAmount, receiveAmount: quote.receiveAmount, - maxPacketAmount: quote.maxPacketAmount, - minExchangeRate: quote.minExchangeRate.valueOf(), - lowEstimatedExchangeRate: quote.lowEstimatedExchangeRate.valueOf(), - highEstimatedExchangeRate: quote.highEstimatedExchangeRate.valueOf(), createdAt: new Date(+quote.createdAt).toISOString(), - expiresAt: new Date(+quote.expiresAt).toISOString() + expiresAt: new Date(+quote.expiresAt).toISOString(), + estimatedExchangeRate: quote.estimatedExchangeRate?.valueOf() } } diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index dcf104c049..ed137ba407 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -957,18 +957,12 @@ type Quote { debitAmount: Amount! "Amount to receive (fixed receive)" receiveAmount: Amount! - "Maximum value per packet allowed on the possible routes" - maxPacketAmount: UInt64! - "Aggregate exchange rate the payment is guaranteed to meet" - minExchangeRate: Float! - "Lower bound of probed exchange rate" - lowEstimatedExchangeRate: Float! - "Upper bound of probed exchange rate" - highEstimatedExchangeRate: Float! "Date-time of creation" createdAt: String! "Date-time of expiration" expiresAt: String! + "Estimated exchange rate" + estimatedExchangeRate: Float } input AmountInput { diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 10e878774f..db27546a81 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -1113,18 +1113,12 @@ export type Quote = { createdAt: Scalars['String']['output']; /** Amount to send (fixed send) */ debitAmount: Amount; + /** Estimated exchange rate */ + estimatedExchangeRate?: Maybe; /** Date-time of expiration */ expiresAt: Scalars['String']['output']; - /** Upper bound of probed exchange rate */ - highEstimatedExchangeRate: Scalars['Float']['output']; /** Quote id */ id: Scalars['ID']['output']; - /** Lower bound of probed exchange rate */ - lowEstimatedExchangeRate: Scalars['Float']['output']; - /** Maximum value per packet allowed on the possible routes */ - maxPacketAmount: Scalars['UInt64']['output']; - /** Aggregate exchange rate the payment is guaranteed to meet */ - minExchangeRate: Scalars['Float']['output']; /** Amount to receive (fixed receive) */ receiveAmount: Amount; /** Wallet address URL of the receiver */ @@ -2101,12 +2095,9 @@ export type QueryResolvers = { createdAt?: Resolver; debitAmount?: Resolver; + estimatedExchangeRate?: Resolver, ParentType, ContextType>; expiresAt?: Resolver; - highEstimatedExchangeRate?: Resolver; id?: Resolver; - lowEstimatedExchangeRate?: Resolver; - maxPacketAmount?: Resolver; - minExchangeRate?: Resolver; receiveAmount?: Resolver; receiver?: Resolver; walletAddressId?: Resolver; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index e482374c2f..55a68afa65 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -1113,18 +1113,12 @@ export type Quote = { createdAt: Scalars['String']['output']; /** Amount to send (fixed send) */ debitAmount: Amount; + /** Estimated exchange rate */ + estimatedExchangeRate?: Maybe; /** Date-time of expiration */ expiresAt: Scalars['String']['output']; - /** Upper bound of probed exchange rate */ - highEstimatedExchangeRate: Scalars['Float']['output']; /** Quote id */ id: Scalars['ID']['output']; - /** Lower bound of probed exchange rate */ - lowEstimatedExchangeRate: Scalars['Float']['output']; - /** Maximum value per packet allowed on the possible routes */ - maxPacketAmount: Scalars['UInt64']['output']; - /** Aggregate exchange rate the payment is guaranteed to meet */ - minExchangeRate: Scalars['Float']['output']; /** Amount to receive (fixed receive) */ receiveAmount: Amount; /** Wallet address URL of the receiver */ @@ -2101,12 +2095,9 @@ export type QueryResolvers = { createdAt?: Resolver; debitAmount?: Resolver; + estimatedExchangeRate?: Resolver, ParentType, ContextType>; expiresAt?: Resolver; - highEstimatedExchangeRate?: Resolver; id?: Resolver; - lowEstimatedExchangeRate?: Resolver; - maxPacketAmount?: Resolver; - minExchangeRate?: Resolver; receiveAmount?: Resolver; receiver?: Resolver; walletAddressId?: Resolver; diff --git a/test/integration/lib/admin-client.ts b/test/integration/lib/admin-client.ts index 08f176ad67..589e6b5ae6 100644 --- a/test/integration/lib/admin-client.ts +++ b/test/integration/lib/admin-client.ts @@ -68,11 +68,7 @@ export class AdminClient { quote { createdAt expiresAt - highEstimatedExchangeRate id - lowEstimatedExchangeRate - maxPacketAmount - minExchangeRate walletAddressId receiveAmount { assetCode diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index e482374c2f..55a68afa65 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -1113,18 +1113,12 @@ export type Quote = { createdAt: Scalars['String']['output']; /** Amount to send (fixed send) */ debitAmount: Amount; + /** Estimated exchange rate */ + estimatedExchangeRate?: Maybe; /** Date-time of expiration */ expiresAt: Scalars['String']['output']; - /** Upper bound of probed exchange rate */ - highEstimatedExchangeRate: Scalars['Float']['output']; /** Quote id */ id: Scalars['ID']['output']; - /** Lower bound of probed exchange rate */ - lowEstimatedExchangeRate: Scalars['Float']['output']; - /** Maximum value per packet allowed on the possible routes */ - maxPacketAmount: Scalars['UInt64']['output']; - /** Aggregate exchange rate the payment is guaranteed to meet */ - minExchangeRate: Scalars['Float']['output']; /** Amount to receive (fixed receive) */ receiveAmount: Amount; /** Wallet address URL of the receiver */ @@ -2101,12 +2095,9 @@ export type QueryResolvers = { createdAt?: Resolver; debitAmount?: Resolver; + estimatedExchangeRate?: Resolver, ParentType, ContextType>; expiresAt?: Resolver; - highEstimatedExchangeRate?: Resolver; id?: Resolver; - lowEstimatedExchangeRate?: Resolver; - maxPacketAmount?: Resolver; - minExchangeRate?: Resolver; receiveAmount?: Resolver; receiver?: Resolver; walletAddressId?: Resolver;