From b2da0b1e1c4849e457f5e561a59521a0a47aae2d Mon Sep 17 00:00:00 2001 From: Lior Agnin Date: Tue, 9 Sep 2025 13:45:29 +0300 Subject: [PATCH 01/17] fix: Improve error handling in BundlerApiInterceptor and PaymasterApiService - Enhanced logging for JSON-RPC errors in BundlerApiInterceptor, including specific messages for validation failures and CALL_EXCEPTION errors. - Updated error handling in PaymasterApiService to log service unavailability errors (502, 503) for better debugging. --- .../bundler-api/bundler-api.interceptor.ts | 41 ++++++++++++++----- .../paymaster-api/paymaster-api.service.ts | 10 ++++- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/apps/charge-api-service/src/bundler-api/bundler-api.interceptor.ts b/apps/charge-api-service/src/bundler-api/bundler-api.interceptor.ts index dc36bae0..a9cd7858 100644 --- a/apps/charge-api-service/src/bundler-api/bundler-api.interceptor.ts +++ b/apps/charge-api-service/src/bundler-api/bundler-api.interceptor.ts @@ -46,21 +46,40 @@ export class BundlerApiInterceptor implements NestInterceptor { .request(requestConfig) .pipe( map((axiosResponse: AxiosResponse) => { - this.logger.log(`BundlerApiInterceptor succeeded: ${JSON.stringify(axiosResponse.data)}`) - return axiosResponse.data + const data = axiosResponse.data + if (data?.error) { + this.logger.error(`BundlerApiInterceptor JSON-RPC error: ${JSON.stringify(data.error)}`) + // Parse specific error codes + if (data.error?.data?.includes('0xe0cff05f')) { + this.logger.error('FailedOp: UserOperation validation failed at EntryPoint') + } + // For CALL_EXCEPTION errors specifically + if (data.error?.message?.includes('CALL_EXCEPTION')) { + this.logger.error('Transaction simulation failed - possible validation, gas, or contract execution error') + } + } else { + this.logger.log(`BundlerApiInterceptor succeeded: ${JSON.stringify(data)}`) + } + return data }) ) .pipe( catchError((e) => { - const errorReason = - e?.response?.data?.error || - e?.response?.data?.errors?.message || - '' - this.logger.log(`BundlerApiInterceptor error: ${JSON.stringify(e)}`) - throw new HttpException( - `${e?.response?.statusText}: ${errorReason}`, - e?.response?.status - ) + const errorData = e?.response?.data + let errorMessage = e?.response?.statusText || 'Bundler API error' + + if (errorData?.error) { + const error = errorData.error + if (error.data?.includes('0xe0cff05f')) { + errorMessage = 'UserOperation validation failed - possible causes: expired validUntil timestamp, insufficient gas, or paymaster validation failure' + this.logger.error(`FailedOp details: ${JSON.stringify(error)}`) + } else { + errorMessage = error.message || errorMessage + } + } + + this.logger.error(`BundlerApiInterceptor error: ${JSON.stringify(e)}`) + throw new HttpException(errorMessage, e?.response?.status || 500) }) ) ) diff --git a/apps/charge-api-service/src/paymaster-api/paymaster-api.service.ts b/apps/charge-api-service/src/paymaster-api/paymaster-api.service.ts index e67c6007..f9d4f95e 100644 --- a/apps/charge-api-service/src/paymaster-api/paymaster-api.service.ts +++ b/apps/charge-api-service/src/paymaster-api/paymaster-api.service.ts @@ -157,10 +157,16 @@ export class PaymasterApiService { ) .pipe( catchError((e) => { + // Log specific error types for better debugging + if (e?.response?.status === 503 || e?.response?.status === 502) { + this.logger.error(`Service unavailable error (${e?.response?.status}): RPC node may be down`) + } + const errorReason = + e?.response?.data?.error?.message || e?.result?.error || - e?.result?.error?.message || - '' + e?.message || + 'Unknown error during gas estimation' this.logger.error(`RpcException catchError: ${errorReason} ${JSON.stringify(e)}`) throw new RpcException(errorReason) From c93e32e734be651ae4177c28f4718446b6a45ed9 Mon Sep 17 00:00:00 2001 From: JackRooty <58828970+JackRooty@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:19:58 +0300 Subject: [PATCH 02/17] feat(helm) Change version of external-secrets to v1 --- k8s/helm/charts/accounts/templates/externalsecret.yaml | 2 +- k8s/helm/charts/api/templates/externalsecret.yaml | 2 +- k8s/helm/charts/apps/templates/externalsecret.yaml | 2 +- k8s/helm/charts/centrifugo/templates/externalsecret.yaml | 2 +- k8s/helm/charts/network/templates/externalsecret.yaml | 2 +- k8s/helm/charts/notifications/templates/externalsecret.yaml | 2 +- k8s/helm/charts/relay/templates/externalsecret.yaml | 2 +- k8s/helm/charts/skandha/templates/externalsecret.yaml | 2 +- k8s/helm/charts/smart-wallets/templates/externalsecret.yaml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/k8s/helm/charts/accounts/templates/externalsecret.yaml b/k8s/helm/charts/accounts/templates/externalsecret.yaml index bef5d2d3..235ed177 100644 --- a/k8s/helm/charts/accounts/templates/externalsecret.yaml +++ b/k8s/helm/charts/accounts/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: accounts diff --git a/k8s/helm/charts/api/templates/externalsecret.yaml b/k8s/helm/charts/api/templates/externalsecret.yaml index 1115d4ed..9e21e890 100644 --- a/k8s/helm/charts/api/templates/externalsecret.yaml +++ b/k8s/helm/charts/api/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: api diff --git a/k8s/helm/charts/apps/templates/externalsecret.yaml b/k8s/helm/charts/apps/templates/externalsecret.yaml index 8154426f..2521001b 100644 --- a/k8s/helm/charts/apps/templates/externalsecret.yaml +++ b/k8s/helm/charts/apps/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: apps diff --git a/k8s/helm/charts/centrifugo/templates/externalsecret.yaml b/k8s/helm/charts/centrifugo/templates/externalsecret.yaml index 7ec99ddb..f3bc0ff2 100644 --- a/k8s/helm/charts/centrifugo/templates/externalsecret.yaml +++ b/k8s/helm/charts/centrifugo/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: centrifugo diff --git a/k8s/helm/charts/network/templates/externalsecret.yaml b/k8s/helm/charts/network/templates/externalsecret.yaml index 377f4946..38904ed5 100644 --- a/k8s/helm/charts/network/templates/externalsecret.yaml +++ b/k8s/helm/charts/network/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: network diff --git a/k8s/helm/charts/notifications/templates/externalsecret.yaml b/k8s/helm/charts/notifications/templates/externalsecret.yaml index 008a632b..3a8c57f2 100644 --- a/k8s/helm/charts/notifications/templates/externalsecret.yaml +++ b/k8s/helm/charts/notifications/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: notifications diff --git a/k8s/helm/charts/relay/templates/externalsecret.yaml b/k8s/helm/charts/relay/templates/externalsecret.yaml index 6aae9d99..08ac91d4 100644 --- a/k8s/helm/charts/relay/templates/externalsecret.yaml +++ b/k8s/helm/charts/relay/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: relay diff --git a/k8s/helm/charts/skandha/templates/externalsecret.yaml b/k8s/helm/charts/skandha/templates/externalsecret.yaml index d778ad9a..ccf7f9bb 100644 --- a/k8s/helm/charts/skandha/templates/externalsecret.yaml +++ b/k8s/helm/charts/skandha/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: skandha diff --git a/k8s/helm/charts/smart-wallets/templates/externalsecret.yaml b/k8s/helm/charts/smart-wallets/templates/externalsecret.yaml index 79dfbf07..6c5d1a53 100644 --- a/k8s/helm/charts/smart-wallets/templates/externalsecret.yaml +++ b/k8s/helm/charts/smart-wallets/templates/externalsecret.yaml @@ -1,4 +1,4 @@ -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: smart-wallets From 35768a41c68e7b893f161ce0bf0476bfdaba3607 Mon Sep 17 00:00:00 2001 From: JackRooty <58828970+JackRooty@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:57:18 +0300 Subject: [PATCH 03/17] Test template k8s-deploy-qa.yaml --- .github/workflows/k8s-deploy-qa.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/k8s-deploy-qa.yaml b/.github/workflows/k8s-deploy-qa.yaml index b7754f87..123a9a28 100644 --- a/.github/workflows/k8s-deploy-qa.yaml +++ b/.github/workflows/k8s-deploy-qa.yaml @@ -99,12 +99,7 @@ jobs: - name: Release run: | - helm upgrade \ - --install \ - --history-max 3 \ - --debug \ - --atomic \ - --wait \ + helm template \ --values helm/values/qa.yaml \ --set accounts-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/accounts-service \ --set accounts-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ From cb28a6393e0ec2c9698b72f58a05d39a9e9c0af5 Mon Sep 17 00:00:00 2001 From: JackRooty <58828970+JackRooty@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:11:00 +0300 Subject: [PATCH 04/17] Rollback helm deploy --- .github/workflows/k8s-deploy-qa.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/k8s-deploy-qa.yaml b/.github/workflows/k8s-deploy-qa.yaml index 123a9a28..b7754f87 100644 --- a/.github/workflows/k8s-deploy-qa.yaml +++ b/.github/workflows/k8s-deploy-qa.yaml @@ -99,7 +99,12 @@ jobs: - name: Release run: | - helm template \ + helm upgrade \ + --install \ + --history-max 3 \ + --debug \ + --atomic \ + --wait \ --values helm/values/qa.yaml \ --set accounts-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/accounts-service \ --set accounts-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ From 341c8258f514df23a6d48e769f35209eeb3f43f3 Mon Sep 17 00:00:00 2001 From: JackRooty <58828970+JackRooty@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:06:02 +0300 Subject: [PATCH 05/17] Check that external secret can be created --- .github/workflows/k8s-deploy-qa.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/k8s-deploy-qa.yaml b/.github/workflows/k8s-deploy-qa.yaml index b7754f87..120ec79f 100644 --- a/.github/workflows/k8s-deploy-qa.yaml +++ b/.github/workflows/k8s-deploy-qa.yaml @@ -99,6 +99,7 @@ jobs: - name: Release run: | + kubectl auth can-i create externalsecrets -n ${{ env.HELM_RELEASE_NAMESPACE }} helm upgrade \ --install \ --history-max 3 \ From 21a54da8e8dd46d061ab5ab97ec2ac00f3fb910f Mon Sep 17 00:00:00 2001 From: JackRooty <58828970+JackRooty@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:24:01 +0300 Subject: [PATCH 06/17] Try force flag --- .github/workflows/k8s-deploy-qa.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/k8s-deploy-qa.yaml b/.github/workflows/k8s-deploy-qa.yaml index 120ec79f..54dc155f 100644 --- a/.github/workflows/k8s-deploy-qa.yaml +++ b/.github/workflows/k8s-deploy-qa.yaml @@ -99,8 +99,8 @@ jobs: - name: Release run: | - kubectl auth can-i create externalsecrets -n ${{ env.HELM_RELEASE_NAMESPACE }} helm upgrade \ + --force \ --install \ --history-max 3 \ --debug \ From 9231b98d76299d3d9eda5c5465fd3d74df44db78 Mon Sep 17 00:00:00 2001 From: JackRooty <58828970+JackRooty@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:30:11 +0300 Subject: [PATCH 07/17] Remove whitespace --- .github/workflows/k8s-deploy-qa.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/k8s-deploy-qa.yaml b/.github/workflows/k8s-deploy-qa.yaml index 54dc155f..db20a1d7 100644 --- a/.github/workflows/k8s-deploy-qa.yaml +++ b/.github/workflows/k8s-deploy-qa.yaml @@ -100,7 +100,7 @@ jobs: - name: Release run: | helm upgrade \ - --force \ + --force \ --install \ --history-max 3 \ --debug \ From db29834da0f185bf2922e1c3f39c5efaf3a9991f Mon Sep 17 00:00:00 2001 From: JackRooty <58828970+JackRooty@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:36:03 +0300 Subject: [PATCH 08/17] Try reset values flag --- .github/workflows/k8s-deploy-qa.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/k8s-deploy-qa.yaml b/.github/workflows/k8s-deploy-qa.yaml index db20a1d7..d45f0239 100644 --- a/.github/workflows/k8s-deploy-qa.yaml +++ b/.github/workflows/k8s-deploy-qa.yaml @@ -101,6 +101,7 @@ jobs: run: | helm upgrade \ --force \ + --reset-values \ --install \ --history-max 3 \ --debug \ From ea67deb0b96480ff9fe2e1d5fde2a700ae70f417 Mon Sep 17 00:00:00 2001 From: Evgeny Koren Date: Thu, 25 Sep 2025 10:07:24 +0300 Subject: [PATCH 09/17] Add prod to GA --- .github/workflows/k8s-deploy-prod.yaml | 117 +++++ helm/values/prod.yaml | 659 +++++++++++++++++++++++++ 2 files changed, 776 insertions(+) create mode 100644 .github/workflows/k8s-deploy-prod.yaml create mode 100644 helm/values/prod.yaml diff --git a/.github/workflows/k8s-deploy-prod.yaml b/.github/workflows/k8s-deploy-prod.yaml new file mode 100644 index 00000000..672f933a --- /dev/null +++ b/.github/workflows/k8s-deploy-prod.yaml @@ -0,0 +1,117 @@ +--- +name: Build & Deploy to Kubernetes [PRIVATE_REGISTRY_PASSWORD:] + +on: + push: + branches: + - Add-Prod-to-OVH + +jobs: + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - dockerfile: ./apps/charge-accounts-service/Dockerfile + image: accounts-service + - dockerfile: ./apps/charge-api-service/Dockerfile + image: api-service + - dockerfile: ./apps/charge-network-service/Dockerfile + image: network-service + - dockerfile: ./apps/charge-notifications-service/Dockerfile + image: notifications-service + - dockerfile: ./apps/charge-smart-wallets-service/Dockerfile + image: smart-wallets-service + env: + PRIVATE_REGISTRY_ENDPOINT: ${{ secrets.PRIVATE_REGISTRY_ENDPOINT }} + PRIVATE_REGISTRY_REPOSITORY: ${{ secrets.PRIVATE_REGISTRY_REPOSITORY_PROD }} + PRIVATE_REGISTRY_USERNAME: ${{ secrets.PRIVATE_REGISTRY_USERNAME_PROD }} + PRIVATE_REGISTRY_PASSWORD: ${{ secrets.PRIVATE_REGISTRY_PASSWORD_PROD }} + outputs: + short_sha: ${{ steps.sha.outputs.short_sha }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set GitHub commit short SHA + id: sha + run: | + CALCULATED_SHA=$(git rev-parse --short ${{ github.sha }}) + echo "short_sha=$CALCULATED_SHA" >> $GITHUB_OUTPUT + + - name: Login to private registry + uses: docker/login-action@v3 + with: + registry: ${{ env.PRIVATE_REGISTRY_ENDPOINT }} + username: ${{ env.PRIVATE_REGISTRY_USERNAME }} + password: ${{ env.PRIVATE_REGISTRY_PASSWORD }} + logout: true + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image(s) + uses: docker/build-push-action@v5 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + tags: | + ${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/${{ matrix.image }}:latest + ${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/${{ matrix.image }}:${{ steps.sha.outputs.short_sha }} + + deploy: + name: Deploy + runs-on: ubuntu-latest + env: + PRIVATE_REGISTRY_ENDPOINT: ${{ secrets.PRIVATE_REGISTRY_ENDPOINT }} + PRIVATE_REGISTRY_REPOSITORY: ${{ secrets.PRIVATE_REGISTRY_REPOSITORY_PROD }} + K8S_KUBECONFIG: ${{ secrets.K8S_KUBECONFIG_PROD }} + HELM_CHART_REGISTRY_ENDPOINT: ${{ secrets.HELM_CHART_REGISTRY_ENDPOINT }} + HELM_CHART_REGISTRY_REPOSITORY: ${{ secrets.HELM_CHART_REGISTRY_REPOSITORY }} + HELM_CHART_REGISTRY_USERNAME: ${{ secrets.HELM_CHART_REGISTRY_USERNAME }} + HELM_CHART_REGISTRY_PASSWORD: ${{ secrets.HELM_CHART_REGISTRY_PASSWORD }} + HELM_CHART_VERSION: ${{ vars.HELM_CHART_VERSION_PROD }} + HELM_RELEASE_NAME: ${{ vars.HELM_RELEASE_NAME }} + HELM_RELEASE_NAMESPACE: ${{ vars.HELM_RELEASE_NAMESPACE }} + needs: build-and-push + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Login to private registry + uses: docker/login-action@v3 + with: + registry: ${{ env.HELM_CHART_REGISTRY_ENDPOINT }} + username: ${{ env.HELM_CHART_REGISTRY_USERNAME }} + password: ${{ env.HELM_CHART_REGISTRY_PASSWORD }} + logout: true + + - name: Configure Kubernetes config file + run: | + mkdir -p $HOME/.kube/ + echo "${{ env.K8S_KUBECONFIG }}" | base64 -d > $HOME/.kube/config + + - name: Release + run: | + helm template \ + --values helm/values/prod.yaml \ + --set accounts-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/accounts-service \ + --set accounts-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ + --set api-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/api-service \ + --set api-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ + --set network-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/network-service \ + --set network-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ + --set notifications-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/notifications-service \ + --set notifications-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ + --set smart-wallets-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/smart-wallets-service \ + --set smart-wallets-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ + -n ${{ env.HELM_RELEASE_NAMESPACE }} \ + ${{ env.HELM_RELEASE_NAME }} \ + oci://${{ env.HELM_CHART_REGISTRY_ENDPOINT }}/${{ env.HELM_CHART_REGISTRY_REPOSITORY }} \ + --version ${{ env.HELM_CHART_VERSION }} diff --git a/helm/values/prod.yaml b/helm/values/prod.yaml new file mode 100644 index 00000000..95e11d57 --- /dev/null +++ b/helm/values/prod.yaml @@ -0,0 +1,659 @@ +# Default values for fusebox-backend. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +accounts-service: + enabled: true + + externalSecret: + secretStoreName: "hashicorp-vault" + remoteRefKey: "fuseio/fusebox-backend/accounts-service" + refreshInterval: "15s" + secretKey: + - MONGO_URI + - SMART_WALLETS_JWT_SECRET + - PAYMASTER_FUNDER_PRIVATE_KEY + - PAYMASTER_FUNDER_API_SECRET_KEY + - AMPLITUDE_API_KEY + - INCOMING_TOKEN_TRANSFERS_WEBHOOK_ID + - GOOGLE_OPERATOR_FORM_URL + - OPERATOR_REFRESH_JWT_SECRET + - CHARGE_PAYMENTS_API_KEY + - COIN_GECKO_API_KEY + - CRON_JOB_SECRET + + replicaCount: 2 + + image: + repository: fusebox-backend/accounts-service + pullPolicy: IfNotPresent + tag: "" + + imagePullSecrets: + - name: regcred + + nameOverride: "" + fullnameOverride: "" + + command: [] + + args: [] + + serviceAccount: + create: false + automount: true + annotations: {} + name: "" + + deploymentAnnotations: + reloader.stakater.com/auto: "true" + + podAnnotations: {} + podLabels: {} + + podSecurityContext: {} + + securityContext: {} + + service: + type: ClusterIP + ports: + - name: http + port: 5001 + protocol: TCP + targetPort: 5001 + - name: tcp + port: 8875 + protocol: TCP + targetPort: 8875 + + ingress: + enabled: true + className: "nginx" + annotations: {} + hosts: + - host: accounts.test.fuse.io + paths: + - path: / + pathType: Prefix + tls: + - secretName: fusebox-backend-accounts-service-tls-certificate + hosts: + - accounts.test.fuse.io + + resources: {} + + livenessProbe: + httpGet: + path: /accounts/v1/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /accounts/v1/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + + volumes: [] + + volumeMounts: [] + + configMap: + ACCOUNTS_HOST: "0.0.0.0" + ACCOUNTS_PORT: "5001" + ACCOUNTS_TCP_PORT: "8875" + API_HOST: "fusebox-backend-api-service" + API_TCP_PORT: "8876" + SMART_WALLETS_HOST: "fusebox-backend-smart-wallets-service" + SMART_WALLETS_TCP_PORT: "8881" + NOTIFICATIONS_HOST: "fusebox-backend-notifications-service" + NOTIFICATIONS_TCP_PORT: "8879" + AUTH0_ISSUER_URL: "https://auth.fuse.io//" + AUTH0_AUDIENCE: "https://accounts.test.fuse.io" + PAYMASTER_SANDBOX_CONTRACT_ADDRESS_V_0_1_0: "0x324999f067EA822EEf78e7A4793F672A4F5E80f6" + PAYMASTER_PRODUCTION_CONTRACT_ADDRESS_V_0_1_0: "0xEA1Ba4305A07cEd2bB5e42224D71aBE0BC3C3f28" + ENTRYPOINT_SANDBOX_CONTRACT_ADDRESS_V_0_1_0: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + ENTRYPOINT_PRODUCTION_CONTRACT_ADDRESS_V_0_1_0: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + ETHERSPOT_WALLET_FACTORY_SANDBOX_CONTRACT_ADDRESS_V_0_1_0: "0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E" + ETHERSPOT_WALLET_FACTORY_PRODUCTION_CONTRACT_ADDRESS_V_0_1_0: "0x7f6d8F107fE8551160BD5351d5F1514A6aD5d40E" + PAYMASTER_FUNDER_API_KEY: "pk_yWkBe0hnObJmMkYjq4Ggce1g" + PAYMASTER_FUNDER_WEBHOOK_ID: "65c4ba66394c8d9c3e4801ff" + USDC_CONTRACT_ADDRESS_MAINNET: "0x28C3d1cD466Ba22f6cae51b1a4692a831696391A" + USDC_CONTRACT_ADDRESS_TESTNET: "0x28C3d1cD466Ba22f6cae51b1a4692a831696391A" + CHARGE_PAYMENTS_API_URL: "https://payments.chargeweb3.com" + COIN_GECKO_URL: "https://pro-api.coingecko.com/api/v3" + WFUSE_CONTRACT_ADDRESS_MAINNET: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629" + WFUSE_CONTRACT_ADDRESS_TESTNET: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629" + + nodeSelector: {} + + tolerations: [] + + affinity: {} + +api-service: + enabled: true + + externalSecret: + secretStoreName: "hashicorp-vault" + remoteRefKey: "fuseio/fusebox-backend/api-service" + refreshInterval: "15s" + secretKey: + - MONGO_URI + - RPC_URL + - FUSE_STUDIO_ADMIN_JWT + - LEGACY_JWT_SECRET + - SMART_WALLETS_JWT_SECRET + - PAYMASTER_PRODUCTION_SIGNER_PRIVATE_KEY_V_0_1_0 + - PAYMASTER_SANDBOX_SIGNER_PRIVATE_KEY_V_0_1_0 + - EXPLORER_API_KEY + - BUNDLER_API_PRD_URL + - PIMLICO_API_PRD_URL + - PIMLICO_API_SANDBOX_URL + - AMPLITUDE_API_KEY + + replicaCount: 2 + + image: + repository: fusebox-backend/api-service + pullPolicy: IfNotPresent + tag: "" + + imagePullSecrets: + - name: regcred + + nameOverride: "" + fullnameOverride: "" + + command: [] + + args: [] + + serviceAccount: + create: false + automount: true + annotations: {} + name: "" + + deploymentAnnotations: + reloader.stakater.com/auto: "true" + + podAnnotations: {} + podLabels: {} + + podSecurityContext: {} + + securityContext: {} + + service: + type: ClusterIP + ports: + - name: http + port: 5002 + protocol: TCP + targetPort: 5002 + - name: tcp + port: 8876 + protocol: TCP + targetPort: 8876 + + ingress: + enabled: true + className: "nginx" + annotations: {} + hosts: + - host: api.test.fuse.io + paths: + - path: / + pathType: Prefix + tls: + - secretName: fusebox-backend-api-service-tls-certificate + hosts: + - api.test.fuse.io + + resources: {} + + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + + volumes: [] + + volumeMounts: [] + + configMap: + API_HOST: "0.0.0.0" + API_PORT: "5002" + API_TCP_PORT: "8876" + ACCOUNTS_HOST: "fusebox-backend-accounts-service" + ACCOUNTS_TCP_PORT: "8875" + NOTIFICATIONS_HOST: "fusebox-backend-notifications-service" + NOTIFICATIONS_TCP_PORT: "8879" + NETWORK_HOST: "fusebox-backend-network-service" + NETWORK_TCP_PORT: "8878" + SMART_WALLETS_HOST: "fusebox-backend-smart-wallets-service" + SMART_WALLETS_PORT: "5008" + SMART_WALLETS_TCP_PORT: "8881" + EXPLORER_API_URL: "https://explorer.fuse.io/api" + QA_MODE: "false" + BUNDLER_API_SANDBOX_URL: "https://testnet-rpc.etherspot.io/v1/123" + SPARK_RPC_URL: "https://rpc.fusespark.io" + LEGACY_FUSE_ADMIN_API_URL: "https://studio.fuse.io" + LEGACY_FUSE_WALLET_API_URL: "https://wallet.fuse.io" + VOLTAGE_ROUTER_API_URL: "https://router.voltage.finance" + + nodeSelector: {} + + tolerations: [] + + affinity: {} + +# ---------------------- +# !!! - DEPRECATED - !!! +# ---------------------- +apps-service: + enabled: false + +network-service: + enabled: true + + externalSecret: + secretStoreName: "hashicorp-vault" + remoteRefKey: "fuseio/fusebox-backend/network-service" + refreshInterval: "15s" + secretKey: + - RPC_URL + - UNMARSHAL_AUTH_KEY + - EXPLORER_API_KEY + - COIN_GECKO_API_KEY + + replicaCount: 1 + + image: + repository: fusebox-backend/network-service + pullPolicy: IfNotPresent + tag: "" + + imagePullSecrets: + - name: regcred + + nameOverride: "" + fullnameOverride: "" + + command: [] + + args: [] + + serviceAccount: + create: false + automount: true + annotations: {} + name: "" + + deploymentAnnotations: + reloader.stakater.com/auto: "true" + + podAnnotations: {} + podLabels: {} + + podSecurityContext: {} + + securityContext: {} + + service: + type: ClusterIP + ports: + - name: http + port: 5004 + protocol: TCP + targetPort: 5004 + - name: tcp + port: 8878 + protocol: TCP + targetPort: 8878 + + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + + resources: {} + + livenessProbe: + httpGet: + path: /network/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /network/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + + volumes: [] + + volumeMounts: [] + + configMap: + NETWORK_HOST: "0.0.0.0" + NETWORK_PORT: "5004" + NETWORK_TCP_PORT: "8878" + PRIMARY_SERVICE: "explorer" + EXPLORER_API_URL: "https://explorer.fuse.io/api" + UNMARSHAL_BASE_URL: "https://api.unmarshal.com" + COIN_GECKO_URL: "https://pro-api.coingecko.com/api/v3" + + nodeSelector: {} + + tolerations: [] + + affinity: {} + +notifications-service: + enabled: true + + externalSecret: + secretStoreName: "hashicorp-vault" + remoteRefKey: "fuseio/fusebox-backend/notifications-service" + refreshInterval: "15s" + secretKey: + - MONGO_URI + - SMART_WALLETS_JWT_SECRET + - RPC_URL + - FULL_ARCHIVE_RPC_URL + + replicaCount: 1 + + image: + repository: fusebox-backend/notifications-service + pullPolicy: IfNotPresent + tag: "" + + imagePullSecrets: + - name: regcred + + nameOverride: "" + fullnameOverride: "" + + command: [] + + args: [] + + serviceAccount: + create: false + automount: true + annotations: {} + name: "" + + deploymentAnnotations: + reloader.stakater.com/auto: "true" + + podAnnotations: {} + podLabels: {} + + podSecurityContext: {} + + securityContext: {} + + service: + type: ClusterIP + ports: + - name: http + port: 5005 + protocol: TCP + targetPort: 5005 + - name: tcp + port: 8879 + protocol: TCP + targetPort: 8879 + + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + + resources: {} + + livenessProbe: + httpGet: + path: /notifications/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /notifications/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + + volumes: [] + + volumeMounts: [] + + configMap: + NOTIFICATIONS_HOST: "0.0.0.0" + NOTIFICATIONS_PORT: "5005" + NOTIFICATIONS_TCP_PORT: "8879" + SMART_WALLETS_HOST: "fusebox-backend-smart-wallets-service" + SMART_WALLETS_TCP_PORT: "8881" + NETWORK_NAME: "fuse" + CHAIN_ID: "122" + MAX_BLOCKS: "1500" + TIMEOUT_INTERVAL: "20000" + + nodeSelector: {} + + tolerations: [] + + affinity: {} + +# ---------------------- +# !!! - DEPRECATED - !!! +# ---------------------- +relay-service: + enabled: false + +smart-wallets-service: + enabled: true + + externalSecret: + secretStoreName: "hashicorp-vault" + remoteRefKey: "fuseio/fusebox-backend/smart-wallets-service" + refreshInterval: "15s" + secretKey: + - AMPLITUDE_API_KEY + - MONGO_URI + - SMART_WALLETS_JWT_SECRET + - FUSE_WALLET_BACKEND_JWT + - CENTRIFUGO_JWT + - CENTRIFUGO_API_KEY + - CHARGE_PUBLIC_KEY + - CHARGE_SECRET_KEY + - INCOMING_TOKEN_TRANSFERS_WEBHOOK_ID + - COIN_GECKO_API_KEY + + replicaCount: 1 + + image: + repository: fusebox-backend/smart-wallets-service + pullPolicy: IfNotPresent + tag: "" + + imagePullSecrets: + - name: regcred + + nameOverride: "" + fullnameOverride: "" + + command: [] + + args: [] + + serviceAccount: + create: false + automount: true + annotations: {} + name: "" + + deploymentAnnotations: + reloader.stakater.com/auto: "true" + + podAnnotations: {} + podLabels: {} + + podSecurityContext: {} + + securityContext: {} + + service: + type: ClusterIP + ports: + - name: http + port: 5007 + protocol: TCP + targetPort: 5007 + - name: tcp + port: 8881 + protocol: TCP + targetPort: 8881 + + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + + resources: {} + + livenessProbe: + httpGet: + path: /smart-wallets/v1/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /smart-wallets/v1/health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + + volumes: [] + + volumeMounts: [] + + configMap: + SMART_WALLETS_HOST: "0.0.0.0" + SMART_WALLETS_PORT: "5007" + SMART_WALLETS_TCP_PORT: "8881" + API_HOST: "fusebox-backend-api-service" + API_TCP_PORT: "8876" + ACCOUNTS_HOST: "fusebox-backend-accounts-service" + ACCOUNTS_TCP_PORT: "8875" + CENTRIFUGO_URI: "wss://ws.test.fuse.io/connection/websocket" + CENTRIFUGO_API_URL: "https://ws.test.fuse.io/api" + LEGACY_FUSE_WALLET_API_URL: "https://wallet.fuse.io" + CHARGE_BASE_URL: "https://api.test.fuse.io" + COIN_GECKO_URL: "https://pro-api.coingecko.com/api/v3" + + nodeSelector: {} + + tolerations: [] + + affinity: {} From f885efe8fb0f886ccffd8296834290ed5b78575b Mon Sep 17 00:00:00 2001 From: Evgeny Koren Date: Thu, 25 Sep 2025 10:30:19 +0300 Subject: [PATCH 10/17] Remove Mongo --- helm/values/prod.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/helm/values/prod.yaml b/helm/values/prod.yaml index 95e11d57..9e257565 100644 --- a/helm/values/prod.yaml +++ b/helm/values/prod.yaml @@ -10,7 +10,6 @@ accounts-service: remoteRefKey: "fuseio/fusebox-backend/accounts-service" refreshInterval: "15s" secretKey: - - MONGO_URI - SMART_WALLETS_JWT_SECRET - PAYMASTER_FUNDER_PRIVATE_KEY - PAYMASTER_FUNDER_API_SECRET_KEY @@ -154,7 +153,6 @@ api-service: remoteRefKey: "fuseio/fusebox-backend/api-service" refreshInterval: "15s" secretKey: - - MONGO_URI - RPC_URL - FUSE_STUDIO_ADMIN_JWT - LEGACY_JWT_SECRET @@ -415,7 +413,6 @@ notifications-service: remoteRefKey: "fuseio/fusebox-backend/notifications-service" refreshInterval: "15s" secretKey: - - MONGO_URI - SMART_WALLETS_JWT_SECRET - RPC_URL - FULL_ARCHIVE_RPC_URL @@ -540,7 +537,6 @@ smart-wallets-service: refreshInterval: "15s" secretKey: - AMPLITUDE_API_KEY - - MONGO_URI - SMART_WALLETS_JWT_SECRET - FUSE_WALLET_BACKEND_JWT - CENTRIFUGO_JWT From 525226d1b57117e26af8ccb766fa906c4eca7739 Mon Sep 17 00:00:00 2001 From: Evgeny Koren Date: Thu, 25 Sep 2025 10:46:08 +0300 Subject: [PATCH 11/17] change template to deploy for helm --- .github/workflows/k8s-deploy-prod.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/k8s-deploy-prod.yaml b/.github/workflows/k8s-deploy-prod.yaml index 672f933a..a6fb8cdb 100644 --- a/.github/workflows/k8s-deploy-prod.yaml +++ b/.github/workflows/k8s-deploy-prod.yaml @@ -99,7 +99,14 @@ jobs: - name: Release run: | - helm template \ + helm upgrade \ + --force \ + --reset-values \ + --install \ + --history-max 3 \ + --debug \ + --atomic \ + --wait \ --values helm/values/prod.yaml \ --set accounts-service.image.repository=${{ env.PRIVATE_REGISTRY_ENDPOINT }}/${{ env.PRIVATE_REGISTRY_REPOSITORY }}/accounts-service \ --set accounts-service.image.tag=${{ needs.build-and-push.outputs.short_sha }} \ From cbf6f429d508daf8f1ad42f42595d396c9d4f54c Mon Sep 17 00:00:00 2001 From: Lior Agnin Date: Sun, 28 Sep 2025 15:12:09 +0300 Subject: [PATCH 12/17] fix: Enhance error handling in Smart Wallet services - Updated error handling in SmartWalletsAAService, SmartWalletsLegacyService, and RelayAPIService to use RpcException for better microservice context. - Improved robustness of error messages by checking for existing properties before accessing them. - Ensured consistent error handling across services to enhance debugging and maintainability. --- .../src/common/services/relay-api.service.ts | 18 ++++++--- .../services/smart-wallets-aa.service.ts | 13 +++++- .../services/smart-wallets-legacy.service.ts | 40 +++++++++++++++---- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts b/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts index e36b57cd..9bb5b97e 100644 --- a/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts +++ b/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts @@ -1,5 +1,5 @@ import { HttpService } from '@nestjs/axios' -import { HttpException, Injectable } from '@nestjs/common' +import { HttpException, HttpStatus, Injectable } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { AxiosRequestConfig } from 'axios' import { catchError, lastValueFrom, map } from 'rxjs' @@ -62,13 +62,19 @@ export default class RelayAPIService { .pipe(map(res => res.data)) .pipe( catchError(e => { + // More robust error handling - check if response exists before accessing properties const errorReason = e?.response?.data?.error || - e?.response?.data?.errors?.message || '' + e?.response?.data?.errors?.message || + e?.message || + 'Unknown error occurred' - throw new HttpException( - `${e?.response?.statusText}: ${errorReason}`, - e?.response?.status - ) + const statusText = e?.response?.statusText || 'Error' + const status = e?.response?.status || HttpStatus.INTERNAL_SERVER_ERROR + + // Create error message safely + const errorMessage = errorReason ? `${statusText}: ${errorReason}` : statusText + + throw new HttpException(errorMessage, status) }) ) return await lastValueFrom(observable) diff --git a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts index ec937c8b..de42a1b4 100644 --- a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts +++ b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts @@ -1,5 +1,6 @@ import { SmartWalletsAuthDto } from '@app/smart-wallets-service/dto/smart-wallets-auth.dto' -import { Inject, HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' +import { Inject, HttpStatus, Injectable, Logger } from '@nestjs/common' +import { RpcException } from '@nestjs/microservices' import { JwtService } from '@nestjs/jwt' import { arrayify, computeAddress, hashMessage, recoverPublicKey } from 'nestjs-ethers' import { SmartWalletService } from '@app/smart-wallets-service/smart-wallets/interfaces/smart-wallets.interface' @@ -57,7 +58,15 @@ export class SmartWalletsAAService implements SmartWalletService { } } catch (err) { this.logger.error(`An error occurred during Smart Wallets Auth. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + // If it's already an RpcException, re-throw it + if (err instanceof RpcException) { + throw err + } + // Otherwise wrap it in RpcException for microservice context + throw new RpcException({ + message: err.message || 'Authentication error', + statusCode: HttpStatus.BAD_REQUEST + }) } } diff --git a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts index 24157fda..57638302 100644 --- a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts +++ b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts @@ -1,6 +1,7 @@ import { Model } from 'mongoose' import { SmartWalletsAuthDto } from '@app/smart-wallets-service/dto/smart-wallets-auth.dto' -import { HttpException, HttpStatus, Inject, Injectable, Logger } from '@nestjs/common' +import { HttpStatus, Inject, Injectable, Logger } from '@nestjs/common' +import { RpcException } from '@nestjs/microservices' import { JwtService } from '@nestjs/jwt' import { arrayify, computeAddress, hashMessage, recoverPublicKey } from 'nestjs-ethers' import { ConfigService } from '@nestjs/config' @@ -52,7 +53,10 @@ export class SmartWalletsLegacyService implements SmartWalletService { } } catch (err) { this.logger.error(`An error occurred during Smart Wallets Auth. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + message: err.message || 'Authentication error', + statusCode: HttpStatus.BAD_REQUEST + }) } } @@ -63,7 +67,7 @@ export class SmartWalletsLegacyService implements SmartWalletService { const smartWallet = await this.smartWalletModel.findOne({ ownerAddress }) if (!smartWallet) { this.logger.warn(`Smart Wallet not found for owner address: ${ownerAddress}`) - throw new Error('Not found') + throw new RpcException({ message: 'Smart wallet not found', statusCode: HttpStatus.NOT_FOUND }) } if (!smartWallet.isContractDeployed) { this.logger.log(`Smart Wallet not deployed for owner address: ${ownerAddress}, deploying...`) @@ -102,7 +106,15 @@ export class SmartWalletsLegacyService implements SmartWalletService { } } catch (err) { this.logger.error(`An error occurred during fetching Legacy Smart Wallet. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + // If it's already an RpcException, re-throw it + if (err instanceof RpcException) { + throw err + } + // Otherwise wrap it in RpcException for microservice context + throw new RpcException({ + message: err.message || 'Error fetching smart wallet', + statusCode: HttpStatus.BAD_REQUEST + }) } } @@ -110,7 +122,10 @@ export class SmartWalletsLegacyService implements SmartWalletService { try { const { ownerAddress } = smartWalletUser if (await this.smartWalletModel.findOne({ ownerAddress })) { - throw new Error('Owner address already has a deployed smart wallet') + throw new RpcException({ + message: 'Owner address already has a deployed smart wallet', + statusCode: HttpStatus.CONFLICT + }) } const salt = generateSalt() const transactionId = generateTransactionId(salt) @@ -131,7 +146,10 @@ export class SmartWalletsLegacyService implements SmartWalletService { } } catch (err) { this.logger.error(`An error occurred during Smart Wallets Creation. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + message: err.message || 'Error creating smart wallet', + statusCode: HttpStatus.BAD_REQUEST + }) } } @@ -150,7 +168,10 @@ export class SmartWalletsLegacyService implements SmartWalletService { } } catch (err) { this.logger.error(`An error occurred during relay execution. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + message: err.message || 'Relay execution error', + statusCode: HttpStatus.BAD_REQUEST + }) } } @@ -163,7 +184,10 @@ export class SmartWalletsLegacyService implements SmartWalletService { } } catch (error) { this.logger.error(`An error occurred during fetching historical txs. ${error}`) - throw new HttpException(error.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + message: error.message || 'Error fetching historical transactions', + statusCode: HttpStatus.BAD_REQUEST + }) } } From 271b9ade3a8ade9ba7d715d113e4cbee68551f97 Mon Sep 17 00:00:00 2001 From: Lior Agnin Date: Tue, 30 Sep 2025 15:07:18 +0300 Subject: [PATCH 13/17] fix: Improve error logging and handling in RelayAPIService and client-proxy - Added a logger to RelayAPIService to capture and log errors more effectively. - Enhanced error handling in client-proxy to include statusCode and message for better clarity in exceptions. - Ensured consistent error messaging across services for improved debugging. --- .../src/common/services/relay-api.service.ts | 4 +++- libs/common/src/utils/client-proxy.ts | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts b/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts index 9bb5b97e..319de136 100644 --- a/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts +++ b/apps/charge-smart-wallets-service/src/common/services/relay-api.service.ts @@ -1,11 +1,12 @@ import { HttpService } from '@nestjs/axios' -import { HttpException, HttpStatus, Injectable } from '@nestjs/common' +import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { AxiosRequestConfig } from 'axios' import { catchError, lastValueFrom, map } from 'rxjs' @Injectable() export default class RelayAPIService { + private readonly logger = new Logger(RelayAPIService.name) constructor ( private readonly httpService: HttpService, private readonly configService: ConfigService @@ -62,6 +63,7 @@ export default class RelayAPIService { .pipe(map(res => res.data)) .pipe( catchError(e => { + this.logger.error(`RelayAPIService error: ${JSON.stringify(e)}`) // More robust error handling - check if response exists before accessing properties const errorReason = e?.response?.data?.error || e?.response?.data?.errors?.message || diff --git a/libs/common/src/utils/client-proxy.ts b/libs/common/src/utils/client-proxy.ts index e3b827bb..276040d7 100644 --- a/libs/common/src/utils/client-proxy.ts +++ b/libs/common/src/utils/client-proxy.ts @@ -15,9 +15,12 @@ export async function callMSFunction (client: ClientProxy, pattern: string, data if (error instanceof TimeoutError) { return throwError(() => new HttpException(`Timeout in ${serviceName} microservice call`, HttpStatus.REQUEST_TIMEOUT)) } + // Handle RpcException errors that contain statusCode + const statusCode = error.statusCode || error.status || HttpStatus.INTERNAL_SERVER_ERROR + const message = error.message || 'Unknown error' return throwError(() => new HttpException( - `Error in ${serviceName} microservice: ${error.message || 'Unknown error'}`, - error.status || HttpStatus.INTERNAL_SERVER_ERROR + `Error in ${serviceName} microservice: ${message}`, + statusCode )) }) ) From 74ac7ec659a04faffa58c32a3d7b519e251d0bfa Mon Sep 17 00:00:00 2001 From: Lior Agnin Date: Tue, 30 Sep 2025 16:44:12 +0300 Subject: [PATCH 14/17] fix: Standardize error handling in Smart Wallet services - Updated error responses in SmartWalletsAAService and SmartWalletsLegacyService to use 'error' and 'status' fields for consistency. - Enhanced error handling in AllExceptionsFilter and client-proxy to accommodate new error structure. - Improved logging for better debugging and clarity in error messages across services. --- .../services/smart-wallets-aa.service.ts | 4 +-- .../services/smart-wallets-legacy.service.ts | 31 +++++++++++-------- .../src/exceptions/all-exceptions.filter.ts | 22 +++++++++++-- libs/common/src/utils/client-proxy.ts | 20 ++++++++++-- 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts index de42a1b4..b2201e86 100644 --- a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts +++ b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-aa.service.ts @@ -64,8 +64,8 @@ export class SmartWalletsAAService implements SmartWalletService { } // Otherwise wrap it in RpcException for microservice context throw new RpcException({ - message: err.message || 'Authentication error', - statusCode: HttpStatus.BAD_REQUEST + error: err.message || 'Authentication error', + status: HttpStatus.BAD_REQUEST }) } } diff --git a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts index 57638302..c2afdc0e 100644 --- a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts +++ b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.ts @@ -54,8 +54,8 @@ export class SmartWalletsLegacyService implements SmartWalletService { } catch (err) { this.logger.error(`An error occurred during Smart Wallets Auth. ${err}`) throw new RpcException({ - message: err.message || 'Authentication error', - statusCode: HttpStatus.BAD_REQUEST + error: err.message || 'Authentication error', + status: HttpStatus.BAD_REQUEST }) } } @@ -67,7 +67,12 @@ export class SmartWalletsLegacyService implements SmartWalletService { const smartWallet = await this.smartWalletModel.findOne({ ownerAddress }) if (!smartWallet) { this.logger.warn(`Smart Wallet not found for owner address: ${ownerAddress}`) - throw new RpcException({ message: 'Smart wallet not found', statusCode: HttpStatus.NOT_FOUND }) + const errorObj = { + error: 'Smart wallet not found', + status: HttpStatus.NOT_FOUND + } + this.logger.debug(`Throwing RpcException with: ${JSON.stringify(errorObj)}`) + throw new RpcException(errorObj) } if (!smartWallet.isContractDeployed) { this.logger.log(`Smart Wallet not deployed for owner address: ${ownerAddress}, deploying...`) @@ -112,8 +117,8 @@ export class SmartWalletsLegacyService implements SmartWalletService { } // Otherwise wrap it in RpcException for microservice context throw new RpcException({ - message: err.message || 'Error fetching smart wallet', - statusCode: HttpStatus.BAD_REQUEST + error: err.message || 'Error fetching smart wallet', + status: HttpStatus.BAD_REQUEST }) } } @@ -123,8 +128,8 @@ export class SmartWalletsLegacyService implements SmartWalletService { const { ownerAddress } = smartWalletUser if (await this.smartWalletModel.findOne({ ownerAddress })) { throw new RpcException({ - message: 'Owner address already has a deployed smart wallet', - statusCode: HttpStatus.CONFLICT + error: 'Owner address already has a deployed smart wallet', + status: HttpStatus.CONFLICT }) } const salt = generateSalt() @@ -147,8 +152,8 @@ export class SmartWalletsLegacyService implements SmartWalletService { } catch (err) { this.logger.error(`An error occurred during Smart Wallets Creation. ${err}`) throw new RpcException({ - message: err.message || 'Error creating smart wallet', - statusCode: HttpStatus.BAD_REQUEST + error: err.message || 'Error creating smart wallet', + status: HttpStatus.BAD_REQUEST }) } } @@ -169,8 +174,8 @@ export class SmartWalletsLegacyService implements SmartWalletService { } catch (err) { this.logger.error(`An error occurred during relay execution. ${err}`) throw new RpcException({ - message: err.message || 'Relay execution error', - statusCode: HttpStatus.BAD_REQUEST + error: err.message || 'Relay execution error', + status: HttpStatus.BAD_REQUEST }) } } @@ -185,8 +190,8 @@ export class SmartWalletsLegacyService implements SmartWalletService { } catch (error) { this.logger.error(`An error occurred during fetching historical txs. ${error}`) throw new RpcException({ - message: error.message || 'Error fetching historical transactions', - statusCode: HttpStatus.BAD_REQUEST + error: error.message || 'Error fetching historical transactions', + status: HttpStatus.BAD_REQUEST }) } } diff --git a/libs/common/src/exceptions/all-exceptions.filter.ts b/libs/common/src/exceptions/all-exceptions.filter.ts index 42dd8de0..8e4fc8d4 100644 --- a/libs/common/src/exceptions/all-exceptions.filter.ts +++ b/libs/common/src/exceptions/all-exceptions.filter.ts @@ -8,6 +8,7 @@ import { RpcException } from '@nestjs/microservices' import { ServerResponse } from 'http' import { MongoServerError } from 'mongodb' import { throwError } from 'rxjs' +import { get, isPlainObject } from 'lodash' @Catch() export class AllExceptionsFilter implements ExceptionFilter { @@ -36,8 +37,19 @@ export class AllExceptionsFilter implements ExceptionFilter { errorMessage = `${Object.keys(exception?.keyValue)} must be unique` } } else if (exception instanceof RpcException) { - httpStatus = HttpStatus.INTERNAL_SERVER_ERROR - errorMessage = exception.message + // RpcException can have custom status in the error object + const rpcError = exception.getError() + this.logger.debug(`RpcException caught - Raw error object: ${JSON.stringify(rpcError)}`) + + if (isPlainObject(rpcError)) { + httpStatus = get(rpcError, 'status', get(rpcError, 'statusCode', HttpStatus.INTERNAL_SERVER_ERROR)) + errorMessage = get(rpcError, 'error', get(rpcError, 'message', exception.message)) + this.logger.debug(`RpcException - Extracted status: ${httpStatus}, message: ${errorMessage}`) + } else { + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR + errorMessage = exception.message + this.logger.debug(`RpcException - Using defaults, status: ${httpStatus}, message: ${errorMessage}`) + } } else { httpStatus = HttpStatus.INTERNAL_SERVER_ERROR errorMessage = 'Critical Internal Server Error Occurred' @@ -52,7 +64,11 @@ export class AllExceptionsFilter implements ExceptionFilter { } if (host.getType() === 'rpc') { - return throwError(() => ({ message: errorMessage, status: httpStatus })) + this.logger.debug(`Returning RPC error - status: ${httpStatus}, message: ${errorMessage}`) + return throwError(() => ({ + error: errorMessage, // Use 'error' field for consistency + status: httpStatus + })) } response.statusCode = httpStatus diff --git a/libs/common/src/utils/client-proxy.ts b/libs/common/src/utils/client-proxy.ts index 276040d7..4e411b4e 100644 --- a/libs/common/src/utils/client-proxy.ts +++ b/libs/common/src/utils/client-proxy.ts @@ -1,6 +1,7 @@ import { HttpException, HttpStatus } from '@nestjs/common' import { ClientProxy } from '@nestjs/microservices' import { lastValueFrom, takeLast, catchError, throwError, timeout, TimeoutError } from 'rxjs' +import { get } from 'lodash' export async function callMSFunction (client: ClientProxy, pattern: string, data: any) { const serviceName = (client as any).host || 'UnknownService' @@ -12,12 +13,25 @@ export async function callMSFunction (client: ClientProxy, pattern: string, data takeLast(1), catchError((error) => { console.error(`Error in microservice call to ${serviceName} (pattern: ${pattern}):`, error) + console.debug('Raw error object structure:', { + status: get(error, 'status'), + statusCode: get(error, 'statusCode'), + error: get(error, 'error'), + message: get(error, 'message'), + fullError: JSON.stringify(error) + }) + if (error instanceof TimeoutError) { return throwError(() => new HttpException(`Timeout in ${serviceName} microservice call`, HttpStatus.REQUEST_TIMEOUT)) } - // Handle RpcException errors that contain statusCode - const statusCode = error.statusCode || error.status || HttpStatus.INTERNAL_SERVER_ERROR - const message = error.message || 'Unknown error' + + // Handle RpcException errors - they use 'error' and 'status' fields + // Also support legacy 'message' and 'statusCode' fields for backward compatibility + const statusCode = get(error, 'status', get(error, 'statusCode', HttpStatus.INTERNAL_SERVER_ERROR)) + const message = get(error, 'error', get(error, 'message', 'Unknown error')) + + console.debug(`Extracted from error - statusCode: ${statusCode}, message: ${message}`) + return throwError(() => new HttpException( `Error in ${serviceName} microservice: ${message}`, statusCode From 7c1b249d024cfe75a271cf041f76f050ba2f028b Mon Sep 17 00:00:00 2001 From: Evgeny Koren Date: Mon, 20 Oct 2025 14:52:55 +0300 Subject: [PATCH 15/17] Migration --- helm/values/prod.yaml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/helm/values/prod.yaml b/helm/values/prod.yaml index 9e257565..90bb58b0 100644 --- a/helm/values/prod.yaml +++ b/helm/values/prod.yaml @@ -10,6 +10,7 @@ accounts-service: remoteRefKey: "fuseio/fusebox-backend/accounts-service" refreshInterval: "15s" secretKey: + - MONGO_URI - SMART_WALLETS_JWT_SECRET - PAYMASTER_FUNDER_PRIVATE_KEY - PAYMASTER_FUNDER_API_SECRET_KEY @@ -71,14 +72,14 @@ accounts-service: className: "nginx" annotations: {} hosts: - - host: accounts.test.fuse.io + - host: accounts.fuse.io paths: - path: / pathType: Prefix tls: - secretName: fusebox-backend-accounts-service-tls-certificate hosts: - - accounts.test.fuse.io + - accounts.fuse.io resources: {} @@ -123,7 +124,7 @@ accounts-service: NOTIFICATIONS_HOST: "fusebox-backend-notifications-service" NOTIFICATIONS_TCP_PORT: "8879" AUTH0_ISSUER_URL: "https://auth.fuse.io//" - AUTH0_AUDIENCE: "https://accounts.test.fuse.io" + AUTH0_AUDIENCE: "https://accounts.fuse.io" PAYMASTER_SANDBOX_CONTRACT_ADDRESS_V_0_1_0: "0x324999f067EA822EEf78e7A4793F672A4F5E80f6" PAYMASTER_PRODUCTION_CONTRACT_ADDRESS_V_0_1_0: "0xEA1Ba4305A07cEd2bB5e42224D71aBE0BC3C3f28" ENTRYPOINT_SANDBOX_CONTRACT_ADDRESS_V_0_1_0: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" @@ -153,6 +154,7 @@ api-service: remoteRefKey: "fuseio/fusebox-backend/api-service" refreshInterval: "15s" secretKey: + - MONGO_URI - RPC_URL - FUSE_STUDIO_ADMIN_JWT - LEGACY_JWT_SECRET @@ -215,14 +217,14 @@ api-service: className: "nginx" annotations: {} hosts: - - host: api.test.fuse.io + - host: api.fuse.io paths: - path: / pathType: Prefix tls: - secretName: fusebox-backend-api-service-tls-certificate hosts: - - api.test.fuse.io + - api.fuse.io resources: {} @@ -413,6 +415,7 @@ notifications-service: remoteRefKey: "fuseio/fusebox-backend/notifications-service" refreshInterval: "15s" secretKey: + - MONGO_URI - SMART_WALLETS_JWT_SECRET - RPC_URL - FULL_ARCHIVE_RPC_URL @@ -536,6 +539,7 @@ smart-wallets-service: remoteRefKey: "fuseio/fusebox-backend/smart-wallets-service" refreshInterval: "15s" secretKey: + - MONGO_URI - AMPLITUDE_API_KEY - SMART_WALLETS_JWT_SECRET - FUSE_WALLET_BACKEND_JWT @@ -645,7 +649,7 @@ smart-wallets-service: CENTRIFUGO_URI: "wss://ws.test.fuse.io/connection/websocket" CENTRIFUGO_API_URL: "https://ws.test.fuse.io/api" LEGACY_FUSE_WALLET_API_URL: "https://wallet.fuse.io" - CHARGE_BASE_URL: "https://api.test.fuse.io" + CHARGE_BASE_URL: "https://api.fuse.io" COIN_GECKO_URL: "https://pro-api.coingecko.com/api/v3" nodeSelector: {} From 2e5094d3d791f53f299f0803e82695979495dd36 Mon Sep 17 00:00:00 2001 From: Evgeny Koren Date: Tue, 21 Oct 2025 12:39:09 +0300 Subject: [PATCH 16/17] fix temp names and notification service port --- .github/workflows/k8s-deploy-prod.yaml | 2 +- helm/values/prod.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/k8s-deploy-prod.yaml b/.github/workflows/k8s-deploy-prod.yaml index a6fb8cdb..aa956e92 100644 --- a/.github/workflows/k8s-deploy-prod.yaml +++ b/.github/workflows/k8s-deploy-prod.yaml @@ -4,7 +4,7 @@ name: Build & Deploy to Kubernetes [PRIVATE_REGISTRY_PASSWORD:] on: push: branches: - - Add-Prod-to-OVH + - prod jobs: build-and-push: diff --git a/helm/values/prod.yaml b/helm/values/prod.yaml index 90bb58b0..47d64828 100644 --- a/helm/values/prod.yaml +++ b/helm/values/prod.yaml @@ -269,7 +269,7 @@ api-service: NETWORK_HOST: "fusebox-backend-network-service" NETWORK_TCP_PORT: "8878" SMART_WALLETS_HOST: "fusebox-backend-smart-wallets-service" - SMART_WALLETS_PORT: "5008" + SMART_WALLETS_PORT: "5007" SMART_WALLETS_TCP_PORT: "8881" EXPLORER_API_URL: "https://explorer.fuse.io/api" QA_MODE: "false" @@ -646,8 +646,8 @@ smart-wallets-service: API_TCP_PORT: "8876" ACCOUNTS_HOST: "fusebox-backend-accounts-service" ACCOUNTS_TCP_PORT: "8875" - CENTRIFUGO_URI: "wss://ws.test.fuse.io/connection/websocket" - CENTRIFUGO_API_URL: "https://ws.test.fuse.io/api" + CENTRIFUGO_URI: "wss://ws.fuse.io/connection/websocket" + CENTRIFUGO_API_URL: "https://ws.fuse.io/api" LEGACY_FUSE_WALLET_API_URL: "https://wallet.fuse.io" CHARGE_BASE_URL: "https://api.fuse.io" COIN_GECKO_URL: "https://pro-api.coingecko.com/api/v3" From 5285c2e4d05c0cf081ad761f603fe580d09c9029 Mon Sep 17 00:00:00 2001 From: Evgeny Koren Date: Tue, 21 Oct 2025 16:04:17 +0300 Subject: [PATCH 17/17] Change prod branch name fo master ( for CI ) --- .github/workflows/k8s-deploy-prod.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/k8s-deploy-prod.yaml b/.github/workflows/k8s-deploy-prod.yaml index aa956e92..06bcdf7b 100644 --- a/.github/workflows/k8s-deploy-prod.yaml +++ b/.github/workflows/k8s-deploy-prod.yaml @@ -4,7 +4,7 @@ name: Build & Deploy to Kubernetes [PRIVATE_REGISTRY_PASSWORD:] on: push: branches: - - prod + - master jobs: build-and-push: